[go: nahoru, domu]

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