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",