diff options
| author | 2025-03-05 07:32:01 -0800 | |
|---|---|---|
| committer | 2025-03-05 07:32:01 -0800 | |
| commit | d576dacb9263aafb51acd1718ad58b484ba1f6f1 (patch) | |
| tree | d805038bcd3dd7bbba8e2c567d2395865f81c2e6 | |
| parent | a1273fc2349227d26cf496e1279e5d1f2cb0fef3 (diff) | |
| parent | 1f1db489dab169fadf3ed06ac40bb723d48cefc2 (diff) | |
Merge "Add a crash handler to run on Init" into main
7 files changed, 292 insertions, 31 deletions
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/HomeIntentProvider.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/HomeIntentProvider.kt new file mode 100644 index 000000000000..8751b65dce94 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/HomeIntentProvider.kt @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2025 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.wm.shell.common + +import android.app.ActivityManager +import android.app.ActivityOptions +import android.app.PendingIntent +import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN +import android.content.Context +import android.content.Intent +import android.os.UserHandle +import android.view.Display.DEFAULT_DISPLAY +import android.window.WindowContainerTransaction +import com.android.window.flags.Flags + +/** Creates home intent **/ +class HomeIntentProvider( + private val context: Context, +) { + fun addLaunchHomePendingIntent( + wct: WindowContainerTransaction, displayId: Int, userId: Int? = null + ) { + val userHandle = + if (userId != null) UserHandle.of(userId) else UserHandle.of(ActivityManager.getCurrentUser()) + + val launchHomeIntent = Intent(Intent.ACTION_MAIN).apply { + if (displayId != DEFAULT_DISPLAY) { + addCategory(Intent.CATEGORY_SECONDARY_HOME) + } else { + addCategory(Intent.CATEGORY_HOME) + } + } + val options = ActivityOptions.makeBasic().apply { + launchWindowingMode = WINDOWING_MODE_FULLSCREEN + pendingIntentBackgroundActivityStartMode = + ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS + if (Flags.enablePerDisplayDesktopWallpaperActivity()) { + launchDisplayId = displayId + } + } + val pendingIntent = PendingIntent.getActivityAsUser( + context, + /* requestCode= */ 0, + launchHomeIntent, + PendingIntent.FLAG_IMMUTABLE, + /* options= */ null, + userHandle, + ) + wct.sendPendingIntent(pendingIntent, launchHomeIntent, options.toBundle()) + } +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/crashhandling/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/crashhandling/OWNERS new file mode 100644 index 000000000000..007528e2e054 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/crashhandling/OWNERS @@ -0,0 +1,2 @@ +# WM shell sub-module crash handling owners +uysalorhan@google.com
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/crashhandling/ShellCrashHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/crashhandling/ShellCrashHandler.kt new file mode 100644 index 000000000000..2e34d043cbe1 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/crashhandling/ShellCrashHandler.kt @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2025 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.wm.shell.crashhandling + +import android.app.WindowConfiguration +import android.content.Context +import android.view.Display.DEFAULT_DISPLAY +import android.window.DesktopExperienceFlags +import android.window.WindowContainerTransaction +import com.android.wm.shell.ShellTaskOrganizer +import com.android.wm.shell.common.HomeIntentProvider +import com.android.wm.shell.shared.desktopmode.DesktopModeStatus +import com.android.wm.shell.sysui.ShellInit + +/** [ShellCrashHandler] for shell to use when it's being initialized. Currently it only restores + * the home task to top. + **/ +class ShellCrashHandler( + private val context: Context, + private val shellTaskOrganizer: ShellTaskOrganizer, + private val homeIntentProvider: HomeIntentProvider, + shellInit: ShellInit, +) { + init { + shellInit.addInitCallback(::onInit, this) + } + + private fun onInit() { + handleCrashIfNeeded() + } + + private fun handleCrashIfNeeded() { + // For now only handle crashes when desktop mode is enabled on the device. + if (DesktopModeStatus.canEnterDesktopMode(context) && + !DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) { + var freeformTaskExists = false + // If there are running tasks at init, WMShell has crashed but WMCore is still alive. + for (task in shellTaskOrganizer.getRunningTasks()) { + if (task.windowingMode == WindowConfiguration.WINDOWING_MODE_FREEFORM) { + freeformTaskExists = true + } + + if (freeformTaskExists) { + shellTaskOrganizer.applyTransaction( + addLaunchHomePendingIntent(WindowContainerTransaction(), DEFAULT_DISPLAY) + ) + break + } + } + } + } + + private fun addLaunchHomePendingIntent( + wct: WindowContainerTransaction, displayId: Int + ): WindowContainerTransaction { + // TODO: b/400462917 - Check that crashes are also handled correctly on HSUM devices. We + // might need to pass the [userId] here to launch the correct home. + homeIntentProvider.addLaunchHomePendingIntent(wct, displayId) + return wct + } +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java index 8fa7d5c1ddce..d230425680ce 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java @@ -69,6 +69,7 @@ import com.android.wm.shell.common.DisplayImeController; import com.android.wm.shell.common.DisplayInsetsController; import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.FloatingContentCoordinator; +import com.android.wm.shell.common.HomeIntentProvider; import com.android.wm.shell.common.LaunchAdjacentController; import com.android.wm.shell.common.MultiDisplayDragMoveIndicatorController; import com.android.wm.shell.common.MultiDisplayDragMoveIndicatorSurface; @@ -80,6 +81,7 @@ import com.android.wm.shell.common.UserProfileContexts; import com.android.wm.shell.common.split.SplitState; import com.android.wm.shell.compatui.letterbox.LetterboxCommandHandler; import com.android.wm.shell.compatui.letterbox.LetterboxTransitionObserver; +import com.android.wm.shell.crashhandling.ShellCrashHandler; import com.android.wm.shell.dagger.back.ShellBackAnimationModule; import com.android.wm.shell.dagger.pip.PipModule; import com.android.wm.shell.desktopmode.CloseDesktopTaskTransitionHandler; @@ -779,7 +781,8 @@ public abstract class WMShellModule { UserProfileContexts userProfileContexts, DesktopModeCompatPolicy desktopModeCompatPolicy, DragToDisplayTransitionHandler dragToDisplayTransitionHandler, - DesktopModeMoveToDisplayTransitionHandler moveToDisplayTransitionHandler) { + DesktopModeMoveToDisplayTransitionHandler moveToDisplayTransitionHandler, + HomeIntentProvider homeIntentProvider) { return new DesktopTasksController( context, shellInit, @@ -820,7 +823,8 @@ public abstract class WMShellModule { userProfileContexts, desktopModeCompatPolicy, dragToDisplayTransitionHandler, - moveToDisplayTransitionHandler); + moveToDisplayTransitionHandler, + homeIntentProvider); } @WMSingleton @@ -1544,7 +1548,8 @@ public abstract class WMShellModule { Optional<DesktopTasksTransitionObserver> desktopTasksTransitionObserverOptional, Optional<DesktopDisplayEventHandler> desktopDisplayEventHandler, Optional<DesktopModeKeyGestureHandler> desktopModeKeyGestureHandler, - Optional<SystemModalsTransitionHandler> systemModalsTransitionHandler) { + Optional<SystemModalsTransitionHandler> systemModalsTransitionHandler, + ShellCrashHandler shellCrashHandler) { return new Object(); } @@ -1564,4 +1569,20 @@ public abstract class WMShellModule { return new UserProfileContexts(context, shellController, shellInit); } + @WMSingleton + @Provides + static ShellCrashHandler provideShellCrashHandler( + Context context, + ShellTaskOrganizer shellTaskOrganizer, + HomeIntentProvider homeIntentProvider, + ShellInit shellInit) { + return new ShellCrashHandler(context, shellTaskOrganizer, homeIntentProvider, shellInit); + } + + @WMSingleton + @Provides + static HomeIntentProvider provideHomeIntentProvider(Context context) { + return new HomeIntentProvider(context); + } + } 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 880ec8353798..2835a18cd809 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 @@ -85,6 +85,7 @@ import com.android.wm.shell.bubbles.BubbleController import com.android.wm.shell.common.DisplayController import com.android.wm.shell.common.DisplayLayout import com.android.wm.shell.common.ExternalInterfaceBinder +import com.android.wm.shell.common.HomeIntentProvider import com.android.wm.shell.common.MultiInstanceHelper import com.android.wm.shell.common.MultiInstanceHelper.Companion.getComponent import com.android.wm.shell.common.RemoteCallable @@ -211,6 +212,7 @@ class DesktopTasksController( private val desktopModeCompatPolicy: DesktopModeCompatPolicy, private val dragToDisplayTransitionHandler: DragToDisplayTransitionHandler, private val moveToDisplayTransitionHandler: DesktopModeMoveToDisplayTransitionHandler, + private val homeIntentProvider: HomeIntentProvider, ) : RemoteCallable<DesktopTasksController>, Transitions.TransitionHandler, @@ -1718,34 +1720,7 @@ class DesktopTasksController( } private fun addLaunchHomePendingIntent(wct: WindowContainerTransaction, displayId: Int) { - val userHandle = UserHandle.of(userId) - val launchHomeIntent = - Intent(Intent.ACTION_MAIN).apply { - if (displayId != DEFAULT_DISPLAY) { - addCategory(Intent.CATEGORY_SECONDARY_HOME) - } else { - addCategory(Intent.CATEGORY_HOME) - } - } - val options = - ActivityOptions.makeBasic().apply { - launchWindowingMode = WINDOWING_MODE_FULLSCREEN - pendingIntentBackgroundActivityStartMode = - ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS - if (Flags.enablePerDisplayDesktopWallpaperActivity()) { - launchDisplayId = displayId - } - } - val pendingIntent = - PendingIntent.getActivityAsUser( - context, - /* requestCode= */ 0, - launchHomeIntent, - PendingIntent.FLAG_IMMUTABLE, - /* options= */ null, - userHandle, - ) - wct.sendPendingIntent(pendingIntent, launchHomeIntent, options.toBundle()) + homeIntentProvider.addLaunchHomePendingIntent(wct, displayId, userId) } private fun addWallpaperActivity(displayId: Int, wct: WindowContainerTransaction) { diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/crashhandling/ShellCrashHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/crashhandling/ShellCrashHandlerTest.kt new file mode 100644 index 000000000000..5c77f78a7dfa --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/crashhandling/ShellCrashHandlerTest.kt @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2025 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.wm.shell.crashhandling + +import android.app.ActivityManager.RunningTaskInfo +import android.app.PendingIntent +import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.platform.test.annotations.DisableFlags +import android.view.Display.DEFAULT_DISPLAY +import android.window.IWindowContainerToken +import android.window.WindowContainerToken +import android.window.WindowContainerTransaction +import android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_PENDING_INTENT +import com.android.modules.utils.testing.ExtendedMockitoRule +import com.android.window.flags.Flags +import com.android.wm.shell.ShellTaskOrganizer +import com.android.wm.shell.ShellTestCase +import com.android.wm.shell.common.HomeIntentProvider +import com.android.wm.shell.common.ShellExecutor +import com.android.wm.shell.shared.desktopmode.DesktopModeStatus +import com.android.wm.shell.sysui.ShellInit +import com.google.common.truth.Truth.assertThat +import kotlin.test.Test +import org.junit.Before +import org.junit.Rule +import org.mockito.ArgumentCaptor +import org.mockito.Mockito +import org.mockito.kotlin.any +import org.mockito.kotlin.mock +import org.mockito.kotlin.spy +import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever + +class ShellCrashHandlerTest : ShellTestCase() { + @JvmField + @Rule + val extendedMockitoRule = + ExtendedMockitoRule.Builder(this) + .mockStatic(DesktopModeStatus::class.java) + .mockStatic(PendingIntent::class.java) + .build()!! + + private val testExecutor = mock<ShellExecutor>() + private val context = mock<Context>() + private val shellTaskOrganizer = mock<ShellTaskOrganizer>() + + private lateinit var homeIntentProvider: HomeIntentProvider + private lateinit var crashHandler: ShellCrashHandler + private lateinit var shellInit: ShellInit + + + @Before + fun setup() { + whenever(DesktopModeStatus.canEnterDesktopMode(any())).thenReturn(true) + whenever(PendingIntent.getActivity(any(), any(), any(), any(), any())).thenReturn(mock()) + + shellInit = spy(ShellInit(testExecutor)) + + homeIntentProvider = HomeIntentProvider(context) + crashHandler = ShellCrashHandler(context, shellTaskOrganizer, homeIntentProvider, shellInit) + } + + @Test + @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) + fun init_freeformTaskExists_sendsHomeIntent() { + val wctCaptor = ArgumentCaptor.forClass(WindowContainerTransaction::class.java) + whenever(shellTaskOrganizer.getRunningTasks()).thenReturn(arrayListOf(createTaskInfo(1))) + + shellInit.init() + + verify(shellTaskOrganizer).applyTransaction( + wctCaptor.capture() + ) + wctCaptor.value.assertPendingIntentAt(0, launchHomeIntent(DEFAULT_DISPLAY)) + } + + private fun launchHomeIntent(displayId: Int): Intent { + return Intent(Intent.ACTION_MAIN).apply { + if (displayId != DEFAULT_DISPLAY) { + addCategory(Intent.CATEGORY_SECONDARY_HOME) + } else { + addCategory(Intent.CATEGORY_HOME) + } + } + } + + private fun createTaskInfo(id: Int, windowingMode: Int = WINDOWING_MODE_FREEFORM) = + RunningTaskInfo().apply { + taskId = id + displayId = DEFAULT_DISPLAY + configuration.windowConfiguration.windowingMode = windowingMode + token = WindowContainerToken(Mockito.mock(IWindowContainerToken::class.java)) + baseIntent = Intent().apply { component = ComponentName("package", "component.name") } + } + + private fun WindowContainerTransaction.assertPendingIntentAt(index: Int, intent: Intent) { + val op = hierarchyOps[index] + assertThat(op.type).isEqualTo(HIERARCHY_OP_TYPE_PENDING_INTENT) + assertThat(op.activityIntent?.component).isEqualTo(intent.component) + assertThat(op.activityIntent?.categories).isEqualTo(intent.categories) + } +}
\ No newline at end of file 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 87b7d344a3ec..d093629000f3 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 @@ -101,6 +101,7 @@ import com.android.wm.shell.TestShellExecutor import com.android.wm.shell.bubbles.BubbleController import com.android.wm.shell.common.DisplayController import com.android.wm.shell.common.DisplayLayout +import com.android.wm.shell.common.HomeIntentProvider import com.android.wm.shell.common.MultiInstanceHelper import com.android.wm.shell.common.ShellExecutor import com.android.wm.shell.common.SyncTransactionQueue @@ -275,6 +276,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() private lateinit var testScope: CoroutineScope private lateinit var desktopModeCompatPolicy: DesktopModeCompatPolicy private lateinit var spyContext: TestableContext + private lateinit var homeIntentProvider: HomeIntentProvider private val shellExecutor = TestShellExecutor() private val bgExecutor = TestShellExecutor() @@ -330,6 +332,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() mockHandler, ) desktopModeCompatPolicy = spy(DesktopModeCompatPolicy(spyContext)) + homeIntentProvider = HomeIntentProvider(context) whenever(shellTaskOrganizer.getRunningTasks(anyInt())).thenAnswer { runningTasks } whenever(transitions.startTransition(anyInt(), any(), anyOrNull())).thenAnswer { Binder() } @@ -449,6 +452,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() desktopModeCompatPolicy, dragToDisplayTransitionHandler, moveToDisplayTransitionHandler, + homeIntentProvider, ) @After |