Utilize the AssistContent#getWebUri when available
Gets the assist content and, if the captured link is unavailable and
assist content contains a web uri, sets that as the browser link.
Bug: 349695493
Test: open in browser from docs
Flag: com.android.window.flags.enable_desktop_windowing_app_to_web
Change-Id: Id2cb79c95e1f6dff77ce7b66184b56e4264d85c5
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 0000000..249185e
--- /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 02ecfd9..7054c17c 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.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 @@
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
InteractionJankMonitor interactionJankMonitor,
AppToWebGenericLinksParser genericLinksParser,
+ AssistContentRequester assistContentRequester,
MultiInstanceHelper multiInstanceHelper,
Optional<DesktopTasksLimiter> desktopTasksLimiter,
Optional<DesktopActivityOrientationChangeHandler> desktopActivityOrientationHandler) {
@@ -263,6 +265,7 @@
rootTaskDisplayAreaOrganizer,
interactionJankMonitor,
genericLinksParser,
+ assistContentRequester,
multiInstanceHelper,
desktopTasksLimiter,
desktopActivityOrientationHandler);
@@ -291,6 +294,15 @@
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 c88c1e2..7919068 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.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 @@
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 @@
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
InteractionJankMonitor interactionJankMonitor,
AppToWebGenericLinksParser genericLinksParser,
+ AssistContentRequester assistContentRequester,
MultiInstanceHelper multiInstanceHelper,
Optional<DesktopTasksLimiter> desktopTasksLimiter,
Optional<DesktopActivityOrientationChangeHandler> activityOrientationChangeHandler
@@ -238,6 +241,7 @@
transitions,
desktopTasksController,
genericLinksParser,
+ assistContentRequester,
multiInstanceHelper,
new DesktopModeWindowDecoration.Factory(),
new InputMonitorFactory(),
@@ -267,6 +271,7 @@
Transitions transitions,
Optional<DesktopTasksController> desktopTasksController,
AppToWebGenericLinksParser genericLinksParser,
+ AssistContentRequester assistContentRequester,
MultiInstanceHelper multiInstanceHelper,
DesktopModeWindowDecoration.Factory desktopModeWindowDecorFactory,
InputMonitorFactory inputMonitorFactory,
@@ -304,6 +309,7 @@
mInteractionJankMonitor = interactionJankMonitor;
mDesktopTasksLimiter = desktopTasksLimiter;
mActivityOrientationChangeHandler = activityOrientationChangeHandler;
+ mAssistContentRequester = assistContentRequester;
mOnDisplayChangingListener = (displayId, fromRotation, toRotation, displayAreaInfo, t) -> {
DesktopModeWindowDecoration decoration;
RunningTaskInfo taskInfo;
@@ -626,7 +632,7 @@
} 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 @@
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 8a012cd..142be91 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.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.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 @@
private CharSequence mAppName;
private CapturedLink mCapturedLink;
private Uri mGenericLink;
+ private Uri mWebUri;
private Consumer<Uri> mOpenInBrowserClickListener;
private ExclusionRegionListener mExclusionRegionListener;
@@ -159,6 +162,7 @@
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 @@
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 @@
SyncTransactionQueue syncQueue,
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
AppToWebGenericLinksParser genericLinksParser,
+ AssistContentRequester assistContentRequester,
Supplier<SurfaceControl.Builder> surfaceControlBuilderSupplier,
Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier,
Supplier<WindowContainerTransaction> windowContainerTransactionSupplier,
@@ -231,6 +236,7 @@
mSyncQueue = syncQueue;
mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer;
mGenericLinksParser = genericLinksParser;
+ mAssistContentRequester = assistContentRequester;
mMaximizeMenuFactory = maximizeMenuFactory;
mHandleMenuFactory = handleMenuFactory;
mMultiInstanceHelper = multiInstanceHelper;
@@ -489,6 +495,8 @@
// 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 @@
}
/**
- * 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 @@
mHandleMenu.show(
/* onToDesktopClickListener= */ () -> {
mOnToDesktopClickListener.accept(APP_HANDLE_MENU_BUTTON);
+ mOnToDesktopClickListener.accept(APP_HANDLE_MENU_BUTTON);
return Unit.INSTANCE;
},
/* onToFullscreenClickListener= */ mOnToFullscreenClickListener,
@@ -1340,6 +1363,7 @@
SyncTransactionQueue syncQueue,
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
AppToWebGenericLinksParser genericLinksParser,
+ AssistContentRequester assistContentRequester,
MultiInstanceHelper multiInstanceHelper) {
return new DesktopModeWindowDecoration(
context,
@@ -1355,6 +1379,7 @@
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 be0549b..3dd8a2b 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.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 @@
@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 @@
mockTransitions,
Optional.of(mockDesktopTasksController),
mockGenericLinksParser,
+ mockAssistContentRequester,
mockMultiInstanceHelper,
mockDesktopModeWindowDecorFactory,
mockInputMonitorFactory,
@@ -1131,7 +1134,7 @@
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 258c860..b9e542a0 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.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.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 @@
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 @@
@Mock
private WindowManager mMockWindowManager;
@Mock
+ private AssistContentRequester mMockAssistContentRequester;
+ @Mock
private HandleMenu mMockHandleMenu;
@Mock
private HandleMenuFactory mMockHandleMenuFactory;
@@ -189,7 +194,8 @@
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 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 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 @@
// 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 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 @@
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 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 @@
// 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 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 @@
// 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 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 @@
@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 @@
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 @@
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 @@
}
+ 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;