End live tile when invoking Circle to Search over Overview.

This fixes many animation/state issues related to switching tasks
from the live tile to AGA.

Demo before: https://drive.google.com/file/d/1aBsn_-4tRHRsfGSqsx44_1B7dgdPOq_v/view?usp=drive_link&resourcekey=0-EHyEiKRVEC2ooUo-0rcjHg
Demo after: https://drive.google.com/file/d/1Sf5cUh8hC-slUZc_efOChzxxw710pG65/view?usp=drive_link&resourcekey=0-Wr33tL3ytedMibcVNd6auw

Bug: 372592549
Bug: 297831970
Test: Manual, updated ContextualSearchInvokerTest.
Flag: EXEMPT bugfix
Change-Id: I538cb1de61e48de4cf7c6fca8710a655933d07a3
diff --git a/quickstep/src/com/android/quickstep/OverviewComponentObserver.java b/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
index 1f6c671..3507937 100644
--- a/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
+++ b/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
@@ -87,10 +87,16 @@
         mCurrentHomeIntent = createHomeIntent();
         mMyHomeIntent = new Intent(mCurrentHomeIntent).setPackage(mContext.getPackageName());
         ResolveInfo info = context.getPackageManager().resolveActivity(mMyHomeIntent, 0);
+        ActivityInfo myHomeActivityInfo = info == null ? null : info.activityInfo;
+        int myHomeConfigChanges = myHomeActivityInfo == null ? 0 : myHomeActivityInfo.configChanges;
         ComponentName myHomeComponent =
-                new ComponentName(context.getPackageName(), info.activityInfo.name);
-        mMyHomeIntent.setComponent(myHomeComponent);
-        mConfigChangesMap.append(myHomeComponent.hashCode(), info.activityInfo.configChanges);
+                myHomeActivityInfo == null
+                        ? mMyHomeIntent.resolveActivity(context.getPackageManager())
+                        : new ComponentName(context.getPackageName(), myHomeActivityInfo.name);
+        if (myHomeComponent != null) {
+            mMyHomeIntent.setComponent(myHomeComponent);
+            mConfigChangesMap.append(myHomeComponent.hashCode(), myHomeConfigChanges);
+        }
         mSetupWizardPkg = context.getString(R.string.setup_wizard_pkg);
 
         ComponentName fallbackComponent = new ComponentName(mContext, RecentsActivity.class);
diff --git a/quickstep/src/com/android/quickstep/util/ContextualSearchInvoker.kt b/quickstep/src/com/android/quickstep/util/ContextualSearchInvoker.kt
index bd454c0..58a2d89 100644
--- a/quickstep/src/com/android/quickstep/util/ContextualSearchInvoker.kt
+++ b/quickstep/src/com/android/quickstep/util/ContextualSearchInvoker.kt
@@ -21,6 +21,7 @@
 import android.app.contextualsearch.ContextualSearchManager.FEATURE_CONTEXTUAL_SEARCH
 import android.content.Context
 import android.util.Log
+import androidx.annotation.VisibleForTesting
 import com.android.internal.app.AssistUtils
 import com.android.launcher3.R
 import com.android.launcher3.logging.StatsLogManager
@@ -32,9 +33,13 @@
 import com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_LAUNCH_OMNI_FAILED_SETTING_DISABLED
 import com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_LAUNCH_OMNI_SUCCESSFUL_HOME
 import com.android.launcher3.util.ResourceBasedOverride
+import com.android.quickstep.BaseContainerInterface
 import com.android.quickstep.DeviceConfigWrapper
+import com.android.quickstep.OverviewComponentObserver
+import com.android.quickstep.RecentsAnimationDeviceState
 import com.android.quickstep.SystemUiProxy
 import com.android.quickstep.TopTaskTracker
+import com.android.quickstep.views.RecentsView
 import com.android.systemui.shared.system.QuickStepContract
 
 /** Handles invocations and checks for Contextual Search. */
@@ -183,7 +188,15 @@
         if (contextualSearchManager == null) {
             return false
         }
-        contextualSearchManager.startContextualSearch(entryPoint)
+        val recentsContainerInterface = getRecentsContainerInterface()
+        if (recentsContainerInterface?.isInLiveTileMode() == true) {
+            Log.i(TAG, "Contextual Search invocation attempted: live tile")
+            endLiveTileMode(recentsContainerInterface) {
+                contextualSearchManager.startContextualSearch(entryPoint)
+            }
+        } else {
+            contextualSearchManager.startContextualSearch(entryPoint)
+        }
         return true
     }
 
@@ -199,6 +212,42 @@
         return systemUiProxy.lastSystemUiStateFlags and KEYGUARD_SHOWING_SYSUI_FLAGS != 0L
     }
 
+    @VisibleForTesting
+    fun getRecentsContainerInterface(): BaseContainerInterface<*, *>? {
+        val rads = RecentsAnimationDeviceState(context)
+        val observer = OverviewComponentObserver(context, rads)
+        try {
+            return observer.containerInterface
+        } finally {
+            observer.onDestroy()
+            rads.destroy()
+        }
+    }
+
+    /**
+     * End the live tile mode.
+     *
+     * @param onCompleteRunnable Runnable to run when the live tile is paused. May run immediately.
+     */
+    private fun endLiveTileMode(
+        recentsContainerInterface: BaseContainerInterface<*, *>?,
+        onCompleteRunnable: Runnable,
+    ) {
+        val recentsViewContainer = recentsContainerInterface?.createdContainer
+        if (recentsViewContainer == null) {
+            onCompleteRunnable.run()
+            return
+        }
+        val recentsView: RecentsView<*, *> = recentsViewContainer.getOverviewPanel()
+        recentsView.switchToScreenshot {
+            recentsView.finishRecentsAnimation(
+                true, /* toRecents */
+                false, /* shouldPip */
+                onCompleteRunnable,
+            )
+        }
+    }
+
     companion object {
         private const val TAG = "ContextualSearchInvoker"
         const val SHADE_EXPANDED_SYSUI_FLAGS =
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/ContextualSearchInvokerTest.java b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/ContextualSearchInvokerTest.java
index 543ffe6..88774be 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/ContextualSearchInvokerTest.java
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/ContextualSearchInvokerTest.java
@@ -31,6 +31,8 @@
 
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
@@ -46,13 +48,17 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.launcher3.logging.StatsLogManager;
+import com.android.quickstep.BaseContainerInterface;
 import com.android.quickstep.DeviceConfigWrapper;
 import com.android.quickstep.SystemUiProxy;
 import com.android.quickstep.TopTaskTracker;
+import com.android.quickstep.views.RecentsView;
+import com.android.quickstep.views.RecentsViewContainer;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
@@ -73,6 +79,9 @@
     private @Mock StatsLogManager.StatsLogger mMockStatsLogger;
     private @Mock ContextualSearchHapticManager mMockContextualSearchHapticManager;
     private @Mock ContextualSearchManager mMockContextualSearchManager;
+    private @Mock BaseContainerInterface mMockContainerInterface;
+    private @Mock RecentsViewContainer mMockRecentsViewContainer;
+    private @Mock RecentsView mMockRecentsView;
     private ContextualSearchInvoker mContextualSearchInvoker;
 
     @Before
@@ -86,10 +95,15 @@
         when(mMockStateManager.isContextualSearchIntentAvailable()).thenReturn(true);
         when(mMockStateManager.isContextualSearchSettingEnabled()).thenReturn(true);
         when(mMockStatsLogManager.logger()).thenReturn(mMockStatsLogger);
+        when(mMockContainerInterface.getCreatedContainer()).thenReturn(mMockRecentsViewContainer);
+        when(mMockRecentsViewContainer.getOverviewPanel()).thenReturn(mMockRecentsView);
 
-        mContextualSearchInvoker = new ContextualSearchInvoker(context, mMockStateManager,
+        mContextualSearchInvoker = spy(new ContextualSearchInvoker(context, mMockStateManager,
                 mMockTopTaskTracker, mMockSystemUiProxy, mMockStatsLogManager,
-                mMockContextualSearchHapticManager, mMockContextualSearchManager);
+                mMockContextualSearchHapticManager, mMockContextualSearchManager
+        ));
+        doReturn(mMockContainerInterface).when(mContextualSearchInvoker)
+                .getRecentsContainerInterface();
     }
 
     @Test
@@ -244,6 +258,64 @@
         }
     }
 
+    @Test
+    public void invokeContextualSearchUncheckedWithHaptic_liveTile() {
+        when(mMockContainerInterface.isInLiveTileMode()).thenReturn(true);
+        ArgumentCaptor<Runnable> switchToScreenshotCaptor = ArgumentCaptor.forClass(Runnable.class);
+        ArgumentCaptor<Runnable> finishRecentsAnimationCaptor =
+                ArgumentCaptor.forClass(Runnable.class);
+
+        assertTrue("Expected invocation unchecked to succeed",
+                mContextualSearchInvoker.invokeContextualSearchUncheckedWithHaptic(
+                        CONTEXTUAL_SEARCH_ENTRY_POINT));
+        verify(mMockRecentsView).switchToScreenshot(switchToScreenshotCaptor.capture());
+        switchToScreenshotCaptor.getValue().run();
+        verify(mMockRecentsView).finishRecentsAnimation(anyBoolean(), anyBoolean(),
+                finishRecentsAnimationCaptor.capture());
+        finishRecentsAnimationCaptor.getValue().run();
+        verify(mMockContextualSearchManager).startContextualSearch(CONTEXTUAL_SEARCH_ENTRY_POINT);
+        verifyNoMoreInteractions(mMockStatsLogManager);
+    }
+
+    @Test
+    public void invokeContextualSearchUncheckedWithHaptic_liveTile_failsToSwitchToScreenshot() {
+        when(mMockContainerInterface.isInLiveTileMode()).thenReturn(true);
+        ArgumentCaptor<Runnable> switchToScreenshotCaptor = ArgumentCaptor.forClass(Runnable.class);
+        ArgumentCaptor<Runnable> finishRecentsAnimationCaptor =
+                ArgumentCaptor.forClass(Runnable.class);
+
+        assertTrue("Expected invocation unchecked to succeed",
+                mContextualSearchInvoker.invokeContextualSearchUncheckedWithHaptic(
+                        CONTEXTUAL_SEARCH_ENTRY_POINT));
+        verify(mMockRecentsView).switchToScreenshot(switchToScreenshotCaptor.capture());
+
+        // Don't run switchToScreenshot's callback. Therefore, recents animation should not finish.
+        verify(mMockRecentsView, never()).finishRecentsAnimation(anyBoolean(), anyBoolean(),
+                finishRecentsAnimationCaptor.capture());
+        // And ContextualSearch should not start.
+        verify(mMockContextualSearchManager, never()).startContextualSearch(anyInt());
+        verifyNoMoreInteractions(mMockStatsLogManager);
+    }
+
+    @Test
+    public void invokeContextualSearchUncheckedWithHaptic_liveTile_failsToFinishRecentsAnimation() {
+        when(mMockContainerInterface.isInLiveTileMode()).thenReturn(true);
+        ArgumentCaptor<Runnable> switchToScreenshotCaptor = ArgumentCaptor.forClass(Runnable.class);
+        ArgumentCaptor<Runnable> finishRecentsAnimationCaptor =
+                ArgumentCaptor.forClass(Runnable.class);
+
+        assertTrue("Expected invocation unchecked to succeed",
+                mContextualSearchInvoker.invokeContextualSearchUncheckedWithHaptic(
+                        CONTEXTUAL_SEARCH_ENTRY_POINT));
+        verify(mMockRecentsView).switchToScreenshot(switchToScreenshotCaptor.capture());
+        switchToScreenshotCaptor.getValue().run();
+        verify(mMockRecentsView).finishRecentsAnimation(anyBoolean(), anyBoolean(),
+                finishRecentsAnimationCaptor.capture());
+        // Don't run finishRecentsAnimation's callback. Therefore ContextualSearch should not start.
+        verify(mMockContextualSearchManager, never()).startContextualSearch(anyInt());
+        verifyNoMoreInteractions(mMockStatsLogManager);
+    }
+
     private AutoCloseable overrideSearchHapticCommitFlag(boolean value) {
         return TestExtensions.overrideNavConfigFlag(
                 "ENABLE_SEARCH_HAPTIC_COMMIT",