Invoke onUiContainerChanged when window visibility changes
When onWindowVisibilityChanged is triggered on a
SandboxedSdkView, also trigger viewability calculation
if necessary. This will ensure that an accurate onScreenGeometry
is sent when the activity hosting the SandboxedSdkView is paused or
resumed.
When the hosting window is not visible, an empty Rect will
be sent as the on screen geometry value.
Relnote: Invoke onUiContainerChanged when the window visibility changes
Test: SandboxedSdkViewTest, manual
Bug: 352690169
Change-Id: I541cfd2fc205a4874f7f9387dd0a85b360b46b78
diff --git a/privacysandbox/ui/ui-client/src/androidTest/AndroidManifest.xml b/privacysandbox/ui/ui-client/src/androidTest/AndroidManifest.xml
index 869e25c..1f5dbd3 100644
--- a/privacysandbox/ui/ui-client/src/androidTest/AndroidManifest.xml
+++ b/privacysandbox/ui/ui-client/src/androidTest/AndroidManifest.xml
@@ -24,6 +24,8 @@
android:name="androidx.privacysandbox.ui.client.test.UiLibActivity"
android:configChanges="orientation|screenSize"
android:exported="true"/>
+ <activity android:name=".SecondActivity"
+ android:exported="true"/>
</application>
<queries>
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 a14cd24..2ac83da 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
@@ -18,6 +18,7 @@
import android.annotation.SuppressLint
import android.content.Context
+import android.content.Intent
import android.content.pm.ActivityInfo
import android.content.res.Configuration
import android.graphics.Rect
@@ -78,6 +79,7 @@
const val SHORTEST_TIME_BETWEEN_SIGNALS_MS = 200
}
+ private lateinit var uiDevice: UiDevice
private lateinit var context: Context
private lateinit var view: SandboxedSdkView
private lateinit var layoutParams: LayoutParams
@@ -257,6 +259,7 @@
testSandboxedUiAdapter = TestSandboxedUiAdapter()
view.setAdapter(testSandboxedUiAdapter)
}
+ uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
}
@Test
@@ -405,18 +408,17 @@
@Test
fun onConfigurationChangedTest() {
addViewToLayout()
- val device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
testSandboxedUiAdapter.assertSessionOpened()
// newWindow() will be triggered by a window state change, even if the activity handles
// orientation changes without recreating the activity.
- device.performActionAndWait(
- { device.setOrientationLeft() },
+ uiDevice.performActionAndWait(
+ { uiDevice.setOrientationLeft() },
Until.newWindow(),
UI_INTENSIVE_TIMEOUT
)
testSandboxedUiAdapter.assertSessionOpened()
- device.performActionAndWait(
- { device.setOrientationNatural() },
+ uiDevice.performActionAndWait(
+ { uiDevice.setOrientationNatural() },
Until.newWindow(),
UI_INTENSIVE_TIMEOUT
)
@@ -798,6 +800,25 @@
.isAtLeast(SHORTEST_TIME_BETWEEN_SIGNALS_MS)
}
+ @Test
+ fun signalsSentWhenHostActivityStateChanges() {
+ addViewToLayoutAndWaitToBeActive()
+ val session = testSandboxedUiAdapter.testSession!!
+ session.runAndRetrieveNextUiChange {}
+ // Replace the first activity with a new activity. The onScreenGeometry should now be empty.
+ var sandboxedSdkViewUiInfo =
+ session.runAndRetrieveNextUiChange {
+ activityScenarioRule.scenario.onActivity {
+ val intent = Intent(it, SecondActivity::class.java)
+ it.startActivity(intent)
+ }
+ }
+ assertThat(sandboxedSdkViewUiInfo.onScreenGeometry.isEmpty).isTrue()
+ // Return to the first activity. The onScreenGeometry should now be non-empty.
+ sandboxedSdkViewUiInfo = session.runAndRetrieveNextUiChange { uiDevice.pressBack() }
+ assertThat(sandboxedSdkViewUiInfo.onScreenGeometry.isEmpty).isFalse()
+ }
+
private fun addViewToLayout(waitToBeActive: Boolean = false, viewToAdd: View = view) {
activityScenarioRule.withActivity {
val mainLayout: LinearLayout = findViewById(R.id.mainlayout)
diff --git a/privacysandbox/ui/ui-client/src/androidTest/java/androidx/privacysandbox/ui/client/test/SecondActivity.kt b/privacysandbox/ui/ui-client/src/androidTest/java/androidx/privacysandbox/ui/client/test/SecondActivity.kt
new file mode 100644
index 0000000..4e0e81e
--- /dev/null
+++ b/privacysandbox/ui/ui-client/src/androidTest/java/androidx/privacysandbox/ui/client/test/SecondActivity.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright 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 androidx.privacysandbox.ui.client.test
+
+import android.os.Bundle
+import androidx.appcompat.app.AppCompatActivity
+
+class SecondActivity : AppCompatActivity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_second)
+ }
+}
diff --git a/privacysandbox/ui/ui-client/src/androidTest/res/layout/activity_second.xml b/privacysandbox/ui/ui-client/src/androidTest/res/layout/activity_second.xml
new file mode 100644
index 0000000..5a64ff3
--- /dev/null
+++ b/privacysandbox/ui/ui-client/src/androidTest/res/layout/activity_second.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ Copyright 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.
+ -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"/>
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 39f555f..6113489 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
@@ -371,6 +371,7 @@
if (visibility == VISIBLE) {
checkClientOpenSession()
}
+ signalMeasurer?.maybeSendSignals()
}
override fun setAlpha(alpha: Float) {
diff --git a/privacysandbox/ui/ui-client/src/main/java/androidx/privacysandbox/ui/client/view/SandboxedSdkViewSignalMeasurer.kt b/privacysandbox/ui/ui-client/src/main/java/androidx/privacysandbox/ui/client/view/SandboxedSdkViewSignalMeasurer.kt
index 57d201a..dd35657 100644
--- a/privacysandbox/ui/ui-client/src/main/java/androidx/privacysandbox/ui/client/view/SandboxedSdkViewSignalMeasurer.kt
+++ b/privacysandbox/ui/ui-client/src/main/java/androidx/privacysandbox/ui/client/view/SandboxedSdkViewSignalMeasurer.kt
@@ -18,6 +18,7 @@
import android.graphics.Rect
import android.os.SystemClock
+import android.view.View
import androidx.privacysandbox.ui.core.SandboxedSdkViewUiInfo
import androidx.privacysandbox.ui.core.SandboxedUiAdapter
@@ -92,13 +93,17 @@
/** Updates the [SandboxedSdkViewUiInfo] that represents the state of the view. */
private fun updateUiContainerInfo() {
- val isVisible = view.getGlobalVisibleRect(onScreenGeometry)
- if (!isVisible) {
- onScreenGeometry.set(-1, -1, -1, -1)
+ if (view.windowVisibility == View.VISIBLE) {
+ val isVisible = view.getGlobalVisibleRect(onScreenGeometry)
+ if (!isVisible) {
+ onScreenGeometry.set(-1, -1, -1, -1)
+ } else {
+ view.getLocationOnScreen(windowLocation)
+ onScreenGeometry.offset(-windowLocation[0], -windowLocation[1])
+ onScreenGeometry.intersect(0, 0, view.width, view.height)
+ }
} else {
- view.getLocationOnScreen(windowLocation)
- onScreenGeometry.offset(-windowLocation[0], -windowLocation[1])
- onScreenGeometry.intersect(0, 0, view.width, view.height)
+ onScreenGeometry.set(-1, -1, -1, -1)
}
containerHeightPx = view.height
containerWidthPx = view.width