Merge "Add first frame timestamps to AppStartInfo" into main
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 5e9fdfb..1e824a1 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -6257,6 +6257,29 @@
* {@link #RESTRICTION_LEVEL_ADAPTIVE} is a normal state, where there is default lifecycle
* management applied to the app. Also, {@link #RESTRICTION_LEVEL_EXEMPTED} is used when the
* app is being put in a power-save allowlist.
+ * <p>
+ * Example arguments when user force-stops an app from Settings:
+ * <pre>
+ * noteAppRestrictionEnabled(
+ * "com.example.app",
+ * appUid,
+ * RESTRICTION_LEVEL_FORCE_STOPPED,
+ * true,
+ * RESTRICTION_REASON_USER,
+ * "settings",
+ * 0);
+ * </pre>
+ * Example arguments when app is put in restricted standby bucket for exceeding X hours of jobs:
+ * <pre>
+ * noteAppRestrictionEnabled(
+ * "com.example.app",
+ * appUid,
+ * RESTRICTION_LEVEL_RESTRICTED_BUCKET,
+ * true,
+ * RESTRICTION_REASON_SYSTEM_HEALTH,
+ * "job_duration",
+ * X * 3600 * 1000L);
+ * </pre>
*
* @param packageName the package name of the app
* @param uid the uid of the app
@@ -6264,11 +6287,20 @@
* @param enabled whether the state is being applied or removed
* @param reason the reason for the restriction state change, from {@code RestrictionReason}
* @param subReason a string sub reason limited to 16 characters that specifies additional
- * information about the reason for restriction.
+ * information about the reason for restriction. This string must only contain
+ * reasons related to excessive system resource usage or in some cases,
+ * source of the restriction. This string must not contain any details that
+ * identify user behavior beyond their actions to restrict/unrestrict/launch
+ * apps in some way.
+ * Examples of system resource usage: wakelock, wakeups, mobile_data,
+ * binder_calls, memory, excessive_threads, excessive_cpu, gps_scans, etc.
+ * Examples of user actions: settings, notification, command_line, launch, etc.
+ *
* @param threshold for reasons that are due to exceeding some threshold, the threshold value
* must be specified. The unit of the threshold depends on the reason and/or
* subReason. For time, use milliseconds. For memory, use KB. For count, use
- * the actual count or normalized as per-hour. For power, use milliwatts. Etc.
+ * the actual count or if rate limited, normalized per-hour. For power,
+ * use milliwatts. For CPU, use mcycles.
*
* @hide
*/
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/content/PermissionChecker.java b/core/java/android/content/PermissionChecker.java
index 0e3217d..cb8eb83 100644
--- a/core/java/android/content/PermissionChecker.java
+++ b/core/java/android/content/PermissionChecker.java
@@ -73,13 +73,12 @@
public static final int PERMISSION_GRANTED = PermissionCheckerManager.PERMISSION_GRANTED;
/**
- * The permission is denied. Applicable only to runtime and app op permissions.
+ * The permission is denied. Applicable only to runtime permissions.
*
* <p>Returned when:
* <ul>
* <li>the runtime permission is granted, but the corresponding app op is denied
* for runtime permissions.</li>
- * <li>the app ops is ignored for app op permissions.</li>
* </ul>
*
* @hide
diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java
index 55bb430..7e51cb0 100644
--- a/core/java/android/permission/PermissionManager.java
+++ b/core/java/android/permission/PermissionManager.java
@@ -112,7 +112,7 @@
public static final int PERMISSION_GRANTED = 0;
/**
- * The permission is denied. Applicable only to runtime and app op permissions.
+ * The permission is denied. Applicable only to runtime permissions.
* <p>
* The app isn't expecting the permission to be denied so that a "no-op" action should be taken,
* such as returning an empty result.
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/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_maximize_menu.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_maximize_menu.xml
index 9f0a425..9599658 100644
--- a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_maximize_menu.xml
+++ b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_maximize_menu.xml
@@ -23,7 +23,8 @@
android:orientation="horizontal"
android:gravity="center"
android:padding="16dp"
- android:background="@drawable/desktop_mode_maximize_menu_background">
+ android:background="@drawable/desktop_mode_maximize_menu_background"
+ android:elevation="1dp">
<LinearLayout
android:layout_width="wrap_content"
@@ -37,7 +38,8 @@
android:background="@drawable/desktop_mode_maximize_menu_layout_background"
android:padding="4dp"
android:layout_marginRight="8dp"
- android:layout_marginBottom="4dp">
+ android:layout_marginBottom="4dp"
+ android:alpha="0">
<Button
android:id="@+id/maximize_menu_maximize_button"
style="?android:attr/buttonBarButtonStyle"
@@ -48,6 +50,7 @@
</FrameLayout>
<TextView
+ android:id="@+id/maximize_menu_maximize_window_text"
android:layout_width="94dp"
android:layout_height="18dp"
android:textSize="11sp"
@@ -55,7 +58,8 @@
android:gravity="center"
android:fontFamily="google-sans-text"
android:text="@string/desktop_mode_maximize_menu_maximize_text"
- android:textColor="?androidprv:attr/materialColorOnSurface"/>
+ android:textColor="?androidprv:attr/materialColorOnSurface"
+ android:alpha="0"/>
</LinearLayout>
<LinearLayout
@@ -69,7 +73,8 @@
android:orientation="horizontal"
android:padding="4dp"
android:background="@drawable/desktop_mode_maximize_menu_layout_background"
- android:layout_marginBottom="4dp">
+ android:layout_marginBottom="4dp"
+ android:alpha="0">
<Button
android:id="@+id/maximize_menu_snap_left_button"
style="?android:attr/buttonBarButtonStyle"
@@ -88,6 +93,7 @@
android:stateListAnimator="@null"/>
</LinearLayout>
<TextView
+ android:id="@+id/maximize_menu_snap_window_text"
android:layout_width="94dp"
android:layout_height="18dp"
android:textSize="11sp"
@@ -96,6 +102,8 @@
android:gravity="center"
android:fontFamily="google-sans-text"
android:text="@string/desktop_mode_maximize_menu_snap_text"
- android:textColor="?androidprv:attr/materialColorOnSurface"/>
+ android:textColor="?androidprv:attr/materialColorOnSurface"
+ android:alpha="0"/>
</LinearLayout>
-</LinearLayout>
\ No newline at end of file
+</LinearLayout>
+
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index c8bfe7a4..f532f96 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -464,6 +464,9 @@
<!-- The height of the maximize menu in desktop mode. -->
<dimen name="desktop_mode_maximize_menu_height">114dp</dimen>
+ <!-- The padding of the maximize menu in desktop mode. -->
+ <dimen name="desktop_mode_menu_padding">16dp</dimen>
+
<!-- The height of the buttons in the maximize menu. -->
<dimen name="desktop_mode_maximize_menu_button_height">52dp</dimen>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt
index 899b7cc..22f0adc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt
@@ -16,6 +16,9 @@
package com.android.wm.shell.windowdecor
+import android.animation.AnimatorSet
+import android.animation.ObjectAnimator
+import android.animation.ValueAnimator
import android.annotation.IdRes
import android.app.ActivityManager.RunningTaskInfo
import android.content.Context
@@ -30,16 +33,21 @@
import android.view.View.OnClickListener
import android.view.View.OnGenericMotionListener
import android.view.View.OnTouchListener
+import android.view.View.SCALE_Y
+import android.view.View.TRANSLATION_Y
+import android.view.View.TRANSLATION_Z
import android.view.WindowManager
import android.view.WindowlessWindowManager
import android.widget.Button
import android.widget.FrameLayout
import android.widget.LinearLayout
+import android.widget.TextView
import android.window.TaskConstants
import androidx.core.content.withStyledAttributes
import com.android.internal.R.attr.colorAccentPrimary
import com.android.wm.shell.R
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
+import com.android.wm.shell.animation.Interpolators.EMPHASIZED_DECELERATE
import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.common.SyncTransactionQueue
import com.android.wm.shell.windowdecor.WindowDecoration.AdditionalWindow
@@ -65,14 +73,13 @@
private var maximizeMenu: AdditionalWindow? = null
private lateinit var viewHost: SurfaceControlViewHost
private lateinit var leash: SurfaceControl
- private val shadowRadius = loadDimensionPixelSize(
- R.dimen.desktop_mode_maximize_menu_shadow_radius
- ).toFloat()
+ private val openMenuAnimatorSet = AnimatorSet()
private val cornerRadius = loadDimensionPixelSize(
R.dimen.desktop_mode_maximize_menu_corner_radius
).toFloat()
private val menuWidth = loadDimensionPixelSize(R.dimen.desktop_mode_maximize_menu_width)
private val menuHeight = loadDimensionPixelSize(R.dimen.desktop_mode_maximize_menu_height)
+ private val menuPadding = loadDimensionPixelSize(R.dimen.desktop_mode_menu_padding)
private lateinit var snapRightButton: Button
private lateinit var snapLeftButton: Button
@@ -91,10 +98,12 @@
if (maximizeMenu != null) return
createMaximizeMenu()
setupMaximizeMenu()
+ animateOpenMenu()
}
/** Closes the maximize window and releases its view. */
fun close() {
+ openMenuAnimatorSet.cancel()
maximizeMenu?.releaseView()
maximizeMenu = null
}
@@ -134,8 +143,6 @@
// Bring menu to front when open
t.setLayer(leash, TaskConstants.TASK_CHILD_LAYER_FLOATING_MENU)
.setPosition(leash, menuPosition.x, menuPosition.y)
- .setWindowCrop(leash, menuWidth, menuHeight)
- .setShadowRadius(leash, shadowRadius)
.setCornerRadius(leash, cornerRadius)
.show(leash)
maximizeMenu = AdditionalWindow(leash, viewHost, transactionSupplier)
@@ -146,6 +153,77 @@
}
}
+ private fun animateOpenMenu() {
+ val viewHost = maximizeMenu?.mWindowViewHost
+ val maximizeMenuView = viewHost?.view ?: return
+ val maximizeWindowText = maximizeMenuView.requireViewById<TextView>(
+ R.id.maximize_menu_maximize_window_text)
+ val snapWindowText = maximizeMenuView.requireViewById<TextView>(
+ R.id.maximize_menu_snap_window_text)
+
+ openMenuAnimatorSet.playTogether(
+ ObjectAnimator.ofFloat(maximizeMenuView, SCALE_Y, STARTING_MENU_HEIGHT_SCALE, 1f)
+ .apply {
+ duration = MENU_HEIGHT_ANIMATION_DURATION_MS
+ interpolator = EMPHASIZED_DECELERATE
+ },
+ ValueAnimator.ofFloat(STARTING_MENU_HEIGHT_SCALE, 1f)
+ .apply {
+ duration = MENU_HEIGHT_ANIMATION_DURATION_MS
+ interpolator = EMPHASIZED_DECELERATE
+ addUpdateListener {
+ // Animate padding so that controls stay pinned to the bottom of
+ // the menu.
+ val value = animatedValue as Float
+ val topPadding = menuPadding -
+ ((1 - value) * menuHeight).toInt()
+ maximizeMenuView.setPadding(menuPadding, topPadding,
+ menuPadding, menuPadding)
+ }
+ },
+ ValueAnimator.ofFloat(1 / STARTING_MENU_HEIGHT_SCALE, 1f).apply {
+ duration = MENU_HEIGHT_ANIMATION_DURATION_MS
+ interpolator = EMPHASIZED_DECELERATE
+ addUpdateListener {
+ // Scale up the children of the maximize menu so that the menu
+ // scale is cancelled out and only the background is scaled.
+ val value = animatedValue as Float
+ maximizeButtonLayout.scaleY = value
+ snapButtonsLayout.scaleY = value
+ maximizeWindowText.scaleY = value
+ snapWindowText.scaleY = value
+ }
+ },
+ ObjectAnimator.ofFloat(maximizeMenuView, TRANSLATION_Y,
+ (STARTING_MENU_HEIGHT_SCALE - 1) * menuHeight, 0f).apply {
+ duration = MENU_HEIGHT_ANIMATION_DURATION_MS
+ interpolator = EMPHASIZED_DECELERATE
+ },
+ ObjectAnimator.ofInt(maximizeMenuView.background, "alpha",
+ MAX_DRAWABLE_ALPHA_VALUE).apply {
+ duration = ALPHA_ANIMATION_DURATION_MS
+ },
+ ValueAnimator.ofFloat(0f, 1f)
+ .apply {
+ duration = ALPHA_ANIMATION_DURATION_MS
+ startDelay = CONTROLS_ALPHA_ANIMATION_DELAY_MS
+ addUpdateListener {
+ val value = animatedValue as Float
+ maximizeButtonLayout.alpha = value
+ snapButtonsLayout.alpha = value
+ maximizeWindowText.alpha = value
+ snapWindowText.alpha = value
+ }
+ },
+ ObjectAnimator.ofFloat(maximizeMenuView, TRANSLATION_Z, MENU_Z_TRANSLATION)
+ .apply {
+ duration = ELEVATION_ANIMATION_DURATION_MS
+ startDelay = CONTROLS_ALPHA_ANIMATION_DELAY_MS
+ }
+ )
+ openMenuAnimatorSet.start()
+ }
+
private fun loadDimensionPixelSize(resourceId: Int): Int {
return if (resourceId == Resources.ID_NULL) {
0
@@ -263,6 +341,14 @@
}
companion object {
+ // Open menu animation constants
+ private const val ALPHA_ANIMATION_DURATION_MS = 50L
+ private const val MAX_DRAWABLE_ALPHA_VALUE = 255
+ private const val STARTING_MENU_HEIGHT_SCALE = 0.8f
+ private const val MENU_HEIGHT_ANIMATION_DURATION_MS = 300L
+ private const val ELEVATION_ANIMATION_DURATION_MS = 50L
+ private const val CONTROLS_ALPHA_ANIMATION_DELAY_MS = 33L
+ private const val MENU_Z_TRANSLATION = 1f
fun isMaximizeMenuView(@IdRes viewId: Int): Boolean {
return viewId == R.id.maximize_menu ||
viewId == R.id.maximize_menu_maximize_button ||
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/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/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/haptics/qs/QSLongPressEffectViewBinder.kt b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffectViewBinder.kt
index c464ed1..4875f48 100644
--- a/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffectViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffectViewBinder.kt
@@ -30,6 +30,7 @@
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.qs.tileimpl.QSTileViewImpl
import kotlinx.coroutines.DisposableHandle
+import kotlinx.coroutines.flow.filterNotNull
object QSLongPressEffectViewBinder {
@@ -49,63 +50,55 @@
launch({ "${tileSpec ?: "unknownTileSpec"}#LongPressEffect#action" }) {
var effectAnimator: ValueAnimator? = null
- qsLongPressEffect.actionType.collect { action ->
- action?.let {
- when (it) {
- QSLongPressEffect.ActionType.CLICK -> {
- tile.performClick()
- qsLongPressEffect.clearActionType()
- }
- QSLongPressEffect.ActionType.LONG_PRESS -> {
- tile.prepareForLaunch()
- tile.performLongClick()
- qsLongPressEffect.clearActionType()
- }
- QSLongPressEffect.ActionType.RESET_AND_LONG_PRESS -> {
- tile.resetLongPressEffectProperties()
- tile.performLongClick()
- qsLongPressEffect.clearActionType()
- }
- QSLongPressEffect.ActionType.START_ANIMATOR -> {
- if (effectAnimator?.isRunning != true) {
- effectAnimator =
- ValueAnimator.ofFloat(0f, 1f).apply {
- this.duration =
- qsLongPressEffect.effectDuration.toLong()
- interpolator = AccelerateDecelerateInterpolator()
+ qsLongPressEffect.actionType.filterNotNull().collect { action ->
+ when (action) {
+ QSLongPressEffect.ActionType.CLICK -> {
+ tile.performClick()
+ qsLongPressEffect.clearActionType()
+ }
+ QSLongPressEffect.ActionType.LONG_PRESS -> {
+ tile.prepareForLaunch()
+ tile.performLongClick()
+ qsLongPressEffect.clearActionType()
+ }
+ QSLongPressEffect.ActionType.RESET_AND_LONG_PRESS -> {
+ tile.resetLongPressEffectProperties()
+ tile.performLongClick()
+ qsLongPressEffect.clearActionType()
+ }
+ QSLongPressEffect.ActionType.START_ANIMATOR -> {
+ if (effectAnimator?.isRunning != true) {
+ effectAnimator =
+ ValueAnimator.ofFloat(0f, 1f).apply {
+ this.duration =
+ qsLongPressEffect.effectDuration.toLong()
+ interpolator = AccelerateDecelerateInterpolator()
- doOnStart {
- qsLongPressEffect.handleAnimationStart()
+ doOnStart { qsLongPressEffect.handleAnimationStart() }
+ addUpdateListener {
+ val value = animatedValue as Float
+ if (value == 0f) {
+ tile.bringToFront()
+ } else {
+ tile.updateLongPressEffectProperties(value)
}
- addUpdateListener {
- val value = animatedValue as Float
- if (value == 0f) {
- tile.bringToFront()
- } else {
- tile.updateLongPressEffectProperties(value)
- }
- }
- doOnEnd {
- qsLongPressEffect.handleAnimationComplete()
- }
- doOnCancel {
- qsLongPressEffect.handleAnimationCancel()
- }
- start()
}
- }
+ doOnEnd { qsLongPressEffect.handleAnimationComplete() }
+ doOnCancel { qsLongPressEffect.handleAnimationCancel() }
+ start()
+ }
}
- QSLongPressEffect.ActionType.REVERSE_ANIMATOR -> {
- effectAnimator?.let {
- val pausedProgress = it.animatedFraction
- qsLongPressEffect.playReverseHaptics(pausedProgress)
- it.reverse()
- }
+ }
+ QSLongPressEffect.ActionType.REVERSE_ANIMATOR -> {
+ effectAnimator?.let {
+ val pausedProgress = it.animatedFraction
+ qsLongPressEffect.playReverseHaptics(pausedProgress)
+ it.reverse()
}
- QSLongPressEffect.ActionType.CANCEL_ANIMATOR -> {
- tile.resetLongPressEffectProperties()
- effectAnimator?.cancel()
- }
+ }
+ QSLongPressEffect.ActionType.CANCEL_ANIMATOR -> {
+ tile.resetLongPressEffectProperties()
+ effectAnimator?.cancel()
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java
index 00ec1a1..44e795c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java
@@ -187,18 +187,15 @@
return;
}
- // current indication is updated to empty
+ // Current indication is updated to empty.
+ // Update to empty even if `currMsgShownForMinTime` is false.
if (mCurrIndicationType == type
&& !hasNewIndication
&& showAsap) {
- if (currMsgShownForMinTime) {
- if (mShowNextIndicationRunnable != null) {
- mShowNextIndicationRunnable.runImmediately();
- } else {
- showIndication(INDICATION_TYPE_NONE);
- }
+ if (mShowNextIndicationRunnable != null) {
+ mShowNextIndicationRunnable.runImmediately();
} else {
- scheduleShowNextIndication(minShowDuration - timeSinceLastIndicationSwitch);
+ showIndication(INDICATION_TYPE_NONE);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepository.kt
index a49b3ae..4a726ae 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepository.kt
@@ -65,43 +65,23 @@
* @param blueprintId
* @return whether the transition has succeeded.
*/
- fun applyBlueprint(index: Int): Boolean {
- ArrayList(blueprintIdMap.values)[index]?.let {
- applyBlueprint(it)
- return true
- }
- return false
- }
-
- /**
- * Emits the blueprint value to the collectors.
- *
- * @param blueprintId
- * @return whether the transition has succeeded.
- */
fun applyBlueprint(blueprintId: String?): Boolean {
val blueprint = blueprintIdMap[blueprintId]
- return if (blueprint != null) {
- applyBlueprint(blueprint)
- true
- } else {
+ if (blueprint == null) {
Log.e(
TAG,
"Could not find blueprint with id: $blueprintId. " +
"Perhaps it was not added to KeyguardBlueprintModule?"
)
- false
+ return false
}
- }
- /** Emits the blueprint value to the collectors. */
- fun applyBlueprint(blueprint: KeyguardBlueprint?) {
if (blueprint == this.blueprint.value) {
- refreshBlueprint()
- return
+ return true
}
- blueprint?.let { this.blueprint.value = it }
+ this.blueprint.value = blueprint
+ return true
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt
index da4f85e..cf995fa 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt
@@ -82,12 +82,17 @@
}
/**
- * Transitions to a blueprint.
+ * Transitions to a blueprint, or refreshes it if already applied.
*
* @param blueprintId
* @return whether the transition has succeeded.
*/
- fun transitionToBlueprint(blueprintId: String): Boolean {
+ fun transitionOrRefreshBlueprint(blueprintId: String): Boolean {
+ if (blueprintId == blueprint.value.id) {
+ refreshBlueprint()
+ return true
+ }
+
return keyguardBlueprintRepository.applyBlueprint(blueprintId)
}
@@ -97,7 +102,7 @@
* @param blueprintId
* @return whether the transition has succeeded.
*/
- fun transitionToBlueprint(blueprintId: Int): Boolean {
+ fun transitionToBlueprint(blueprintId: String): Boolean {
return keyguardBlueprintRepository.applyBlueprint(blueprintId)
}
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/keyguard/ui/binder/DeviceEntryIconViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt
index b0d45ed..db47bfb 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt
@@ -132,8 +132,12 @@
view.getIconState(viewModel.type, viewModel.useAodVariant),
/* merge */ false
)
- fgIconView.contentDescription =
- fgIconView.resources.getString(viewModel.type.contentDescriptionResId)
+ if (viewModel.type.contentDescriptionResId != -1) {
+ fgIconView.contentDescription =
+ fgIconView.resources.getString(
+ viewModel.type.contentDescriptionResId
+ )
+ }
fgIconView.imageTintList = ColorStateList.valueOf(viewModel.tint)
fgIconView.setPadding(
viewModel.padding,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/DeviceEntryIconView.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/DeviceEntryIconView.kt
index 2735aed..5713a15 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/DeviceEntryIconView.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/DeviceEntryIconView.kt
@@ -214,7 +214,7 @@
R.id.unlocked,
R.id.locked_aod,
context.getDrawable(R.drawable.unlocked_to_aod_lock) as AnimatedVectorDrawable,
- /* reversible */ true,
+ /* reversible */ false,
)
}
@@ -252,6 +252,7 @@
IconType.LOCK -> lockIconState[0] = android.R.attr.state_first
IconType.UNLOCK -> lockIconState[0] = android.R.attr.state_last
IconType.FINGERPRINT -> lockIconState[0] = android.R.attr.state_middle
+ IconType.NONE -> return StateSet.NOTHING
}
if (aod) {
lockIconState[1] = android.R.attr.state_single
@@ -265,6 +266,7 @@
LOCK(R.string.accessibility_lock_icon),
UNLOCK(R.string.accessibility_unlock_button),
FINGERPRINT(R.string.accessibility_fingerprint_label),
+ NONE(-1),
}
enum class AccessibilityHintType {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/KeyguardBlueprintCommandListener.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/KeyguardBlueprintCommandListener.kt
index ce7ec0e..962cdf1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/KeyguardBlueprintCommandListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/KeyguardBlueprintCommandListener.kt
@@ -46,15 +46,14 @@
return
}
- if (
- arg.isDigitsOnly() && keyguardBlueprintInteractor.transitionToBlueprint(arg.toInt())
- ) {
- pw.println("Transition succeeded!")
- } else if (keyguardBlueprintInteractor.transitionToBlueprint(arg)) {
- pw.println("Transition succeeded!")
- } else {
- pw.println("Invalid argument! To see available blueprint ids, run:")
- pw.println("$ adb shell cmd statusbar blueprint help")
+ when {
+ arg.isDigitsOnly() -> pw.println("Invalid argument! Use string ids.")
+ keyguardBlueprintInteractor.transitionOrRefreshBlueprint(arg) ->
+ pw.println("Transition succeeded!")
+ else -> {
+ pw.println("Invalid argument! To see available blueprint ids, run:")
+ pw.println("$ adb shell cmd statusbar blueprint help")
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
index e26b75f..40be73e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
@@ -195,7 +195,14 @@
isUnlocked,
) { isListeningForUdfps, isUnlocked ->
if (isListeningForUdfps) {
- DeviceEntryIconView.IconType.FINGERPRINT
+ if (isUnlocked) {
+ // Don't show any UI until isUnlocked=false. This covers the case
+ // when the "Power button instantly locks > 0s" or the device doesn't lock
+ // immediately after a screen time.
+ DeviceEntryIconView.IconType.NONE
+ } else {
+ DeviceEntryIconView.IconType.FINGERPRINT
+ }
} else if (isUnlocked) {
DeviceEntryIconView.IconType.UNLOCK
} else {
@@ -211,7 +218,8 @@
when (deviceEntryStatus) {
DeviceEntryIconView.IconType.LOCK -> isUdfps
DeviceEntryIconView.IconType.UNLOCK -> true
- DeviceEntryIconView.IconType.FINGERPRINT -> false
+ DeviceEntryIconView.IconType.FINGERPRINT,
+ DeviceEntryIconView.IconType.NONE -> false
}
}
@@ -239,8 +247,8 @@
DeviceEntryIconView.IconType.LOCK ->
DeviceEntryIconView.AccessibilityHintType.AUTHENTICATE
DeviceEntryIconView.IconType.UNLOCK -> DeviceEntryIconView.AccessibilityHintType.ENTER
- DeviceEntryIconView.IconType.FINGERPRINT ->
- DeviceEntryIconView.AccessibilityHintType.NONE
+ DeviceEntryIconView.IconType.FINGERPRINT,
+ DeviceEntryIconView.IconType.NONE -> DeviceEntryIconView.AccessibilityHintType.NONE
}
}
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/KeyguardIndicationRotateTextViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewControllerTest.java
index 39fcd41..5b836b6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewControllerTest.java
@@ -346,6 +346,24 @@
}
@Test
+ public void testStartDozing_withMinShowTime() {
+ // GIVEN a biometric message is showing
+ mController.updateIndication(INDICATION_TYPE_BIOMETRIC_MESSAGE,
+ new KeyguardIndication.Builder()
+ .setMessage("test_message")
+ .setMinVisibilityMillis(5000L)
+ .setTextColor(ColorStateList.valueOf(Color.WHITE))
+ .build(),
+ true);
+
+ // WHEN the device wants to hide the biometric message
+ mController.hideIndication(INDICATION_TYPE_BIOMETRIC_MESSAGE);
+
+ // THEN switch to INDICATION_TYPE_NONE
+ verify(mView).switchIndication(null);
+ }
+
+ @Test
public void testStoppedDozing() {
// GIVEN we're dozing & we have an indication message
mStatusBarStateListener.onDozingChanged(true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryTest.kt
index bcaad01..f5b5261 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryTest.kt
@@ -19,24 +19,20 @@
package com.android.systemui.keyguard.data.repository
-import android.os.fakeExecutorHandler
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.ui.data.repository.ConfigurationRepository
-import com.android.systemui.concurrency.fakeExecutor
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.keyguard.ui.view.layout.blueprints.DefaultKeyguardBlueprint
-import com.android.systemui.keyguard.ui.view.layout.blueprints.DefaultKeyguardBlueprint.Companion.DEFAULT
+import com.android.systemui.keyguard.ui.view.layout.blueprints.SplitShadeKeyguardBlueprint
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testScope
import com.android.systemui.testKosmos
import com.android.systemui.util.ThreadAssert
-import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
@@ -50,31 +46,32 @@
class KeyguardBlueprintRepositoryTest : SysuiTestCase() {
private lateinit var underTest: KeyguardBlueprintRepository
@Mock lateinit var configurationRepository: ConfigurationRepository
- @Mock lateinit var defaultLockscreenBlueprint: DefaultKeyguardBlueprint
@Mock lateinit var threadAssert: ThreadAssert
+
private val testScope = TestScope(StandardTestDispatcher())
private val kosmos: Kosmos = testKosmos()
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
- with(kosmos) {
- whenever(defaultLockscreenBlueprint.id).thenReturn(DEFAULT)
- underTest =
- KeyguardBlueprintRepository(
- setOf(defaultLockscreenBlueprint),
- fakeExecutorHandler,
- threadAssert,
- )
- }
+ underTest = kosmos.keyguardBlueprintRepository
}
@Test
fun testApplyBlueprint_DefaultLayout() {
testScope.runTest {
val blueprint by collectLastValue(underTest.blueprint)
- underTest.applyBlueprint(defaultLockscreenBlueprint)
- assertThat(blueprint).isEqualTo(defaultLockscreenBlueprint)
+ underTest.applyBlueprint(DefaultKeyguardBlueprint.DEFAULT)
+ assertThat(blueprint).isEqualTo(kosmos.defaultKeyguardBlueprint)
+ }
+ }
+
+ @Test
+ fun testApplyBlueprint_SplitShadeLayout() {
+ testScope.runTest {
+ val blueprint by collectLastValue(underTest.blueprint)
+ underTest.applyBlueprint(SplitShadeKeyguardBlueprint.ID)
+ assertThat(blueprint).isEqualTo(kosmos.splitShadeBlueprint)
}
}
@@ -83,33 +80,22 @@
testScope.runTest {
val blueprint by collectLastValue(underTest.blueprint)
underTest.refreshBlueprint()
- assertThat(blueprint).isEqualTo(defaultLockscreenBlueprint)
+ assertThat(blueprint).isEqualTo(kosmos.defaultKeyguardBlueprint)
}
}
@Test
- fun testTransitionToLayout_validId() {
- assertThat(underTest.applyBlueprint(DEFAULT)).isTrue()
+ fun testTransitionToDefaultLayout_validId() {
+ assertThat(underTest.applyBlueprint(DefaultKeyguardBlueprint.DEFAULT)).isTrue()
+ }
+
+ @Test
+ fun testTransitionToSplitShadeLayout_validId() {
+ assertThat(underTest.applyBlueprint(SplitShadeKeyguardBlueprint.ID)).isTrue()
}
@Test
fun testTransitionToLayout_invalidId() {
assertThat(underTest.applyBlueprint("abc")).isFalse()
}
-
- @Test
- fun testTransitionToSameBlueprint_refreshesBlueprint() =
- with(kosmos) {
- testScope.runTest {
- val transition by collectLastValue(underTest.refreshTransition)
- fakeExecutor.runAllReady()
- runCurrent()
-
- underTest.applyBlueprint(defaultLockscreenBlueprint)
- fakeExecutor.runAllReady()
- runCurrent()
-
- assertThat(transition).isNotNull()
- }
- }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/KeyguardBlueprintCommandListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/KeyguardBlueprintCommandListenerTest.kt
index dbf6a29..8a0613f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/KeyguardBlueprintCommandListenerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/KeyguardBlueprintCommandListenerTest.kt
@@ -66,25 +66,19 @@
fun testHelp() {
command().execute(pw, listOf("help"))
verify(pw, atLeastOnce()).println(anyString())
- verify(keyguardBlueprintInteractor, never()).transitionToBlueprint(anyString())
+ verify(keyguardBlueprintInteractor, never()).transitionOrRefreshBlueprint(anyString())
}
@Test
fun testBlank() {
command().execute(pw, listOf())
verify(pw, atLeastOnce()).println(anyString())
- verify(keyguardBlueprintInteractor, never()).transitionToBlueprint(anyString())
+ verify(keyguardBlueprintInteractor, never()).transitionOrRefreshBlueprint(anyString())
}
@Test
fun testValidArg() {
command().execute(pw, listOf("fake"))
- verify(keyguardBlueprintInteractor).transitionToBlueprint("fake")
- }
-
- @Test
- fun testValidArg_Int() {
- command().execute(pw, listOf("1"))
- verify(keyguardBlueprintInteractor).transitionToBlueprint(1)
+ verify(keyguardBlueprintInteractor).transitionOrRefreshBlueprint("fake")
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelTest.kt
index 4bb0d47..0bca367 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelTest.kt
@@ -26,7 +26,7 @@
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
-import com.android.systemui.keyguard.data.repository.keyguardRepository
+import com.android.systemui.keyguard.ui.view.DeviceEntryIconView
import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryIconViewModel.Companion.UNLOCKED_DELAY_MS
import com.android.systemui.kosmos.testScope
import com.android.systemui.testKosmos
@@ -110,6 +110,46 @@
assertThat(isVisible).isTrue()
}
+ @Test
+ fun iconType_fingerprint() =
+ testScope.runTest {
+ val iconType by collectLastValue(underTest.iconType)
+ keyguardRepository.setKeyguardDismissible(false)
+ fingerprintPropertyRepository.supportsUdfps()
+ fingerprintAuthRepository.setIsRunning(true)
+ assertThat(iconType).isEqualTo(DeviceEntryIconView.IconType.FINGERPRINT)
+ }
+
+ @Test
+ fun iconType_locked() =
+ testScope.runTest {
+ val iconType by collectLastValue(underTest.iconType)
+ keyguardRepository.setKeyguardDismissible(false)
+ fingerprintAuthRepository.setIsRunning(false)
+ assertThat(iconType).isEqualTo(DeviceEntryIconView.IconType.LOCK)
+ }
+
+ @Test
+ fun iconType_unlocked() =
+ testScope.runTest {
+ val iconType by collectLastValue(underTest.iconType)
+ keyguardRepository.setKeyguardDismissible(true)
+ advanceTimeBy(UNLOCKED_DELAY_MS * 2) // wait for unlocked delay
+ fingerprintAuthRepository.setIsRunning(false)
+ assertThat(iconType).isEqualTo(DeviceEntryIconView.IconType.UNLOCK)
+ }
+
+ @Test
+ fun iconType_none() =
+ testScope.runTest {
+ val iconType by collectLastValue(underTest.iconType)
+ keyguardRepository.setKeyguardDismissible(true)
+ advanceTimeBy(UNLOCKED_DELAY_MS * 2) // wait for unlocked delay
+ fingerprintPropertyRepository.supportsUdfps()
+ fingerprintAuthRepository.setIsRunning(true)
+ assertThat(iconType).isEqualTo(DeviceEntryIconView.IconType.NONE)
+ }
+
private fun deviceEntryIconTransitionAlpha(alpha: Float) {
deviceEntryIconTransition.setDeviceEntryParentViewAlpha(alpha)
}
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/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/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/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 8ec0e3d..1b3b198 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -20283,7 +20283,7 @@
final int userId = UserHandle.getCallingUserId();
final long callingId = Binder.clearCallingIdentity();
try {
- if (uid == -1) {
+ if (uid == INVALID_UID) {
uid = mPackageManagerInt.getPackageUid(packageName, 0, userId);
}
mAppRestrictionController.noteAppRestrictionEnabled(packageName, uid, restrictionType,
diff --git a/services/core/java/com/android/server/am/AppRestrictionController.java b/services/core/java/com/android/server/am/AppRestrictionController.java
index c5cad14..f5f1928 100644
--- a/services/core/java/com/android/server/am/AppRestrictionController.java
+++ b/services/core/java/com/android/server/am/AppRestrictionController.java
@@ -2387,8 +2387,8 @@
// Limit the length of the free-form subReason string
if (subReason != null && subReason.length() > RESTRICTION_SUBREASON_MAX_LENGTH) {
+ Slog.e(TAG, "subReason is too long, truncating " + subReason);
subReason = subReason.substring(0, RESTRICTION_SUBREASON_MAX_LENGTH);
- Slog.e(TAG, "Subreason is too long, truncating: " + subReason);
}
// Log the restriction reason
diff --git a/services/core/java/com/android/server/apphibernation/AppHibernationService.java b/services/core/java/com/android/server/apphibernation/AppHibernationService.java
index ce41079..e066c23 100644
--- a/services/core/java/com/android/server/apphibernation/AppHibernationService.java
+++ b/services/core/java/com/android/server/apphibernation/AppHibernationService.java
@@ -572,12 +572,8 @@
packageName, uid, ActivityManager.RESTRICTION_LEVEL_FORCE_STOPPED,
true, ActivityManager.RESTRICTION_REASON_DORMANT, null,
/* TODO: fetch actual timeout - 90 days */ 90 * 24 * 60 * 60_000L);
- } else {
- mIActivityManager.noteAppRestrictionEnabled(
- packageName, uid, ActivityManager.RESTRICTION_LEVEL_FORCE_STOPPED,
- false, ActivityManager.RESTRICTION_REASON_USAGE, null,
- 0L);
}
+ // No need to log the unhibernate case as an unstop is logged already in ActivityMS
} catch (RemoteException e) {
Slog.e(TAG, "Couldn't set restriction state change");
}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
index 3e23f97..b709174 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
@@ -21,6 +21,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.UserIdInt;
import android.app.ActivityOptions;
import android.app.PendingIntent;
import android.content.ComponentName;
@@ -63,6 +64,7 @@
/** Time in milliseconds that the IME service has to bind before it is reconnected. */
static final long TIME_TO_RECONNECT = 3 * 1000;
+ @UserIdInt final int mUserId;
@NonNull private final InputMethodManagerService mService;
@NonNull private final Context mContext;
@NonNull private final PackageManagerInternal mPackageManagerInternal;
@@ -107,12 +109,15 @@
| Context.BIND_INCLUDE_CAPABILITIES
| Context.BIND_SHOWING_UI;
- InputMethodBindingController(@NonNull InputMethodManagerService service) {
- this(service, IME_CONNECTION_BIND_FLAGS, null /* latchForTesting */);
+ InputMethodBindingController(@UserIdInt int userId,
+ @NonNull InputMethodManagerService service) {
+ this(userId, service, IME_CONNECTION_BIND_FLAGS, null /* latchForTesting */);
}
- InputMethodBindingController(@NonNull InputMethodManagerService service,
- int imeConnectionBindFlags, CountDownLatch latchForTesting) {
+ InputMethodBindingController(@UserIdInt int userId,
+ @NonNull InputMethodManagerService service, int imeConnectionBindFlags,
+ CountDownLatch latchForTesting) {
+ mUserId = userId;
mService = service;
mContext = mService.mContext;
mPackageManagerInternal = mService.mPackageManagerInternal;
@@ -301,7 +306,8 @@
}
if (DEBUG) Slog.v(TAG, "Initiating attach with token: " + mCurToken);
final InputMethodInfo info =
- mService.queryInputMethodForCurrentUserLocked(mSelectedMethodId);
+ InputMethodSettingsRepository.get(mUserId).getMethodMap().get(
+ mSelectedMethodId);
boolean supportsStylusHwChanged =
mSupportsStylusHw != info.supportsStylusHandwriting();
mSupportsStylusHw = info.supportsStylusHandwriting();
@@ -339,7 +345,7 @@
private void updateCurrentMethodUid() {
final String curMethodPackage = mCurIntent.getComponent().getPackageName();
final int curMethodUid = mPackageManagerInternal.getPackageUid(
- curMethodPackage, 0 /* flags */, mService.getCurrentImeUserIdLocked());
+ curMethodPackage, 0 /* flags */, mUserId);
if (curMethodUid < 0) {
Slog.e(TAG, "Failed to get UID for package=" + curMethodPackage);
mCurMethodUid = Process.INVALID_UID;
@@ -425,7 +431,8 @@
return InputBindResult.NO_IME;
}
- InputMethodInfo info = mService.queryInputMethodForCurrentUserLocked(mSelectedMethodId);
+ InputMethodInfo info = InputMethodSettingsRepository.get(mUserId).getMethodMap().get(
+ mSelectedMethodId);
if (info == null) {
throw new IllegalArgumentException("Unknown id: " + mSelectedMethodId);
}
@@ -497,8 +504,7 @@
Slog.e(TAG, "--- bind failed: service = " + mCurIntent + ", conn = " + conn);
return false;
}
- return mContext.bindServiceAsUser(mCurIntent, conn, flags,
- new UserHandle(mService.getCurrentImeUserIdLocked()));
+ return mContext.bindServiceAsUser(mCurIntent, conn, flags, new UserHandle(mUserId));
}
@GuardedBy("ImfLock.class")
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 8985022e..14d04119 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -205,6 +205,7 @@
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.function.IntConsumer;
+import java.util.function.IntFunction;
/**
* This class provides a system service that manages input methods.
@@ -306,8 +307,6 @@
@MultiUserUnawareField
private final InputMethodMenuController mMenuController;
@MultiUserUnawareField
- @NonNull private final InputMethodBindingController mBindingController;
- @MultiUserUnawareField
@NonNull private final AutofillSuggestionsController mAutofillController;
@GuardedBy("ImfLock.class")
@@ -478,7 +477,8 @@
@GuardedBy("ImfLock.class")
@Nullable
String getSelectedMethodIdLocked() {
- return mBindingController.getSelectedMethodId();
+ final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
+ return userData.mBindingController.getSelectedMethodId();
}
/**
@@ -487,7 +487,8 @@
*/
@GuardedBy("ImfLock.class")
private int getSequenceNumberLocked() {
- return mBindingController.getSequenceNumber();
+ final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
+ return userData.mBindingController.getSequenceNumber();
}
/**
@@ -496,7 +497,8 @@
*/
@GuardedBy("ImfLock.class")
private void advanceSequenceNumberLocked() {
- mBindingController.advanceSequenceNumber();
+ final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
+ userData.mBindingController.advanceSequenceNumber();
}
@GuardedBy("ImfLock.class")
@@ -556,7 +558,8 @@
@GuardedBy("ImfLock.class")
@Nullable
private String getCurIdLocked() {
- return mBindingController.getCurId();
+ final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
+ return userData.mBindingController.getCurId();
}
/**
@@ -580,7 +583,8 @@
*/
@GuardedBy("ImfLock.class")
private boolean hasConnectionLocked() {
- return mBindingController.hasMainConnection();
+ final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
+ return userData.mBindingController.hasMainConnection();
}
/**
@@ -603,7 +607,8 @@
@GuardedBy("ImfLock.class")
@Nullable
private Intent getCurIntentLocked() {
- return mBindingController.getCurIntent();
+ final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
+ return userData.mBindingController.getCurIntent();
}
/**
@@ -613,7 +618,8 @@
@GuardedBy("ImfLock.class")
@Nullable
IBinder getCurTokenLocked() {
- return mBindingController.getCurToken();
+ final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
+ return userData.mBindingController.getCurToken();
}
/**
@@ -654,7 +660,8 @@
@GuardedBy("ImfLock.class")
@Nullable
IInputMethodInvoker getCurMethodLocked() {
- return mBindingController.getCurMethod();
+ final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
+ return userData.mBindingController.getCurMethod();
}
/**
@@ -662,7 +669,8 @@
*/
@GuardedBy("ImfLock.class")
private int getCurMethodUidLocked() {
- return mBindingController.getCurMethodUid();
+ final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
+ return userData.mBindingController.getCurMethodUid();
}
/**
@@ -671,7 +679,8 @@
*/
@GuardedBy("ImfLock.class")
private long getLastBindTimeLocked() {
- return mBindingController.getLastBindTime();
+ final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
+ return userData.mBindingController.getLastBindTime();
}
/**
@@ -1353,7 +1362,7 @@
InputMethodManagerService(
Context context,
@Nullable ServiceThread serviceThreadForTesting,
- @Nullable InputMethodBindingController bindingControllerForTesting) {
+ @Nullable IntFunction<InputMethodBindingController> bindingControllerForTesting) {
synchronized (ImfLock.class) {
mContext = context;
mRes = context.getResources();
@@ -1392,7 +1401,12 @@
AdditionalSubtypeMapRepository.initialize(mHandler, mContext);
mCurrentUserId = mActivityManagerInternal.getCurrentUserId();
- mUserDataRepository = new UserDataRepository(mHandler, mUserManagerInternal);
+ @SuppressWarnings("GuardedBy") final IntFunction<InputMethodBindingController>
+ bindingControllerFactory = userId -> new InputMethodBindingController(userId,
+ InputMethodManagerService.this);
+ mUserDataRepository = new UserDataRepository(mHandler, mUserManagerInternal,
+ bindingControllerForTesting != null ? bindingControllerForTesting
+ : bindingControllerFactory);
for (int id : mUserManagerInternal.getUserIds()) {
mUserDataRepository.getOrCreate(id);
}
@@ -1406,12 +1420,7 @@
new HardwareKeyboardShortcutController(settings.getMethodMap(),
settings.getUserId());
mMenuController = new InputMethodMenuController(this);
- mBindingController =
- bindingControllerForTesting != null
- ? bindingControllerForTesting
- : new InputMethodBindingController(this);
mAutofillController = new AutofillSuggestionsController(this);
-
mVisibilityStateComputer = new ImeVisibilityStateComputer(this);
mVisibilityApplier = new DefaultImeVisibilityApplier(this);
@@ -1544,9 +1553,9 @@
// Note that in b/197848765 we want to see if we can keep the binding alive for better
// profile switching.
- mBindingController.unbindCurrentMethod();
- // TODO(b/325515685): No need to do this once BindingController becomes per-user.
- mBindingController.setSelectedMethodId(null);
+ final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
+ userData.mBindingController.unbindCurrentMethod();
+
unbindCurrentClientLocked(UnbindReason.SWITCH_USER);
// Hereafter we start initializing things for "newUserId".
@@ -1763,9 +1772,10 @@
// Check if selected IME of current user supports handwriting.
if (userId == mCurrentUserId) {
- return mBindingController.supportsStylusHandwriting()
+ final var userData = mUserDataRepository.getOrCreate(userId);
+ return userData.mBindingController.supportsStylusHandwriting()
&& (!connectionless
- || mBindingController.supportsConnectionlessStylusHandwriting());
+ || userData.mBindingController.supportsConnectionlessStylusHandwriting());
}
final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
final InputMethodInfo imi = settings.getMethodMap().get(
@@ -2095,7 +2105,8 @@
curInputMethodInfo != null && curInputMethodInfo.suppressesSpellChecker();
final SparseArray<IAccessibilityInputMethodSession> accessibilityInputMethodSessions =
createAccessibilityInputMethodSessions(mCurClient.mAccessibilitySessions);
- if (mBindingController.supportsStylusHandwriting() && hasSupportedStylusLocked()) {
+ final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
+ if (userData.mBindingController.supportsStylusHandwriting() && hasSupportedStylusLocked()) {
mHwController.setInkWindowInitializer(new InkWindowInitializer());
}
return new InputBindResult(InputBindResult.ResultCode.SUCCESS_WITH_IME_SESSION,
@@ -2216,6 +2227,8 @@
if (connectionIsActive != connectionWasActive) {
mInputManagerInternal.notifyInputMethodConnectionActive(connectionIsActive);
}
+ final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
+
// If configured, we want to avoid starting up the IME if it is not supposed to be showing
if (shouldPreventImeStartupLocked(selectedMethodId, startInputFlags,
@@ -2224,7 +2237,7 @@
Slog.d(TAG, "Avoiding IME startup and unbinding current input method.");
}
invalidateAutofillSessionLocked();
- mBindingController.unbindCurrentMethod();
+ userData.mBindingController.unbindCurrentMethod();
return InputBindResult.NO_EDITOR;
}
@@ -2256,9 +2269,8 @@
}
}
- mBindingController.unbindCurrentMethod();
-
- return mBindingController.bindCurrentMethod();
+ userData.mBindingController.unbindCurrentMethod();
+ return userData.mBindingController.bindCurrentMethod();
}
/**
@@ -2518,11 +2530,13 @@
@GuardedBy("ImfLock.class")
void resetCurrentMethodAndClientLocked(@UnbindReason int unbindClientReason) {
- mBindingController.setSelectedMethodId(null);
+ final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
+ userData.mBindingController.setSelectedMethodId(null);
+
// Callback before clean-up binding states.
// TODO(b/338461930): Check if this is still necessary or not.
onUnbindCurrentMethodByReset();
- mBindingController.unbindCurrentMethod();
+ userData.mBindingController.unbindCurrentMethod();
unbindCurrentClientLocked(unbindClientReason);
}
@@ -3099,7 +3113,8 @@
// mCurMethodId should be updated after setSelectedInputMethodAndSubtypeLocked()
// because mCurMethodId is stored as a history in
// setSelectedInputMethodAndSubtypeLocked().
- mBindingController.setSelectedMethodId(id);
+ final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
+ userData.mBindingController.setSelectedMethodId(id);
if (mActivityManagerInternal.isSystemReady()) {
Intent intent = new Intent(Intent.ACTION_INPUT_METHOD_CHANGED);
@@ -3154,7 +3169,8 @@
@Nullable String delegatorPackageName,
@NonNull IConnectionlessHandwritingCallback callback) {
synchronized (ImfLock.class) {
- if (!mBindingController.supportsConnectionlessStylusHandwriting()) {
+ final var userData = mUserDataRepository.getOrCreate(userId);
+ if (!userData.mBindingController.supportsConnectionlessStylusHandwriting()) {
Slog.w(TAG, "Connectionless stylus handwriting mode unsupported by IME.");
try {
callback.onError(CONNECTIONLESS_HANDWRITING_ERROR_UNSUPPORTED);
@@ -3237,7 +3253,8 @@
}
final long ident = Binder.clearCallingIdentity();
try {
- if (!mBindingController.supportsStylusHandwriting()) {
+ final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
+ if (!userData.mBindingController.supportsStylusHandwriting()) {
Slog.w(TAG,
"Stylus HW unsupported by IME. Ignoring startStylusHandwriting()");
return false;
@@ -3420,7 +3437,8 @@
mVisibilityStateComputer.requestImeVisibility(windowToken, true);
// Ensure binding the connection when IME is going to show.
- mBindingController.setCurrentMethodVisible();
+ final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
+ userData.mBindingController.setCurrentMethodVisible();
final IInputMethodInvoker curMethod = getCurMethodLocked();
ImeTracker.forLogging().onCancelled(mCurStatsToken, ImeTracker.PHASE_SERVER_WAIT_IME);
final boolean readyToDispatchToIme;
@@ -3528,7 +3546,8 @@
} else {
ImeTracker.forLogging().onCancelled(statsToken, ImeTracker.PHASE_SERVER_SHOULD_HIDE);
}
- mBindingController.setCurrentMethodNotVisible();
+ final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
+ userData.mBindingController.setCurrentMethodNotVisible();
mVisibilityStateComputer.clearImeShowFlags();
// Cancel existing statsToken for show IME as we got a hide request.
ImeTracker.forLogging().onCancelled(mCurStatsToken, ImeTracker.PHASE_SERVER_WAIT_IME);
@@ -3810,7 +3829,8 @@
// Note that we can trust client's display ID as long as it matches
// to the display ID obtained from the window.
if (cs.mSelfReportedDisplayId != mCurTokenDisplayId) {
- mBindingController.unbindCurrentMethod();
+ final var userData = mUserDataRepository.getOrCreate(userId);
+ userData.mBindingController.unbindCurrentMethod();
}
}
}
@@ -4271,8 +4291,9 @@
mStylusIds.add(deviceId);
// a new Stylus is detected. If IME supports handwriting, and we don't have
// handwriting initialized, lets do it now.
+ final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
if (!mHwController.getCurrentRequestId().isPresent()
- && mBindingController.supportsStylusHandwriting()) {
+ && userData.mBindingController.supportsStylusHandwriting()) {
scheduleResetStylusHandwriting();
}
}
@@ -4841,7 +4862,8 @@
case MSG_RESET_HANDWRITING: {
synchronized (ImfLock.class) {
- if (mBindingController.supportsStylusHandwriting()
+ final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
+ if (userData.mBindingController.supportsStylusHandwriting()
&& getCurMethodLocked() != null && hasSupportedStylusLocked()) {
Slog.d(TAG, "Initializing Handwriting Spy");
mHwController.initializeHandwritingSpy(mCurTokenDisplayId);
@@ -4866,11 +4888,12 @@
if (curMethod == null || mImeBindingState.mFocusedWindow == null) {
return true;
}
+ final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
final HandwritingModeController.HandwritingSession session =
mHwController.startHandwritingSession(
msg.arg1 /*requestId*/,
msg.arg2 /*pid*/,
- mBindingController.getCurMethodUid(),
+ userData.mBindingController.getCurMethodUid(),
mImeBindingState.mFocusedWindow);
if (session == null) {
Slog.e(TAG,
@@ -5164,7 +5187,8 @@
@GuardedBy("ImfLock.class")
void sendOnNavButtonFlagsChangedLocked() {
- final IInputMethodInvoker curMethod = mBindingController.getCurMethod();
+ final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
+ final IInputMethodInvoker curMethod = userData.mBindingController.getCurMethod();
if (curMethod == null) {
// No need to send the data if the IME is not yet bound.
return;
@@ -5917,9 +5941,10 @@
p.println(" mCurClient=" + client + " mCurSeq=" + getSequenceNumberLocked());
p.println(" mFocusedWindowPerceptible=" + mFocusedWindowPerceptible);
mImeBindingState.dump(" ", p);
+ final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
p.println(" mCurId=" + getCurIdLocked() + " mHaveConnection=" + hasConnectionLocked()
+ " mBoundToMethod=" + mBoundToMethod + " mVisibleBound="
- + mBindingController.isVisibleBound());
+ + userData.mBindingController.isVisibleBound());
p.println(" mCurToken=" + getCurTokenLocked());
p.println(" mCurTokenDisplayId=" + mCurTokenDisplayId);
p.println(" mCurHostInputToken=" + mCurHostInputToken);
@@ -6413,7 +6438,8 @@
if (userId == mCurrentUserId) {
hideCurrentInputLocked(mImeBindingState.mFocusedWindow, 0 /* flags */,
SoftInputShowHideReason.HIDE_RESET_SHELL_COMMAND);
- mBindingController.unbindCurrentMethod();
+ final var userData = mUserDataRepository.getOrCreate(userId);
+ userData.mBindingController.unbindCurrentMethod();
// Enable default IMEs, disable others
var toDisable = settings.getEnabledInputMethodList();
diff --git a/services/core/java/com/android/server/inputmethod/UserDataRepository.java b/services/core/java/com/android/server/inputmethod/UserDataRepository.java
index 7f00229..825cfcb 100644
--- a/services/core/java/com/android/server/inputmethod/UserDataRepository.java
+++ b/services/core/java/com/android/server/inputmethod/UserDataRepository.java
@@ -15,6 +15,7 @@
*/
package com.android.server.inputmethod;
+
import android.annotation.NonNull;
import android.annotation.UserIdInt;
import android.content.pm.UserInfo;
@@ -25,18 +26,21 @@
import com.android.server.pm.UserManagerInternal;
import java.util.function.Consumer;
+import java.util.function.IntFunction;
final class UserDataRepository {
@GuardedBy("ImfLock.class")
private final SparseArray<UserData> mUserData = new SparseArray<>();
+ private final IntFunction<InputMethodBindingController> mBindingControllerFactory;
+
@GuardedBy("ImfLock.class")
@NonNull
UserData getOrCreate(@UserIdInt int userId) {
UserData userData = mUserData.get(userId);
if (userData == null) {
- userData = new UserData(userId);
+ userData = new UserData(userId, mBindingControllerFactory.apply(userId));
mUserData.put(userId, userData);
}
return userData;
@@ -49,7 +53,9 @@
}
}
- UserDataRepository(@NonNull Handler handler, @NonNull UserManagerInternal userManagerInternal) {
+ UserDataRepository(@NonNull Handler handler, @NonNull UserManagerInternal userManagerInternal,
+ @NonNull IntFunction<InputMethodBindingController> bindingControllerFactory) {
+ mBindingControllerFactory = bindingControllerFactory;
userManagerInternal.addUserLifecycleListener(
new UserManagerInternal.UserLifecycleListener() {
@Override
@@ -79,11 +85,16 @@
@UserIdInt
final int mUserId;
- /**
+ @NonNull
+ final InputMethodBindingController mBindingController;
+
+ /**
* Intended to be instantiated only from this file.
*/
- private UserData(@UserIdInt int userId) {
+ private UserData(@UserIdInt int userId,
+ @NonNull InputMethodBindingController bindingController) {
mUserId = userId;
+ mBindingController = bindingController;
}
}
}
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/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index f3e1dfb..5e95a4b 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -4381,7 +4381,8 @@
*/
protected boolean dumpActivity(FileDescriptor fd, PrintWriter pw, String name, String[] args,
int opti, boolean dumpAll, boolean dumpVisibleRootTasksOnly,
- boolean dumpFocusedRootTaskOnly, int displayIdFilter, @UserIdInt int userId) {
+ boolean dumpFocusedRootTaskOnly, int displayIdFilter, @UserIdInt int userId,
+ long timeout) {
ArrayList<ActivityRecord> activities;
synchronized (mGlobalLock) {
@@ -4426,7 +4427,7 @@
}
}
}
- dumpActivity(" ", fd, pw, activities.get(i), newArgs, dumpAll);
+ dumpActivity(" ", fd, pw, activities.get(i), newArgs, dumpAll, timeout);
}
if (!printedAnything) {
// Typically happpens when no task matches displayIdFilter
@@ -4440,7 +4441,7 @@
* there is a thread associated with the activity.
*/
private void dumpActivity(String prefix, FileDescriptor fd, PrintWriter pw,
- ActivityRecord r, String[] args, boolean dumpAll) {
+ ActivityRecord r, String[] args, boolean dumpAll, long timeout) {
String innerPrefix = prefix + " ";
IApplicationThread appThread = null;
synchronized (mGlobalLock) {
@@ -4471,7 +4472,7 @@
pw.flush();
try (TransferPipe tp = new TransferPipe()) {
appThread.dumpActivity(tp.getWriteFd(), r.token, innerPrefix, args);
- tp.go(fd);
+ tp.go(fd, timeout);
} catch (IOException e) {
pw.println(innerPrefix + "Failure while dumping the activity: " + e);
} catch (RemoteException e) {
@@ -6970,7 +6971,8 @@
boolean dumpFocusedRootTaskOnly, int displayIdFilter,
@UserIdInt int userId) {
return ActivityTaskManagerService.this.dumpActivity(fd, pw, name, args, opti, dumpAll,
- dumpVisibleRootTasksOnly, dumpFocusedRootTaskOnly, displayIdFilter, userId);
+ dumpVisibleRootTasksOnly, dumpFocusedRootTaskOnly, displayIdFilter, userId,
+ /* timeout= */ 5000);
}
@Override
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..d0a50d5 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.
@@ -534,6 +539,21 @@
}
@Override
+ public void dumpHigh(FileDescriptor fd, PrintWriter pw, String[] args,
+ boolean asProto) {
+ if (asProto) {
+ return;
+ }
+ mAtmService.dumpActivity(fd, pw, /* name= */ "all", /* args= */ new String[]{},
+ /* opti= */ 0,
+ /* dumpAll= */ true,
+ /* dumpVisibleRootTasksOnly= */ true,
+ /* dumpFocusedRootTaskOnly= */ false, INVALID_DISPLAY, UserHandle.USER_ALL,
+ /* timeout= */ 1000
+ );
+ }
+
+ @Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args, boolean asProto) {
doDump(fd, pw, args, asProto);
}
@@ -2073,7 +2093,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 +5359,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 +8909,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/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 8caf5ae..8755a80 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -1618,7 +1618,8 @@
wm = WindowManagerService.main(context, inputManager, !mFirstBoot,
new PhoneWindowManager(), mActivityManagerService.mActivityTaskManager);
ServiceManager.addService(Context.WINDOW_SERVICE, wm, /* allowIsolated= */ false,
- DUMP_FLAG_PRIORITY_CRITICAL | DUMP_FLAG_PROTO);
+ DUMP_FLAG_PRIORITY_CRITICAL | DUMP_FLAG_PRIORITY_HIGH
+ | DUMP_FLAG_PROTO);
ServiceManager.addService(Context.INPUT_SERVICE, inputManager,
/* allowIsolated= */ false, DUMP_FLAG_PRIORITY_CRITICAL);
t.traceEnd();
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodBindingControllerTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodBindingControllerTest.java
index 1f0a375..70903cb 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodBindingControllerTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodBindingControllerTest.java
@@ -77,9 +77,13 @@
mCountDownLatch = new CountDownLatch(1);
// Remove flag Context.BIND_SCHEDULE_LIKE_TOP_APP because in tests we are not calling
// from system.
- mBindingController =
- new InputMethodBindingController(
- mInputMethodManagerService, mImeConnectionBindFlags, mCountDownLatch);
+ synchronized (ImfLock.class) {
+ mBindingController =
+ new InputMethodBindingController(
+ mInputMethodManagerService.getCurrentImeUserIdLocked(),
+ mInputMethodManagerService, mImeConnectionBindFlags,
+ mCountDownLatch);
+ }
}
@Test
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
index b4cf799..cff2265 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
@@ -222,7 +222,7 @@
Process.THREAD_PRIORITY_FOREGROUND, /* allowIo */
false);
mInputMethodManagerService = new InputMethodManagerService(mContext, mServiceThread,
- mMockInputMethodBindingController);
+ unusedUserId -> mMockInputMethodBindingController);
spyOn(mInputMethodManagerService);
// Start a InputMethodManagerService.Lifecycle to publish and manage the lifecycle of
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/UserDataRepositoryTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/UserDataRepositoryTest.java
index a15b170..c3a87da 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/UserDataRepositoryTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/UserDataRepositoryTest.java
@@ -18,6 +18,7 @@
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;
@@ -38,6 +39,7 @@
import java.util.ArrayList;
import java.util.List;
+import java.util.function.IntFunction;
// This test is designed to run on both device and host (Ravenwood) side.
public final class UserDataRepositoryTest {
@@ -51,19 +53,34 @@
@Mock
private UserManagerInternal mMockUserManagerInternal;
+ @Mock
+ private InputMethodManagerService mMockInputMethodManagerService;
+
private Handler mHandler;
+ private IntFunction<InputMethodBindingController> mBindingControllerFactory;
+
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mHandler = new Handler(Looper.getMainLooper());
+ mBindingControllerFactory = new IntFunction<InputMethodBindingController>() {
+
+ @Override
+ public InputMethodBindingController apply(int userId) {
+ return new InputMethodBindingController(userId, mMockInputMethodManagerService);
+ }
+ };
}
@Test
public void testUserDataRepository_addsNewUserInfoOnUserCreatedEvent() {
// Create UserDataRepository and capture the user lifecycle listener
final var captor = ArgumentCaptor.forClass(UserManagerInternal.UserLifecycleListener.class);
- final var repository = new UserDataRepository(mHandler, mMockUserManagerInternal);
+ final var bindingControllerFactorySpy = spy(mBindingControllerFactory);
+ final var repository = new UserDataRepository(mHandler, mMockUserManagerInternal,
+ bindingControllerFactorySpy);
+
verify(mMockUserManagerInternal, times(1)).addUserLifecycleListener(captor.capture());
final var listener = captor.getValue();
@@ -77,14 +94,20 @@
// Assert UserDataRepository contains the expected UserData
final var allUserData = collectUserData(repository);
assertThat(allUserData).hasSize(1);
- assertThat(allUserData.get(0).mUserId).isEqualTo(userInfo.id);
+ assertThat(allUserData.get(0).mUserId).isEqualTo(ANY_USER_ID);
+
+ // Assert UserDataRepository called the InputMethodBindingController creator function.
+ verify(bindingControllerFactorySpy).apply(ANY_USER_ID);
+ assertThat(allUserData.get(0).mBindingController.mUserId).isEqualTo(ANY_USER_ID);
}
@Test
public void testUserDataRepository_removesUserInfoOnUserRemovedEvent() {
// Create UserDataRepository and capture the user lifecycle listener
final var captor = ArgumentCaptor.forClass(UserManagerInternal.UserLifecycleListener.class);
- final var repository = new UserDataRepository(mHandler, mMockUserManagerInternal);
+ final var repository = new UserDataRepository(mHandler, mMockUserManagerInternal,
+ userId -> new InputMethodBindingController(userId, mMockInputMethodManagerService));
+
verify(mMockUserManagerInternal, times(1)).addUserLifecycleListener(captor.capture());
final var listener = captor.getValue();
@@ -104,7 +127,8 @@
@Test
public void testGetOrCreate() {
- final var repository = new UserDataRepository(mHandler, mMockUserManagerInternal);
+ final var repository = new UserDataRepository(mHandler, mMockUserManagerInternal,
+ mBindingControllerFactory);
synchronized (ImfLock.class) {
final var userData = repository.getOrCreate(ANY_USER_ID);
@@ -114,6 +138,9 @@
final var allUserData = collectUserData(repository);
assertThat(allUserData).hasSize(1);
assertThat(allUserData.get(0).mUserId).isEqualTo(ANY_USER_ID);
+
+ // Assert UserDataRepository called the InputMethodBindingController creator function.
+ assertThat(allUserData.get(0).mBindingController.mUserId).isEqualTo(ANY_USER_ID);
}
private List<UserDataRepository.UserData> collectUserData(UserDataRepository repository) {
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