Add generic links to app-to-web
This Cl does the following:
1. Creates a build-time and server side list of generic links
2. Creates a flag which determins which list will be used
3. Creates a parser to retrieve said list and parse it into a Hashmap,
mapping package names to urls
4. When a user clicks on the "Open in browser" button in the handle
menu, opens the generic link if captured link is unavailable or
expired
Bug: 349695493
Test: Use open in browser button in header menu + atest
AppToWebGenericLinksParserTests
Flag: com.android.window.flags.enable_desktop_windowing_app_to_web
Change-Id: Ibbd8c21b95679125e3c480d0430b9d473110fdbb
diff --git a/libs/WindowManager/Shell/res/values/config.xml b/libs/WindowManager/Shell/res/values/config.xml
index c2ba064..39f6d8c 100644
--- a/libs/WindowManager/Shell/res/values/config.xml
+++ b/libs/WindowManager/Shell/res/values/config.xml
@@ -179,4 +179,8 @@
<!-- Whether pointer pilfer is required to start back animation. -->
<bool name="config_backAnimationRequiresPointerPilfer">true</bool>
+
+ <!-- This is to be overridden to define a list of packages mapped to web links which will be
+ parsed and utilized for desktop windowing's app-to-web feature. -->
+ <string name="generic_links_list" translatable="false"/>
</resources>
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java
index fc4710f..a1ba24c 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java
@@ -70,6 +70,10 @@
private static final boolean ENFORCE_DEVICE_RESTRICTIONS = SystemProperties.getBoolean(
"persist.wm.debug.desktop_mode_enforce_device_restrictions", true);
+ private static final boolean USE_APP_TO_WEB_BUILD_TIME_GENERIC_LINKS =
+ SystemProperties.getBoolean(
+ "persist.wm.debug.use_app_to_web_build_time_generic_links", true);
+
/** Whether the desktop density override is enabled. */
public static final boolean DESKTOP_DENSITY_OVERRIDE_ENABLED =
SystemProperties.getBoolean("persist.wm.debug.desktop_mode_density_enabled", false);
@@ -176,6 +180,13 @@
}
/**
+ * Returns {@code true} if the app-to-web feature is using the build-time generic links list.
+ */
+ public static boolean useAppToWebBuildTimeGenericLinks() {
+ return USE_APP_TO_WEB_BUILD_TIME_GENERIC_LINKS;
+ }
+
+ /**
* Return {@code true} if the override desktop density is enabled.
*/
private static boolean isDesktopDensityOverrideEnabled() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/AppToWebGenericLinksParser.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/AppToWebGenericLinksParser.kt
new file mode 100644
index 0000000..56447de
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/AppToWebGenericLinksParser.kt
@@ -0,0 +1,96 @@
+/*
+ * 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.content.Context
+import android.provider.DeviceConfig
+import android.webkit.URLUtil
+import com.android.internal.annotations.VisibleForTesting
+import com.android.wm.shell.R
+import com.android.wm.shell.common.ShellExecutor
+import com.android.wm.shell.shared.annotations.ShellMainThread
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus.useAppToWebBuildTimeGenericLinks
+
+/**
+ * Retrieves the build-time or server-side generic links list and parses and stores the
+ * package-to-url pairs.
+ */
+class AppToWebGenericLinksParser(
+ private val context: Context,
+ @ShellMainThread private val mainExecutor: ShellExecutor
+) {
+ private val genericLinksMap: MutableMap<String, String> = mutableMapOf()
+
+ init {
+ // If using the server-side generic links list, register a listener
+ if (!useAppToWebBuildTimeGenericLinks()) {
+ DeviceConfigListener()
+ }
+
+ updateGenericLinksMap()
+ }
+
+ /** Returns the generic link associated with the [packageName] or null if there is none. */
+ fun getGenericLink(packageName: String): String? = genericLinksMap[packageName]
+
+ private fun updateGenericLinksMap() {
+ val genericLinksList =
+ if (useAppToWebBuildTimeGenericLinks()) {
+ context.resources.getString(R.string.generic_links_list)
+ } else {
+ DeviceConfig.getString(NAMESPACE, FLAG_GENERIC_LINKS, /* defaultValue= */ "")
+ } ?: return
+
+ parseGenericLinkList(genericLinksList)
+ }
+
+ private fun parseGenericLinkList(genericLinksList: String) {
+ val newEntries =
+ genericLinksList
+ .split(" ")
+ .filter { it.contains(':') }
+ .map {
+ val (packageName, url) = it.split(':', limit = 2)
+ return@map packageName to url
+ }
+ .filter { URLUtil.isNetworkUrl(it.second) }
+
+ genericLinksMap.clear()
+ genericLinksMap.putAll(newEntries)
+ }
+
+ /**
+ * Listens for changes to the server-side generic links list and updates the package to url map
+ * if [DesktopModeStatus#useBuildTimeGenericLinkList()] is set to false.
+ */
+ inner class DeviceConfigListener : DeviceConfig.OnPropertiesChangedListener {
+ init {
+ DeviceConfig.addOnPropertiesChangedListener(NAMESPACE, mainExecutor, this)
+ }
+
+ override fun onPropertiesChanged(properties: DeviceConfig.Properties) {
+ if (properties.keyset.contains(FLAG_GENERIC_LINKS)) {
+ updateGenericLinksMap()
+ }
+ }
+ }
+
+ companion object {
+ private const val NAMESPACE = DeviceConfig.NAMESPACE_APP_COMPAT_OVERRIDES
+ @VisibleForTesting const val FLAG_GENERIC_LINKS = "generic_links_flag"
+ }
+}
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 80f6a63..b8c22c4 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
@@ -35,6 +35,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.bubbles.BubbleController;
import com.android.wm.shell.bubbles.BubbleData;
import com.android.wm.shell.bubbles.BubbleDataRepository;
@@ -223,7 +224,8 @@
Transitions transitions,
Optional<DesktopTasksController> desktopTasksController,
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
- InteractionJankMonitor interactionJankMonitor) {
+ InteractionJankMonitor interactionJankMonitor,
+ AppToWebGenericLinksParser genericLinksParser) {
if (DesktopModeStatus.canEnterDesktopMode(context)) {
return new DesktopModeWindowDecorViewModel(
context,
@@ -242,7 +244,8 @@
transitions,
desktopTasksController,
rootTaskDisplayAreaOrganizer,
- interactionJankMonitor);
+ interactionJankMonitor,
+ genericLinksParser);
}
return new CaptionWindowDecorViewModel(
context,
@@ -259,6 +262,15 @@
transitions);
}
+ @WMSingleton
+ @Provides
+ static AppToWebGenericLinksParser provideGenericLinksParser(
+ Context context,
+ @ShellMainThread ShellExecutor mainExecutor
+ ) {
+ return new AppToWebGenericLinksParser(context, mainExecutor);
+ }
+
//
// 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 8312aef..fc63970 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
@@ -87,6 +87,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.common.DisplayController;
import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.DisplayLayout;
@@ -162,6 +163,7 @@
private final DesktopModeKeyguardChangeListener mDesktopModeKeyguardChangeListener =
new DesktopModeKeyguardChangeListener();
private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
+ private final AppToWebGenericLinksParser mGenericLinksParser;
private final DisplayInsetsController mDisplayInsetsController;
private final Region mExclusionRegion = Region.obtain();
private boolean mInImmersiveMode;
@@ -198,7 +200,8 @@
Transitions transitions,
Optional<DesktopTasksController> desktopTasksController,
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
- InteractionJankMonitor interactionJankMonitor
+ InteractionJankMonitor interactionJankMonitor,
+ AppToWebGenericLinksParser genericLinksParser
) {
this(
context,
@@ -216,6 +219,7 @@
syncQueue,
transitions,
desktopTasksController,
+ genericLinksParser,
new DesktopModeWindowDecoration.Factory(),
new InputMonitorFactory(),
SurfaceControl.Transaction::new,
@@ -241,6 +245,7 @@
SyncTransactionQueue syncQueue,
Transitions transitions,
Optional<DesktopTasksController> desktopTasksController,
+ AppToWebGenericLinksParser genericLinksParser,
DesktopModeWindowDecoration.Factory desktopModeWindowDecorFactory,
InputMonitorFactory inputMonitorFactory,
Supplier<SurfaceControl.Transaction> transactionFactory,
@@ -266,6 +271,7 @@
mInputMonitorFactory = inputMonitorFactory;
mTransactionFactory = transactionFactory;
mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer;
+ mGenericLinksParser = genericLinksParser;
mInputManager = mContext.getSystemService(InputManager.class);
mWindowDecorByTaskId = windowDecorByTaskId;
mSysUIPackageName = mContext.getResources().getString(
@@ -1155,7 +1161,8 @@
mBgExecutor,
mMainChoreographer,
mSyncQueue,
- mRootTaskDisplayAreaOrganizer);
+ mRootTaskDisplayAreaOrganizer,
+ mGenericLinksParser);
mWindowDecorByTaskId.put(taskInfo.taskId, windowDecoration);
final DragPositioningCallback dragPositioningCallback;
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 529def7..3b62bcf 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
@@ -30,6 +30,7 @@
import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getResizeEdgeHandleSize;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.app.ActivityManager;
import android.app.WindowConfiguration.WindowingMode;
@@ -49,8 +50,8 @@
import android.net.Uri;
import android.os.Handler;
import android.os.Trace;
-import android.util.Log;
import android.util.Size;
+import android.util.Slog;
import android.view.Choreographer;
import android.view.MotionEvent;
import android.view.SurfaceControl;
@@ -68,6 +69,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.common.DisplayController;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.ShellExecutor;
@@ -133,12 +135,15 @@
private CharSequence mAppName;
private CapturedLink mCapturedLink;
+ private Uri mGenericLink;
private OpenInBrowserClickListener mOpenInBrowserClickListener;
private ExclusionRegionListener mExclusionRegionListener;
private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
private final MaximizeMenuFactory mMaximizeMenuFactory;
+ private final HandleMenuFactory mHandleMenuFactory;
+ private final AppToWebGenericLinksParser mGenericLinksParser;
// Hover state for the maximize menu and button. The menu will remain open as long as either of
// these is true. See {@link #onMaximizeHoverStateChanged()}.
@@ -161,13 +166,14 @@
@ShellBackgroundThread ShellExecutor bgExecutor,
Choreographer choreographer,
SyncTransactionQueue syncQueue,
- RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer) {
+ RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
+ AppToWebGenericLinksParser genericLinksParser) {
this (context, displayController, splitScreenController, taskOrganizer, taskInfo,
taskSurface, handler, bgExecutor, choreographer, syncQueue,
- rootTaskDisplayAreaOrganizer, SurfaceControl.Builder::new,
+ rootTaskDisplayAreaOrganizer, genericLinksParser, SurfaceControl.Builder::new,
SurfaceControl.Transaction::new, WindowContainerTransaction::new,
SurfaceControl::new, new SurfaceControlViewHostFactory() {},
- DefaultMaximizeMenuFactory.INSTANCE);
+ DefaultMaximizeMenuFactory.INSTANCE, DefaultHandleMenuFactory.INSTANCE);
}
DesktopModeWindowDecoration(
@@ -182,12 +188,14 @@
Choreographer choreographer,
SyncTransactionQueue syncQueue,
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
+ AppToWebGenericLinksParser genericLinksParser,
Supplier<SurfaceControl.Builder> surfaceControlBuilderSupplier,
Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier,
Supplier<WindowContainerTransaction> windowContainerTransactionSupplier,
Supplier<SurfaceControl> surfaceControlSupplier,
SurfaceControlViewHostFactory surfaceControlViewHostFactory,
- MaximizeMenuFactory maximizeMenuFactory) {
+ MaximizeMenuFactory maximizeMenuFactory,
+ HandleMenuFactory handleMenuFactory) {
super(context, displayController, taskOrganizer, taskInfo, taskSurface,
surfaceControlBuilderSupplier, surfaceControlTransactionSupplier,
windowContainerTransactionSupplier, surfaceControlSupplier,
@@ -198,7 +206,9 @@
mChoreographer = choreographer;
mSyncQueue = syncQueue;
mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer;
+ mGenericLinksParser = genericLinksParser;
mMaximizeMenuFactory = maximizeMenuFactory;
+ mHandleMenuFactory = handleMenuFactory;
}
/**
@@ -425,11 +435,23 @@
}
void onOpenInBrowserClick() {
- if (mOpenInBrowserClickListener == null || mCapturedLink == null) return;
- mOpenInBrowserClickListener.onClick(this, mCapturedLink.mUri);
+ if (mOpenInBrowserClickListener == null || mHandleMenu == null) {
+ return;
+ }
+ mOpenInBrowserClickListener.onClick(this, mHandleMenu.getOpenInBrowserLink());
onCapturedLinkExpired();
}
+ @Nullable
+ private Uri getBrowserLink() {
+ // If the captured link is available and has not expired, return the captured link.
+ // Otherwise, return the generic link which is set to null if a generic link is unavailable.
+ if (mCapturedLink != null && !mCapturedLink.mExpired) {
+ return mCapturedLink.mUri;
+ }
+ return mGenericLink;
+ }
+
private void updateDragResizeListener(SurfaceControl oldDecorationSurface) {
if (!isDragResizable(mTaskInfo)) {
if (!mTaskInfo.positionInParent.equals(mPositionInParent)) {
@@ -700,7 +722,7 @@
}
final ComponentName baseActivity = mTaskInfo.baseActivity;
if (baseActivity == null) {
- Log.e(TAG, "Base activity component not found in task");
+ Slog.e(TAG, "Base activity component not found in task");
return;
}
final PackageManager pm = mContext.getApplicationContext().getPackageManager();
@@ -719,7 +741,7 @@
final ApplicationInfo applicationInfo = activityInfo.applicationInfo;
mAppName = pm.getApplicationLabel(applicationInfo);
} catch (PackageManager.NameNotFoundException e) {
- Log.e(TAG, "Base activity's component name cannot be found on the system");
+ Slog.e(TAG, "Base activity's component name cannot be found on the system", e);
} finally {
Trace.endSection();
}
@@ -914,7 +936,8 @@
*/
void createHandleMenu(SplitScreenController splitScreenController) {
loadAppInfoIfNeeded();
- mHandleMenu = new HandleMenu(
+ updateGenericLink();
+ mHandleMenu = mHandleMenuFactory.create(
this,
mRelayoutParams.mLayoutResId,
mOnCaptionButtonClickListener,
@@ -924,7 +947,7 @@
mDisplayController,
splitScreenController,
DesktopModeStatus.canEnterDesktopMode(mContext),
- browserLinkAvailable(),
+ getBrowserLink(),
mResult.mCaptionWidth,
mResult.mCaptionHeight,
mResult.mCaptionX
@@ -933,9 +956,15 @@
mHandleMenu.show();
}
- @VisibleForTesting
- boolean browserLinkAvailable() {
- return mCapturedLink != null && !mCapturedLink.mExpired;
+ private void updateGenericLink() {
+ final ComponentName baseActivity = mTaskInfo.baseActivity;
+ if (baseActivity == null) {
+ return;
+ }
+
+ final String genericLink =
+ mGenericLinksParser.getGenericLink(baseActivity.getPackageName());
+ mGenericLink = genericLink == null ? null : Uri.parse(genericLink);
}
/**
@@ -1219,7 +1248,8 @@
@ShellBackgroundThread ShellExecutor bgExecutor,
Choreographer choreographer,
SyncTransactionQueue syncQueue,
- RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer) {
+ RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
+ AppToWebGenericLinksParser genericLinksParser) {
return new DesktopModeWindowDecoration(
context,
displayController,
@@ -1231,7 +1261,8 @@
bgExecutor,
choreographer,
syncQueue,
- rootTaskDisplayAreaOrganizer);
+ rootTaskDisplayAreaOrganizer,
+ genericLinksParser);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
index e174e83..32522c6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
@@ -27,6 +27,7 @@
import android.graphics.Point
import android.graphics.PointF
import android.graphics.Rect
+import android.net.Uri
import android.view.MotionEvent
import android.view.SurfaceControl
import android.view.View
@@ -68,7 +69,7 @@
private val displayController: DisplayController,
private val splitScreenController: SplitScreenController,
private val shouldShowWindowingPill: Boolean,
- private val shouldShowBrowserPill: Boolean,
+ val openInBrowserLink: Uri?,
private val captionWidth: Int,
private val captionHeight: Int,
captionX: Int
@@ -106,6 +107,9 @@
// those as well.
private val globalMenuPosition: Point = Point()
+ private val shouldShowBrowserPill: Boolean
+ get() = openInBrowserLink != null
+
init {
updateHandleMenuPillPositions(captionX)
}
@@ -497,3 +501,57 @@
private const val SHOULD_SHOW_MORE_ACTIONS_PILL = false
}
}
+
+/** A factory interface to create a [HandleMenu]. */
+interface HandleMenuFactory {
+ fun create(
+ parentDecor: DesktopModeWindowDecoration,
+ layoutResId: Int,
+ onClickListener: View.OnClickListener?,
+ onTouchListener: View.OnTouchListener?,
+ appIconBitmap: Bitmap?,
+ appName: CharSequence?,
+ displayController: DisplayController,
+ splitScreenController: SplitScreenController,
+ shouldShowWindowingPill: Boolean,
+ openInBrowserLink: Uri?,
+ captionWidth: Int,
+ captionHeight: Int,
+ captionX: Int
+ ): HandleMenu
+}
+
+/** A [HandleMenuFactory] implementation that creates a [HandleMenu]. */
+object DefaultHandleMenuFactory : HandleMenuFactory {
+ override fun create(
+ parentDecor: DesktopModeWindowDecoration,
+ layoutResId: Int,
+ onClickListener: View.OnClickListener?,
+ onTouchListener: View.OnTouchListener?,
+ appIconBitmap: Bitmap?,
+ appName: CharSequence?,
+ displayController: DisplayController,
+ splitScreenController: SplitScreenController,
+ shouldShowWindowingPill: Boolean,
+ openInBrowserLink: Uri?,
+ captionWidth: Int,
+ captionHeight: Int,
+ captionX: Int
+ ): HandleMenu {
+ return HandleMenu(
+ parentDecor,
+ layoutResId,
+ onClickListener,
+ onTouchListener,
+ appIconBitmap,
+ appName,
+ displayController,
+ splitScreenController,
+ shouldShowWindowingPill,
+ openInBrowserLink,
+ captionWidth,
+ captionHeight,
+ captionX
+ )
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apptoweb/AppToWebGenericLinksParserTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apptoweb/AppToWebGenericLinksParserTests.kt
new file mode 100644
index 0000000..053027f
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apptoweb/AppToWebGenericLinksParserTests.kt
@@ -0,0 +1,131 @@
+/*
+ * 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.provider.DeviceConfig
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.testing.TestableResources
+import androidx.test.filters.SmallTest
+import com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn
+import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
+import com.android.dx.mockito.inline.extended.StaticMockitoSession
+import com.android.wm.shell.R
+import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.common.ShellExecutor
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
+import com.android.wm.shell.apptoweb.AppToWebGenericLinksParser.Companion.FLAG_GENERIC_LINKS
+import junit.framework.TestCase.assertEquals
+import junit.framework.TestCase.assertNull
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+import org.mockito.quality.Strictness
+
+/**
+ * Tests for [AppToWebGenericLinksParser].
+ *
+ * Build/Install/Run: atest WMShellUnitTests:AppToWebGenericLinksParserTests
+ */
+@SmallTest
+@TestableLooper.RunWithLooper
+@RunWith(AndroidTestingRunner::class)
+class AppToWebGenericLinksParserTests : ShellTestCase() {
+ @Mock private lateinit var mockExecutor: ShellExecutor
+
+ private lateinit var genericLinksParser: AppToWebGenericLinksParser
+ private lateinit var mockitoSession: StaticMockitoSession
+ private lateinit var resources: TestableResources
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+
+ mockitoSession =
+ mockitoSession()
+ .strictness(Strictness.LENIENT)
+ .spyStatic(DesktopModeStatus::class.java)
+ .startMocking()
+ resources = mContext.getOrCreateTestableResources()
+ resources.addOverride(R.string.generic_links_list, BUILD_TIME_LIST)
+ DeviceConfig.setProperty(
+ NAMESPACE,
+ FLAG_GENERIC_LINKS,
+ SERVER_SIDE_LIST,
+ false /* makeDefault */
+ )
+ }
+
+ @After
+ fun tearDown() {
+ mockitoSession.finishMocking()
+ }
+
+ @Test
+ fun init_usingBuildTimeList() {
+ doReturn(true).`when` { DesktopModeStatus.useAppToWebBuildTimeGenericLinks() }
+ genericLinksParser = AppToWebGenericLinksParser(mContext, mockExecutor)
+ // Assert build-time list correctly parsed
+ assertEquals(URL_B, genericLinksParser.getGenericLink(PACKAGE_NAME_1))
+ }
+
+ @Test
+ fun init_usingServerSideList() {
+ doReturn(false).`when` { DesktopModeStatus.useAppToWebBuildTimeGenericLinks() }
+ genericLinksParser = AppToWebGenericLinksParser(mContext, mockExecutor)
+ // Assert server side list correctly parsed
+ assertEquals(URL_S, genericLinksParser.getGenericLink(PACKAGE_NAME_1))
+ }
+
+ @Test
+ fun init_ignoresMalformedPair() {
+ doReturn(true).`when` { DesktopModeStatus.useAppToWebBuildTimeGenericLinks() }
+ val packageName2 = "com.google.android.slides"
+ val url2 = "https://docs.google.com"
+ resources.addOverride(R.string.generic_links_list,
+ "$PACKAGE_NAME_1:$URL_B error $packageName2:$url2")
+ genericLinksParser = AppToWebGenericLinksParser(mContext, mockExecutor)
+ // Assert generics links list correctly parsed
+ assertEquals(URL_B, genericLinksParser.getGenericLink(PACKAGE_NAME_1))
+ assertEquals(url2, genericLinksParser.getGenericLink(packageName2))
+ }
+
+
+ @Test
+ fun onlySavesValidPackageToUrlMaps() {
+ doReturn(true).`when` { DesktopModeStatus.useAppToWebBuildTimeGenericLinks() }
+ resources.addOverride(R.string.generic_links_list, "$PACKAGE_NAME_1:www.yout")
+ genericLinksParser = AppToWebGenericLinksParser(mContext, mockExecutor)
+ // Verify map with invalid url not saved
+ assertNull(genericLinksParser.getGenericLink(PACKAGE_NAME_1))
+ }
+
+ companion object {
+ private const val PACKAGE_NAME_1 = "com.google.android.youtube"
+
+ private const val URL_B = "http://www.youtube.com"
+ private const val URL_S = "http://www.google.com"
+
+ private const val SERVER_SIDE_LIST = "$PACKAGE_NAME_1:$URL_S"
+ private const val BUILD_TIME_LIST = "$PACKAGE_NAME_1:$URL_B"
+
+ private const val NAMESPACE = DeviceConfig.NAMESPACE_APP_COMPAT_OVERRIDES
+ }
+}
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 aeae0be..01c4f3a 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
@@ -64,6 +64,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.common.DisplayController
import com.android.wm.shell.common.DisplayInsetsController
import com.android.wm.shell.common.DisplayLayout
@@ -140,6 +141,7 @@
@Mock private lateinit var mockShellCommandHandler: ShellCommandHandler
@Mock private lateinit var mockWindowManager: IWindowManager
@Mock private lateinit var mockInteractionJankMonitor: InteractionJankMonitor
+ @Mock private lateinit var mockGenericLinksParser: AppToWebGenericLinksParser
private val bgExecutor = TestShellExecutor()
private val transactionFactory = Supplier<SurfaceControl.Transaction> {
@@ -171,11 +173,13 @@
mockSyncQueue,
mockTransitions,
Optional.of(mockDesktopTasksController),
+ mockGenericLinksParser,
mockDesktopModeWindowDecorFactory,
mockInputMonitorFactory,
transactionFactory,
mockRootTaskDisplayAreaOrganizer,
- windowDecorByTaskIdSpy, mockInteractionJankMonitor
+ windowDecorByTaskIdSpy,
+ mockInteractionJankMonitor
)
desktopModeWindowDecorViewModel.setSplitScreenController(mockSplitScreenController)
whenever(mockDisplayController.getDisplayLayout(any())).thenReturn(mockDisplayLayout)
@@ -218,7 +222,8 @@
bgExecutor,
mockMainChoreographer,
mockSyncQueue,
- mockRootTaskDisplayAreaOrganizer
+ mockRootTaskDisplayAreaOrganizer,
+ mockGenericLinksParser
)
verify(decoration).close()
}
@@ -244,7 +249,8 @@
bgExecutor,
mockMainChoreographer,
mockSyncQueue,
- mockRootTaskDisplayAreaOrganizer
+ mockRootTaskDisplayAreaOrganizer,
+ mockGenericLinksParser
)
task.setWindowingMode(WINDOWING_MODE_FREEFORM)
@@ -261,7 +267,8 @@
bgExecutor,
mockMainChoreographer,
mockSyncQueue,
- mockRootTaskDisplayAreaOrganizer
+ mockRootTaskDisplayAreaOrganizer,
+ mockGenericLinksParser
)
}
@@ -359,7 +366,7 @@
verify(mockDesktopModeWindowDecorFactory, never())
.create(any(), any(), any(), any(), eq(task), any(), any(), any(), any(), any(),
- any())
+ any(), any())
}
@Test
@@ -381,7 +388,7 @@
onTaskOpening(task)
verify(mockDesktopModeWindowDecorFactory)
.create(any(), any(), any(), any(), eq(task), any(), any(), any(), any(),
- any(), any())
+ any(), any(), any())
} finally {
mockitoSession.finishMocking()
}
@@ -399,7 +406,7 @@
verify(mockDesktopModeWindowDecorFactory, never())
.create(any(), any(), any(), any(), eq(task), any(), any(), any(), any(), any(),
- any())
+ any(), any())
}
@Test
@@ -416,7 +423,7 @@
onTaskOpening(task)
verify(mockDesktopModeWindowDecorFactory, never())
- .create(any(), any(), any(), any(), eq(task), any(), any(), any(), any(),
+ .create(any(), any(), any(), any(), eq(task), any(), any(), any(), any(), any(),
any(), any())
}
@@ -515,7 +522,7 @@
onTaskOpening(task)
verify(mockDesktopModeWindowDecorFactory, never())
.create(any(), any(), any(), any(), eq(task), any(), any(), any(), any(), any(),
- any())
+ any(), any())
} finally {
mockitoSession.finishMocking()
}
@@ -540,7 +547,7 @@
onTaskOpening(task)
verify(mockDesktopModeWindowDecorFactory)
.create(any(), any(), any(), any(), eq(task), any(), any(), any(), any(), any(),
- any())
+ any(), any())
} finally {
mockitoSession.finishMocking()
}
@@ -564,7 +571,7 @@
onTaskOpening(task)
verify(mockDesktopModeWindowDecorFactory)
.create(any(), any(), any(), any(), eq(task), any(), any(), any(), any(), any(),
- any())
+ any(), any())
} finally {
mockitoSession.finishMocking()
}
@@ -702,8 +709,8 @@
private fun setUpMockDecorationForTask(task: RunningTaskInfo): DesktopModeWindowDecoration {
val decoration = mock(DesktopModeWindowDecoration::class.java)
whenever(
- mockDesktopModeWindowDecorFactory.create(
- any(), any(), any(), any(), eq(task), any(), any(), any(), any(), any(), any())
+ mockDesktopModeWindowDecorFactory.create(any(), any(), any(), any(), eq(task), 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 412fef3..4b069f9 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
@@ -31,6 +31,7 @@
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.any;
@@ -59,6 +60,7 @@
import android.platform.test.flag.junit.SetFlagsRule;
import android.testing.AndroidTestingRunner;
import android.testing.TestableContext;
+import android.testing.TestableLooper;
import android.view.AttachedSurfaceControl;
import android.view.Choreographer;
import android.view.Display;
@@ -83,6 +85,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.common.DisplayController;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
@@ -114,6 +117,7 @@
* atest WMShellUnitTests:DesktopModeWindowDecorationTests
*/
@SmallTest
+@TestableLooper.RunWithLooper
@RunWith(AndroidTestingRunner.class)
public class DesktopModeWindowDecorationTests extends ShellTestCase {
private static final String USE_WINDOW_SHADOWS_SYSPROP_KEY =
@@ -123,7 +127,8 @@
private static final String USE_ROUNDED_CORNERS_SYSPROP_KEY =
"persist.wm.debug.desktop_use_rounded_corners";
- private static final Uri TEST_URI = Uri.parse("www.google.com");
+ private static final Uri TEST_URI1 = Uri.parse("https://www.google.com/");
+ private static final Uri TEST_URI2 = Uri.parse("https://docs.google.com/");
@Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT);
@@ -161,6 +166,12 @@
private Handler mMockHandler;
@Mock
private DesktopModeWindowDecoration.OpenInBrowserClickListener mMockOpenInBrowserClickListener;
+ @Mock
+ private AppToWebGenericLinksParser mMockGenericLinksParser;
+ @Mock
+ private HandleMenu mMockHandleMenu;
+ @Mock
+ private HandleMenuFactory mMockHandleMenuFactory;
@Captor
private ArgumentCaptor<Function1<Boolean, Unit>> mOnMaxMenuHoverChangeListener;
@Captor
@@ -204,6 +215,8 @@
final Display defaultDisplay = mock(Display.class);
doReturn(defaultDisplay).when(mMockDisplayController).getDisplay(Display.DEFAULT_DISPLAY);
doReturn(mInsetsState).when(mMockDisplayController).getInsetsState(anyInt());
+ doReturn(mMockHandleMenu).when(mMockHandleMenuFactory).create(any(), anyInt(), any(), any(),
+ any(), any(), any(), any(), anyBoolean(), any(), anyInt(), anyInt(), anyInt());
}
@After
@@ -572,65 +585,123 @@
verify(mMockHandler).removeCallbacks(any());
}
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB)
+ public void capturedLink_handleMenuBrowserLinkSetToCapturedLinkIfValid() {
+ final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */);
+ final DesktopModeWindowDecoration decor = createWindowDecoration(
+ taskInfo, TEST_URI1 /* captured link */, TEST_URI2 /* generic link */);
+
+ // Verify handle menu's browser link set as captured link
+ decor.createHandleMenu(mMockSplitScreenController);
+ verifyHandleMenuCreated(TEST_URI1);
+ }
+
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB)
public void capturedLink_postsOnCapturedLinkExpiredRunnable() {
final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */);
+ final DesktopModeWindowDecoration decor = createWindowDecoration(
+ taskInfo, TEST_URI1 /* captured link */, null /* generic link */);
final ArgumentCaptor<Runnable> runnableArgument = ArgumentCaptor.forClass(Runnable.class);
- final DesktopModeWindowDecoration decor = createWindowDecoration(taskInfo);
- decor.relayout(taskInfo);
- // Assert captured link is set
- assertTrue(decor.browserLinkAvailable());
- // Asset runnable posted to set captured link to expired
+ // Run runnable to set captured link to expired
verify(mMockHandler).postDelayed(runnableArgument.capture(), anyLong());
runnableArgument.getValue().run();
- assertFalse(decor.browserLinkAvailable());
+
+ // Verify captured link is no longer valid by verifying link is not set as handle menu
+ // browser link.
+ decor.createHandleMenu(mMockSplitScreenController);
+ verifyHandleMenuCreated(null /* uri */);
}
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB)
public void capturedLink_capturedLinkNotResetToSameLink() {
final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */);
- final DesktopModeWindowDecoration decor = createWindowDecoration(taskInfo);
+ final DesktopModeWindowDecoration decor = createWindowDecoration(
+ taskInfo, TEST_URI1 /* captured link */, null /* generic link */);
final ArgumentCaptor<Runnable> runnableArgument = ArgumentCaptor.forClass(Runnable.class);
- // Set captured link and run on captured link expired runnable
- decor.relayout(taskInfo);
+ // Run runnable to set captured link to expired
verify(mMockHandler).postDelayed(runnableArgument.capture(), anyLong());
runnableArgument.getValue().run();
+ // Relayout decor with same captured link
decor.relayout(taskInfo);
- // Assert captured link not set to same value twice
- assertFalse(decor.browserLinkAvailable());
+
+ // Verify handle menu's browser link not set to captured link since link is expired
+ decor.createHandleMenu(mMockSplitScreenController);
+ verifyHandleMenuCreated(null /* uri */);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB)
+ public void capturedLink_capturedLinkStillUsedIfExpiredAfterHandleMenuCreation() {
+ final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */);
+ final DesktopModeWindowDecoration decor = createWindowDecoration(
+ taskInfo, TEST_URI1 /* captured link */, null /* generic link */);
+ final ArgumentCaptor<Runnable> runnableArgument = ArgumentCaptor.forClass(Runnable.class);
+
+ // Create handle menu before link expires
+ decor.createHandleMenu(mMockSplitScreenController);
+
+ // Run runnable to set captured link to expired
+ verify(mMockHandler).postDelayed(runnableArgument.capture(), anyLong());
+ runnableArgument.getValue().run();
+
+ // Verify handle menu's browser link is set to captured link since menu was opened before
+ // captured link expired
+ decor.createHandleMenu(mMockSplitScreenController);
+ verifyHandleMenuCreated(TEST_URI1);
}
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB)
public void capturedLink_capturedLinkExpiresAfterClick() {
final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */);
- final DesktopModeWindowDecoration decor = createWindowDecoration(taskInfo);
-
- decor.relayout(taskInfo);
- // Assert captured link is set
- assertTrue(decor.browserLinkAvailable());
+ final DesktopModeWindowDecoration decor = createWindowDecoration(
+ taskInfo, TEST_URI1 /* captured link */, null /* generic link */);
+ // Simulate menu opening and clicking open in browser button
+ decor.createHandleMenu(mMockSplitScreenController);
decor.onOpenInBrowserClick();
- //Assert Captured link expires after button is clicked
- assertFalse(decor.browserLinkAvailable());
+
+ // Verify handle menu's browser link not set to captured link since link not valid after
+ // open in browser clicked
+ decor.createHandleMenu(mMockSplitScreenController);
+ verifyHandleMenuCreated(null /* uri */);
}
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB)
public void capturedLink_openInBrowserListenerCalledOnClick() {
final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */);
- final DesktopModeWindowDecoration decor = createWindowDecoration(taskInfo);
-
- decor.relayout(taskInfo);
+ final DesktopModeWindowDecoration decor = createWindowDecoration(
+ taskInfo, TEST_URI1 /* captured link */, null /* generic link */);
+ decor.createHandleMenu(mMockSplitScreenController);
decor.onOpenInBrowserClick();
verify(mMockOpenInBrowserClickListener).onClick(any(), any());
}
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB)
+ public void genericLink_genericLinkUsedWhenCapturedLinkUnavailable() {
+ 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);
+ verifyHandleMenuCreated(TEST_URI2);
+ }
+
+ private void verifyHandleMenuCreated(@Nullable Uri uri) {
+ verify(mMockHandleMenuFactory).create(any(), anyInt(), any(), any(), any(), any(), any(),
+ any(), anyBoolean(), eq(uri), anyInt(), anyInt(), anyInt());
+ }
+
private void createMaximizeMenu(DesktopModeWindowDecoration decoration, MaximizeMenu menu) {
final OnTaskActionClickListener l = (taskId, tag) -> {};
decoration.setOnMaximizeOrRestoreClickListener(l);
@@ -657,6 +728,18 @@
R.dimen.rounded_corner_radius_bottom, fillValue);
}
+ private DesktopModeWindowDecoration createWindowDecoration(
+ ActivityManager.RunningTaskInfo taskInfo, @Nullable Uri capturedLink,
+ @Nullable Uri genericLink) {
+ taskInfo.capturedLink = capturedLink;
+ taskInfo.capturedLinkTimestamp = System.currentTimeMillis();
+ final String genericLinkString = genericLink == null ? null : genericLink.toString();
+ doReturn(genericLinkString).when(mMockGenericLinksParser).getGenericLink(any());
+ final DesktopModeWindowDecoration decor = createWindowDecoration(taskInfo);
+ // Relayout to set captured link
+ decor.relayout(taskInfo);
+ return decor;
+ }
private DesktopModeWindowDecoration createWindowDecoration(
ActivityManager.RunningTaskInfo taskInfo) {
@@ -669,14 +752,15 @@
final DesktopModeWindowDecoration windowDecor = new DesktopModeWindowDecoration(mContext,
mMockDisplayController, mMockSplitScreenController, mMockShellTaskOrganizer,
taskInfo, mMockSurfaceControl, mMockHandler, mBgExecutor, mMockChoreographer,
- mMockSyncQueue, mMockRootTaskDisplayAreaOrganizer,
+ mMockSyncQueue, mMockRootTaskDisplayAreaOrganizer, mMockGenericLinksParser,
SurfaceControl.Builder::new, mMockTransactionSupplier,
WindowContainerTransaction::new, SurfaceControl::new,
- mMockSurfaceControlViewHostFactory, maximizeMenuFactory);
+ mMockSurfaceControlViewHostFactory, maximizeMenuFactory, mMockHandleMenuFactory);
windowDecor.setCaptionListeners(mMockTouchEventListener, mMockTouchEventListener,
mMockTouchEventListener, mMockTouchEventListener);
windowDecor.setExclusionRegionListener(mMockExclusionRegionListener);
windowDecor.setOpenInBrowserClickListener(mMockOpenInBrowserClickListener);
+ windowDecor.mDecorWindowContext = mContext;
return windowDecor;
}
@@ -692,8 +776,6 @@
"DesktopModeWindowDecorationTests");
taskInfo.baseActivity = new ComponentName("com.android.wm.shell.windowdecor",
"DesktopModeWindowDecorationTests");
- taskInfo.capturedLink = TEST_URI;
- taskInfo.capturedLinkTimestamp = System.currentTimeMillis();
return taskInfo;
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt
index e548f8f..ed43aa3 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt
@@ -229,7 +229,7 @@
val handleMenu = HandleMenu(mockDesktopWindowDecoration, layoutId,
onClickListener, onTouchListener, appIcon, appName, displayController,
splitScreenController, shouldShowWindowingPill = true,
- shouldShowBrowserPill = true, captionWidth = HANDLE_WIDTH, captionHeight = 50,
+ null /* openInBrowserLink */, captionWidth = HANDLE_WIDTH, captionHeight = 50,
captionX = captionX
)
handleMenu.show()