summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Orhan Uysal <uysalorhan@google.com> 2025-03-05 07:32:01 -0800
committer Android (Google) Code Review <android-gerrit@google.com> 2025-03-05 07:32:01 -0800
commitd576dacb9263aafb51acd1718ad58b484ba1f6f1 (patch)
treed805038bcd3dd7bbba8e2c567d2395865f81c2e6
parenta1273fc2349227d26cf496e1279e5d1f2cb0fef3 (diff)
parent1f1db489dab169fadf3ed06ac40bb723d48cefc2 (diff)
Merge "Add a crash handler to run on Init" into main
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/HomeIntentProvider.kt65
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/crashhandling/OWNERS2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/crashhandling/ShellCrashHandler.kt75
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java27
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt31
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/crashhandling/ShellCrashHandlerTest.kt119
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt4
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