diff options
24 files changed, 530 insertions, 230 deletions
diff --git a/api/StubLibraries.bp b/api/StubLibraries.bp index b3a674fbd70e..d1aa23c8ea5f 100644 --- a/api/StubLibraries.bp +++ b/api/StubLibraries.bp @@ -466,6 +466,32 @@ java_library { } java_library { + name: "android-non-updatable.stubs.system_server", + defaults: ["android-non-updatable_defaults"], + static_libs: [ + "android-non-updatable.stubs.system_server.from-source", + ], + product_variables: { + build_from_text_stub: { + static_libs: [ + "android-non-updatable.stubs.system_server.from-text", + ], + exclude_static_libs: [ + "android-non-updatable.stubs.system_server.from-source", + ], + }, + }, +} + +java_library { + name: "android-non-updatable.stubs.exportable.system_server", + defaults: ["android-non-updatable_defaults"], + static_libs: [ + "android-non-updatable.stubs.exportable.system_server.from-source", + ], +} + +java_library { name: "android-non-updatable.stubs.from-source", defaults: [ "android-non-updatable_defaults", @@ -561,6 +587,30 @@ java_library { }, } +java_library { + name: "android-non-updatable.stubs.system_server.from-source", + defaults: [ + "android-non-updatable_defaults", + "android-non-updatable_from_source_defaults", + ], + srcs: [":services-non-updatable-stubs"], + libs: non_updatable_api_deps_on_modules, +} + +java_library { + name: "android-non-updatable.stubs.exportable.system_server.from-source", + defaults: [ + "android-non-updatable_defaults", + "android-non-updatable_from_source_defaults", + "android-non-updatable_exportable_from_source_defaults", + ], + srcs: [":services-non-updatable-stubs{.exportable}"], + libs: non_updatable_api_deps_on_modules, + dist: { + dir: "apistubs/android/system-server", + }, +} + java_defaults { name: "android-non-updatable_from_text_defaults", defaults: ["android-non-updatable-stubs-libs-defaults"], @@ -662,6 +712,25 @@ java_api_library { libs: ["all-modules-system-stubs"], } +java_api_library { + name: "android-non-updatable.stubs.system_server.from-text", + api_surface: "system_server", + api_contributions: [ + "api-stubs-docs-non-updatable.api.contribution", + "system-api-stubs-docs-non-updatable.api.contribution", + "module-lib-api-stubs-docs-non-updatable.api.contribution", + "services-non-updatable-stubs.api.contribution", + ], + defaults: [ + "module-classpath-java-defaults", + "android-non-updatable_everything_from_text_defaults", + ], + + // Use full Android API not just the non-updatable API as the latter is incomplete + // and can result in incorrect behavior. + previous_api: ":android.api.combined.system-server.latest", +} + java_defaults { name: "android_stubs_dists_default", dist: { @@ -813,9 +882,9 @@ java_library { defaults: [ "android.jar_defaults", ], - srcs: [":services-non-updatable-stubs"], installable: false, static_libs: [ + "android-non-updatable.stubs.system_server", "android_module_lib_stubs_current", ], visibility: ["//frameworks/base/services"], @@ -827,9 +896,9 @@ java_library { "android.jar_defaults", "android_stubs_dists_default", ], - srcs: [":services-non-updatable-stubs{.exportable}"], installable: false, static_libs: [ + "android-non-updatable.stubs.exportable.system_server", "android_module_lib_stubs_current_exportable", ], dist: { diff --git a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java index 4db9ddf128da..fbc058cc0330 100644 --- a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java +++ b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java @@ -210,6 +210,8 @@ public class PerfettoProtoLogImpl extends IProtoLogClient.Stub implements IProto DataSourceParams .PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_DROP) .build(); + // NOTE: Registering that datasource is an async operation, so there may be no data traced + // for some messages logged right after the construction of this class. mDataSource.register(params); this.mViewerConfigInputStreamProvider = viewerConfigInputStreamProvider; this.mViewerConfigReader = viewerConfigReader; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt index 853284a58904..b8ebbcdbfb9d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt @@ -305,13 +305,18 @@ class DesktopTasksController( private fun getSplitFocusedTask(task1: RunningTaskInfo, task2: RunningTaskInfo) = if (task1.taskId == task2.parentTaskId) task2 else task1 - private fun isFreeformDisplay(displayId: Int): Boolean { + private fun forceEnterDesktop(displayId: Int): Boolean { + if (!DesktopModeStatus.enterDesktopByDefaultOnFreeformDisplay(context)) { + return false + } + val tdaInfo = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(displayId) requireNotNull(tdaInfo) { "This method can only be called with the ID of a display having non-null DisplayArea." } val tdaWindowingMode = tdaInfo.configuration.windowConfiguration.windowingMode - return tdaWindowingMode == WINDOWING_MODE_FREEFORM + val isFreeformDisplay = tdaWindowingMode == WINDOWING_MODE_FREEFORM + return isFreeformDisplay } /** Moves task to desktop mode if task is running, else launches it in desktop mode. */ @@ -1191,10 +1196,11 @@ class DesktopTasksController( val wct = WindowContainerTransaction() if (!isDesktopModeShowing(task.displayId)) { logD("Bring desktop tasks to front on transition=taskId=%d", task.taskId) - // We are outside of desktop mode and already existing desktop task is being launched. - // We should make this task go to fullscreen instead of freeform. Note that this means - // any re-launch of a freeform window outside of desktop will be in fullscreen. - if (taskRepository.isActiveTask(task.taskId)) { + if (taskRepository.isActiveTask(task.taskId) && !forceEnterDesktop(task.displayId)) { + // We are outside of desktop mode and already existing desktop task is being + // launched. We should make this task go to fullscreen instead of freeform. Note + // that this means any re-launch of a freeform window outside of desktop will be in + // fullscreen as long as default-desktop flag is disabled. addMoveToFullscreenChanges(wct, task) return wct } @@ -1231,9 +1237,7 @@ class DesktopTasksController( transition: IBinder ): WindowContainerTransaction? { logV("handleFullscreenTaskLaunch") - val forceEnterDesktop = DesktopModeStatus.enterDesktopByDefaultOnFreeformDisplay(context) && - isFreeformDisplay(task.displayId) - if (isDesktopModeShowing(task.displayId) || forceEnterDesktop) { + if (isDesktopModeShowing(task.displayId) || forceEnterDesktop(task.displayId)) { logD("Switch fullscreen task to freeform on transition: taskId=%d", task.taskId) return WindowContainerTransaction().also { wct -> addMoveToDesktopChanges(wct, task) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java index 6f3f41191485..6eb5cca9ad1a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java @@ -26,6 +26,8 @@ import android.graphics.PointF; import android.graphics.Rect; import android.os.Handler; import android.os.IBinder; +import android.os.Looper; +import android.view.Choreographer; import android.view.Surface; import android.view.SurfaceControl; import android.window.TransitionInfo; @@ -124,6 +126,11 @@ public class VeiledResizeTaskPositioner implements TaskPositioner, Transitions.T @Override public Rect onDragPositioningMove(float x, float y) { + if (Looper.myLooper() != mHandler.getLooper()) { + // This method must run on the shell main thread to use the correct Choreographer + // instance below. + throw new IllegalStateException("This method must run on the shell main thread."); + } PointF delta = DragPositioningCallbackUtility.calculateDelta(x, y, mRepositionStartPoint); if (isResizing() && DragPositioningCallbackUtility.changeBounds(mCtrlType, mRepositionTaskBounds, mTaskBoundsAtDragStart, mStableBounds, delta, @@ -141,6 +148,7 @@ public class VeiledResizeTaskPositioner implements TaskPositioner, Transitions.T final SurfaceControl.Transaction t = mTransactionSupplier.get(); DragPositioningCallbackUtility.setPositionOnDrag(mDesktopWindowDecoration, mRepositionTaskBounds, mTaskBoundsAtDragStart, mRepositionStartPoint, t, x, y); + t.setFrameTimeline(Choreographer.getInstance().getVsyncId()); t.apply(); } return new Rect(mRepositionTaskBounds); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt index e610ebd6bfab..8f20841e76b3 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt @@ -1764,6 +1764,37 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test + fun handleRequest_freeformTask_relaunchTask_enforceDesktop_freeformDisplay_noWinModeChange() { + assumeTrue(ENABLE_SHELL_TRANSITIONS) + whenever(DesktopModeStatus.enterDesktopByDefaultOnFreeformDisplay(context)).thenReturn(true) + val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!! + tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM + + val freeformTask = setUpFreeformTask() + markTaskHidden(freeformTask) + val wct = controller.handleRequest(Binder(), createTransition(freeformTask)) + + assertNotNull(wct, "should handle request") + assertFalse(wct.anyWindowingModeChange(freeformTask.token)) + } + + @Test + fun handleRequest_freeformTask_relaunchTask_enforceDesktop_fullscreenDisplay_becomesUndefined() { + assumeTrue(ENABLE_SHELL_TRANSITIONS) + whenever(DesktopModeStatus.enterDesktopByDefaultOnFreeformDisplay(context)).thenReturn(true) + val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!! + tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN + + val freeformTask = setUpFreeformTask() + markTaskHidden(freeformTask) + val wct = controller.handleRequest(Binder(), createTransition(freeformTask)) + + assertNotNull(wct, "should handle request") + assertThat(wct.changes[freeformTask.token.asBinder()]?.windowingMode) + .isEqualTo(WINDOWING_MODE_UNDEFINED) + } + + @Test @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) fun handleRequest_freeformTask_desktopWallpaperDisabled_freeformNotVisible_reorderedToTop() { assumeTrue(ENABLE_SHELL_TRANSITIONS) @@ -3493,6 +3524,14 @@ private fun WindowContainerTransaction?.anyDensityConfigChange( } ?: false } +private fun WindowContainerTransaction?.anyWindowingModeChange( + token: WindowContainerToken +): Boolean { +return this?.changes?.any { change -> + change.key == token.asBinder() && change.value.windowingMode >= 0 +} ?: false +} + private fun createTaskInfo(id: Int) = RecentTaskInfo().apply { taskId = id diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt index ab41d9c80177..1273ee823159 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt @@ -23,6 +23,7 @@ import android.graphics.Point import android.graphics.Rect import android.os.Handler import android.os.IBinder +import android.os.Looper import android.testing.AndroidTestingRunner import android.view.Display import android.view.Surface.ROTATION_0 @@ -34,6 +35,7 @@ import android.view.WindowManager.TRANSIT_CHANGE import android.window.TransitionInfo import android.window.WindowContainerToken import androidx.test.filters.SmallTest +import androidx.test.internal.runner.junit4.statement.UiThreadStatement.runOnUiThread import com.android.internal.jank.InteractionJankMonitor import com.android.wm.shell.ShellTaskOrganizer import com.android.wm.shell.ShellTestCase @@ -108,8 +110,7 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() { private lateinit var mockResources: Resources @Mock private lateinit var mockInteractionJankMonitor: InteractionJankMonitor - @Mock - private lateinit var mockHandler: Handler + private val mainHandler = Handler(Looper.getMainLooper()) private lateinit var taskPositioner: VeiledResizeTaskPositioner @@ -159,12 +160,12 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() { mockTransactionFactory, mockTransitions, mockInteractionJankMonitor, - mockHandler, + mainHandler, ) } @Test - fun testDragResize_noMove_doesNotShowResizeVeil() { + fun testDragResize_noMove_doesNotShowResizeVeil() = runOnUiThread { taskPositioner.onDragPositioningStart( CTRL_TYPE_TOP or CTRL_TYPE_RIGHT, STARTING_BOUNDS.left.toFloat(), @@ -176,6 +177,7 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() { STARTING_BOUNDS.left.toFloat(), STARTING_BOUNDS.top.toFloat() ) + verify(mockTransitions, never()).startTransition(eq(TRANSIT_CHANGE), argThat { wct -> return@argThat wct.changes.any { (token, change) -> token == taskBinder && @@ -186,7 +188,7 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() { } @Test - fun testDragResize_movesTask_doesNotShowResizeVeil() { + fun testDragResize_movesTask_doesNotShowResizeVeil() = runOnUiThread { taskPositioner.onDragPositioningStart( CTRL_TYPE_UNDEFINED, STARTING_BOUNDS.left.toFloat(), @@ -221,7 +223,7 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() { } @Test - fun testDragResize_resize_boundsUpdateOnEnd() { + fun testDragResize_resize_boundsUpdateOnEnd() = runOnUiThread { taskPositioner.onDragPositioningStart( CTRL_TYPE_RIGHT or CTRL_TYPE_TOP, STARTING_BOUNDS.right.toFloat(), @@ -262,7 +264,7 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() { } @Test - fun testDragResize_noEffectiveMove_skipsTransactionOnEnd() { + fun testDragResize_noEffectiveMove_skipsTransactionOnEnd() = runOnUiThread { taskPositioner.onDragPositioningStart( CTRL_TYPE_TOP or CTRL_TYPE_RIGHT, STARTING_BOUNDS.left.toFloat(), @@ -294,9 +296,8 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() { }) } - @Test - fun testDragResize_drag_setBoundsNotRunIfDragEndsInDisallowedEndArea() { + fun testDragResize_drag_setBoundsNotRunIfDragEndsInDisallowedEndArea() = runOnUiThread { taskPositioner.onDragPositioningStart( CTRL_TYPE_UNDEFINED, // drag STARTING_BOUNDS.left.toFloat(), @@ -321,7 +322,7 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() { } @Test - fun testDragResize_resize_resizingTaskReorderedToTopWhenNotFocused() { + fun testDragResize_resize_resizingTaskReorderedToTopWhenNotFocused() = runOnUiThread { mockDesktopWindowDecoration.mTaskInfo.isFocused = false taskPositioner.onDragPositioningStart( CTRL_TYPE_RIGHT, // Resize right @@ -337,7 +338,7 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() { } @Test - fun testDragResize_resize_resizingTaskNotReorderedToTopWhenFocused() { + fun testDragResize_resize_resizingTaskNotReorderedToTopWhenFocused() = runOnUiThread { mockDesktopWindowDecoration.mTaskInfo.isFocused = true taskPositioner.onDragPositioningStart( CTRL_TYPE_RIGHT, // Resize right @@ -353,7 +354,7 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() { } @Test - fun testDragResize_drag_draggedTaskNotReorderedToTop() { + fun testDragResize_drag_draggedTaskNotReorderedToTop() = runOnUiThread { mockDesktopWindowDecoration.mTaskInfo.isFocused = false taskPositioner.onDragPositioningStart( CTRL_TYPE_UNDEFINED, // drag @@ -370,7 +371,7 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() { } @Test - fun testDragResize_drag_updatesStableBoundsOnRotate() { + fun testDragResize_drag_updatesStableBoundsOnRotate() = runOnUiThread { // Test landscape stable bounds performDrag(STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.bottom.toFloat(), STARTING_BOUNDS.right.toFloat() + 2000, STARTING_BOUNDS.bottom.toFloat() + 2000, @@ -416,7 +417,7 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() { } @Test - fun testIsResizingOrAnimatingResizeSet() { + fun testIsResizingOrAnimatingResizeSet() = runOnUiThread { Assert.assertFalse(taskPositioner.isResizingOrAnimating) taskPositioner.onDragPositioningStart( @@ -443,7 +444,7 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() { } @Test - fun testIsResizingOrAnimatingResizeResetAfterStartAnimation() { + fun testIsResizingOrAnimatingResizeResetAfterStartAnimation() = runOnUiThread { performDrag( STARTING_BOUNDS.left.toFloat(), STARTING_BOUNDS.top.toFloat(), STARTING_BOUNDS.left.toFloat() - 20, STARTING_BOUNDS.top.toFloat() - 20, @@ -457,7 +458,7 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() { } @Test - fun testStartAnimation_useEndRelOffset() { + fun testStartAnimation_useEndRelOffset() = runOnUiThread { val changeMock = mock(TransitionInfo.Change::class.java) val startTransaction = mock(Transaction::class.java) val finishTransaction = mock(Transaction::class.java) diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialog.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialog.kt index 022ddedd1062..265864e1b3fd 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialog.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialog.kt @@ -17,11 +17,16 @@ package com.android.settingslib.spa.widget.dialog import android.content.res.Configuration +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.width import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material3.AlertDialog +import androidx.compose.material3.Button +import androidx.compose.material3.OutlinedButton import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable @@ -32,9 +37,11 @@ import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.window.DialogProperties +import com.android.settingslib.spa.framework.theme.isSpaExpressiveEnabled data class AlertDialogButton( val text: String, @@ -85,27 +92,41 @@ private fun AlertDialogPresenter.SettingsAlertDialog( AlertDialog( onDismissRequest = ::close, modifier = Modifier.width(getDialogWidth()), - confirmButton = { confirmButton?.let { Button(it) } }, - dismissButton = dismissButton?.let { { Button(it) } }, - title = title?.let { { Text(it) } }, - text = text?.let { - { - Column(Modifier.verticalScroll(rememberScrollState())) { - text() - } - } + confirmButton = { + confirmButton?.let { if (isSpaExpressiveEnabled) ConfirmButton(it) else Button(it) } }, + dismissButton = + dismissButton?.let { + { if (isSpaExpressiveEnabled) DismissButton(it) else Button(it) } + }, + title = title?.let { { CenterRow { Text(it) } } }, + text = + text?.let { + { CenterRow { Column(Modifier.verticalScroll(rememberScrollState())) { text() } } } + }, properties = DialogProperties(usePlatformDefaultWidth = false), ) } @Composable +internal fun CenterRow(content: @Composable (() -> Unit)) { + if (isSpaExpressiveEnabled) { + Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center) { + content() + } + } else { + content() + } +} + +@Composable fun getDialogWidth(): Dp { val configuration = LocalConfiguration.current - return configuration.screenWidthDp.dp * when (configuration.orientation) { - Configuration.ORIENTATION_LANDSCAPE -> 0.65f - else -> 0.85f - } + return configuration.screenWidthDp.dp * + when (configuration.orientation) { + Configuration.ORIENTATION_LANDSCAPE -> 0.65f + else -> 0.85f + } } @Composable @@ -120,3 +141,47 @@ private fun AlertDialogPresenter.Button(button: AlertDialogButton) { Text(button.text) } } + +@Composable +private fun AlertDialogPresenter.DismissButton(button: AlertDialogButton) { + OutlinedButton( + onClick = { + close() + button.onClick() + }, + enabled = button.enabled, + ) { + Text(button.text) + } +} + +@Composable +private fun AlertDialogPresenter.ConfirmButton(button: AlertDialogButton) { + Button( + onClick = { + close() + button.onClick() + }, + enabled = button.enabled, + ) { + Text(button.text) + } +} + +@Preview +@Composable +private fun AlertDialogPreview() { + val alertDialogPresenter = remember { + object : AlertDialogPresenter { + override fun open() {} + + override fun close() {} + } + } + alertDialogPresenter.SettingsAlertDialog( + confirmButton = AlertDialogButton("Ok"), + dismissButton = AlertDialogButton("Cancel"), + title = "Title", + text = { Text("Text") }, + ) +} diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialogWithIcon.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialogWithIcon.kt index 030522d73b26..58a83fa72532 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialogWithIcon.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialogWithIcon.kt @@ -40,10 +40,7 @@ fun SettingsAlertDialogWithIcon( dismissButton: AlertDialogButton?, title: String?, icon: @Composable (() -> Unit)? = { - Icon( - Icons.Default.WarningAmber, - contentDescription = null - ) + Icon(Icons.Default.WarningAmber, contentDescription = null) }, text: @Composable (() -> Unit)?, ) { @@ -52,43 +49,22 @@ fun SettingsAlertDialogWithIcon( icon = icon, modifier = Modifier.width(getDialogWidth()), confirmButton = { - confirmButton?.let { - Button( - onClick = { - it.onClick() - }, - ) { - Text(it.text) - } - } + confirmButton?.let { Button(onClick = { it.onClick() }) { Text(it.text) } } }, - dismissButton = dismissButton?.let { - { - OutlinedButton( - onClick = { - it.onClick() - }, - ) { - Text(it.text) + dismissButton = + dismissButton?.let { { OutlinedButton(onClick = { it.onClick() }) { Text(it.text) } } }, + title = + title?.let { + { + CenterRow { + Text(it, modifier = Modifier.fillMaxWidth(), textAlign = TextAlign.Center) + } } - } - }, - title = title?.let { - { - Text( - it, - modifier = Modifier.fillMaxWidth(), - textAlign = TextAlign.Center - ) - } - }, - text = text?.let { - { - Column(Modifier.verticalScroll(rememberScrollState())) { - text() - } - } - }, + }, + text = + text?.let { + { CenterRow { Column(Modifier.verticalScroll(rememberScrollState())) { text() } } } + }, properties = DialogProperties(usePlatformDefaultWidth = false), ) -}
\ No newline at end of file +} diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlterDialogContent.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlterDialogContent.kt index bef0bca1c5a4..9f2210d852a9 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlterDialogContent.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlterDialogContent.kt @@ -58,10 +58,7 @@ fun SettingsAlertDialogContent( dismissButton: AlertDialogButton?, title: String?, icon: @Composable (() -> Unit)? = { - Icon( - Icons.Default.WarningAmber, - contentDescription = null - ) + Icon(Icons.Default.WarningAmber, contentDescription = null) }, text: @Composable (() -> Unit)?, ) { @@ -69,42 +66,22 @@ fun SettingsAlertDialogContent( buttons = { AlertDialogFlowRow( mainAxisSpacing = ButtonsMainAxisSpacing, - crossAxisSpacing = ButtonsCrossAxisSpacing + crossAxisSpacing = ButtonsCrossAxisSpacing, ) { - dismissButton?.let { - OutlinedButton(onClick = it.onClick) { - Text(it.text) - } - } - confirmButton?.let { - Button( - onClick = { - it.onClick() - }, - ) { - Text(it.text) - } - } + dismissButton?.let { OutlinedButton(onClick = it.onClick) { Text(it.text) } } + confirmButton?.let { Button(onClick = { it.onClick() }) { Text(it.text) } } } }, icon = icon, modifier = Modifier.width(getDialogWidth()), - title = title?.let { - { - Text( - it, - modifier = Modifier.fillMaxWidth(), - textAlign = TextAlign.Center - ) - } - }, - text = text?.let { - { - Column(Modifier.verticalScroll(rememberScrollState())) { - text() - } - } - }, + title = + title?.let { + { Text(it, modifier = Modifier.fillMaxWidth(), textAlign = TextAlign.Center) } + }, + text = + text?.let { + { CenterRow { Column(Modifier.verticalScroll(rememberScrollState())) { text() } } } + }, ) } @@ -121,18 +98,12 @@ internal fun SettingsAlertDialogContent( shape = SettingsShape.CornerExtraLarge, color = MaterialTheme.colorScheme.surfaceContainerHigh, ) { - Column( - modifier = Modifier.padding(DialogPadding) - ) { + Column(modifier = Modifier.padding(DialogPadding)) { icon?.let { CompositionLocalProvider( - LocalContentColor provides AlertDialogDefaults.iconContentColor, + LocalContentColor provides AlertDialogDefaults.iconContentColor ) { - Box( - Modifier - .padding(IconPadding) - .align(Alignment.CenterHorizontally) - ) { + Box(Modifier.padding(IconPadding).align(Alignment.CenterHorizontally)) { icon() } } @@ -144,8 +115,7 @@ internal fun SettingsAlertDialogContent( ) { Box( // Align the title to the center when an icon is present. - Modifier - .padding(TitlePadding) + Modifier.padding(TitlePadding) .align( if (icon == null) { Alignment.Start @@ -161,11 +131,10 @@ internal fun SettingsAlertDialogContent( text?.let { ProvideContentColorTextStyle( contentColor = AlertDialogDefaults.textContentColor, - textStyle = MaterialTheme.typography.bodyMedium + textStyle = MaterialTheme.typography.bodyMedium, ) { Box( - Modifier - .weight(weight = 1f, fill = false) + Modifier.weight(weight = 1f, fill = false) .padding(TextPadding) .align(Alignment.Start) ) { @@ -177,7 +146,7 @@ internal fun SettingsAlertDialogContent( ProvideContentColorTextStyle( contentColor = MaterialTheme.colorScheme.primary, textStyle = MaterialTheme.typography.labelLarge, - content = buttons + content = buttons, ) } } @@ -188,7 +157,7 @@ internal fun SettingsAlertDialogContent( internal fun AlertDialogFlowRow( mainAxisSpacing: Dp, crossAxisSpacing: Dp, - content: @Composable () -> Unit + content: @Composable () -> Unit, ) { Layout(content) { measurables, constraints -> val sequences = mutableListOf<List<Placeable>>() @@ -204,8 +173,9 @@ internal fun AlertDialogFlowRow( // Return whether the placeable can be added to the current sequence. fun canAddToCurrentSequence(placeable: Placeable) = - currentSequence.isEmpty() || currentMainAxisSize + mainAxisSpacing.roundToPx() + - placeable.width <= constraints.maxWidth + currentSequence.isEmpty() || + currentMainAxisSize + mainAxisSpacing.roundToPx() + placeable.width <= + constraints.maxWidth // Store current sequence information and start a new sequence. fun startNewSequence() { @@ -213,8 +183,7 @@ internal fun AlertDialogFlowRow( crossAxisSpace += crossAxisSpacing.roundToPx() } // Ensures that confirming actions appear above dismissive actions. - @Suppress("ListIterator") - sequences.add(0, currentSequence.toList()) + @Suppress("ListIterator") sequences.add(0, currentSequence.toList()) crossAxisSizes += currentCrossAxisSize crossAxisPositions += crossAxisSpace @@ -254,23 +223,23 @@ internal fun AlertDialogFlowRow( layout(layoutWidth, layoutHeight) { sequences.fastForEachIndexed { i, placeables -> - val childrenMainAxisSizes = IntArray(placeables.size) { j -> - placeables[j].width + - if (j < placeables.lastIndex) mainAxisSpacing.roundToPx() else 0 - } + val childrenMainAxisSizes = + IntArray(placeables.size) { j -> + placeables[j].width + + if (j < placeables.lastIndex) mainAxisSpacing.roundToPx() else 0 + } val arrangement = Arrangement.End val mainAxisPositions = IntArray(childrenMainAxisSizes.size) { 0 } with(arrangement) { arrange( - mainAxisLayoutSize, childrenMainAxisSizes, - layoutDirection, mainAxisPositions + mainAxisLayoutSize, + childrenMainAxisSizes, + layoutDirection, + mainAxisPositions, ) } placeables.fastForEachIndexed { j, placeable -> - placeable.place( - x = mainAxisPositions[j], - y = crossAxisPositions[i] - ) + placeable.place(x = mainAxisPositions[j], y = crossAxisPositions[i]) } } } @@ -289,8 +258,8 @@ private val ButtonsCrossAxisSpacing = 12.dp /** * ProvideContentColorTextStyle * - * A convenience method to provide values to both LocalContentColor and LocalTextStyle in - * one call. This is less expensive than nesting calls to CompositionLocalProvider. + * A convenience method to provide values to both LocalContentColor and LocalTextStyle in one call. + * This is less expensive than nesting calls to CompositionLocalProvider. * * Text styles will be merged with the current value of LocalTextStyle. */ @@ -298,12 +267,12 @@ private val ButtonsCrossAxisSpacing = 12.dp private fun ProvideContentColorTextStyle( contentColor: Color, textStyle: TextStyle, - content: @Composable () -> Unit + content: @Composable () -> Unit, ) { val mergedStyle = LocalTextStyle.current.merge(textStyle) CompositionLocalProvider( LocalContentColor provides contentColor, LocalTextStyle provides mergedStyle, - content = content + content = content, ) } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingServiceConnection.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingServiceConnection.kt index 7eae5b2a1f5f..3d8ff86c9377 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingServiceConnection.kt +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingServiceConnection.kt @@ -56,11 +56,13 @@ import kotlinx.coroutines.flow.flatMapConcat import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.flow.shareIn import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch +import kotlinx.coroutines.plus import kotlinx.coroutines.withContext @OptIn(ExperimentalCoroutinesApi::class) @@ -101,8 +103,7 @@ class DeviceSettingServiceConnection( } else if (allStatus.all { it is ServiceConnectionStatus.Connected }) { allStatus .filterIsInstance< - ServiceConnectionStatus.Connected< - IDeviceSettingsProviderService> + ServiceConnectionStatus.Connected<IDeviceSettingsProviderService> >() .all { it.service.serviceStatus?.enabled == true } } else { @@ -232,7 +233,7 @@ class DeviceSettingServiceConnection( IDeviceSettingsProviderService.Stub::asInterface, ) .stateIn( - coroutineScope, + coroutineScope.plus(backgroundCoroutineContext), SharingStarted.WhileSubscribed(), ServiceConnectionStatus.Connecting, ) @@ -263,21 +264,30 @@ class DeviceSettingServiceConnection( transform: ((IBinder) -> T), ): Flow<ServiceConnectionStatus<T>> { return callbackFlow { - val serviceConnection = - object : ServiceConnection { - override fun onServiceConnected(name: ComponentName, service: IBinder) { - launch { send(ServiceConnectionStatus.Connected(transform(service))) } - } + val serviceConnection = + object : ServiceConnection { + override fun onServiceConnected(name: ComponentName, service: IBinder) { + launch { send(ServiceConnectionStatus.Connected(transform(service))) } + } - override fun onServiceDisconnected(name: ComponentName?) { - launch { send(ServiceConnectionStatus.Connecting) } + override fun onServiceDisconnected(name: ComponentName?) { + launch { send(ServiceConnectionStatus.Connecting) } + } } + if ( + !context.bindService( + intent, + Context.BIND_AUTO_CREATE, + { launch { it.run() } }, + serviceConnection, + ) + ) { + Log.w(TAG, "Fail to bind service $intent") + launch { send(ServiceConnectionStatus.Failed) } } - if (!context.bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE)) { - launch { send(ServiceConnectionStatus.Failed) } + awaitClose { context.unbindService(serviceConnection) } } - awaitClose { context.unbindService(serviceConnection) } - } + .flowOn(backgroundCoroutineContext) } private suspend fun tryGetEndpointFromMetadata(cachedDevice: CachedBluetoothDevice): EndPoint? = diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepositoryTest.kt b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepositoryTest.kt index 81b56343ceed..0cb6bc1b1261 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepositoryTest.kt +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepositoryTest.kt @@ -102,9 +102,9 @@ class DeviceSettingRepositoryTest { `when`(settingProviderService2.queryLocalInterface(anyString())) .thenReturn(settingProviderService2) - `when`(context.bindService(any(), any(), anyInt())).then { input -> + `when`(context.bindService(any(), anyInt(), any(), any())).then { input -> val intent = input.getArgument<Intent?>(0) - val connection = input.getArgument<ServiceConnection>(1) + val connection = input.getArgument<ServiceConnection>(3) when (intent?.action) { CONFIG_SERVICE_INTENT_ACTION -> @@ -210,7 +210,7 @@ class DeviceSettingRepositoryTest { fun getDeviceSettingsConfig_bindingServiceFail_returnNull() { testScope.runTest { `when`(configService.getDeviceSettingsConfig(any())).thenReturn(DEVICE_SETTING_CONFIG) - doReturn(false).`when`(context).bindService(any(), any(), anyInt()) + doReturn(false).`when`(context).bindService(any(), anyInt(), any(), any()) val config = underTest.getDeviceSettingsConfig(cachedDevice) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaControlInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaControlInteractorTest.kt index d594f3a2f932..62d06254e541 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaControlInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaControlInteractorTest.kt @@ -121,7 +121,7 @@ class MediaControlInteractorTest : SysuiTestCase() { MediaData( userId = USER_ID, instanceId = InstanceId.fakeInstanceId(2), - artist = ARTIST + artist = ARTIST, ) mediaDataFilter.onMediaDataLoaded(KEY, KEY, mediaData) @@ -145,10 +145,17 @@ class MediaControlInteractorTest : SysuiTestCase() { val clickIntent = mock<PendingIntent> { whenever(it.isActivity).thenReturn(true) } val expandable = mock<Expandable>() + val activityController = mock<ActivityTransitionAnimator.Controller>() + whenever(expandable.activityTransitionController(any())).thenReturn(activityController) underTest.startClickIntent(expandable, clickIntent, SMARTSPACE_CARD_CLICK_EVENT, 1) - verify(clickIntent).send(any<Bundle>()) + verify(activityStarter) + .startPendingIntentMaybeDismissingKeyguard( + eq(clickIntent), + eq(null), + eq(activityController), + ) } @Test @@ -174,7 +181,7 @@ class MediaControlInteractorTest : SysuiTestCase() { mediaData.appUid, surface = SURFACE, cardinality = 2, - rank = 1 + rank = 1, ) verify(activityStarter) .postStartActivityDismissingKeyguard(eq(clickIntent), eq(activityController)) @@ -232,7 +239,7 @@ class MediaControlInteractorTest : SysuiTestCase() { eq(true), eq(dialogTransitionController), eq(null), - eq(null) + eq(null), ) } @@ -248,7 +255,7 @@ class MediaControlInteractorTest : SysuiTestCase() { .createBroadcastDialogWithController( eq(APP_NAME), eq(PACKAGE_NAME), - eq(dialogTransitionController) + eq(dialogTransitionController), ) } @@ -279,7 +286,7 @@ class MediaControlInteractorTest : SysuiTestCase() { anyInt(), anyInt(), anyInt(), - anyBoolean() + anyBoolean(), ) verify(listener).onMediaDataRemoved(eq(KEY), eq(true)) } @@ -307,7 +314,7 @@ class MediaControlInteractorTest : SysuiTestCase() { mediaData.appUid, surface = SURFACE, cardinality = 2, - rank = 1 + rank = 1, ) verify(listener).onMediaDataRemoved(eq(KEY), eq(true)) } diff --git a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java index 8f913ff01337..78a8a42e2743 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java @@ -113,6 +113,7 @@ import android.view.accessibility.CaptioningManager; import android.view.inputmethod.InputMethodManager; import android.view.textclassifier.TextClassificationManager; +import androidx.annotation.NonNull; import androidx.asynclayoutinflater.view.AsyncLayoutInflater; import androidx.core.app.NotificationManagerCompat; @@ -718,6 +719,19 @@ public class FrameworkServicesModule { @Provides @Singleton + static ViewCaptureAwareWindowManager.Factory viewCaptureAwareWindowManagerFactory( + Lazy<ViewCapture> daggerLazyViewCapture) { + return new ViewCaptureAwareWindowManager.Factory() { + @NonNull + @Override + public ViewCaptureAwareWindowManager create(@NonNull WindowManager windowManager) { + return provideViewCaptureAwareWindowManager(windowManager, daggerLazyViewCapture); + } + }; + } + + @Provides + @Singleton static PermissionManager providePermissionManager(Context context) { PermissionManager pm = context.getSystemService(PermissionManager.class); if (pm != null) { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index 1a0525d97d30..ba23eb341b89 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -346,6 +346,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, private boolean mShuttingDown; private boolean mDozing; private boolean mAnimatingScreenOff; + private boolean mIgnoreDismiss; private final Context mContext; private final FalsingCollector mFalsingCollector; @@ -627,18 +628,20 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, public void onUserSwitching(int userId) { Log.d(TAG, String.format("onUserSwitching %d", userId)); synchronized (KeyguardViewMediator.this) { + mIgnoreDismiss = true; notifyTrustedChangedLocked(mUpdateMonitor.getUserHasTrust(userId)); resetKeyguardDonePendingLocked(); - dismiss(null /* callback */, null /* message */); + resetStateLocked(); adjustStatusBarLocked(); } } @Override public void onUserSwitchComplete(int userId) { + mIgnoreDismiss = false; Log.d(TAG, String.format("onUserSwitchComplete %d", userId)); - // We are calling dismiss again and with a delay as there are race conditions - // in some scenarios caused by async layout listeners + // We are calling dismiss with a delay as there are race conditions in some scenarios + // caused by async layout listeners mHandler.postDelayed(() -> dismiss(null /* callback */, null /* message */), 500); } @@ -2442,6 +2445,10 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, } public void dismiss(IKeyguardDismissCallback callback, CharSequence message) { + if (mIgnoreDismiss) { + android.util.Log.i(TAG, "Ignoring request to dismiss (user switch in progress?)"); + return; + } mHandler.obtainMessage(DISMISS, new DismissMessage(callback, message)).sendToTarget(); } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaControlInteractor.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaControlInteractor.kt index e34ff7b1028c..1f339dddd4d1 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaControlInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaControlInteractor.kt @@ -125,7 +125,7 @@ constructor( location: Int, ) { logSmartspaceUserEvent(eventId, location) - if (!launchOverLockscreen(clickIntent)) { + if (!launchOverLockscreen(expandable, clickIntent)) { activityStarter.postStartActivityDismissingKeyguard( clickIntent, expandable.activityTransitionController(Cuj.CUJ_SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER), @@ -135,7 +135,7 @@ constructor( fun startDeviceIntent(deviceIntent: PendingIntent) { if (deviceIntent.isActivity) { - if (!launchOverLockscreen(deviceIntent)) { + if (!launchOverLockscreen(expandable = null, deviceIntent)) { activityStarter.postStartActivityDismissingKeyguard(deviceIntent) } } else { @@ -143,7 +143,10 @@ constructor( } } - private fun launchOverLockscreen(pendingIntent: PendingIntent): Boolean { + private fun launchOverLockscreen( + expandable: Expandable?, + pendingIntent: PendingIntent, + ): Boolean { val showOverLockscreen = keyguardStateController.isShowing && activityIntentHelper.wouldPendingShowOverLockscreen( @@ -152,11 +155,21 @@ constructor( ) if (showOverLockscreen) { try { - val options = BroadcastOptions.makeBasic() - options.isInteractive = true - options.pendingIntentBackgroundActivityStartMode = - ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED - pendingIntent.send(options.toBundle()) + if (expandable != null) { + activityStarter.startPendingIntentMaybeDismissingKeyguard( + pendingIntent, + /* intentSentUiThreadCallback = */ null, + expandable.activityTransitionController( + Cuj.CUJ_SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER + ), + ) + } else { + val options = BroadcastOptions.makeBasic() + options.isInteractive = true + options.pendingIntentBackgroundActivityStartMode = + ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED + pendingIntent.send(options.toBundle()) + } } catch (e: PendingIntent.CanceledException) { Log.e(TAG, "pending intent of $instanceId was canceled") } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt index 526c64c15696..55943a527a8c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt @@ -16,6 +16,8 @@ package com.android.systemui.statusbar.dagger +import android.content.Context +import com.android.app.viewcapture.ViewCaptureAwareWindowManager import com.android.systemui.CoreStartable import com.android.systemui.dagger.SysUISingleton import com.android.systemui.log.LogBuffer @@ -63,12 +65,18 @@ abstract class StatusBarModule { @Binds abstract fun statusBarInitializer(impl: StatusBarInitializerImpl): StatusBarInitializer - @Binds - abstract fun statusBarWindowController( - impl: StatusBarWindowControllerImpl - ): StatusBarWindowController - companion object { + + @Provides + @SysUISingleton + fun statusBarWindowController( + context: Context?, + viewCaptureAwareWindowManager: ViewCaptureAwareWindowManager?, + factory: StatusBarWindowControllerImpl.Factory, + ): StatusBarWindowController { + return factory.create(context, viewCaptureAwareWindowManager) + } + @Provides @SysUISingleton @OngoingCallLog diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerImpl.java index 1a0327cdd809..1ee7cf3490f4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerImpl.java @@ -28,7 +28,6 @@ import static com.android.systemui.util.leak.RotationUtils.ROTATION_SEASCAPE; import static com.android.systemui.util.leak.RotationUtils.ROTATION_UPSIDE_DOWN; import android.content.Context; -import android.content.res.Resources; import android.graphics.Insets; import android.graphics.PixelFormat; import android.graphics.Rect; @@ -52,23 +51,23 @@ import com.android.app.viewcapture.ViewCaptureAwareWindowManager; import com.android.internal.policy.SystemBarUtils; import com.android.systemui.animation.ActivityTransitionAnimator; import com.android.systemui.animation.DelegateTransitionAnimatorController; -import com.android.systemui.dagger.SysUISingleton; -import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.fragments.FragmentHostManager; import com.android.systemui.fragments.FragmentService; import com.android.systemui.res.R; import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider; +import com.android.systemui.statusbar.window.StatusBarWindowModule.InternalWindowViewInflater; import com.android.systemui.unfold.UnfoldTransitionProgressProvider; import com.android.systemui.unfold.util.JankMonitorTransitionProgressListener; -import java.util.Optional; +import dagger.assisted.Assisted; +import dagger.assisted.AssistedFactory; +import dagger.assisted.AssistedInject; -import javax.inject.Inject; +import java.util.Optional; /** * Encapsulates all logic for the status bar window state management. */ -@SysUISingleton public class StatusBarWindowControllerImpl implements StatusBarWindowController { private static final String TAG = "StatusBarWindowController"; private static final boolean DEBUG = false; @@ -90,21 +89,20 @@ public class StatusBarWindowControllerImpl implements StatusBarWindowController private final WindowManager.LayoutParams mLpChanged; private final Binder mInsetsSourceOwner = new Binder(); - @Inject + @AssistedInject public StatusBarWindowControllerImpl( - Context context, - @StatusBarWindowModule.InternalWindowView StatusBarWindowView statusBarWindowView, - ViewCaptureAwareWindowManager viewCaptureAwareWindowManager, + @Assisted Context context, + @InternalWindowViewInflater StatusBarWindowViewInflater statusBarWindowViewInflater, + @Assisted ViewCaptureAwareWindowManager viewCaptureAwareWindowManager, IWindowManager iWindowManager, StatusBarContentInsetsProvider contentInsetsProvider, FragmentService fragmentService, - @Main Resources resources, Optional<UnfoldTransitionProgressProvider> unfoldTransitionProgressProvider) { mContext = context; mWindowManager = viewCaptureAwareWindowManager; mIWindowManager = iWindowManager; mContentInsetsProvider = contentInsetsProvider; - mStatusBarWindowView = statusBarWindowView; + mStatusBarWindowView = statusBarWindowViewInflater.inflate(context); mFragmentService = fragmentService; mLaunchAnimationContainer = mStatusBarWindowView.findViewById( R.id.status_bar_launch_animation_container); @@ -354,4 +352,13 @@ public class StatusBarWindowControllerImpl implements StatusBarWindowController mLpChanged.forciblyShownTypes &= ~WindowInsets.Type.statusBars(); } } + + @AssistedFactory + public interface Factory { + /** Creates a new instance. */ + StatusBarWindowControllerImpl create( + Context context, + ViewCaptureAwareWindowManager viewCaptureAwareWindowManager); + } + } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowModule.kt index 1c7debcdf39d..ebfbac7be916 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowModule.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowModule.kt @@ -1,47 +1,36 @@ package com.android.systemui.statusbar.window -import android.view.LayoutInflater -import com.android.systemui.res.R import com.android.systemui.dagger.SysUISingleton +import dagger.Binds import dagger.Module -import dagger.Provides import javax.inject.Qualifier /** Module providing dependencies related to the status bar window. */ @Module abstract class StatusBarWindowModule { + /** - * Provides a [StatusBarWindowView]. + * Binds a [StatusBarWindowViewInflater]. * - * Only [StatusBarWindowController] should inject the view. + * Only [StatusBarWindowControllerImpl] should inject it. */ - @Module - companion object { - @JvmStatic - @Provides - @SysUISingleton - @InternalWindowView - fun providesStatusBarWindowView(layoutInflater: LayoutInflater): StatusBarWindowView { - return layoutInflater.inflate( - R.layout.super_status_bar, - /* root= */null - ) as StatusBarWindowView? - ?: throw IllegalStateException( - "R.layout.super_status_bar could not be properly inflated" - ) - } - } + @Binds + @SysUISingleton + @InternalWindowViewInflater + abstract fun providesStatusBarWindowViewInflater( + inflaterImpl: StatusBarWindowViewInflaterImpl + ): StatusBarWindowViewInflater /** - * We want [StatusBarWindowView] to be provided to [StatusBarWindowController]'s constructor via - * dagger so that we can provide a fake window view when testing the controller. However, we wan - * want *only* the controller to be able to inject the window view. + * We want [StatusBarWindowViewInflater] to be provided to [StatusBarWindowControllerImpl]'s + * constructor via dagger so that we can provide a fake window view when testing the controller. + * However, we wan want *only* the controller to be able to inject the window view. * - * This protected qualifier annotation achieves this. [StatusBarWindowView] can only be injected - * if it's annotated with [InternalWindowView], and only classes inside this [statusbar.window] - * package can access the annotation. + * This protected qualifier annotation achieves this. [StatusBarWindowViewInflater] can only be + * injected if it's annotated with [InternalWindowViewInflater], and only classes inside this + * [statusbar.window] package can access the annotation. */ @Retention(AnnotationRetention.BINARY) @Qualifier - protected annotation class InternalWindowView + protected annotation class InternalWindowViewInflater } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowViewInflater.kt b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowViewInflater.kt new file mode 100644 index 000000000000..f030a4ac0d0e --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowViewInflater.kt @@ -0,0 +1,42 @@ +/* + * 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.window + +import android.content.Context +import android.view.LayoutInflater +import com.android.systemui.res.R +import javax.inject.Inject + +/** + * Inflates a [StatusBarWindowView]. Exists so that it can be injected into + * [StatusBarWindowControllerImpl] and be swapped for a fake implementation in tests. + */ +interface StatusBarWindowViewInflater { + fun inflate(context: Context): StatusBarWindowView +} + +class StatusBarWindowViewInflaterImpl @Inject constructor() : StatusBarWindowViewInflater { + + override fun inflate(context: Context): StatusBarWindowView { + val layoutInflater = LayoutInflater.from(context) + return layoutInflater.inflate(R.layout.super_status_bar, /* root= */ null) + as StatusBarWindowView? + ?: throw IllegalStateException( + "R.layout.super_status_bar could not be properly inflated" + ) + } +} diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 3d5b2732e948..6009b4a70e3e 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -6218,7 +6218,8 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { public void onProcessRemoved(String name, int uid) { synchronized (mGlobalLockWithoutBoost) { final WindowProcessController proc = mProcessNames.remove(name, uid); - if (proc != null && !mStartingProcessActivities.isEmpty()) { + if (proc != null && !proc.mHasEverAttached + && !mStartingProcessActivities.isEmpty()) { // Use a copy in case finishIfPossible changes the list indirectly. final ArrayList<ActivityRecord> activities = new ArrayList<>(mStartingProcessActivities); diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index 8f5612c61e1c..84072e26761a 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -1841,6 +1841,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent> } boolean attachApplication(WindowProcessController app) throws RemoteException { + app.mHasEverAttached = true; final ArrayList<ActivityRecord> activities = mService.mStartingProcessActivities; RemoteException remoteException = null; boolean hasActivityStarted = false; diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java index 30d6f0a46bae..32fe303b9e90 100644 --- a/services/core/java/com/android/server/wm/WindowProcessController.java +++ b/services/core/java/com/android/server/wm/WindowProcessController.java @@ -204,6 +204,9 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio // Set to true when process was launched with a wrapper attached private volatile boolean mUsingWrapper; + /** Whether this process has ever completed ActivityThread#handleBindApplication. */ + boolean mHasEverAttached; + /** Non-null if this process may have a window. */ @Nullable Session mWindowSession; diff --git a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java index 957b5e04fef6..ae0c6e551246 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java @@ -331,6 +331,7 @@ public class RootWindowContainerTests extends WindowTestsBase { final WindowProcessController proc = mSystemServicesTestRule.addProcess( activity.packageName, activity.processName, 6789 /* pid */, activity.info.applicationInfo.uid); + assertFalse(proc.mHasEverAttached); try { mRootWindowContainer.attachApplication(proc); verify(mSupervisor).realStartActivityLocked(eq(topActivity), eq(proc), @@ -338,6 +339,15 @@ public class RootWindowContainerTests extends WindowTestsBase { } catch (RemoteException e) { e.rethrowAsRuntimeException(); } + + // Verify that onProcessRemoved won't clear the launching activities if an attached process + // is died. Because in real case, it should be handled from WindowProcessController's + // and ActivityRecord's handleAppDied to decide whether to remove the activities. + assertTrue(proc.mHasEverAttached); + assertTrue(mAtm.mStartingProcessActivities.isEmpty()); + mAtm.mStartingProcessActivities.add(activity); + mAtm.mInternal.onProcessRemoved(proc.mName, proc.mUid); + assertFalse(mAtm.mStartingProcessActivities.isEmpty()); } /** diff --git a/tests/Tracing/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java b/tests/Tracing/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java index c882b4e569a1..cfb2645dee0d 100644 --- a/tests/Tracing/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java +++ b/tests/Tracing/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java @@ -16,6 +16,8 @@ package com.android.internal.protolog; +import static android.tools.traces.Utils.executeShellCommand; + import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; @@ -48,6 +50,7 @@ import com.android.internal.protolog.common.LogDataType; import com.android.internal.protolog.common.LogLevel; import com.google.common.truth.Truth; +import com.google.protobuf.InvalidProtocolBufferException; import org.junit.After; import org.junit.Before; @@ -57,12 +60,14 @@ import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import org.mockito.Mockito; +import perfetto.protos.PerfettoConfig.TracingServiceState; import perfetto.protos.Protolog; import perfetto.protos.ProtologCommon; import java.io.File; import java.io.IOException; import java.util.List; +import java.util.Optional; import java.util.Random; import java.util.concurrent.atomic.AtomicInteger; @@ -178,6 +183,8 @@ public class PerfettoProtoLogImplTest { viewerConfigInputStreamProvider, sReader, () -> sCacheUpdater.run(), TestProtoLogGroup.values(), dataSourceBuilder, sProtoLogConfigurationService); } + + waitDataSourceIsAvailable(); } @Before @@ -863,6 +870,54 @@ public class PerfettoProtoLogImplTest { .isEqualTo("This message should also be logged 567"); } + private static void waitDataSourceIsAvailable() { + final int timeoutMs = 10000; + final int busyWaitIntervalMs = 100; + + int elapsedMs = 0; + + while (!isDataSourceAvailable()) { + SystemClock.sleep(busyWaitIntervalMs); + elapsedMs += busyWaitIntervalMs; + if (elapsedMs >= timeoutMs) { + throw new RuntimeException("Data source didn't become available." + + " Waited for: " + timeoutMs + " ms"); + } + } + } + + private static boolean isDataSourceAvailable() { + byte[] proto = executeShellCommand("perfetto --query-raw"); + + try { + TracingServiceState state = TracingServiceState.parseFrom(proto); + + Optional<Integer> producerId = Optional.empty(); + + for (TracingServiceState.Producer producer : state.getProducersList()) { + if (producer.getPid() == android.os.Process.myPid()) { + producerId = Optional.of(producer.getId()); + break; + } + } + + if (producerId.isEmpty()) { + return false; + } + + for (TracingServiceState.DataSource ds : state.getDataSourcesList()) { + if (ds.getDsDescriptor().getName().equals(TEST_PROTOLOG_DATASOURCE_NAME) + && ds.getProducerId() == producerId.get()) { + return true; + } + } + } catch (InvalidProtocolBufferException e) { + throw new RuntimeException(e); + } + + return false; + } + private enum TestProtoLogGroup implements IProtoLogGroup { TEST_GROUP(true, true, false, "TEST_TAG"); |