diff options
6 files changed, 244 insertions, 38 deletions
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/AssistContentRequester.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/AssistContentRequester.kt new file mode 100644 index 000000000000..249185eca323 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/AssistContentRequester.kt @@ -0,0 +1,126 @@ +/* + * 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.wm.shell.apptoweb + +import android.app.ActivityTaskManager +import android.app.IActivityTaskManager +import android.app.IAssistDataReceiver +import android.app.assist.AssistContent +import android.content.Context +import android.graphics.Bitmap +import android.os.Bundle +import android.os.RemoteException +import android.util.Slog +import java.lang.ref.WeakReference +import java.util.Collections +import java.util.WeakHashMap +import java.util.concurrent.Executor + +/** + * Can be used to request the AssistContent from a provided task id, useful for getting the web uri + * if provided from the task. + */ +class AssistContentRequester( + context: Context, + private val callBackExecutor: Executor, + private val systemInteractionExecutor: Executor +) { + interface Callback { + // Called when the [AssistContent] of the requested task is available. + fun onAssistContentAvailable(assistContent: AssistContent?) + } + + private val activityTaskManager: IActivityTaskManager = ActivityTaskManager.getService() + private val attributionTag: String? = context.attributionTag + private val packageName: String = context.applicationContext.packageName + + // If system loses the callback, our internal cache of original callback will also get cleared. + private val pendingCallbacks = Collections.synchronizedMap(WeakHashMap<Any, Callback>()) + + /** + * Request the [AssistContent] from the task with the provided id. + * + * @param taskId to query for the content. + * @param callback to call when the content is available, called on the main thread. + */ + fun requestAssistContent(taskId: Int, callback: Callback) { + // ActivityTaskManager interaction here is synchronous, so call off the main thread. + systemInteractionExecutor.execute { + try { + val success = activityTaskManager.requestAssistDataForTask( + AssistDataReceiver(callback, this), + taskId, + packageName, + attributionTag, + false /* fetchStructure */ + ) + if (!success) { + executeOnMainExecutor { callback.onAssistContentAvailable(null) } + } + } catch (e: RemoteException) { + Slog.e(TAG, "Requesting assist content failed for task: $taskId", e) + } + } + } + + private fun executeOnMainExecutor(callback: Runnable) { + callBackExecutor.execute(callback) + } + + private class AssistDataReceiver( + callback: Callback, + parent: AssistContentRequester + ) : IAssistDataReceiver.Stub() { + // The AssistDataReceiver binder callback object is passed to a system server, that may + // keep hold of it for longer than the lifetime of the AssistContentRequester object, + // potentially causing a memory leak. In the callback passed to the system server, only + // keep a weak reference to the parent object and lookup its callback if it still exists. + private val parentRef: WeakReference<AssistContentRequester> + private val callbackKey = Any() + + init { + parent.pendingCallbacks[callbackKey] = callback + parentRef = WeakReference(parent) + } + + override fun onHandleAssistData(data: Bundle?) { + val content = data?.getParcelable(ASSIST_KEY_CONTENT, AssistContent::class.java) + if (content == null) { + Slog.d(TAG, "Received AssistData, but no AssistContent found") + return + } + val requester = parentRef.get() + if (requester != null) { + val callback = requester.pendingCallbacks[callbackKey] + if (callback != null) { + requester.executeOnMainExecutor { callback.onAssistContentAvailable(content) } + } else { + Slog.d(TAG, "Callback received after calling UI was disposed of") + } + } else { + Slog.d(TAG, "Callback received after Requester was collected") + } + } + + override fun onHandleAssistScreenshot(screenshot: Bitmap) {} + } + + companion object { + private const val TAG = "AssistContentRequester" + private const val ASSIST_KEY_CONTENT = "content" + } +}
\ 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 02ecfd983d73..7054c17cfeb0 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 @@ -38,6 +38,7 @@ import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.WindowManagerShellWrapper; import com.android.wm.shell.activityembedding.ActivityEmbeddingController; import com.android.wm.shell.apptoweb.AppToWebGenericLinksParser; +import com.android.wm.shell.apptoweb.AssistContentRequester; import com.android.wm.shell.bubbles.BubbleController; import com.android.wm.shell.bubbles.BubbleData; import com.android.wm.shell.bubbles.BubbleDataRepository; @@ -240,6 +241,7 @@ public abstract class WMShellModule { RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer, InteractionJankMonitor interactionJankMonitor, AppToWebGenericLinksParser genericLinksParser, + AssistContentRequester assistContentRequester, MultiInstanceHelper multiInstanceHelper, Optional<DesktopTasksLimiter> desktopTasksLimiter, Optional<DesktopActivityOrientationChangeHandler> desktopActivityOrientationHandler) { @@ -263,6 +265,7 @@ public abstract class WMShellModule { rootTaskDisplayAreaOrganizer, interactionJankMonitor, genericLinksParser, + assistContentRequester, multiInstanceHelper, desktopTasksLimiter, desktopActivityOrientationHandler); @@ -291,6 +294,15 @@ public abstract class WMShellModule { return new AppToWebGenericLinksParser(context, mainExecutor); } + @Provides + static AssistContentRequester provideAssistContentRequester( + Context context, + @ShellMainThread ShellExecutor shellExecutor, + @ShellBackgroundThread ShellExecutor bgExecutor + ) { + return new AssistContentRequester(context, shellExecutor, bgExecutor); + } + // // Freeform // diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java index c88c1e28b011..79190689adc1 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java @@ -90,6 +90,7 @@ import com.android.wm.shell.R; import com.android.wm.shell.RootTaskDisplayAreaOrganizer; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.apptoweb.AppToWebGenericLinksParser; +import com.android.wm.shell.apptoweb.AssistContentRequester; import com.android.wm.shell.common.DisplayChangeController; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayInsetsController; @@ -182,6 +183,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { private final Region mExclusionRegion = Region.obtain(); private boolean mInImmersiveMode; private final String mSysUIPackageName; + private final AssistContentRequester mAssistContentRequester; private final DisplayChangeController.OnDisplayChangingListener mOnDisplayChangingListener; private final ISystemGestureExclusionListener mGestureExclusionListener = @@ -217,6 +219,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer, InteractionJankMonitor interactionJankMonitor, AppToWebGenericLinksParser genericLinksParser, + AssistContentRequester assistContentRequester, MultiInstanceHelper multiInstanceHelper, Optional<DesktopTasksLimiter> desktopTasksLimiter, Optional<DesktopActivityOrientationChangeHandler> activityOrientationChangeHandler @@ -238,6 +241,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { transitions, desktopTasksController, genericLinksParser, + assistContentRequester, multiInstanceHelper, new DesktopModeWindowDecoration.Factory(), new InputMonitorFactory(), @@ -267,6 +271,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { Transitions transitions, Optional<DesktopTasksController> desktopTasksController, AppToWebGenericLinksParser genericLinksParser, + AssistContentRequester assistContentRequester, MultiInstanceHelper multiInstanceHelper, DesktopModeWindowDecoration.Factory desktopModeWindowDecorFactory, InputMonitorFactory inputMonitorFactory, @@ -304,6 +309,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { mInteractionJankMonitor = interactionJankMonitor; mDesktopTasksLimiter = desktopTasksLimiter; mActivityOrientationChangeHandler = activityOrientationChangeHandler; + mAssistContentRequester = assistContentRequester; mOnDisplayChangingListener = (displayId, fromRotation, toRotation, displayAreaInfo, t) -> { DesktopModeWindowDecoration decoration; RunningTaskInfo taskInfo; @@ -626,7 +632,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { } else if (id == R.id.caption_handle || id == R.id.open_menu_button) { if (!decoration.isHandleMenuActive()) { moveTaskToFront(decoration.mTaskInfo); - decoration.createHandleMenu(mSplitScreenController); + decoration.createHandleMenu(); } } else if (id == R.id.maximize_window) { // TODO(b/346441962): move click detection logic into the decor's @@ -1270,6 +1276,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { mSyncQueue, mRootTaskDisplayAreaOrganizer, mGenericLinksParser, + mAssistContentRequester, mMultiInstanceHelper); mWindowDecorByTaskId.put(taskInfo.taskId, windowDecoration); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java index 8a012cd4f6dd..142be91fe942 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java @@ -38,6 +38,7 @@ import android.annotation.Nullable; import android.annotation.SuppressLint; import android.app.ActivityManager; import android.app.WindowConfiguration.WindowingMode; +import android.app.assist.AssistContent; import android.content.ComponentName; import android.content.Context; import android.content.pm.ActivityInfo; @@ -76,6 +77,7 @@ import com.android.wm.shell.RootTaskDisplayAreaOrganizer; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.apptoweb.AppToWebGenericLinksParser; import com.android.wm.shell.apptoweb.AppToWebUtils; +import com.android.wm.shell.apptoweb.AssistContentRequester; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.MultiInstanceHelper; @@ -151,6 +153,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin private CharSequence mAppName; private CapturedLink mCapturedLink; private Uri mGenericLink; + private Uri mWebUri; private Consumer<Uri> mOpenInBrowserClickListener; private ExclusionRegionListener mExclusionRegionListener; @@ -159,6 +162,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin private final MaximizeMenuFactory mMaximizeMenuFactory; private final HandleMenuFactory mHandleMenuFactory; private final AppToWebGenericLinksParser mGenericLinksParser; + private final AssistContentRequester mAssistContentRequester; // Hover state for the maximize menu and button. The menu will remain open as long as either of // these is true. See {@link #onMaximizeHoverStateChanged()}. @@ -185,16 +189,16 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin SyncTransactionQueue syncQueue, RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer, AppToWebGenericLinksParser genericLinksParser, + AssistContentRequester assistContentRequester, MultiInstanceHelper multiInstanceHelper) { this (context, userContext, displayController, splitScreenController, taskOrganizer, taskInfo, taskSurface, handler, bgExecutor, choreographer, syncQueue, - rootTaskDisplayAreaOrganizer, genericLinksParser, SurfaceControl.Builder::new, - SurfaceControl.Transaction::new, WindowContainerTransaction::new, - SurfaceControl::new, new WindowManagerWrapper( + rootTaskDisplayAreaOrganizer, genericLinksParser, assistContentRequester, + SurfaceControl.Builder::new, SurfaceControl.Transaction::new, + WindowContainerTransaction::new, SurfaceControl::new, new WindowManagerWrapper( context.getSystemService(WindowManager.class)), - new SurfaceControlViewHostFactory() {}, - DefaultMaximizeMenuFactory.INSTANCE, DefaultHandleMenuFactory.INSTANCE, - multiInstanceHelper); + new SurfaceControlViewHostFactory() {}, DefaultMaximizeMenuFactory.INSTANCE, + DefaultHandleMenuFactory.INSTANCE, multiInstanceHelper); } DesktopModeWindowDecoration( @@ -211,6 +215,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin SyncTransactionQueue syncQueue, RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer, AppToWebGenericLinksParser genericLinksParser, + AssistContentRequester assistContentRequester, Supplier<SurfaceControl.Builder> surfaceControlBuilderSupplier, Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier, Supplier<WindowContainerTransaction> windowContainerTransactionSupplier, @@ -231,6 +236,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin mSyncQueue = syncQueue; mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer; mGenericLinksParser = genericLinksParser; + mAssistContentRequester = assistContentRequester; mMaximizeMenuFactory = maximizeMenuFactory; mHandleMenuFactory = handleMenuFactory; mMultiInstanceHelper = multiInstanceHelper; @@ -489,6 +495,8 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin // Otherwise, return the generic link which is set to null if a generic link is unavailable. if (mCapturedLink != null && !mCapturedLink.mExpired) { return mCapturedLink.mUri; + } else if (mWebUri != null) { + return mWebUri; } return mGenericLink; } @@ -994,18 +1002,32 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin } /** - * Create and display handle menu window. + * Updates app info and creates and displays handle menu window. */ - void createHandleMenu(SplitScreenController splitScreenController) { + void createHandleMenu() { + // Requests assist content. When content is received, calls {@link #onAssistContentReceived} + // which sets app info and creates the handle menu. + mAssistContentRequester.requestAssistContent( + mTaskInfo.taskId, this::onAssistContentReceived); + } + + /** + * Called when assist content is received. updates the saved links and creates the handle menu. + */ + @VisibleForTesting + void onAssistContentReceived(@Nullable AssistContent assistContent) { + mWebUri = assistContent == null ? null : assistContent.getWebUri(); loadAppInfoIfNeeded(); updateGenericLink(); + + // Create and display handle menu mHandleMenu = mHandleMenuFactory.create( this, mWindowManagerWrapper, mRelayoutParams.mLayoutResId, mAppIconBitmap, mAppName, - splitScreenController, + mSplitScreenController, DesktopModeStatus.canEnterDesktopMode(mContext), Flags.enableDesktopWindowingMultiInstanceFeatures() && mMultiInstanceHelper @@ -1019,6 +1041,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin mHandleMenu.show( /* onToDesktopClickListener= */ () -> { mOnToDesktopClickListener.accept(APP_HANDLE_MENU_BUTTON); + mOnToDesktopClickListener.accept(APP_HANDLE_MENU_BUTTON); return Unit.INSTANCE; }, /* onToFullscreenClickListener= */ mOnToFullscreenClickListener, @@ -1340,6 +1363,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin SyncTransactionQueue syncQueue, RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer, AppToWebGenericLinksParser genericLinksParser, + AssistContentRequester assistContentRequester, MultiInstanceHelper multiInstanceHelper) { return new DesktopModeWindowDecoration( context, @@ -1355,6 +1379,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin syncQueue, rootTaskDisplayAreaOrganizer, genericLinksParser, + assistContentRequester, multiInstanceHelper); } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt index be0549b6655d..3dd8a2bacbcd 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt @@ -73,6 +73,7 @@ import com.android.wm.shell.ShellTestCase import com.android.wm.shell.TestRunningTaskInfoBuilder import com.android.wm.shell.TestShellExecutor import com.android.wm.shell.apptoweb.AppToWebGenericLinksParser +import com.android.wm.shell.apptoweb.AssistContentRequester import com.android.wm.shell.common.DisplayChangeController import com.android.wm.shell.common.DisplayController import com.android.wm.shell.common.DisplayInsetsController @@ -165,6 +166,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { @Mock private lateinit var mockInteractionJankMonitor: InteractionJankMonitor @Mock private lateinit var mockGenericLinksParser: AppToWebGenericLinksParser @Mock private lateinit var mockUserHandle: UserHandle + @Mock private lateinit var mockAssistContentRequester: AssistContentRequester @Mock private lateinit var mockToast: Toast private val bgExecutor = TestShellExecutor() @Mock private lateinit var mockMultiInstanceHelper: MultiInstanceHelper @@ -218,6 +220,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { mockTransitions, Optional.of(mockDesktopTasksController), mockGenericLinksParser, + mockAssistContentRequester, mockMultiInstanceHelper, mockDesktopModeWindowDecorFactory, mockInputMonitorFactory, @@ -1131,7 +1134,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { whenever( mockDesktopModeWindowDecorFactory.create( any(), any(), any(), any(), any(), eq(task), any(), any(), any(), any(), any(), - any(), any(), any()) + any(), any(), any(), any()) ).thenReturn(decoration) decoration.mTaskInfo = task whenever(decoration.isFocused).thenReturn(task.isFocused) diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java index 258c86094a36..b9e542a0e0c1 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java @@ -46,6 +46,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.ActivityManager; +import android.app.assist.AssistContent; import android.content.ComponentName; import android.content.Context; import android.content.pm.ActivityInfo; @@ -88,6 +89,7 @@ import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.TestRunningTaskInfoBuilder; import com.android.wm.shell.TestShellExecutor; import com.android.wm.shell.apptoweb.AppToWebGenericLinksParser; +import com.android.wm.shell.apptoweb.AssistContentRequester; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.MultiInstanceHelper; import com.android.wm.shell.common.ShellExecutor; @@ -133,6 +135,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { private static final Uri TEST_URI1 = Uri.parse("https://www.google.com/"); private static final Uri TEST_URI2 = Uri.parse("https://docs.google.com/"); + private static final Uri TEST_URI3 = Uri.parse("https://slides.google.com/"); @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT); @@ -175,6 +178,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { @Mock private WindowManager mMockWindowManager; @Mock + private AssistContentRequester mMockAssistContentRequester; + @Mock private HandleMenu mMockHandleMenu; @Mock private HandleMenuFactory mMockHandleMenuFactory; @@ -189,7 +194,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { private SurfaceControl.Transaction mMockTransaction; private StaticMockitoSession mMockitoSession; private TestableContext mTestableContext; - private ShellExecutor mBgExecutor = new TestShellExecutor(); + private final ShellExecutor mBgExecutor = new TestShellExecutor(); + private final AssistContent mAssistContent = new AssistContent(); /** Set up run before test class. */ @BeforeClass @@ -673,10 +679,11 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { public void capturedLink_handleMenuBrowserLinkSetToCapturedLinkIfValid() { final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */); final DesktopModeWindowDecoration decor = createWindowDecoration( - taskInfo, TEST_URI1 /* captured link */, TEST_URI2 /* generic link */); + taskInfo, TEST_URI1 /* captured link */, TEST_URI2 /* web uri */, + TEST_URI3 /* generic link */); // Verify handle menu's browser link set as captured link - decor.createHandleMenu(mMockSplitScreenController); + createHandleMenu(decor); verifyHandleMenuCreated(TEST_URI1); } @@ -685,7 +692,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { public void capturedLink_postsOnCapturedLinkExpiredRunnable() { final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */); final DesktopModeWindowDecoration decor = createWindowDecoration( - taskInfo, TEST_URI1 /* captured link */, null /* generic link */); + taskInfo, TEST_URI1 /* captured link */, null /* web uri */, + null /* generic link */); final ArgumentCaptor<Runnable> runnableArgument = ArgumentCaptor.forClass(Runnable.class); // Run runnable to set captured link to expired @@ -694,7 +702,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { // Verify captured link is no longer valid by verifying link is not set as handle menu // browser link. - decor.createHandleMenu(mMockSplitScreenController); + createHandleMenu(decor); verifyHandleMenuCreated(null /* uri */); } @@ -703,7 +711,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { public void capturedLink_capturedLinkNotResetToSameLink() { final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */); final DesktopModeWindowDecoration decor = createWindowDecoration( - taskInfo, TEST_URI1 /* captured link */, null /* generic link */); + taskInfo, TEST_URI1 /* captured link */, null /* web uri */, + null /* generic link */); final ArgumentCaptor<Runnable> runnableArgument = ArgumentCaptor.forClass(Runnable.class); // Run runnable to set captured link to expired @@ -714,7 +723,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { decor.relayout(taskInfo); // Verify handle menu's browser link not set to captured link since link is expired - decor.createHandleMenu(mMockSplitScreenController); + createHandleMenu(decor); verifyHandleMenuCreated(null /* uri */); } @@ -723,11 +732,12 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { public void capturedLink_capturedLinkStillUsedIfExpiredAfterHandleMenuCreation() { final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */); final DesktopModeWindowDecoration decor = createWindowDecoration( - taskInfo, TEST_URI1 /* captured link */, null /* generic link */); + taskInfo, TEST_URI1 /* captured link */, null /* web uri */, + null /* generic link */); final ArgumentCaptor<Runnable> runnableArgument = ArgumentCaptor.forClass(Runnable.class); // Create handle menu before link expires - decor.createHandleMenu(mMockSplitScreenController); + createHandleMenu(decor); // Run runnable to set captured link to expired verify(mMockHandler).postDelayed(runnableArgument.capture(), anyLong()); @@ -735,7 +745,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { // Verify handle menu's browser link is set to captured link since menu was opened before // captured link expired - decor.createHandleMenu(mMockSplitScreenController); + createHandleMenu(decor); verifyHandleMenuCreated(TEST_URI1); } @@ -744,12 +754,13 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { public void capturedLink_capturedLinkExpiresAfterClick() { final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */); final DesktopModeWindowDecoration decor = createWindowDecoration( - taskInfo, TEST_URI1 /* captured link */, null /* generic link */); + taskInfo, TEST_URI1 /* captured link */, null /* web uri */, + null /* generic link */); final ArgumentCaptor<Function1<Uri, Unit>> openInBrowserCaptor = ArgumentCaptor.forClass(Function1.class); // Simulate menu opening and clicking open in browser button - decor.createHandleMenu(mMockSplitScreenController); + createHandleMenu(decor); verify(mMockHandleMenu).show( any(), any(), @@ -763,7 +774,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { // Verify handle menu's browser link not set to captured link since link not valid after // open in browser clicked - decor.createHandleMenu(mMockSplitScreenController); + createHandleMenu(decor); verifyHandleMenuCreated(null /* uri */); } @@ -772,10 +783,11 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { public void capturedLink_openInBrowserListenerCalledOnClick() { final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */); final DesktopModeWindowDecoration decor = createWindowDecoration( - taskInfo, TEST_URI1 /* captured link */, null /* generic link */); + taskInfo, TEST_URI1 /* captured link */, null /* web uri */, + null /* generic link */); final ArgumentCaptor<Function1<Uri, Unit>> openInBrowserCaptor = ArgumentCaptor.forClass(Function1.class); - decor.createHandleMenu(mMockSplitScreenController); + createHandleMenu(decor); verify(mMockHandleMenu).show( any(), any(), @@ -793,24 +805,38 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB) - public void genericLink_genericLinkUsedWhenCapturedLinkUnavailable() { + public void webUriLink_webUriLinkUsedWhenCapturedLinkUnavailable() { final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */); final DesktopModeWindowDecoration decor = createWindowDecoration( - taskInfo, null /* captured link */, TEST_URI2 /* generic link */); - - // Verify handle menu's browser link set as generic link no captured link is available - decor.createHandleMenu(mMockSplitScreenController); + taskInfo, null /* captured link */, TEST_URI2 /* web uri */, + TEST_URI3 /* generic link */); + // Verify handle menu's browser link set as web uri link when captured link is unavailable + createHandleMenu(decor); verifyHandleMenuCreated(TEST_URI2); } @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB) + public void genericLink_genericLinkUsedWhenCapturedLinkAndWebUriUnavailable() { + final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */); + final DesktopModeWindowDecoration decor = createWindowDecoration( + taskInfo, null /* captured link */, null /* web uri */, + TEST_URI3 /* generic link */); + + // Verify handle menu's browser link set as generic link when captured link and web uri link + // are unavailable + createHandleMenu(decor); + verifyHandleMenuCreated(TEST_URI3); + } + + @Test public void handleMenu_onCloseMenuClick_closesMenu() { final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true); final DesktopModeWindowDecoration decoration = createWindowDecoration(taskInfo, true /* relayout */); final ArgumentCaptor<Function0<Unit>> closeClickListener = ArgumentCaptor.forClass(Function0.class); - decoration.createHandleMenu(mMockSplitScreenController); + createHandleMenu(decoration); verify(mMockHandleMenu).show( any(), any(), @@ -860,9 +886,10 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { private DesktopModeWindowDecoration createWindowDecoration( ActivityManager.RunningTaskInfo taskInfo, @Nullable Uri capturedLink, - @Nullable Uri genericLink) { + @Nullable Uri webUri, @Nullable Uri genericLink) { taskInfo.capturedLink = capturedLink; taskInfo.capturedLinkTimestamp = System.currentTimeMillis(); + mAssistContent.setWebUri(webUri); final String genericLinkString = genericLink == null ? null : genericLink.toString(); doReturn(genericLinkString).when(mMockGenericLinksParser).getGenericLink(any()); // Relayout to set captured link @@ -894,11 +921,10 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { mContext, mMockDisplayController, mMockSplitScreenController, mMockShellTaskOrganizer, taskInfo, mMockSurfaceControl, mMockHandler, mBgExecutor, mMockChoreographer, mMockSyncQueue, mMockRootTaskDisplayAreaOrganizer, - mMockGenericLinksParser, SurfaceControl.Builder::new, mMockTransactionSupplier, - WindowContainerTransaction::new, SurfaceControl::new, - new WindowManagerWrapper(mMockWindowManager), - mMockSurfaceControlViewHostFactory, maximizeMenuFactory, mMockHandleMenuFactory, - mMockMultiInstanceHelper); + mMockGenericLinksParser, mMockAssistContentRequester, SurfaceControl.Builder::new, + mMockTransactionSupplier, WindowContainerTransaction::new, SurfaceControl::new, + new WindowManagerWrapper(mMockWindowManager), mMockSurfaceControlViewHostFactory, + maximizeMenuFactory, mMockHandleMenuFactory, mMockMultiInstanceHelper); windowDecor.setCaptionListeners(mMockTouchEventListener, mMockTouchEventListener, mMockTouchEventListener, mMockTouchEventListener); windowDecor.setExclusionRegionListener(mMockExclusionRegionListener); @@ -926,6 +952,13 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { } + private void createHandleMenu(@NonNull DesktopModeWindowDecoration decor) { + decor.createHandleMenu(); + // Call DesktopModeWindowDecoration#onAssistContentReceived because decor waits to receive + // {@link AssistContent} before creating the menu + decor.onAssistContentReceived(mAssistContent); + } + private static boolean hasNoInputChannelFeature(RelayoutParams params) { return (params.mInputFeatures & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) != 0; |