Add windowInputToken parameter to SandboxedUiAdapter
This input token is used when creating the SurfaceControlViewHost
that will host the UI provider's content. This is necessary to
ensure that the SurfaceControlViewHost's token is correctly
associated with the host of the embedded hierachy.
The input token is obtained by creating a throwaway SurfaceView whenever
the SandboxedSdkView attaches to a window. The input token will be obtained
when the SurfaceView is attached. The SurfaceView will be removed immediately
after it has been attached to the window. The throwaway SurfaceView
is never visible.
Bug: 283748196
Test: Manual, SandboxedSdkViewTest
Relnote: Added windowInputToken parameter to SandboxedUiAdapter
Change-Id: Ief578bfa0202f8577e6a426c5b049f763c0b4846
diff --git a/privacysandbox/tools/tools-apigenerator/src/test/test-data/callbacks/output/com/sdkwithcallbacks/MyUiInterfaceClientProxy.kt b/privacysandbox/tools/tools-apigenerator/src/test/test-data/callbacks/output/com/sdkwithcallbacks/MyUiInterfaceClientProxy.kt
index 00ccc68..6aa0aae 100644
--- a/privacysandbox/tools/tools-apigenerator/src/test/test-data/callbacks/output/com/sdkwithcallbacks/MyUiInterfaceClientProxy.kt
+++ b/privacysandbox/tools/tools-apigenerator/src/test/test-data/callbacks/output/com/sdkwithcallbacks/MyUiInterfaceClientProxy.kt
@@ -2,6 +2,7 @@
import android.content.Context
import android.os.Bundle
+import android.os.IBinder
import androidx.privacysandbox.ui.client.SandboxedUiAdapterFactory
import androidx.privacysandbox.ui.core.SandboxedUiAdapter
import androidx.privacysandbox.ui.core.SandboxedUiAdapter.SessionClient
@@ -20,13 +21,14 @@
public override fun openSession(
context: Context,
+ windowInputToken: IBinder,
initialWidth: Int,
initialHeight: Int,
isZOrderOnTop: Boolean,
clientExecutor: Executor,
client: SandboxedUiAdapter.SessionClient,
): Unit {
- sandboxedUiAdapter.openSession(context, initialWidth, initialHeight, isZOrderOnTop,
- clientExecutor, client)
+ sandboxedUiAdapter.openSession(context, windowInputToken, initialWidth, initialHeight,
+ isZOrderOnTop, clientExecutor, client)
}
}
diff --git a/privacysandbox/tools/tools-apigenerator/src/test/test-data/interfaces/output/com/sdk/MySecondInterfaceClientProxy.kt b/privacysandbox/tools/tools-apigenerator/src/test/test-data/interfaces/output/com/sdk/MySecondInterfaceClientProxy.kt
index 2b6a244..48db338 100644
--- a/privacysandbox/tools/tools-apigenerator/src/test/test-data/interfaces/output/com/sdk/MySecondInterfaceClientProxy.kt
+++ b/privacysandbox/tools/tools-apigenerator/src/test/test-data/interfaces/output/com/sdk/MySecondInterfaceClientProxy.kt
@@ -2,6 +2,7 @@
import android.content.Context
import android.os.Bundle
+import android.os.IBinder
import androidx.privacysandbox.ui.client.SandboxedUiAdapterFactory
import androidx.privacysandbox.ui.core.SandboxedUiAdapter
import androidx.privacysandbox.ui.core.SandboxedUiAdapter.SessionClient
@@ -20,13 +21,14 @@
public override fun openSession(
context: Context,
+ windowInputToken: IBinder,
initialWidth: Int,
initialHeight: Int,
isZOrderOnTop: Boolean,
clientExecutor: Executor,
client: SandboxedUiAdapter.SessionClient,
): Unit {
- sandboxedUiAdapter.openSession(context, initialWidth, initialHeight, isZOrderOnTop,
- clientExecutor, client)
+ sandboxedUiAdapter.openSession(context, windowInputToken, initialWidth, initialHeight,
+ isZOrderOnTop, clientExecutor, client)
}
}
diff --git a/privacysandbox/tools/tools-apigenerator/src/test/test-data/values/output/com/sdkwithvalues/MyUiInterfaceClientProxy.kt b/privacysandbox/tools/tools-apigenerator/src/test/test-data/values/output/com/sdkwithvalues/MyUiInterfaceClientProxy.kt
index 7a2c6e0..bd43324 100644
--- a/privacysandbox/tools/tools-apigenerator/src/test/test-data/values/output/com/sdkwithvalues/MyUiInterfaceClientProxy.kt
+++ b/privacysandbox/tools/tools-apigenerator/src/test/test-data/values/output/com/sdkwithvalues/MyUiInterfaceClientProxy.kt
@@ -2,6 +2,7 @@
import android.content.Context
import android.os.Bundle
+import android.os.IBinder
import androidx.privacysandbox.ui.client.SandboxedUiAdapterFactory
import androidx.privacysandbox.ui.core.SandboxedUiAdapter
import androidx.privacysandbox.ui.core.SandboxedUiAdapter.SessionClient
@@ -20,13 +21,14 @@
public override fun openSession(
context: Context,
+ windowInputToken: IBinder,
initialWidth: Int,
initialHeight: Int,
isZOrderOnTop: Boolean,
clientExecutor: Executor,
client: SandboxedUiAdapter.SessionClient,
): Unit {
- sandboxedUiAdapter.openSession(context, initialWidth, initialHeight, isZOrderOnTop,
- clientExecutor, client)
+ sandboxedUiAdapter.openSession(context, windowInputToken, initialWidth, initialHeight,
+ isZOrderOnTop, clientExecutor, client)
}
}
diff --git a/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/generator/ClientProxyTypeGenerator.kt b/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/generator/ClientProxyTypeGenerator.kt
index ec4f236..c562ae2 100644
--- a/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/generator/ClientProxyTypeGenerator.kt
+++ b/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/generator/ClientProxyTypeGenerator.kt
@@ -139,6 +139,7 @@
addParameters(
listOf(
ParameterSpec(contextPropertyName, contextClass),
+ ParameterSpec("windowInputToken", ClassName("android.os", "IBinder")),
ParameterSpec("initialWidth", Types.int.poetClassName()),
ParameterSpec("initialHeight", Types.int.poetClassName()),
ParameterSpec("isZOrderOnTop", Types.boolean.poetClassName()),
@@ -151,8 +152,8 @@
)
)
addStatement(
- "$sandboxedUiAdapterPropertyName.openSession(%N, initialWidth, initialHeight, " +
- "isZOrderOnTop, clientExecutor, client)",
+ "$sandboxedUiAdapterPropertyName.openSession(%N, windowInputToken, initialWidth, " +
+ "initialHeight, isZOrderOnTop, clientExecutor, client)",
contextPropertyName,
)
}
diff --git a/privacysandbox/ui/integration-tests/testsdkprovider/src/main/java/androidx/privacysandbox/ui/integration/testsdkprovider/SdkApi.kt b/privacysandbox/ui/integration-tests/testsdkprovider/src/main/java/androidx/privacysandbox/ui/integration/testsdkprovider/SdkApi.kt
index c855ee2..4ceebcb 100644
--- a/privacysandbox/ui/integration-tests/testsdkprovider/src/main/java/androidx/privacysandbox/ui/integration/testsdkprovider/SdkApi.kt
+++ b/privacysandbox/ui/integration-tests/testsdkprovider/src/main/java/androidx/privacysandbox/ui/integration/testsdkprovider/SdkApi.kt
@@ -24,6 +24,7 @@
import android.graphics.Paint
import android.net.Uri
import android.os.Bundle
+import android.os.IBinder
import android.provider.Settings
import android.util.Log
import android.view.View
@@ -54,11 +55,12 @@
SandboxedUiAdapter {
override fun openSession(
context: Context,
+ windowInputToken: IBinder,
initialWidth: Int,
initialHeight: Int,
isZOrderOnTop: Boolean,
clientExecutor: Executor,
- client: SandboxedUiAdapter.SessionClient
+ client: SandboxedUiAdapter.SessionClient,
) {
Log.d(TAG, "Session requested")
lateinit var adView: View
@@ -90,7 +92,8 @@
override fun notifyResized(width: Int, height: Int) {
Log.i(TAG, "Resized $width $height")
- view.layoutParams = ViewGroup.LayoutParams(width, height)
+ view.layoutParams.width = width
+ view.layoutParams.height = height
}
override fun notifyZOrderChanged(isZOrderOnTop: Boolean) {
diff --git a/privacysandbox/ui/ui-client/src/androidTest/java/androidx/privacysandbox/ui/client/test/SandboxedSdkViewTest.kt b/privacysandbox/ui/ui-client/src/androidTest/java/androidx/privacysandbox/ui/client/test/SandboxedSdkViewTest.kt
index 9670896..f545175 100644
--- a/privacysandbox/ui/ui-client/src/androidTest/java/androidx/privacysandbox/ui/client/test/SandboxedSdkViewTest.kt
+++ b/privacysandbox/ui/ui-client/src/androidTest/java/androidx/privacysandbox/ui/client/test/SandboxedSdkViewTest.kt
@@ -21,6 +21,8 @@
import android.content.pm.ActivityInfo
import android.content.res.Configuration
import android.os.Build
+import android.os.IBinder
+import android.view.SurfaceView
import android.view.View
import android.view.ViewGroup
import android.view.ViewTreeObserver
@@ -74,6 +76,7 @@
class FailingTestSandboxedUiAdapter : SandboxedUiAdapter {
override fun openSession(
context: Context,
+ windowInputToken: IBinder,
initialWidth: Int,
initialHeight: Int,
isZOrderOnTop: Boolean,
@@ -96,6 +99,7 @@
var internalClient: SandboxedUiAdapter.SessionClient? = null
var testSession: TestSession? = null
var isZOrderOnTop = true
+ var inputToken: IBinder? = null
// When set to true, the onSessionOpened callback will only be invoked when specified
// by the test. This is to test race conditions when the session is being loaded.
@@ -103,6 +107,7 @@
override fun openSession(
context: Context,
+ windowInputToken: IBinder,
initialWidth: Int,
initialHeight: Int,
isZOrderOnTop: Boolean,
@@ -117,6 +122,7 @@
}
isSessionOpened = true
this.isZOrderOnTop = isZOrderOnTop
+ this.inputToken = windowInputToken
openSessionLatch?.countDown()
}
@@ -248,7 +254,6 @@
@Test
fun childViewRemovedOnErrorTest() {
assertTrue(view.childCount == 0)
-
addViewToLayout()
openSessionLatch.await(TIMEOUT, TimeUnit.MILLISECONDS)
@@ -404,6 +409,49 @@
)
}
+ /**
+ * Ensures that the input token passed when opening a session is non-null and is the same host
+ * token as another [SurfaceView] in the same activity.
+ */
+ @Test
+ fun inputTokenIsCorrect() {
+ lateinit var layout: LinearLayout
+ val surfaceView = SurfaceView(context)
+ val surfaceViewLatch = CountDownLatch(1)
+
+ // Attach SurfaceView
+ activity.runOnUiThread {
+ layout = activity.findViewById(
+ R.id.mainlayout
+ )
+ layout.addView(surfaceView)
+ }
+ var token: IBinder? = null
+ surfaceView.addOnAttachStateChangeListener(
+ object : View.OnAttachStateChangeListener {
+ override fun onViewAttachedToWindow(p0: View) {
+ token = surfaceView.hostToken
+ surfaceViewLatch.countDown()
+ }
+
+ override fun onViewDetachedFromWindow(p0: View) {
+ }
+ }
+ )
+
+ // Verify SurfaceView has a non-null token when attached.
+ assertThat(surfaceViewLatch.await(TIMEOUT, TimeUnit.MILLISECONDS)).isTrue()
+ assertThat(surfaceView.hostToken).isNotNull()
+ activity.runOnUiThread {
+ layout.removeView(surfaceView)
+ }
+
+ // Verify that the UI adapter receives the same host token object when opening a session.
+ addViewToLayout()
+ assertThat(openSessionLatch.await(TIMEOUT, TimeUnit.MILLISECONDS)).isTrue()
+ assertThat(testSandboxedUiAdapter.inputToken).isEqualTo(token)
+ }
+
private fun addViewToLayout() {
activity.runOnUiThread {
activity.findViewById<LinearLayout>(
diff --git a/privacysandbox/ui/ui-client/src/main/java/androidx/privacysandbox/ui/client/SandboxedUiAdapterFactory.kt b/privacysandbox/ui/ui-client/src/main/java/androidx/privacysandbox/ui/client/SandboxedUiAdapterFactory.kt
index f4d3d5c..de5b048 100644
--- a/privacysandbox/ui/ui-client/src/main/java/androidx/privacysandbox/ui/client/SandboxedUiAdapterFactory.kt
+++ b/privacysandbox/ui/ui-client/src/main/java/androidx/privacysandbox/ui/client/SandboxedUiAdapterFactory.kt
@@ -19,9 +19,9 @@
import android.content.Context
import android.content.res.Configuration
import android.hardware.display.DisplayManager
-import android.os.Binder
import android.os.Build
import android.os.Bundle
+import android.os.IBinder
import android.view.Display
import android.view.SurfaceControlViewHost
import android.view.SurfaceView
@@ -62,6 +62,7 @@
override fun openSession(
context: Context,
+ windowInputToken: IBinder,
initialWidth: Int,
initialHeight: Int,
isZOrderOnTop: Boolean,
@@ -73,7 +74,7 @@
val displayId = mDisplayManager.getDisplay(Display.DEFAULT_DISPLAY).displayId
adapterInterface.openRemoteSession(
- Binder(), // Host Token
+ windowInputToken,
displayId,
initialWidth,
initialHeight,
diff --git a/privacysandbox/ui/ui-client/src/main/java/androidx/privacysandbox/ui/client/view/SandboxedSdkView.kt b/privacysandbox/ui/ui-client/src/main/java/androidx/privacysandbox/ui/client/view/SandboxedSdkView.kt
index fdd5e4a..0b6b31c 100644
--- a/privacysandbox/ui/ui-client/src/main/java/androidx/privacysandbox/ui/client/view/SandboxedSdkView.kt
+++ b/privacysandbox/ui/ui-client/src/main/java/androidx/privacysandbox/ui/client/view/SandboxedSdkView.kt
@@ -19,7 +19,9 @@
import android.content.Context
import android.content.res.Configuration
import android.os.Build
+import android.os.IBinder
import android.util.AttributeSet
+import android.view.SurfaceView
import android.view.View
import android.view.ViewGroup
import androidx.annotation.RequiresApi
@@ -85,10 +87,13 @@
// TODO(b/268014171): Remove API requirements once S- support is added
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
-class SandboxedSdkView @JvmOverloads constructor(
- context: Context,
- attrs: AttributeSet? = null
-) : ViewGroup(context, attrs) {
+class SandboxedSdkView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
+ ViewGroup(context, attrs) {
+
+ // TODO(b/284147223): Remove this logic in V+
+ private val surfaceView = SurfaceView(context).apply {
+ visibility = GONE
+ }
private var adapter: SandboxedUiAdapter? = null
private var client: Client? = null
@@ -97,6 +102,7 @@
private var requestedWidth = -1
private var requestedHeight = -1
private var isTransitionGroupSet = false
+ private var windowInputToken: IBinder? = null
internal val stateListenerManager: StateListenerManager = StateListenerManager()
/**
@@ -136,11 +142,13 @@
private fun checkClientOpenSession() {
val adapter = adapter
- if (client == null && adapter != null && isAttachedToWindow && width > 0 && height > 0) {
+ if (client == null && adapter != null && windowInputToken != null &&
+ width > 0 && height > 0) {
stateListenerManager.currentUiSessionState = SandboxedSdkUiSessionState.Loading
client = Client(this)
adapter.openSession(
context,
+ windowInputToken!!,
width,
height,
isZOrderOnTop,
@@ -150,6 +158,33 @@
}
}
+ /**
+ * Attaches a temporary [SurfaceView] to the view hierarchy. This [SurfaceView] will be removed
+ * once it has been attached to the window and its host token is non-null.
+ *
+ * TODO(b/284147223): Remove this logic in V+
+ */
+ private fun attachTemporarySurfaceView() {
+ val onSurfaceViewAttachedListener =
+ object : OnAttachStateChangeListener {
+ override fun onViewAttachedToWindow(view: View) {
+ view.removeOnAttachStateChangeListener(this)
+ removeSurfaceViewAndOpenSession()
+ }
+
+ override fun onViewDetachedFromWindow(view: View) {
+ }
+ }
+ surfaceView.addOnAttachStateChangeListener(onSurfaceViewAttachedListener)
+ super.addView(surfaceView, 0, generateDefaultLayoutParams())
+ }
+
+ internal fun removeSurfaceViewAndOpenSession() {
+ windowInputToken = surfaceView.hostToken
+ super.removeView(surfaceView)
+ checkClientOpenSession()
+ }
+
internal fun requestSize(width: Int, height: Int) {
if (width == this.width && height == this.height) return
requestedWidth = width
@@ -157,7 +192,7 @@
requestLayout()
}
- internal fun removeContentView() {
+ private fun removeContentView() {
if (childCount == 1) {
super.removeViewAt(0)
}
@@ -243,12 +278,13 @@
override fun onAttachedToWindow() {
super.onAttachedToWindow()
- checkClientOpenSession()
+ attachTemporarySurfaceView()
}
override fun onDetachedFromWindow() {
client?.close()
client = null
+ windowInputToken = null
super.onDetachedFromWindow()
}
diff --git a/privacysandbox/ui/ui-core/api/current.txt b/privacysandbox/ui/ui-core/api/current.txt
index db32e1e..7aef7ba 100644
--- a/privacysandbox/ui/ui-core/api/current.txt
+++ b/privacysandbox/ui/ui-core/api/current.txt
@@ -2,7 +2,7 @@
package androidx.privacysandbox.ui.core {
public interface SandboxedUiAdapter {
- method public void openSession(android.content.Context context, int initialWidth, int initialHeight, boolean isZOrderOnTop, java.util.concurrent.Executor clientExecutor, androidx.privacysandbox.ui.core.SandboxedUiAdapter.SessionClient client);
+ method public void openSession(android.content.Context context, android.os.IBinder windowInputToken, int initialWidth, int initialHeight, boolean isZOrderOnTop, java.util.concurrent.Executor clientExecutor, androidx.privacysandbox.ui.core.SandboxedUiAdapter.SessionClient client);
}
public static interface SandboxedUiAdapter.Session {
diff --git a/privacysandbox/ui/ui-core/api/restricted_current.txt b/privacysandbox/ui/ui-core/api/restricted_current.txt
index db32e1e..7aef7ba 100644
--- a/privacysandbox/ui/ui-core/api/restricted_current.txt
+++ b/privacysandbox/ui/ui-core/api/restricted_current.txt
@@ -2,7 +2,7 @@
package androidx.privacysandbox.ui.core {
public interface SandboxedUiAdapter {
- method public void openSession(android.content.Context context, int initialWidth, int initialHeight, boolean isZOrderOnTop, java.util.concurrent.Executor clientExecutor, androidx.privacysandbox.ui.core.SandboxedUiAdapter.SessionClient client);
+ method public void openSession(android.content.Context context, android.os.IBinder windowInputToken, int initialWidth, int initialHeight, boolean isZOrderOnTop, java.util.concurrent.Executor clientExecutor, androidx.privacysandbox.ui.core.SandboxedUiAdapter.SessionClient client);
}
public static interface SandboxedUiAdapter.Session {
diff --git a/privacysandbox/ui/ui-core/src/main/java/androidx/privacysandbox/ui/core/SandboxedUiAdapter.kt b/privacysandbox/ui/ui-core/src/main/java/androidx/privacysandbox/ui/core/SandboxedUiAdapter.kt
index 2ebe9a8..1a724ed 100644
--- a/privacysandbox/ui/ui-core/src/main/java/androidx/privacysandbox/ui/core/SandboxedUiAdapter.kt
+++ b/privacysandbox/ui/ui-core/src/main/java/androidx/privacysandbox/ui/core/SandboxedUiAdapter.kt
@@ -18,6 +18,7 @@
import android.content.Context
import android.content.res.Configuration
+import android.os.IBinder
import android.view.View
import java.util.concurrent.Executor
@@ -36,6 +37,7 @@
*/
fun openSession(
context: Context,
+ windowInputToken: IBinder,
initialWidth: Int,
initialHeight: Int,
isZOrderOnTop: Boolean,
diff --git a/privacysandbox/ui/ui-provider/src/main/java/androidx/privacysandbox/ui/provider/BinderAdapterDelegate.kt b/privacysandbox/ui/ui-provider/src/main/java/androidx/privacysandbox/ui/provider/BinderAdapterDelegate.kt
index 9163257..76008d1 100644
--- a/privacysandbox/ui/ui-provider/src/main/java/androidx/privacysandbox/ui/provider/BinderAdapterDelegate.kt
+++ b/privacysandbox/ui/ui-provider/src/main/java/androidx/privacysandbox/ui/provider/BinderAdapterDelegate.kt
@@ -58,6 +58,7 @@
fun openSession(
context: Context,
+ windowInputToken: IBinder,
initialWidth: Int,
initialHeight: Int,
isZOrderOnTop: Boolean,
@@ -65,13 +66,13 @@
client: SandboxedUiAdapter.SessionClient
) {
adapter.openSession(
- context, initialWidth, initialHeight, isZOrderOnTop, clientExecutor,
+ context, windowInputToken, initialWidth, initialHeight, isZOrderOnTop, clientExecutor,
client
)
}
override fun openRemoteSession(
- hostToken: IBinder,
+ windowInputToken: IBinder,
displayId: Int,
initialWidth: Int,
initialHeight: Int,
@@ -87,13 +88,13 @@
sandboxContext.createDisplayContext(mDisplayManager.getDisplay(displayId))
val surfaceControlViewHost = SurfaceControlViewHost(
windowContext,
- mDisplayManager.getDisplay(displayId), hostToken
+ mDisplayManager.getDisplay(displayId), windowInputToken
)
val sessionClient = SessionClientProxy(
surfaceControlViewHost, initialWidth, initialHeight, remoteSessionClient
)
openSession(
- windowContext, initialWidth, initialHeight, isZOrderOnTop,
+ windowContext, windowInputToken, initialWidth, initialHeight, isZOrderOnTop,
Runnable::run, sessionClient
)
} catch (exception: Throwable) {
diff --git a/privacysandbox/ui/ui-tests/src/androidTest/java/androidx/privacysandbox/ui/tests/endtoend/IntegrationTests.kt b/privacysandbox/ui/ui-tests/src/androidTest/java/androidx/privacysandbox/ui/tests/endtoend/IntegrationTests.kt
index a2ea2da..ae63942 100644
--- a/privacysandbox/ui/ui-tests/src/androidTest/java/androidx/privacysandbox/ui/tests/endtoend/IntegrationTests.kt
+++ b/privacysandbox/ui/ui-tests/src/androidTest/java/androidx/privacysandbox/ui/tests/endtoend/IntegrationTests.kt
@@ -19,7 +19,9 @@
import android.content.Context
import android.content.pm.ActivityInfo
import android.content.res.Configuration
+import android.os.Binder
import android.os.Build
+import android.os.IBinder
import android.view.View
import android.view.View.OnLayoutChangeListener
import android.view.ViewGroup
@@ -168,6 +170,7 @@
adapterFromCoreLibInfo.openSession(
context,
+ Binder(),
10 /* initialWidth */,
10 /* initialHeight */,
true,
@@ -242,6 +245,7 @@
override fun openSession(
context: Context,
+ windowInputToken: IBinder,
initialWidth: Int,
initialHeight: Int,
isZOrderOnTop: Boolean,