Loading libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/AppToWebRepository.kt 0 → 100644 +150 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 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.ActivityManager.RunningTaskInfo import android.app.assist.AssistContent import android.content.Context import android.content.Intent import android.net.Uri import android.util.IndentingPrintWriter import androidx.core.net.toUri import com.android.internal.protolog.ProtoLog import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE import java.io.PrintWriter import kotlin.coroutines.suspendCoroutine /** * App-to-Web has the following features: transferring an app session to the web and transferring * a web session to the relevant app. To transfer an app session to the web, we utilize * three different [Uri]s: * 1. webUri: The web URI provided by the app using [AssistContent] * 2. capturedLink: The link used to open the app if app was opened by clicking on a link * 3. genericLink: The system provided link for the app * In order to create the [Intent] to transfer the user from app to the web, the [Uri]s listed above * are checked in the given order and the first non-null link is used. When transferring from the * web to an app, the [Uri] must be provided by the browser application through [AssistContent]. * * This Repository encapsulates the data stored for the App-to-Web feature for a single task and * creates the intents used to open switch between an app or browser session. */ class AppToWebRepository( private val userContext: Context, private val taskId: Int, private val assistContentRequester: AssistContentRequester, private val genericLinksParser: AppToWebGenericLinksParser, ) { private var capturedLink: CapturedLink? = null /** Sets the captured link if a new link is provided. */ fun setCapturedLink(link: Uri, timeStamp: Long) { if (capturedLink?.timeStamp == timeStamp) return capturedLink = CapturedLink(link, timeStamp) } /** * Checks if [capturedLink] is available (non-null and has not been used) to use for switching * to browser session. */ fun isCapturedLinkAvailable(): Boolean { val link = capturedLink ?: return false return !link.used } /** Sets the captured link as used. */ fun onCapturedLinkUsed() { capturedLink?.setUsed() } /** * Retrieves the latest webUri and genericLink. If the task requesting the intent * [isBrowserApp], intent is created to switch to application if link was provided by browser * app and a relevant application exists to host the app. Otherwise, returns intent to switch * to browser if webUri, capturedLink, or genericLink is available. * * Note that the capturedLink should be updated separately using [setCapturedLink] * */ suspend fun getAppToWebIntent(taskInfo: RunningTaskInfo, isBrowserApp: Boolean): Intent? { ProtoLog.d( WM_SHELL_DESKTOP_MODE, "AppToWebRepository: Updating browser links for task $taskId" ) val assistContent = assistContentRequester.requestAssistContent(taskInfo.taskId) val webUri = assistContent?.getSessionWebUri() return if (isBrowserApp) { getAppIntent(webUri) } else { getBrowserIntent(webUri, getGenericLink(taskInfo)) } } private suspend fun AssistContentRequester.requestAssistContent(taskId: Int): AssistContent? = suspendCoroutine { continuation -> requestAssistContent(taskId) { continuation.resumeWith(Result.success(it)) } } /** Returns the browser link associated with the given application if available. */ private fun getBrowserIntent(webUri: Uri?, genericLink: Uri?): Intent? { val browserLink = webUri ?: if (isCapturedLinkAvailable()) { capturedLink?.uri } else { genericLink } ?: return null return getBrowserIntent(browserLink, userContext.packageManager, userContext.userId) } private fun getAppIntent(webUri: Uri?): Intent? { webUri ?: return null return getAppIntent( uri = webUri, packageManager = userContext.packageManager, userId = userContext.userId ) } private fun getGenericLink(taskInfo: RunningTaskInfo): Uri? { ProtoLog.d( WM_SHELL_DESKTOP_MODE, "AppToWebRepository: Updating generic link for task %d", taskId ) val baseActivity = taskInfo.baseActivity ?: return null return genericLinksParser.getGenericLink(baseActivity.packageName)?.toUri() } /** Dumps the repository's current state. */ fun dump(originalWriter: PrintWriter, prefix: String) { val pw = IndentingPrintWriter(originalWriter, " ", prefix) pw.println("AppToWebRepository for task#$taskId") pw.increaseIndent() pw.println("CapturedLink=$capturedLink") } /** Encapsulates data associated with a captured link. */ private data class CapturedLink(val uri: Uri, val timeStamp: Long) { /** Signifies if captured link has already been used, making it invalid. */ var used = false /** Sets the captured link as used. */ fun setUsed() { used = true } } } libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/AssistContentRequester.kt +1 −1 Original line number Diff line number Diff line Loading @@ -39,7 +39,7 @@ class AssistContentRequester( private val callBackExecutor: Executor, private val systemInteractionExecutor: Executor ) { interface Callback { fun interface Callback { // Called when the [AssistContent] of the requested task is available. fun onAssistContentAvailable(assistContent: AssistContent?) } Loading libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java +3 −3 Original line number Diff line number Diff line Loading @@ -193,7 +193,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin private final AppHandleViewHolder.Factory mAppHandleViewHolderFactory; private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer; private final MaximizeMenuFactory mMaximizeMenuFactory; private final HandleMenuFactory mHandleMenuFactory; private final HandleMenu.HandleMenuFactory mHandleMenuFactory; private final AppToWebGenericLinksParser mGenericLinksParser; private final AssistContentRequester mAssistContentRequester; private final DesktopModeCompatPolicy mDesktopModeCompatPolicy; Loading Loading @@ -268,7 +268,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin new SurfaceControlViewHostFactory() {}, windowDecorViewHostSupplier, DefaultMaximizeMenuFactory.INSTANCE, DefaultHandleMenuFactory.INSTANCE, multiInstanceHelper, HandleMenu.HandleMenuFactory.INSTANCE, multiInstanceHelper, windowDecorCaptionRepository, desktopModeEventLogger, desktopModeUiEventLogger, desktopModeCompatPolicy, desktopState, desktopConfig, windowDecorationActions); Loading Loading @@ -305,7 +305,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin SurfaceControlViewHostFactory surfaceControlViewHostFactory, @NonNull WindowDecorViewHostSupplier<WindowDecorViewHost> windowDecorViewHostSupplier, MaximizeMenuFactory maximizeMenuFactory, HandleMenuFactory handleMenuFactory, HandleMenu.HandleMenuFactory handleMenuFactory, MultiInstanceHelper multiInstanceHelper, WindowDecorCaptionRepository windowDecorCaptionRepository, DesktopModeEventLogger desktopModeEventLogger, Loading libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt +187 −71 Original line number Diff line number Diff line Loading @@ -25,10 +25,12 @@ import android.content.Intent import android.content.res.ColorStateList import android.content.res.Resources import android.graphics.Bitmap import android.graphics.PixelFormat import android.graphics.Point import android.graphics.PointF import android.graphics.Rect import android.os.Bundle import android.view.Display import android.view.Display.DEFAULT_DISPLAY import android.view.LayoutInflater import android.view.MotionEvent Loading @@ -38,11 +40,13 @@ import android.view.View import android.view.View.OnClickListener import android.view.ViewGroup import android.view.WindowInsets.Type.systemBars import android.view.WindowManager import android.view.WindowManager.LayoutParams import android.view.WindowlessWindowManager import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction import android.widget.ImageButton import android.widget.ImageView import android.widget.Space import android.window.DesktopExperienceFlags import android.window.DesktopModeFlags import android.window.SurfaceSyncGroup import androidx.annotation.StringRes Loading @@ -64,8 +68,10 @@ import com.android.wm.shell.shared.bubbles.ContextUtils.isRtl import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource.APP_HANDLE_MENU_BUTTON import com.android.wm.shell.shared.split.SplitScreenConstants import com.android.wm.shell.splitscreen.SplitScreenController import com.android.wm.shell.windowdecor.WindowDecoration2.SurfaceControlViewHostFactory import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalSystemViewContainer import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalViewContainer import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalViewHostViewContainer import com.android.wm.shell.windowdecor.common.DecorThemeUtil import com.android.wm.shell.windowdecor.common.DrawableInsets import com.android.wm.shell.windowdecor.common.WindowDecorTaskResourceLoader Loading @@ -77,7 +83,6 @@ import com.android.wm.shell.windowdecor.extension.isPinned import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job import kotlinx.coroutines.MainCoroutineDispatcher import kotlinx.coroutines.isActive import kotlinx.coroutines.launch import kotlinx.coroutines.withContext Loading @@ -91,10 +96,14 @@ import kotlinx.coroutines.withContext * Windowing Options(Proto 2 only): Buttons to change windowing modes. * Additional Options: Miscellaneous functions including screenshot and closing task. */ class HandleMenu( class HandleMenu private constructor( @ShellMainThread private val mainDispatcher: CoroutineDispatcher, @ShellBackgroundThread private val bgScope: CoroutineScope, private val parentDecor: DesktopModeWindowDecoration, private val context: Context, private val taskInfo: RunningTaskInfo, private val parentSurface: SurfaceControl, private val display: Display, private val parentDecor: DesktopModeWindowDecoration?, private val windowManagerWrapper: WindowManagerWrapper, private val windowDecorationActions: WindowDecorationActions, private val taskResourceLoader: WindowDecorTaskResourceLoader, Loading @@ -112,11 +121,11 @@ class HandleMenu( private val captionWidth: Int, private val captionHeight: Int, captionX: Int, captionY: Int captionY: Int, private val surfaceControlBuilderSupplier: () -> SurfaceControl.Builder, private val surfaceControlTransactionSupplier: () -> SurfaceControl.Transaction, private val surfaceControlViewHostFactory: SurfaceControlViewHostFactory, ) { private val context: Context = parentDecor.mDecorWindowContext private val taskInfo: RunningTaskInfo = parentDecor.mTaskInfo private val isViewAboveStatusBar: Boolean get() = (DesktopModeFlags.ENABLE_HANDLE_INPUT_FIX.isTrue() && !taskInfo.isFreeform) Loading Loading @@ -237,6 +246,7 @@ class HandleMenu( } val x = handleMenuPosition.x.toInt() val y = handleMenuPosition.y.toInt() val lpFlags = LayoutParams.FLAG_NOT_FOCUSABLE or LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH handleMenuViewContainer = if ((!taskInfo.isFreeform && DesktopModeFlags.ENABLE_HANDLE_INPUT_FIX.isTrue()) || forceShowSystemBars Loading @@ -248,8 +258,7 @@ class HandleMenu( y = y, width = menuWidth, height = menuHeight, flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH, flags = lpFlags, view = handleMenuView.rootView, forciblyShownTypes = if (forceShowSystemBars) { systemBars() Loading @@ -259,8 +268,13 @@ class HandleMenu( ignoreCutouts = Flags.showAppHandleLargeScreens() || BubbleAnythingFlagHelper.enableBubbleToFullscreen() ) } else if (DesktopExperienceFlags.ENABLE_WINDOW_DECORATION_REFACTOR.isTrue) { createAdditionalViewHostViewContainer( handleMenuView.rootView, t, x, y, menuWidth, menuHeight, lpFlags ) } else { parentDecor.addWindow( val decor = checkNotNull(parentDecor) { "Expected non-null parent decoration" } decor.addWindow( handleMenuView.rootView, "Handle Menu", t, ssg, x, y, menuWidth, menuHeight ) } Loading @@ -268,6 +282,55 @@ class HandleMenu( this.handleMenuView = handleMenuView } /** Creates and returns an [AdditionalViewHostViewContainer] for the handle menu. */ private fun createAdditionalViewHostViewContainer( v: View, t: SurfaceControl.Transaction, xPos: Int, yPos: Int, width: Int, height: Int, flags: Int, ): AdditionalViewHostViewContainer { val builder = surfaceControlBuilderSupplier() val windowSurfaceControl = builder .setName("Handle menu of Task=" + taskInfo.taskId) .setContainerLayer() .setParent(parentSurface) .setCallsite("HandleMenu.createAdditionalViewHostViewContainer") .build() t.setPosition(windowSurfaceControl, xPos.toFloat(), yPos.toFloat()) .setWindowCrop(windowSurfaceControl, width, height) .show(windowSurfaceControl) val lp = LayoutParams( width, height, LayoutParams.TYPE_APPLICATION, flags, PixelFormat.TRANSPARENT ).apply { title = "Handle menu of task=" + taskInfo.taskId setTrustedOverlay() } val windowManager = WindowlessWindowManager( taskInfo.configuration, windowSurfaceControl, /* hostInputTransferToken= */ null ) val viewHost = surfaceControlViewHostFactory.create( context, display, windowManager ).apply { setView(v, lp) } return AdditionalViewHostViewContainer( windowSurfaceControl, viewHost, surfaceControlTransactionSupplier, ) } /** * Updates handle menu's position variables to reflect its next position. */ Loading Loading @@ -429,7 +492,8 @@ class HandleMenu( } if (!shouldShowRestartButton) { menuHeight -= loadDimensionPixelSize( R.dimen.desktop_mode_handle_menu_restart_button_height) R.dimen.desktop_mode_handle_menu_restart_button_height ) } if (!shouldShowMoreActionsPill) { menuHeight -= pillTopMargin Loading Loading @@ -753,7 +817,7 @@ class HandleMenu( appIconView.setImageBitmap(icon) } /** Animates the menu openInAppOrBrowserg. */ /** Animates the menu opening. */ fun animateOpenMenu() { if (taskInfo.isFullscreen || taskInfo.isMultiWindow) { animator.animateCaptionHandleExpandToOpen() Loading Loading @@ -929,7 +993,8 @@ class HandleMenu( topRadius, topRadius, topRadius, topRadius, bottomRadius, bottomRadius, bottomRadius, bottomRadius ), drawableInsets = DrawableInsets()) drawableInsets = DrawableInsets() ) } } // The restart button is nested to show an error icon on the right. Update the Loading @@ -956,7 +1021,8 @@ class HandleMenu( background = createBackgroundDrawable( color = style.textColor, cornerRadius = handleMenuCornerRadius, drawableInsets = DrawableInsets()) drawableInsets = DrawableInsets() ) textView.apply { text = btnText setTextColor(style.textColor) Loading @@ -971,7 +1037,8 @@ class HandleMenu( background = createBackgroundDrawable( color = style.textColor, cornerRadius = iconButtonRippleRadius, drawableInsets = iconButtonDrawableInsetEnd) drawableInsets = iconButtonDrawableInsetEnd ) } } Loading Loading @@ -1003,14 +1070,17 @@ class HandleMenu( fun shouldShowRestartButton(taskInfo: RunningTaskInfo): Boolean = taskInfo.appCompatTaskInfo.isRestartMenuEnabledForDisplayMove } } /** A factory interface to create a [HandleMenu]. */ interface HandleMenuFactory { /** Factory to create a new [HandleMenu]. */ object HandleMenuFactory { @JvmOverloads fun create( @ShellMainThread mainDispatcher: MainCoroutineDispatcher, @ShellMainThread mainDispatcher: CoroutineDispatcher, @ShellBackgroundThread bgScope: CoroutineScope, parentDecor: DesktopModeWindowDecoration, context: Context, taskInfo: RunningTaskInfo, parentSurface: SurfaceControl, display: Display, windowManagerWrapper: WindowManagerWrapper, windowDecorationActions: WindowDecorationActions, taskResourceLoader: WindowDecorTaskResourceLoader, Loading @@ -1029,13 +1099,47 @@ interface HandleMenuFactory { captionHeight: Int, captionX: Int, captionY: Int, ): HandleMenu } surfaceControlBuilderSupplier: () -> SurfaceControl.Builder = { SurfaceControl.Builder() }, surfaceControlTransactionSupplier: () -> SurfaceControl.Transaction = { SurfaceControl.Transaction() }, surfaceControlViewHostFactory: SurfaceControlViewHostFactory = object : SurfaceControlViewHostFactory {}, ): HandleMenu = HandleMenu( mainDispatcher, bgScope, context, taskInfo, parentSurface, display, parentDecor = null, windowManagerWrapper, windowDecorationActions, taskResourceLoader, layoutResId, splitScreenController, shouldShowWindowingPill, shouldShowNewWindowButton, shouldShowManageWindowsButton, shouldShowChangeAspectRatioButton, shouldShowDesktopModeButton, shouldShowRestartButton, isBrowserApp, openInAppOrBrowserIntent, desktopModeUiEventLogger, captionWidth, captionHeight, captionX, captionY, surfaceControlBuilderSupplier, surfaceControlTransactionSupplier, surfaceControlViewHostFactory, ) /** A [HandleMenuFactory] implementation that creates a [HandleMenu]. */ object DefaultHandleMenuFactory : HandleMenuFactory { override fun create( @ShellMainThread mainDispatcher: MainCoroutineDispatcher, @Deprecated("Handle menu should no longer have reference to window decoration") @JvmOverloads fun create( @ShellMainThread mainDispatcher: CoroutineDispatcher, @ShellBackgroundThread bgScope: CoroutineScope, parentDecor: DesktopModeWindowDecoration, windowManagerWrapper: WindowManagerWrapper, Loading @@ -1056,10 +1160,19 @@ object DefaultHandleMenuFactory : HandleMenuFactory { captionHeight: Int, captionX: Int, captionY: Int, ): HandleMenu { return HandleMenu( surfaceControlBuilderSupplier: () -> SurfaceControl.Builder = { SurfaceControl.Builder() }, surfaceControlTransactionSupplier: () -> SurfaceControl.Transaction = { SurfaceControl.Transaction() }, surfaceControlViewHostFactory: SurfaceControlViewHostFactory = object : SurfaceControlViewHostFactory {} ): HandleMenu = HandleMenu( mainDispatcher, bgScope, parentDecor.mDecorWindowContext, parentDecor.mTaskInfo, parentDecor.mDecorationContainerSurface, parentDecor.mDisplay, parentDecor, windowManagerWrapper, windowDecorationActions, Loading @@ -1079,6 +1192,9 @@ object DefaultHandleMenuFactory : HandleMenuFactory { captionHeight, captionX, captionY, surfaceControlBuilderSupplier, surfaceControlTransactionSupplier, surfaceControlViewHostFactory, ) } } libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration2.kt +0 −69 Original line number Diff line number Diff line Loading @@ -21,7 +21,6 @@ import android.content.Context import android.content.res.Configuration import android.content.res.Resources import android.graphics.Color import android.graphics.PixelFormat import android.graphics.Rect import android.graphics.Region import android.gui.BorderSettings Loading @@ -40,14 +39,11 @@ import android.window.DesktopExperienceFlags import android.window.TaskConstants import android.window.WindowContainerTransaction import com.android.app.tracing.traceSection import com.android.internal.protolog.ProtoLog import com.android.wm.shell.ShellTaskOrganizer import com.android.wm.shell.common.BoxShadowHelper import com.android.wm.shell.common.DisplayController import com.android.wm.shell.common.DisplayController.OnDisplaysChangedListener import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_WINDOW_DECORATION import com.android.wm.shell.shared.annotations.ShellMainThread import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalViewHostViewContainer import com.android.wm.shell.windowdecor.caption.CaptionController import com.android.wm.shell.windowdecor.extension.getDimensionPixelSize import com.android.wm.shell.windowdecor.extension.isVisible Loading Loading @@ -469,71 +465,6 @@ abstract class WindowDecoration2<T>( surfaceControlSupplier: () -> SurfaceControl ) = surfaceControlSupplier().apply { copyFrom(sc, TAG) } /** * Create a window associated with this WindowDecoration. * Note that subclass must dispose of this when the task is hidden/closed. * * @param v View to attach to the window * @param t the transaction to apply * @param xPos x position of new window * @param yPos y position of new window * @param width width of new window * @param height height of new window * @return the [AdditionalViewHostViewContainer] that was added. */ fun addWindow( v: View, namePrefix: String, t: SurfaceControl.Transaction, xPos: Int, yPos: Int, width: Int, height: Int ): AdditionalViewHostViewContainer? { if (display == null) { ProtoLog.e(WM_SHELL_WINDOW_DECORATION, "Attempting to add window to null display") return null } val builder = surfaceControlBuilderSupplier() val windowSurfaceControl = builder .setName(namePrefix + " of Task=" + taskInfo.taskId) .setContainerLayer() .setParent(checkNotNull(decorationContainerSurface) { "expected non-null decoration container surface control" }) .setCallsite("WindowDecoration2.addWindow") .build() t.setPosition(windowSurfaceControl, xPos.toFloat(), yPos.toFloat()) .setWindowCrop(windowSurfaceControl, width, height) .show(windowSurfaceControl) val lp = LayoutParams( width, height, LayoutParams.TYPE_APPLICATION, LayoutParams.FLAG_NOT_FOCUSABLE or LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH, PixelFormat.TRANSPARENT ).apply { title = "Additional window of Task=" + taskInfo.taskId setTrustedOverlay() } val windowManager = WindowlessWindowManager( taskInfo.configuration, windowSurfaceControl, /* hostInputTransferToken = */ null ) val viewHost = surfaceControlViewHostFactory.create( decorWindowContext, checkNotNull(display) { "expected non-null display" }, windowManager ).apply { setView(v, lp) } return AdditionalViewHostViewContainer( windowSurfaceControl, viewHost, surfaceControlTransactionSupplier, ) } /** Holds the data required to update the window decorations. */ data class RelayoutParams( val runningTaskInfo: RunningTaskInfo, Loading Loading
libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/AppToWebRepository.kt 0 → 100644 +150 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 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.ActivityManager.RunningTaskInfo import android.app.assist.AssistContent import android.content.Context import android.content.Intent import android.net.Uri import android.util.IndentingPrintWriter import androidx.core.net.toUri import com.android.internal.protolog.ProtoLog import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE import java.io.PrintWriter import kotlin.coroutines.suspendCoroutine /** * App-to-Web has the following features: transferring an app session to the web and transferring * a web session to the relevant app. To transfer an app session to the web, we utilize * three different [Uri]s: * 1. webUri: The web URI provided by the app using [AssistContent] * 2. capturedLink: The link used to open the app if app was opened by clicking on a link * 3. genericLink: The system provided link for the app * In order to create the [Intent] to transfer the user from app to the web, the [Uri]s listed above * are checked in the given order and the first non-null link is used. When transferring from the * web to an app, the [Uri] must be provided by the browser application through [AssistContent]. * * This Repository encapsulates the data stored for the App-to-Web feature for a single task and * creates the intents used to open switch between an app or browser session. */ class AppToWebRepository( private val userContext: Context, private val taskId: Int, private val assistContentRequester: AssistContentRequester, private val genericLinksParser: AppToWebGenericLinksParser, ) { private var capturedLink: CapturedLink? = null /** Sets the captured link if a new link is provided. */ fun setCapturedLink(link: Uri, timeStamp: Long) { if (capturedLink?.timeStamp == timeStamp) return capturedLink = CapturedLink(link, timeStamp) } /** * Checks if [capturedLink] is available (non-null and has not been used) to use for switching * to browser session. */ fun isCapturedLinkAvailable(): Boolean { val link = capturedLink ?: return false return !link.used } /** Sets the captured link as used. */ fun onCapturedLinkUsed() { capturedLink?.setUsed() } /** * Retrieves the latest webUri and genericLink. If the task requesting the intent * [isBrowserApp], intent is created to switch to application if link was provided by browser * app and a relevant application exists to host the app. Otherwise, returns intent to switch * to browser if webUri, capturedLink, or genericLink is available. * * Note that the capturedLink should be updated separately using [setCapturedLink] * */ suspend fun getAppToWebIntent(taskInfo: RunningTaskInfo, isBrowserApp: Boolean): Intent? { ProtoLog.d( WM_SHELL_DESKTOP_MODE, "AppToWebRepository: Updating browser links for task $taskId" ) val assistContent = assistContentRequester.requestAssistContent(taskInfo.taskId) val webUri = assistContent?.getSessionWebUri() return if (isBrowserApp) { getAppIntent(webUri) } else { getBrowserIntent(webUri, getGenericLink(taskInfo)) } } private suspend fun AssistContentRequester.requestAssistContent(taskId: Int): AssistContent? = suspendCoroutine { continuation -> requestAssistContent(taskId) { continuation.resumeWith(Result.success(it)) } } /** Returns the browser link associated with the given application if available. */ private fun getBrowserIntent(webUri: Uri?, genericLink: Uri?): Intent? { val browserLink = webUri ?: if (isCapturedLinkAvailable()) { capturedLink?.uri } else { genericLink } ?: return null return getBrowserIntent(browserLink, userContext.packageManager, userContext.userId) } private fun getAppIntent(webUri: Uri?): Intent? { webUri ?: return null return getAppIntent( uri = webUri, packageManager = userContext.packageManager, userId = userContext.userId ) } private fun getGenericLink(taskInfo: RunningTaskInfo): Uri? { ProtoLog.d( WM_SHELL_DESKTOP_MODE, "AppToWebRepository: Updating generic link for task %d", taskId ) val baseActivity = taskInfo.baseActivity ?: return null return genericLinksParser.getGenericLink(baseActivity.packageName)?.toUri() } /** Dumps the repository's current state. */ fun dump(originalWriter: PrintWriter, prefix: String) { val pw = IndentingPrintWriter(originalWriter, " ", prefix) pw.println("AppToWebRepository for task#$taskId") pw.increaseIndent() pw.println("CapturedLink=$capturedLink") } /** Encapsulates data associated with a captured link. */ private data class CapturedLink(val uri: Uri, val timeStamp: Long) { /** Signifies if captured link has already been used, making it invalid. */ var used = false /** Sets the captured link as used. */ fun setUsed() { used = true } } }
libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/AssistContentRequester.kt +1 −1 Original line number Diff line number Diff line Loading @@ -39,7 +39,7 @@ class AssistContentRequester( private val callBackExecutor: Executor, private val systemInteractionExecutor: Executor ) { interface Callback { fun interface Callback { // Called when the [AssistContent] of the requested task is available. fun onAssistContentAvailable(assistContent: AssistContent?) } Loading
libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java +3 −3 Original line number Diff line number Diff line Loading @@ -193,7 +193,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin private final AppHandleViewHolder.Factory mAppHandleViewHolderFactory; private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer; private final MaximizeMenuFactory mMaximizeMenuFactory; private final HandleMenuFactory mHandleMenuFactory; private final HandleMenu.HandleMenuFactory mHandleMenuFactory; private final AppToWebGenericLinksParser mGenericLinksParser; private final AssistContentRequester mAssistContentRequester; private final DesktopModeCompatPolicy mDesktopModeCompatPolicy; Loading Loading @@ -268,7 +268,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin new SurfaceControlViewHostFactory() {}, windowDecorViewHostSupplier, DefaultMaximizeMenuFactory.INSTANCE, DefaultHandleMenuFactory.INSTANCE, multiInstanceHelper, HandleMenu.HandleMenuFactory.INSTANCE, multiInstanceHelper, windowDecorCaptionRepository, desktopModeEventLogger, desktopModeUiEventLogger, desktopModeCompatPolicy, desktopState, desktopConfig, windowDecorationActions); Loading Loading @@ -305,7 +305,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin SurfaceControlViewHostFactory surfaceControlViewHostFactory, @NonNull WindowDecorViewHostSupplier<WindowDecorViewHost> windowDecorViewHostSupplier, MaximizeMenuFactory maximizeMenuFactory, HandleMenuFactory handleMenuFactory, HandleMenu.HandleMenuFactory handleMenuFactory, MultiInstanceHelper multiInstanceHelper, WindowDecorCaptionRepository windowDecorCaptionRepository, DesktopModeEventLogger desktopModeEventLogger, Loading
libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt +187 −71 Original line number Diff line number Diff line Loading @@ -25,10 +25,12 @@ import android.content.Intent import android.content.res.ColorStateList import android.content.res.Resources import android.graphics.Bitmap import android.graphics.PixelFormat import android.graphics.Point import android.graphics.PointF import android.graphics.Rect import android.os.Bundle import android.view.Display import android.view.Display.DEFAULT_DISPLAY import android.view.LayoutInflater import android.view.MotionEvent Loading @@ -38,11 +40,13 @@ import android.view.View import android.view.View.OnClickListener import android.view.ViewGroup import android.view.WindowInsets.Type.systemBars import android.view.WindowManager import android.view.WindowManager.LayoutParams import android.view.WindowlessWindowManager import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction import android.widget.ImageButton import android.widget.ImageView import android.widget.Space import android.window.DesktopExperienceFlags import android.window.DesktopModeFlags import android.window.SurfaceSyncGroup import androidx.annotation.StringRes Loading @@ -64,8 +68,10 @@ import com.android.wm.shell.shared.bubbles.ContextUtils.isRtl import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource.APP_HANDLE_MENU_BUTTON import com.android.wm.shell.shared.split.SplitScreenConstants import com.android.wm.shell.splitscreen.SplitScreenController import com.android.wm.shell.windowdecor.WindowDecoration2.SurfaceControlViewHostFactory import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalSystemViewContainer import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalViewContainer import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalViewHostViewContainer import com.android.wm.shell.windowdecor.common.DecorThemeUtil import com.android.wm.shell.windowdecor.common.DrawableInsets import com.android.wm.shell.windowdecor.common.WindowDecorTaskResourceLoader Loading @@ -77,7 +83,6 @@ import com.android.wm.shell.windowdecor.extension.isPinned import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job import kotlinx.coroutines.MainCoroutineDispatcher import kotlinx.coroutines.isActive import kotlinx.coroutines.launch import kotlinx.coroutines.withContext Loading @@ -91,10 +96,14 @@ import kotlinx.coroutines.withContext * Windowing Options(Proto 2 only): Buttons to change windowing modes. * Additional Options: Miscellaneous functions including screenshot and closing task. */ class HandleMenu( class HandleMenu private constructor( @ShellMainThread private val mainDispatcher: CoroutineDispatcher, @ShellBackgroundThread private val bgScope: CoroutineScope, private val parentDecor: DesktopModeWindowDecoration, private val context: Context, private val taskInfo: RunningTaskInfo, private val parentSurface: SurfaceControl, private val display: Display, private val parentDecor: DesktopModeWindowDecoration?, private val windowManagerWrapper: WindowManagerWrapper, private val windowDecorationActions: WindowDecorationActions, private val taskResourceLoader: WindowDecorTaskResourceLoader, Loading @@ -112,11 +121,11 @@ class HandleMenu( private val captionWidth: Int, private val captionHeight: Int, captionX: Int, captionY: Int captionY: Int, private val surfaceControlBuilderSupplier: () -> SurfaceControl.Builder, private val surfaceControlTransactionSupplier: () -> SurfaceControl.Transaction, private val surfaceControlViewHostFactory: SurfaceControlViewHostFactory, ) { private val context: Context = parentDecor.mDecorWindowContext private val taskInfo: RunningTaskInfo = parentDecor.mTaskInfo private val isViewAboveStatusBar: Boolean get() = (DesktopModeFlags.ENABLE_HANDLE_INPUT_FIX.isTrue() && !taskInfo.isFreeform) Loading Loading @@ -237,6 +246,7 @@ class HandleMenu( } val x = handleMenuPosition.x.toInt() val y = handleMenuPosition.y.toInt() val lpFlags = LayoutParams.FLAG_NOT_FOCUSABLE or LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH handleMenuViewContainer = if ((!taskInfo.isFreeform && DesktopModeFlags.ENABLE_HANDLE_INPUT_FIX.isTrue()) || forceShowSystemBars Loading @@ -248,8 +258,7 @@ class HandleMenu( y = y, width = menuWidth, height = menuHeight, flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH, flags = lpFlags, view = handleMenuView.rootView, forciblyShownTypes = if (forceShowSystemBars) { systemBars() Loading @@ -259,8 +268,13 @@ class HandleMenu( ignoreCutouts = Flags.showAppHandleLargeScreens() || BubbleAnythingFlagHelper.enableBubbleToFullscreen() ) } else if (DesktopExperienceFlags.ENABLE_WINDOW_DECORATION_REFACTOR.isTrue) { createAdditionalViewHostViewContainer( handleMenuView.rootView, t, x, y, menuWidth, menuHeight, lpFlags ) } else { parentDecor.addWindow( val decor = checkNotNull(parentDecor) { "Expected non-null parent decoration" } decor.addWindow( handleMenuView.rootView, "Handle Menu", t, ssg, x, y, menuWidth, menuHeight ) } Loading @@ -268,6 +282,55 @@ class HandleMenu( this.handleMenuView = handleMenuView } /** Creates and returns an [AdditionalViewHostViewContainer] for the handle menu. */ private fun createAdditionalViewHostViewContainer( v: View, t: SurfaceControl.Transaction, xPos: Int, yPos: Int, width: Int, height: Int, flags: Int, ): AdditionalViewHostViewContainer { val builder = surfaceControlBuilderSupplier() val windowSurfaceControl = builder .setName("Handle menu of Task=" + taskInfo.taskId) .setContainerLayer() .setParent(parentSurface) .setCallsite("HandleMenu.createAdditionalViewHostViewContainer") .build() t.setPosition(windowSurfaceControl, xPos.toFloat(), yPos.toFloat()) .setWindowCrop(windowSurfaceControl, width, height) .show(windowSurfaceControl) val lp = LayoutParams( width, height, LayoutParams.TYPE_APPLICATION, flags, PixelFormat.TRANSPARENT ).apply { title = "Handle menu of task=" + taskInfo.taskId setTrustedOverlay() } val windowManager = WindowlessWindowManager( taskInfo.configuration, windowSurfaceControl, /* hostInputTransferToken= */ null ) val viewHost = surfaceControlViewHostFactory.create( context, display, windowManager ).apply { setView(v, lp) } return AdditionalViewHostViewContainer( windowSurfaceControl, viewHost, surfaceControlTransactionSupplier, ) } /** * Updates handle menu's position variables to reflect its next position. */ Loading Loading @@ -429,7 +492,8 @@ class HandleMenu( } if (!shouldShowRestartButton) { menuHeight -= loadDimensionPixelSize( R.dimen.desktop_mode_handle_menu_restart_button_height) R.dimen.desktop_mode_handle_menu_restart_button_height ) } if (!shouldShowMoreActionsPill) { menuHeight -= pillTopMargin Loading Loading @@ -753,7 +817,7 @@ class HandleMenu( appIconView.setImageBitmap(icon) } /** Animates the menu openInAppOrBrowserg. */ /** Animates the menu opening. */ fun animateOpenMenu() { if (taskInfo.isFullscreen || taskInfo.isMultiWindow) { animator.animateCaptionHandleExpandToOpen() Loading Loading @@ -929,7 +993,8 @@ class HandleMenu( topRadius, topRadius, topRadius, topRadius, bottomRadius, bottomRadius, bottomRadius, bottomRadius ), drawableInsets = DrawableInsets()) drawableInsets = DrawableInsets() ) } } // The restart button is nested to show an error icon on the right. Update the Loading @@ -956,7 +1021,8 @@ class HandleMenu( background = createBackgroundDrawable( color = style.textColor, cornerRadius = handleMenuCornerRadius, drawableInsets = DrawableInsets()) drawableInsets = DrawableInsets() ) textView.apply { text = btnText setTextColor(style.textColor) Loading @@ -971,7 +1037,8 @@ class HandleMenu( background = createBackgroundDrawable( color = style.textColor, cornerRadius = iconButtonRippleRadius, drawableInsets = iconButtonDrawableInsetEnd) drawableInsets = iconButtonDrawableInsetEnd ) } } Loading Loading @@ -1003,14 +1070,17 @@ class HandleMenu( fun shouldShowRestartButton(taskInfo: RunningTaskInfo): Boolean = taskInfo.appCompatTaskInfo.isRestartMenuEnabledForDisplayMove } } /** A factory interface to create a [HandleMenu]. */ interface HandleMenuFactory { /** Factory to create a new [HandleMenu]. */ object HandleMenuFactory { @JvmOverloads fun create( @ShellMainThread mainDispatcher: MainCoroutineDispatcher, @ShellMainThread mainDispatcher: CoroutineDispatcher, @ShellBackgroundThread bgScope: CoroutineScope, parentDecor: DesktopModeWindowDecoration, context: Context, taskInfo: RunningTaskInfo, parentSurface: SurfaceControl, display: Display, windowManagerWrapper: WindowManagerWrapper, windowDecorationActions: WindowDecorationActions, taskResourceLoader: WindowDecorTaskResourceLoader, Loading @@ -1029,13 +1099,47 @@ interface HandleMenuFactory { captionHeight: Int, captionX: Int, captionY: Int, ): HandleMenu } surfaceControlBuilderSupplier: () -> SurfaceControl.Builder = { SurfaceControl.Builder() }, surfaceControlTransactionSupplier: () -> SurfaceControl.Transaction = { SurfaceControl.Transaction() }, surfaceControlViewHostFactory: SurfaceControlViewHostFactory = object : SurfaceControlViewHostFactory {}, ): HandleMenu = HandleMenu( mainDispatcher, bgScope, context, taskInfo, parentSurface, display, parentDecor = null, windowManagerWrapper, windowDecorationActions, taskResourceLoader, layoutResId, splitScreenController, shouldShowWindowingPill, shouldShowNewWindowButton, shouldShowManageWindowsButton, shouldShowChangeAspectRatioButton, shouldShowDesktopModeButton, shouldShowRestartButton, isBrowserApp, openInAppOrBrowserIntent, desktopModeUiEventLogger, captionWidth, captionHeight, captionX, captionY, surfaceControlBuilderSupplier, surfaceControlTransactionSupplier, surfaceControlViewHostFactory, ) /** A [HandleMenuFactory] implementation that creates a [HandleMenu]. */ object DefaultHandleMenuFactory : HandleMenuFactory { override fun create( @ShellMainThread mainDispatcher: MainCoroutineDispatcher, @Deprecated("Handle menu should no longer have reference to window decoration") @JvmOverloads fun create( @ShellMainThread mainDispatcher: CoroutineDispatcher, @ShellBackgroundThread bgScope: CoroutineScope, parentDecor: DesktopModeWindowDecoration, windowManagerWrapper: WindowManagerWrapper, Loading @@ -1056,10 +1160,19 @@ object DefaultHandleMenuFactory : HandleMenuFactory { captionHeight: Int, captionX: Int, captionY: Int, ): HandleMenu { return HandleMenu( surfaceControlBuilderSupplier: () -> SurfaceControl.Builder = { SurfaceControl.Builder() }, surfaceControlTransactionSupplier: () -> SurfaceControl.Transaction = { SurfaceControl.Transaction() }, surfaceControlViewHostFactory: SurfaceControlViewHostFactory = object : SurfaceControlViewHostFactory {} ): HandleMenu = HandleMenu( mainDispatcher, bgScope, parentDecor.mDecorWindowContext, parentDecor.mTaskInfo, parentDecor.mDecorationContainerSurface, parentDecor.mDisplay, parentDecor, windowManagerWrapper, windowDecorationActions, Loading @@ -1079,6 +1192,9 @@ object DefaultHandleMenuFactory : HandleMenuFactory { captionHeight, captionX, captionY, surfaceControlBuilderSupplier, surfaceControlTransactionSupplier, surfaceControlViewHostFactory, ) } }
libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration2.kt +0 −69 Original line number Diff line number Diff line Loading @@ -21,7 +21,6 @@ import android.content.Context import android.content.res.Configuration import android.content.res.Resources import android.graphics.Color import android.graphics.PixelFormat import android.graphics.Rect import android.graphics.Region import android.gui.BorderSettings Loading @@ -40,14 +39,11 @@ import android.window.DesktopExperienceFlags import android.window.TaskConstants import android.window.WindowContainerTransaction import com.android.app.tracing.traceSection import com.android.internal.protolog.ProtoLog import com.android.wm.shell.ShellTaskOrganizer import com.android.wm.shell.common.BoxShadowHelper import com.android.wm.shell.common.DisplayController import com.android.wm.shell.common.DisplayController.OnDisplaysChangedListener import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_WINDOW_DECORATION import com.android.wm.shell.shared.annotations.ShellMainThread import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalViewHostViewContainer import com.android.wm.shell.windowdecor.caption.CaptionController import com.android.wm.shell.windowdecor.extension.getDimensionPixelSize import com.android.wm.shell.windowdecor.extension.isVisible Loading Loading @@ -469,71 +465,6 @@ abstract class WindowDecoration2<T>( surfaceControlSupplier: () -> SurfaceControl ) = surfaceControlSupplier().apply { copyFrom(sc, TAG) } /** * Create a window associated with this WindowDecoration. * Note that subclass must dispose of this when the task is hidden/closed. * * @param v View to attach to the window * @param t the transaction to apply * @param xPos x position of new window * @param yPos y position of new window * @param width width of new window * @param height height of new window * @return the [AdditionalViewHostViewContainer] that was added. */ fun addWindow( v: View, namePrefix: String, t: SurfaceControl.Transaction, xPos: Int, yPos: Int, width: Int, height: Int ): AdditionalViewHostViewContainer? { if (display == null) { ProtoLog.e(WM_SHELL_WINDOW_DECORATION, "Attempting to add window to null display") return null } val builder = surfaceControlBuilderSupplier() val windowSurfaceControl = builder .setName(namePrefix + " of Task=" + taskInfo.taskId) .setContainerLayer() .setParent(checkNotNull(decorationContainerSurface) { "expected non-null decoration container surface control" }) .setCallsite("WindowDecoration2.addWindow") .build() t.setPosition(windowSurfaceControl, xPos.toFloat(), yPos.toFloat()) .setWindowCrop(windowSurfaceControl, width, height) .show(windowSurfaceControl) val lp = LayoutParams( width, height, LayoutParams.TYPE_APPLICATION, LayoutParams.FLAG_NOT_FOCUSABLE or LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH, PixelFormat.TRANSPARENT ).apply { title = "Additional window of Task=" + taskInfo.taskId setTrustedOverlay() } val windowManager = WindowlessWindowManager( taskInfo.configuration, windowSurfaceControl, /* hostInputTransferToken = */ null ) val viewHost = surfaceControlViewHostFactory.create( decorWindowContext, checkNotNull(display) { "expected non-null display" }, windowManager ).apply { setView(v, lp) } return AdditionalViewHostViewContainer( windowSurfaceControl, viewHost, surfaceControlTransactionSupplier, ) } /** Holds the data required to update the window decorations. */ data class RelayoutParams( val runningTaskInfo: RunningTaskInfo, Loading