Adding Foldable test activity to uiwidgetstestapp
The test activity is mainly for verifying
CameraX correctness in foldable devices.
It supports
- Able to show preview in areas separated by folding
edge/hinge (Using Jetpack Windows Manager)
- Show windowMetrics and all camera characterisitcs
- taking photos / switching camera
- tap-to-focus and pinch-to-zoom
Bug: 198256729
Test: Manual test
Change-Id: Id4ef3ab5a4919f6022872543d5ff6ab64d1ccff4
diff --git a/camera/integration-tests/uiwidgetstestapp/build.gradle b/camera/integration-tests/uiwidgetstestapp/build.gradle
index 87e0f3a..68b28a6 100644
--- a/camera/integration-tests/uiwidgetstestapp/build.gradle
+++ b/camera/integration-tests/uiwidgetstestapp/build.gradle
@@ -58,6 +58,10 @@
implementation("androidx.appcompat:appcompat:1.2.0")
implementation("androidx.activity:activity-ktx:1.2.0")
implementation("androidx.viewpager2:viewpager2:1.1.0-alpha01")
+ implementation("androidx.concurrent:concurrent-futures-ktx:1.1.0")
+ implementation "androidx.window:window:1.0.0-beta02"
+ implementation "androidx.window:window-java:1.0.0-beta02"
+ implementation(libs.constraintLayout)
// Guava
implementation(libs.guavaAndroid)
diff --git a/camera/integration-tests/uiwidgetstestapp/src/main/AndroidManifest.xml b/camera/integration-tests/uiwidgetstestapp/src/main/AndroidManifest.xml
index d0ccb19..17bb354 100644
--- a/camera/integration-tests/uiwidgetstestapp/src/main/AndroidManifest.xml
+++ b/camera/integration-tests/uiwidgetstestapp/src/main/AndroidManifest.xml
@@ -56,6 +56,10 @@
android:configChanges="orientation|screenSize"
android:label="Rotation - ConfigChanges overridden" />
+ <activity
+ android:exported="false"
+ android:name=".foldable.FoldableCameraActivity"
+ android:label="Foldable Test" />
</application>
<uses-permission android:name="android.permission.CAMERA" />
diff --git a/camera/integration-tests/uiwidgetstestapp/src/main/java/androidx/camera/integration/uiwidgets/MainActivity.kt b/camera/integration-tests/uiwidgetstestapp/src/main/java/androidx/camera/integration/uiwidgets/MainActivity.kt
index 04b07ad..d933958 100644
--- a/camera/integration-tests/uiwidgetstestapp/src/main/java/androidx/camera/integration/uiwidgets/MainActivity.kt
+++ b/camera/integration-tests/uiwidgetstestapp/src/main/java/androidx/camera/integration/uiwidgets/MainActivity.kt
@@ -20,6 +20,7 @@
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.camera.integration.uiwidgets.databinding.ActivityMainBinding
+import androidx.camera.integration.uiwidgets.foldable.FoldableCameraActivity
import androidx.camera.integration.uiwidgets.rotations.LockedOrientationActivity
import androidx.camera.integration.uiwidgets.rotations.OrientationConfigChangesOverriddenActivity
import androidx.camera.integration.uiwidgets.rotations.UnlockedOrientationActivity
@@ -49,6 +50,9 @@
binding.viewpager2.setOnClickListener {
launch(ViewPager2Activity::class.java)
}
+ binding.foldable.setOnClickListener {
+ launch(FoldableCameraActivity::class.java)
+ }
}
private fun <A : AppCompatActivity> launch(activityClass: Class<A>) {
diff --git a/camera/integration-tests/uiwidgetstestapp/src/main/java/androidx/camera/integration/uiwidgets/foldable/FoldableCameraActivity.kt b/camera/integration-tests/uiwidgetstestapp/src/main/java/androidx/camera/integration/uiwidgets/foldable/FoldableCameraActivity.kt
new file mode 100644
index 0000000..b597c4f
--- /dev/null
+++ b/camera/integration-tests/uiwidgetstestapp/src/main/java/androidx/camera/integration/uiwidgets/foldable/FoldableCameraActivity.kt
@@ -0,0 +1,458 @@
+/*
+ * Copyright 2021 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.camera.integration.uiwidgets.foldable
+
+import android.Manifest
+import android.app.Activity
+import android.content.ContentValues
+import android.content.Context
+import android.content.pm.PackageManager
+import android.graphics.Point
+import android.graphics.Rect
+import android.hardware.camera2.CameraCharacteristics
+import android.hardware.camera2.CameraManager
+import android.os.Build
+import android.os.Bundle
+import android.provider.MediaStore
+import android.util.Log
+import android.view.Display
+import android.view.GestureDetector
+import android.view.GestureDetector.SimpleOnGestureListener
+import android.view.MotionEvent
+import android.view.ScaleGestureDetector
+import android.view.ScaleGestureDetector.SimpleOnScaleGestureListener
+import android.view.Surface
+import android.view.View
+import android.view.ViewGroup.LayoutParams.MATCH_PARENT
+import android.widget.Toast
+import androidx.annotation.DoNotInline
+import androidx.annotation.RequiresApi
+import androidx.appcompat.app.AppCompatActivity
+import androidx.camera.core.Camera
+import androidx.camera.core.CameraSelector
+import androidx.camera.core.FocusMeteringAction
+import androidx.camera.core.ImageCapture
+import androidx.camera.core.ImageCaptureException
+import androidx.camera.core.MeteringPointFactory
+import androidx.camera.core.Preview
+import androidx.camera.integration.uiwidgets.databinding.ActivityFoldableCameraBinding
+import androidx.camera.integration.uiwidgets.rotations.CameraActivity
+import androidx.camera.lifecycle.ProcessCameraProvider
+import androidx.concurrent.futures.await
+import androidx.core.app.ActivityCompat
+import androidx.core.content.ContextCompat
+import androidx.lifecycle.lifecycleScope
+import androidx.window.layout.DisplayFeature
+import androidx.window.layout.FoldingFeature
+import androidx.window.layout.WindowInfoRepository
+import androidx.window.layout.WindowInfoRepository.Companion.windowInfoRepository
+import androidx.window.layout.WindowLayoutInfo
+import androidx.window.layout.WindowMetrics
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.launch
+
+class FoldableCameraActivity : AppCompatActivity() {
+ companion object {
+ private const val TAG = "FoldableCameraActivity"
+ private const val REQUEST_CODE_PERMISSIONS = 20
+ val PERMISSIONS =
+ arrayOf(Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE)
+ }
+
+ private lateinit var binding: ActivityFoldableCameraBinding
+ private lateinit var windowInfoRepository: WindowInfoRepository
+ private lateinit var imageCapture: ImageCapture
+ private lateinit var camera: Camera
+ private lateinit var cameraProvider: ProcessCameraProvider
+ private var currentLensFacing = CameraSelector.LENS_FACING_BACK
+ private var isPreviewInLeftTop = true
+ private var activeWindowLayoutInfo: WindowLayoutInfo? = null
+ private var lastWindowMetrics: WindowMetrics? = null
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ binding = ActivityFoldableCameraBinding.inflate(layoutInflater)
+ setContentView(binding.root)
+ windowInfoRepository = windowInfoRepository()
+
+ if (shouldRequestPermissionsAtRuntime() && !hasPermissions()) {
+ ActivityCompat.requestPermissions(this, PERMISSIONS, REQUEST_CODE_PERMISSIONS)
+ } else {
+ startCamera()
+ }
+ }
+
+ override fun onRequestPermissionsResult(
+ requestCode: Int,
+ permissions: Array<out String>,
+ grantResults: IntArray
+ ) {
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults)
+ if (requestCode == REQUEST_CODE_PERMISSIONS) {
+ if (hasPermissions()) {
+ startCamera()
+ } else {
+ Log.d(TAG, "Camera permission is required")
+ finish()
+ }
+ }
+ }
+
+ private fun startCamera() {
+ lifecycleScope.launch {
+ showCamerasAndDisplayInfo()
+ cameraProvider =
+ ProcessCameraProvider.getInstance(this@FoldableCameraActivity).await()
+ bindUseCases(cameraProvider)
+ setupUI()
+ }
+
+ // Runs Flow.collect in separate coroutine because it will block the coroutine.
+ lifecycleScope.launch {
+ windowInfoRepository.currentWindowMetrics.collect {
+ Log.d(TAG, "currentWindowMetrics: ${it.bounds}")
+ lastWindowMetrics = it
+ showCamerasAndDisplayInfo()
+ }
+ }
+
+ // Runs Flow.collect in separate coroutine because it will block the coroutine.
+ lifecycleScope.launch {
+ windowInfoRepository.windowLayoutInfo.collect { newLayoutInfo ->
+ Log.d(TAG, "newLayoutInfo: $newLayoutInfo")
+ activeWindowLayoutInfo = newLayoutInfo
+ adjustPreviewByFoldingState()
+ }
+ }
+ }
+
+ private fun bindUseCases(cameraProvider: ProcessCameraProvider) {
+ val preview = Preview.Builder()
+ .build()
+ .apply {
+ setSurfaceProvider(binding.previewView.surfaceProvider)
+ }
+
+ imageCapture = ImageCapture.Builder()
+ .build()
+
+ val cameraSelector = CameraSelector.Builder().requireLensFacing(currentLensFacing).build()
+ camera = cameraProvider.bindToLifecycle(
+ this,
+ cameraSelector,
+ preview,
+ imageCapture
+ )
+ }
+
+ private fun setupUI() {
+ binding.btnTakePicture.setOnClickListener {
+ val contentValues = ContentValues()
+ contentValues.put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg")
+ val outputFileOptions = ImageCapture.OutputFileOptions.Builder(
+ contentResolver,
+ MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
+ contentValues
+ ).build()
+
+ imageCapture.takePicture(
+ outputFileOptions,
+ ContextCompat.getMainExecutor(this),
+ object : ImageCapture.OnImageSavedCallback {
+ override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) {
+ Toast.makeText(
+ this@FoldableCameraActivity,
+ "Image captured successfully",
+ Toast.LENGTH_SHORT
+ ).show()
+ }
+
+ override fun onError(exception: ImageCaptureException) {
+ Toast.makeText(
+ this@FoldableCameraActivity, "Failed to capture", Toast.LENGTH_SHORT
+ ).show()
+ }
+ }
+ )
+ }
+
+ binding.btnSwitchCamera.setOnClickListener {
+ if (currentLensFacing == CameraSelector.LENS_FACING_BACK) {
+ currentLensFacing = CameraSelector.LENS_FACING_FRONT
+ } else {
+ currentLensFacing = CameraSelector.LENS_FACING_BACK
+ }
+
+ cameraProvider.unbindAll()
+ bindUseCases(cameraProvider)
+ }
+
+ val tapGestureDetector = GestureDetector(this, onTapGestureListener)
+ val scaleDetector = ScaleGestureDetector(this, mScaleGestureListener)
+ binding.previewView.setOnTouchListener { _, event ->
+ val tapEventProcessed = tapGestureDetector.onTouchEvent(event)
+ val scaleEventProcessed = scaleDetector.onTouchEvent(event)
+ tapEventProcessed || scaleEventProcessed
+ }
+
+ binding.btnSwitchArea.setOnClickListener {
+ isPreviewInLeftTop = !isPreviewInLeftTop
+ adjustPreviewByFoldingState()
+ }
+ }
+
+ private val mScaleGestureListener: SimpleOnScaleGestureListener =
+ object : SimpleOnScaleGestureListener() {
+ override fun onScale(detector: ScaleGestureDetector): Boolean {
+ val cameraInfo = camera.cameraInfo
+ val newZoom =
+ cameraInfo.zoomState.value!!.zoomRatio * detector.scaleFactor
+ camera.cameraControl.setZoomRatio(newZoom)
+ return true
+ }
+ }
+ private val onTapGestureListener: GestureDetector.>
+ object : SimpleOnGestureListener() {
+ override fun onSingleTapUp(e: MotionEvent): Boolean {
+ val factory: MeteringPointFactory = binding.previewView.meteringPointFactory
+ val action = FocusMeteringAction.Builder(
+ factory.createPoint(e.x, e.y)
+ ).build()
+
+ val future = camera.cameraControl.startFocusAndMetering(action)
+ future.addListener({}, { v -> v.run() })
+ return true
+ }
+ }
+
+ private fun adjustPreviewByFoldingState() {
+ val previewView = binding.previewView
+ val btnSwitchArea = binding.btnSwitchArea
+ activeWindowLayoutInfo?.displayFeatures?.firstOrNull { it is FoldingFeature }
+ ?.let {
+ val rect = getFeaturePositionInViewRect(
+ it,
+ previewView.parent as View
+ ) ?: return@let
+ val foldingFeature = it as FoldingFeature
+ if (foldingFeature.state == FoldingFeature.State.HALF_OPENED) {
+ btnSwitchArea.visibility = View.VISIBLE
+ when (foldingFeature.orientation) {
+ FoldingFeature.Orientation.VERTICAL -> {
+ if (isPreviewInLeftTop) {
+ previewView.moveToLeftOf(rect)
+ val blankAreaWidth =
+ (btnSwitchArea.parent as View).width - rect.right
+ btnSwitchArea.x = rect.right +
+ (blankAreaWidth - btnSwitchArea.width) / 2f
+ btnSwitchArea.y = (previewView.height - btnSwitchArea.height) / 2f
+ } else {
+ previewView.moveToRightOf(rect)
+ btnSwitchArea.x =
+ (rect.left - btnSwitchArea.width) / 2f
+ btnSwitchArea.y = (previewView.height - btnSwitchArea.height) / 2f
+ }
+ }
+ FoldingFeature.Orientation.HORIZONTAL -> {
+ if (isPreviewInLeftTop) {
+ previewView.moveToTopOf(rect)
+ val blankAreaHeight =
+ (btnSwitchArea.parent as View).height - rect.bottom
+ btnSwitchArea.x = (previewView.width - btnSwitchArea.width) / 2f
+ btnSwitchArea.y = rect.bottom +
+ (blankAreaHeight - btnSwitchArea.height) / 2f
+ } else {
+ previewView.moveToBottomOf(rect)
+ btnSwitchArea.x = (previewView.width - btnSwitchArea.width) / 2f
+ btnSwitchArea.y =
+ (rect.top - btnSwitchArea.height) / 2f
+ }
+ }
+ }
+ } else {
+ previewView.restore()
+ btnSwitchArea.x = 0f
+ btnSwitchArea.y = 0f
+ btnSwitchArea.visibility = View.INVISIBLE
+ }
+ showCamerasAndDisplayInfo()
+ }
+ }
+
+ private fun View.moveToLeftOf(foldingFeatureRect: Rect) {
+ x = 0f
+ layoutParams = layoutParams.apply {
+ width = foldingFeatureRect.left
+ }
+ }
+
+ private fun View.moveToRightOf(foldingFeatureRect: Rect) {
+ x = foldingFeatureRect.left.toFloat()
+ layoutParams = layoutParams.apply {
+ width = (parent as View).width - foldingFeatureRect.left
+ }
+ }
+
+ private fun View.moveToTopOf(foldingFeatureRect: Rect) {
+ y = 0f
+ layoutParams = layoutParams.apply {
+ height = foldingFeatureRect.top
+ }
+ }
+
+ private fun View.moveToBottomOf(foldingFeatureRect: Rect) {
+ y = foldingFeatureRect.top.toFloat()
+ layoutParams = layoutParams.apply {
+ height = (parent as View).height - foldingFeatureRect.top
+ }
+ }
+
+ private fun View.restore() {
+ // Restore to full view
+ layoutParams = layoutParams.apply {
+ width = MATCH_PARENT
+ height = MATCH_PARENT
+ }
+ y = 0f
+ x = 0f
+ }
+
+ private fun shouldRequestPermissionsAtRuntime(): Boolean {
+ return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
+ }
+
+ private fun hasPermissions(): Boolean {
+ return CameraActivity.PERMISSIONS.all {
+ ContextCompat.checkSelfPermission(this, it) == PackageManager.PERMISSION_GRANTED
+ }
+ }
+
+ @Suppress("DEPRECATION")
+ private fun getCurrentDisplay(): Display? {
+ return if (Build.VERSION.SDK_INT >= 30) {
+ Api30Compat.getDisplay(this)
+ } else {
+ windowManager.defaultDisplay
+ }
+ }
+
+ private val Display.rotationString: String
+ get() {
+ return when (rotation) {
+ Surface.ROTATION_0 -> "0"
+ Surface.ROTATION_90 -> "90"
+ Surface.ROTATION_180 -> "180"
+ Surface.ROTATION_270 -> "270"
+ else -> "unknown:$rotation"
+ }
+ }
+
+ @Suppress("DEPRECATION")
+ private fun showCamerasAndDisplayInfo() {
+ val cameraManager = getSystemService(Context.CAMERA_SERVICE) as CameraManager
+ val display = getCurrentDisplay()
+
+ val realPt = Point()
+ display?.getRealSize(realPt)
+ var totalMsg = "Display realSize=$realPt rot=${display?.rotationString} \n" +
+ " WindowMetrics=${lastWindowMetrics?.bounds} \n"
+
+ for (id in cameraManager.cameraIdList) {
+ val characteristics = cameraManager.getCameraCharacteristics(id)
+ val msg = "[$id] ${characteristics.lensFacing} " +
+ "${characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION)} degrees \n" +
+ " array = " +
+ "${characteristics.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE)} \n" +
+ " focal length = [${characteristics.focalLength}] \n"
+ totalMsg += msg
+ }
+
+ binding.cameraInfo.text = totalMsg
+ }
+
+ private val CameraCharacteristics.lensFacing: String
+ get() = when (this.get(CameraCharacteristics.LENS_FACING)) {
+ CameraCharacteristics.LENS_FACING_BACK -> "BACK"
+ CameraCharacteristics.LENS_FACING_FRONT -> "FRONT"
+ CameraCharacteristics.LENS_FACING_EXTERNAL -> "EXTERNAL"
+ else -> "UNKNOWN"
+ }
+
+ private val CameraCharacteristics.focalLength: String
+ get() {
+ val focalLengths = this.get(CameraCharacteristics.LENS_INFO_AVAILABLE_FOCAL_LENGTHS)
+ if (focalLengths == null || focalLengths.isEmpty()) {
+ return "NONE"
+ }
+ return focalLengths.joinToString(",")
+ }
+
+ /**
+ * Gets the bounds of the display feature translated to the View's coordinate space and current
+ * position in the window. This will also include view padding in the calculations.
+ *
+ * Copied from windowManager Jetpack library sample codes.
+ * https://github.com/android/user-interface-samples/tree/main/WindowManager
+ *
+ */
+ fun getFeaturePositionInViewRect(
+ displayFeature: DisplayFeature,
+ view: View,
+ includePadding: Boolean = true
+ ): Rect? {
+ // The location of the view in window to be in the same coordinate space as the feature.
+ val viewLocationInWindow = IntArray(2)
+ view.getLocationInWindow(viewLocationInWindow)
+
+ // Intersect the feature rectangle in window with view rectangle to clip the bounds.
+ val viewRect = Rect(
+ viewLocationInWindow[0], viewLocationInWindow[1],
+ viewLocationInWindow[0] + view.width, viewLocationInWindow[1] + view.height
+ )
+
+ // Include padding if needed
+ if (includePadding) {
+ viewRect.left += view.paddingLeft
+ viewRect.top += view.paddingTop
+ viewRect.right -= view.paddingRight
+ viewRect.bottom -= view.paddingBottom
+ }
+
+ val featureRectInView = Rect(displayFeature.bounds)
+ val intersects = featureRectInView.intersect(viewRect)
+ if ((featureRectInView.width() == 0 && featureRectInView.height() == 0) ||
+ !intersects
+ ) {
+ return null
+ }
+
+ // Offset the feature coordinates to view coordinate space start point
+ featureRectInView.offset(-viewLocationInWindow[0], -viewLocationInWindow[1])
+
+ return featureRectInView
+ }
+}
+
+@RequiresApi(30)
+private object Api30Compat {
+ @JvmStatic
+ @DoNotInline
+ fun getDisplay(activity: Activity): Display? {
+ return activity.display
+ }
+}
\ No newline at end of file
diff --git a/camera/integration-tests/uiwidgetstestapp/src/main/res/layout/activity_foldable_camera.xml b/camera/integration-tests/uiwidgetstestapp/src/main/res/layout/activity_foldable_camera.xml
new file mode 100644
index 0000000..bdab319
--- /dev/null
+++ b/camera/integration-tests/uiwidgetstestapp/src/main/res/layout/activity_foldable_camera.xml
@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ Copyright 2021 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.
+ -->
+
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <Button
+ android:id="@+id/btnSwitchArea"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Switch Area"
+ android:visibility="invisible"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent" />
+
+ <androidx.camera.view.PreviewView
+ android:id="@+id/previewView"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ tools:layout_editor_absoluteX="62dp"
+ tools:layout_editor_absoluteY="0dp">
+
+ </androidx.camera.view.PreviewView>
+
+ <TextView
+ android:id="@+id/cameraInfo"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text=""
+ android:textColor="#FFFFFFFF"
+ android:shadowColor="#FF000000"
+ android:shadowRadius="8"
+ android:shadowDx="5"
+ android:shadowDy="5"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintTop_toTopOf="parent" />
+
+ <Button
+ android:id="@+id/btnTakePicture"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Shot"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent" />
+
+ <Button
+ android:id="@+id/btnSwitchCamera"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Switch"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toEndOf="@+id/btnTakePicture" />
+
+
+</androidx.constraintlayout.widget.ConstraintLayout >
\ No newline at end of file
diff --git a/camera/integration-tests/uiwidgetstestapp/src/main/res/layout/activity_main.xml b/camera/integration-tests/uiwidgetstestapp/src/main/res/layout/activity_main.xml
index d5010e9..9067da8 100644
--- a/camera/integration-tests/uiwidgetstestapp/src/main/res/layout/activity_main.xml
+++ b/camera/integration-tests/uiwidgetstestapp/src/main/res/layout/activity_main.xml
@@ -13,42 +13,51 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:orientation="vertical"
tools:context=".MainActivity">
- <Button
- android:id="@+id/rotationUnlocked"
+ <LinearLayout
android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:text="@string/rotation_unlocked_orientation" />
+ android:layout_height="match_parent"
+ android:orientation="vertical">
- <Button
- android:id="@+id/rotationLocked"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:text="@string/rotation_locked_orientation" />
+ <Button
+ android:id="@+id/rotationUnlocked"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/rotation_unlocked_orientation" />
- <Button
- android:id="@+id/rotationConfigChanges"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:text="@string/rotation_config_changes_overridden" />
+ <Button
+ android:id="@+id/rotationLocked"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/rotation_locked_orientation" />
- <Button
- android:id="@+id/viewpager"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:text="@string/viewpager" />
+ <Button
+ android:id="@+id/rotationConfigChanges"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/rotation_config_changes_overridden" />
- <Button
- android:id="@+id/viewpager2"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:text="@string/viewpager2" />
+ <Button
+ android:id="@+id/viewpager"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/viewpager" />
-</LinearLayout>
\ No newline at end of file
+ <Button
+ android:id="@+id/viewpager2"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/viewpager2" />
+
+ <Button
+ android:id="@+id/foldable"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/foldable" />
+ </LinearLayout>
+</ScrollView>
\ No newline at end of file
diff --git a/camera/integration-tests/uiwidgetstestapp/src/main/res/values/donottranslate-strings.xml b/camera/integration-tests/uiwidgetstestapp/src/main/res/values/donottranslate-strings.xml
index d0f15ab..991afd8 100644
--- a/camera/integration-tests/uiwidgetstestapp/src/main/res/values/donottranslate-strings.xml
+++ b/camera/integration-tests/uiwidgetstestapp/src/main/res/values/donottranslate-strings.xml
@@ -21,4 +21,6 @@
<string name="rotation_config_changes_overridden">Rotation - ConfigChanges overridden</string>
<string name="viewpager">ViewPager</string>
<string name="viewpager2">ViewPager2</string>
+ <string name="foldable">Foldable</string>
+
</resources>
\ No newline at end of file