aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTreehugger Robot <treehugger-gerrit@google.com>2019-04-27 02:20:34 +0000
committerGerrit Code Review <noreply-gerritcodereview@google.com>2019-04-27 02:20:34 +0000
commita243337dccbc9496d9f916fc93b70af9ece39e4e (patch)
tree7c3e181e78a78d8f1554e0016a5b90b63780f41a
parent1679bdb835f6a9a39fa2f04803a5ece7c61fe939 (diff)
parente128705fd2524480a7364a9fa223c88b8907925e (diff)
downloadsupport-a243337dccbc9496d9f916fc93b70af9ece39e4e.tar.gz
Merge "Initial commit for AOSP merge" into androidx-master-dev
-rw-r--r--camera/integration-tests/timingtestapp/build.gradle54
-rw-r--r--camera/integration-tests/timingtestapp/src/main/AndroidManifest.xml66
-rw-r--r--camera/integration-tests/timingtestapp/src/main/ic_launcher-web.pngbin0 -> 449986 bytes
-rw-r--r--camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/AutoFitSurfaceView.kt71
-rw-r--r--camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/AutoFitTextureView.kt71
-rw-r--r--camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/CamViewModel.kt82
-rw-r--r--camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/CameraParams.kt119
-rw-r--r--camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/CameraTimer.kt71
-rw-r--r--camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/CameraUtils.kt281
-rw-r--r--camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/CustomLifecycle.kt102
-rw-r--r--camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/DeviceInfo.kt36
-rw-r--r--camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/ImageUtils.kt346
-rw-r--r--camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/MainActivity.kt465
-rw-r--r--camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/MeasureUtils.kt58
-rw-r--r--camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/MultiTestSettingsFragment.kt45
-rw-r--r--camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/PrefHelper.kt286
-rw-r--r--camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/SettingsDialog.kt151
-rw-r--r--camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/SingleTestSettingsFragment.kt106
-rw-r--r--camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/TestConfig.kt85
-rw-r--r--camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/TestResults.kt413
-rw-r--r--camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/TestUtils.kt554
-rw-r--r--camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/TimingTests.kt593
-rw-r--r--camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/cameracontrollers/Camera1Controller.kt306
-rw-r--r--camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/cameracontrollers/Camera2CaptureCallback.kt134
-rw-r--r--camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/cameracontrollers/Camera2CaptureSessionCallback.kt208
-rw-r--r--camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/cameracontrollers/Camera2Controller.kt306
-rw-r--r--camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/cameracontrollers/Camera2DeviceStateCallback.kt143
-rw-r--r--camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/cameracontrollers/Camera2PreviewSessionStateCallback.kt152
-rw-r--r--camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/cameracontrollers/CameraXCaptureSessionCallback.kt137
-rw-r--r--camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/cameracontrollers/CameraXController.kt305
-rw-r--r--camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/cameracontrollers/CameraXDeviceStateCallback.kt133
-rw-r--r--camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/cameracontrollers/CameraXPreviewSessionStateCallback.kt113
-rw-r--r--camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/cameracontrollers/Common.kt68
-rw-r--r--camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/timing/BaseActivity.java75
-rw-r--r--camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/timing/CustomLifecycle.java75
-rw-r--r--camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/timing/TakePhotoActivity.java280
-rw-r--r--camera/integration-tests/timingtestapp/src/main/res/drawable-v24/ic_launcher_foreground.xml50
-rw-r--r--camera/integration-tests/timingtestapp/src/main/res/drawable/ic_launcher_background.xml185
-rw-r--r--camera/integration-tests/timingtestapp/src/main/res/drawable/rounded_frame.xml28
-rw-r--r--camera/integration-tests/timingtestapp/src/main/res/layout-land/activity_main.xml155
-rw-r--r--camera/integration-tests/timingtestapp/src/main/res/layout/activity_main.xml167
-rw-r--r--camera/integration-tests/timingtestapp/src/main/res/layout/settings_dialog.xml60
-rw-r--r--camera/integration-tests/timingtestapp/src/main/res/menu/main_menu.xml28
-rw-r--r--camera/integration-tests/timingtestapp/src/main/res/mipmap-anydpi-v26/ic_launcher.xml20
-rw-r--r--camera/integration-tests/timingtestapp/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml20
-rw-r--r--camera/integration-tests/timingtestapp/src/main/res/mipmap-hdpi/ic_launcher.pngbin0 -> 11207 bytes
-rw-r--r--camera/integration-tests/timingtestapp/src/main/res/mipmap-hdpi/ic_launcher_foreground.pngbin0 -> 43906 bytes
-rw-r--r--camera/integration-tests/timingtestapp/src/main/res/mipmap-hdpi/ic_launcher_round.pngbin0 -> 12575 bytes
-rw-r--r--camera/integration-tests/timingtestapp/src/main/res/mipmap-mdpi/ic_launcher.pngbin0 -> 5602 bytes
-rw-r--r--camera/integration-tests/timingtestapp/src/main/res/mipmap-mdpi/ic_launcher_foreground.pngbin0 -> 20063 bytes
-rw-r--r--camera/integration-tests/timingtestapp/src/main/res/mipmap-mdpi/ic_launcher_round.pngbin0 -> 6073 bytes
-rw-r--r--camera/integration-tests/timingtestapp/src/main/res/mipmap-xhdpi/ic_launcher.pngbin0 -> 19661 bytes
-rw-r--r--camera/integration-tests/timingtestapp/src/main/res/mipmap-xhdpi/ic_launcher_foreground.pngbin0 -> 76604 bytes
-rw-r--r--camera/integration-tests/timingtestapp/src/main/res/mipmap-xhdpi/ic_launcher_round.pngbin0 -> 21339 bytes
-rw-r--r--camera/integration-tests/timingtestapp/src/main/res/mipmap-xxhdpi/ic_launcher.pngbin0 -> 41477 bytes
-rw-r--r--camera/integration-tests/timingtestapp/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.pngbin0 -> 169275 bytes
-rw-r--r--camera/integration-tests/timingtestapp/src/main/res/mipmap-xxhdpi/ic_launcher_round.pngbin0 -> 44493 bytes
-rw-r--r--camera/integration-tests/timingtestapp/src/main/res/mipmap-xxxhdpi/ic_launcher.pngbin0 -> 70950 bytes
-rw-r--r--camera/integration-tests/timingtestapp/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.pngbin0 -> 293842 bytes
-rw-r--r--camera/integration-tests/timingtestapp/src/main/res/mipmap-xxxhdpi/ic_launcher_round.pngbin0 -> 75878 bytes
-rw-r--r--camera/integration-tests/timingtestapp/src/main/res/values/arrays.xml83
-rw-r--r--camera/integration-tests/timingtestapp/src/main/res/values/colors.xml21
-rw-r--r--camera/integration-tests/timingtestapp/src/main/res/values/ic_launcher_background.xml19
-rw-r--r--camera/integration-tests/timingtestapp/src/main/res/values/strings.xml125
-rw-r--r--camera/integration-tests/timingtestapp/src/main/res/values/style.xml21
-rw-r--r--camera/integration-tests/timingtestapp/src/main/res/values/styles.xml30
-rw-r--r--camera/integration-tests/timingtestapp/src/main/res/xml/multi_test_settings.xml130
-rw-r--r--camera/integration-tests/timingtestapp/src/main/res/xml/single_test_settings.xml98
68 files changed, 7194 insertions, 536 deletions
diff --git a/camera/integration-tests/timingtestapp/build.gradle b/camera/integration-tests/timingtestapp/build.gradle
index 3aa3257d6d7..e6d2fea5514 100644
--- a/camera/integration-tests/timingtestapp/build.gradle
+++ b/camera/integration-tests/timingtestapp/build.gradle
@@ -1,16 +1,15 @@
/*
- * Copyright (C) 2019 The Android Open Source Project
+ * Copyright 2019 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
+ * 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.
+ * 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.
*/
@@ -20,16 +19,19 @@ import static androidx.build.dependencies.DependenciesKt.*
plugins {
id("AndroidXPlugin")
id("com.android.application")
+ id("kotlin-android")
+ id("kotlin-android-extensions")
}
android {
+ compileSdkVersion 28
defaultConfig {
- applicationId "androidx.camera.integration.timing"
+ applicationId "androidx.camera.integration.antelope"
minSdkVersion 21
- versionCode 1
- multiDexEnabled true
+ targetSdkVersion 28
+ versionCode 34
+ versionName "1.34"
}
-
sourceSets {
main.manifest.srcFile 'src/main/AndroidManifest.xml'
main.java.srcDirs = ['src/main/java']
@@ -37,26 +39,42 @@ android {
main.java.includes = ['**/*.java']
main.res.srcDirs = ['src/main/res']
}
-
buildTypes {
- debug {
- testCoverageEnabled true
- }
-
release {
+ minifyEnabled false
}
}
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
}
dependencies {
// Internal library
implementation(project(":camera:camera-camera2"))
+ implementation(project(":camera:camera-core"))
+
+ // Lifecycle and LiveData
+ implementation(ARCH_LIFECYCLE_EXTENSIONS)
- // Android Support Library
- api(CONSTRAINT_LAYOUT, { transitive = true })
- implementation(project(":appcompat"))
+ // Android support library
+ implementation(SUPPORT_APPCOMPAT)
+ implementation(ANDROIDX_COLLECTION)
+ implementation(project(":preference")) // This one is still in alpha
+ implementation("androidx.exifinterface:exifinterface:1.0.0")
+ implementation(CONSTRAINT_LAYOUT, { transitive = true })
+ implementation(KOTLIN_STDLIB)
- // Guava
+ // Testing framework
+ implementation 'androidx.test.espresso:espresso-idling-resource:3.1.0'
+ androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
+ androidTestImplementation(ANDROIDX_TEST_CORE)
+ androidTestImplementation(ANDROIDX_TEST_RUNNER)
+ androidTestImplementation(ANDROIDX_TEST_RULES)
+ androidTestImplementation(ANDROIDX_TEST_UIAUTOMATOR)
+ androidTestImplementation(ESPRESSO_CORE)
+
+ // Statistics library
implementation(GUAVA_ANDROID)
}
-
diff --git a/camera/integration-tests/timingtestapp/src/main/AndroidManifest.xml b/camera/integration-tests/timingtestapp/src/main/AndroidManifest.xml
index 921b5cd0df4..20539cc9505 100644
--- a/camera/integration-tests/timingtestapp/src/main/AndroidManifest.xml
+++ b/camera/integration-tests/timingtestapp/src/main/AndroidManifest.xml
@@ -1,34 +1,54 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2019 The Android Open Source Project
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright 2019 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.
+ -->
-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.
--->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="androidx.camera.integration.timing">
+ package="androidx.camera.integration.antelope">
+
<uses-permission android:name="android.permission.CAMERA" />
- <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
- <uses-permission android:name="android.permission.RECORD_AUDIO" />
- <uses-feature android:name="android.hardware.camera" />
+ <uses-feature
+ android:name="android.hardware.camera.any"
+ android:required="false" />
+ <uses-feature
+ android:name="android.hardware.camera.external"
+ android:required="false" />
+ <uses-feature
+ android:name="android.hardware.camera.front"
+ android:required="false" />
+ <uses-feature
+ android:name="android.hardware.camera.raw"
+ android:required="false" />
- <application android:theme="@style/AppTheme">
- <activity
- android:name=".TakePhotoActivity"
- android:label="Taking Photo">
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+ <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+
+ <application
+ android:allowBackup="true"
+ android:icon="@mipmap/ic_launcher"
+ android:label="@string/app_name"
+ android:roundIcon="@mipmap/ic_launcher_round"
+ android:supportsRtl="true"
+ android:theme="@style/AppTheme">
+ <activity android:name="androidx.camera.integration.antelope.MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
+
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
-</manifest>
+
+</manifest> \ No newline at end of file
diff --git a/camera/integration-tests/timingtestapp/src/main/ic_launcher-web.png b/camera/integration-tests/timingtestapp/src/main/ic_launcher-web.png
new file mode 100644
index 00000000000..ae4c1099d32
--- /dev/null
+++ b/camera/integration-tests/timingtestapp/src/main/ic_launcher-web.png
Binary files differ
diff --git a/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/AutoFitSurfaceView.kt b/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/AutoFitSurfaceView.kt
new file mode 100644
index 00000000000..e9967108a51
--- /dev/null
+++ b/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/AutoFitSurfaceView.kt
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2019 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.antelope
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.SurfaceView
+import android.view.View
+
+/**
+ * A [SurfaceView] that can be adjusted to a specified aspect ratio.
+ */
+class AutoFitSurfaceView @JvmOverloads constructor(
+ context: Context,
+ attrs: AttributeSet? = null,
+ defStyle: Int = 0
+) : SurfaceView(context, attrs, defStyle) {
+
+ private var ratioWidth = 0
+ private var ratioHeight = 0
+
+ /**
+ * Sets the aspect ratio for this view. The size of the view will be measured based on the ratio
+ * calculated from the parameters. Note that the actual sizes of parameters don't matter, that
+ * is, calling setAspectRatio(2, 3) and setAspectRatio(4, 6) have the same result.
+ *
+ * @param width Relative horizontal size
+ * @param height Relative vertical size
+ */
+ fun setAspectRatio(width: Int, height: Int) {
+ if (width < 0 || height < 0) {
+ throw IllegalArgumentException("Size cannot be negative.")
+ }
+ ratioWidth = width
+ ratioHeight = height
+ requestLayout()
+ }
+
+ /**
+ * When the surface is given a new width/height, ensure that it maintains the correct aspect
+ * ratio.
+ */
+ override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec)
+ val width = View.MeasureSpec.getSize(widthMeasureSpec)
+ val height = View.MeasureSpec.getSize(heightMeasureSpec)
+ if (ratioWidth == 0 || ratioHeight == 0) {
+ setMeasuredDimension(width, height)
+ } else {
+ if (width < height * ratioWidth / ratioHeight) {
+ setMeasuredDimension(width, width * ratioHeight / ratioWidth)
+ } else {
+ setMeasuredDimension(height * ratioWidth / ratioHeight, height)
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/AutoFitTextureView.kt b/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/AutoFitTextureView.kt
new file mode 100644
index 00000000000..7f814b556e8
--- /dev/null
+++ b/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/AutoFitTextureView.kt
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2019 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.antelope
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.TextureView
+import android.view.View
+
+/**
+ * A [TextureView] that can be adjusted to a specified aspect ratio.
+ */
+class AutoFitTextureView @JvmOverloads constructor(
+ context: Context,
+ attrs: AttributeSet? = null,
+ defStyle: Int = 0
+) : TextureView(context, attrs, defStyle) {
+
+ private var ratioWidth = 0
+ private var ratioHeight = 0
+
+ /**
+ * Sets the aspect ratio for this view. The size of the view will be measured based on the ratio
+ * calculated from the parameters. Note that the actual sizes of parameters don't matter, that
+ * is, calling setAspectRatio(2, 3) and setAspectRatio(4, 6) make the same result.
+ *
+ * @param width Relative horizontal size
+ * @param height Relative vertical size
+ */
+ fun setAspectRatio(width: Int, height: Int) {
+ if (width < 0 || height < 0) {
+ throw IllegalArgumentException("Size cannot be negative.")
+ }
+ ratioWidth = width
+ ratioHeight = height
+ requestLayout()
+ }
+
+ /**
+ * When the surface is given a new width/height, ensure that it maintains the correct aspect
+ * ratio.
+ */
+ override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec)
+ val width = View.MeasureSpec.getSize(widthMeasureSpec)
+ val height = View.MeasureSpec.getSize(heightMeasureSpec)
+ if (ratioWidth == 0 || ratioHeight == 0) {
+ setMeasuredDimension(width, height)
+ } else {
+ if (width < height * ratioWidth / ratioHeight) {
+ setMeasuredDimension(width, width * ratioHeight / ratioWidth)
+ } else {
+ setMeasuredDimension(height * ratioWidth / ratioHeight, height)
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/CamViewModel.kt b/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/CamViewModel.kt
new file mode 100644
index 00000000000..3c031e72215
--- /dev/null
+++ b/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/CamViewModel.kt
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2019 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.antelope
+
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+
+/**
+ * ViewModel for keeping track of application state across resizes and configuration changes.
+ * This includes:
+ */
+class CamViewModel : ViewModel() {
+ private var cameraParams: HashMap<String, CameraParams> = HashMap<String, CameraParams>()
+
+ private val currentAPI: MutableLiveData<CameraAPI> = MutableLiveData()
+ private val currentCamera: MutableLiveData<Int> = MutableLiveData()
+ private val currentFocusMode: MutableLiveData<FocusMode> = MutableLiveData()
+ private val currentImageCaptureSize: MutableLiveData<ImageCaptureSize> = MutableLiveData()
+ private val shouldOutputLog: MutableLiveData<Boolean> = MutableLiveData()
+ private val humanReadableReport: MutableLiveData<String> = MutableLiveData()
+
+ /** Camera API of the current test */
+ fun getCurrentAPI(): MutableLiveData<CameraAPI> {
+ if (currentAPI.value == null)
+ currentAPI.value = CameraAPI.CAMERA2
+ return currentAPI
+ }
+
+ /** Camera ID of the current test */
+ fun getCurrentCamera(): MutableLiveData<Int> {
+ if (currentCamera.value == null)
+ currentCamera.value = 0
+ return currentCamera
+ }
+
+ /** Focus mode of the current test */
+ fun getCurrentFocusMode(): MutableLiveData<FocusMode> {
+ if (currentFocusMode.value == null)
+ currentFocusMode.value = FocusMode.AUTO
+ return currentFocusMode
+ }
+
+ /** Requested image capture size of the current test */
+ fun getCurrentImageCaptureSize(): MutableLiveData<ImageCaptureSize> {
+ if (currentImageCaptureSize.value == null)
+ currentImageCaptureSize.value = ImageCaptureSize.MAX
+ return currentImageCaptureSize
+ }
+
+ /** Hashmap of the CameraParams associated with all the cameras on the device */
+ fun getCameraParams(): HashMap<String, CameraParams> {
+ return cameraParams
+ }
+
+ /** If the user has asked to output the debugging log */
+ fun getShouldOutputLog(): MutableLiveData<Boolean> {
+ if (shouldOutputLog.value == null)
+ shouldOutputLog.value = false
+ return shouldOutputLog
+ }
+
+ /** Current value of the main output window on screen */
+ fun getHumanReadableReport(): MutableLiveData<String> {
+ if (humanReadableReport.value == null)
+ humanReadableReport.value = "Android Camera Performance Tool"
+ return humanReadableReport
+ }
+} \ No newline at end of file
diff --git a/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/CameraParams.kt b/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/CameraParams.kt
new file mode 100644
index 00000000000..4c84e6c23ab
--- /dev/null
+++ b/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/CameraParams.kt
@@ -0,0 +1,119 @@
+/*
+ * Copyright 2019 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.antelope
+
+import android.hardware.camera2.CameraCaptureSession
+import android.hardware.camera2.CameraCharacteristics
+import android.hardware.camera2.CameraDevice
+import android.hardware.camera2.CaptureRequest
+import android.media.ImageReader
+import android.os.Handler
+import android.os.HandlerThread
+import android.util.Size
+import androidx.camera.integration.antelope.MainActivity.Companion.FIXED_FOCUS_DISTANCE
+import androidx.camera.integration.antelope.MainActivity.Companion.INVALID_FOCAL_LENGTH
+import androidx.camera.integration.antelope.MainActivity.Companion.NO_APERTURE
+import androidx.camera.core.ImageCapture
+import androidx.camera.core.ImageCaptureConfig
+import androidx.camera.core.PreviewConfig
+import androidx.camera.integration.antelope.cameracontrollers.Camera2CaptureSessionCallback
+import androidx.camera.integration.antelope.cameracontrollers.Camera2DeviceStateCallback
+import androidx.camera.integration.antelope.cameracontrollers.CameraState
+import androidx.camera.integration.antelope.cameracontrollers.CameraXDeviceStateCallback
+import androidx.camera.integration.antelope.cameracontrollers.CameraXPreviewSessionStateCallback
+import androidx.camera.integration.antelope.cameracontrollers.CameraXCaptureSessionCallback
+
+/**
+ * CameraParams contains a list of device characteristics for a given camera device.
+ *
+ * These are populated on app startup using initializeCameras in CameraUtils to prevent multiple
+ * calls to the CameraManager.
+ *
+ * In addition, some convenience variables for camera API callbacks, UI surfaces, and ImageReaders
+ * are included to facilitate testing. The calling Activity is responsible to make sure these
+ * convenience variables are coordinated with the active camera device.
+ */
+class CameraParams {
+ // Intrinsic device characteristics
+ internal var id: String = ""
+ internal var device: CameraDevice? = null
+ internal var isFront: Boolean = false
+ internal var isExternal: Boolean = false
+ internal var hasFlash: Boolean = false
+ internal var hasMulti: Boolean = false
+ internal var hasManualControl: Boolean = false
+ internal var physicalCameras: Set<String> = HashSet<String>()
+ internal var focalLengths: FloatArray = FloatArray(0)
+ internal var apertures: FloatArray = FloatArray(0)
+ internal var smallestFocalLength: Float = INVALID_FOCAL_LENGTH
+ internal var minDeltaFromNormal: Float = INVALID_FOCAL_LENGTH
+ internal var minFocusDistance: Float = FIXED_FOCUS_DISTANCE
+ internal var largestAperture: Float = NO_APERTURE
+ internal var effects: IntArray = IntArray(0)
+ internal var hasSepia: Boolean = false
+ internal var hasMono: Boolean = false
+ internal var hasAF: Boolean = false
+ internal var megapixels: Int = 0
+ internal var cam1AFSupported: Boolean = false
+ internal var characteristics: CameraCharacteristics? = null
+
+ // Camera1/Camera2 min/max size (sometimes different for some devices)
+ internal var cam1MinSize: Size = Size(0, 0)
+ internal var cam1MaxSize: Size = Size(0, 0)
+ internal var cam2MinSize: Size = Size(0, 0)
+ internal var cam2MaxSize: Size = Size(0, 0)
+
+ // Current state
+ internal var state = CameraState.UNINITIALIZED
+ internal var isOpen: Boolean = false
+ internal var isPreviewing: Boolean = false
+
+ // Thread to use for this device
+ internal var backgroundThread: HandlerThread? = null
+ internal var backgroundHandler: Handler? = null
+
+ // Convenience UI references
+ internal var imageReader: ImageReader? = null
+ internal var previewSurfaceView: AutoFitSurfaceView? = null
+ internal var cameraXPreviewTexture: AutoFitTextureView? = null
+
+ // Camera 2 API callback references
+ internal var captureRequestBuilder: CaptureRequest.Builder? = null
+
+ internal var camera2CaptureSession: CameraCaptureSession? = null
+ internal var camera2CaptureSessionCallback: Camera2CaptureSessionCallback? = null
+ internal var camera2DeviceStateCallback: Camera2DeviceStateCallback? = null
+ internal var imageAvailableListener: ImageAvailableListener? = null
+
+ // Camera X
+ internal var cameraXDeviceStateCallback: CameraXDeviceStateCallback? = null
+ internal var cameraXPreviewSessionStateCallback: CameraXPreviewSessionStateCallback? = null
+ internal var cameraXCaptureSessionCallback: CameraXCaptureSessionCallback? = null
+ internal var cameraXPreviewConfig: PreviewConfig =
+ PreviewConfig.Builder().build()
+ internal var cameraXCaptureConfig: ImageCaptureConfig =
+ ImageCaptureConfig.Builder().build()
+
+ // Custom lifecycle for CameraX API
+ internal var cameraXLifecycle: CustomLifecycle = CustomLifecycle()
+ internal var cameraXImageCaptureUseCase: ImageCapture =
+ ImageCapture(ImageCaptureConfig.Builder().build())
+
+ // Testing variables
+ internal var timer: CameraTimer = CameraTimer()
+ internal var autoFocusStuckCounter = 0
+} \ No newline at end of file
diff --git a/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/CameraTimer.kt b/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/CameraTimer.kt
new file mode 100644
index 00000000000..17ab0fd9d4d
--- /dev/null
+++ b/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/CameraTimer.kt
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2019 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.antelope
+
+/** Contains all the different timing values for a test run */
+class CameraTimer {
+ internal var testStart: Long = 0
+ internal var testEnd: Long = 0
+
+ internal var openStart: Long = 0
+ internal var openEnd: Long = 0
+
+ internal var cameraCloseStart: Long = 0
+ internal var cameraCloseEnd: Long = 0
+
+ internal var previewFillStart: Long = 0
+ internal var previewFillEnd: Long = 0
+
+ internal var captureStart: Long = 0
+ internal var captureEnd: Long = 0
+
+ internal var autofocusStart: Long = 0
+ internal var autofocusEnd: Long = 0
+
+ internal var imageReaderStart: Long = 0
+ internal var imageReaderEnd: Long = 0
+
+ internal var imageSaveStart: Long = 0
+ internal var imageSaveEnd: Long = 0
+
+ internal var previewStart: Long = 0
+ internal var previewEnd: Long = 0
+
+ internal var previewCloseStart: Long = 0
+ internal var previewCloseEnd: Long = 0
+
+ internal var switchToSecondStart: Long = 0
+ internal var switchToSecondEnd: Long = 0
+ internal var switchToFirstStart: Long = 0
+ internal var switchToFirstEnd: Long = 0
+
+ internal var isFirstPhoto: Boolean = true
+ internal var isHDRPlus: Boolean = false
+
+ /** Reset timers related to an individual capture */
+ fun clearImageTimers() {
+ captureStart = 0L
+ captureEnd = 0L
+ autofocusStart = 0L
+ autofocusEnd = 0L
+ imageReaderStart = 0L
+ imageReaderEnd = 0L
+ imageSaveStart = 0L
+ imageSaveEnd = 0L
+ isHDRPlus = false
+ }
+} \ No newline at end of file
diff --git a/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/CameraUtils.kt b/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/CameraUtils.kt
new file mode 100644
index 00000000000..19d3b130e68
--- /dev/null
+++ b/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/CameraUtils.kt
@@ -0,0 +1,281 @@
+/*
+ * Copyright 2019 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.antelope
+
+import android.graphics.ImageFormat
+import android.graphics.Rect
+import android.hardware.camera2.CameraAccessException
+import android.hardware.camera2.CameraCharacteristics
+import android.hardware.camera2.CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE
+import android.hardware.camera2.CameraManager
+import android.hardware.camera2.CameraMetadata
+import android.hardware.camera2.CaptureRequest
+import android.media.ImageReader
+import android.os.Build
+import android.util.SparseIntArray
+import android.view.Surface
+import androidx.appcompat.app.AppCompatActivity
+import androidx.camera.core.CameraX
+import androidx.camera.core.ImageCaptureConfig
+import androidx.camera.core.PreviewConfig
+import androidx.camera.integration.antelope.MainActivity.Companion.FIXED_FOCUS_DISTANCE
+import androidx.camera.integration.antelope.MainActivity.Companion.cameraParams
+import androidx.camera.integration.antelope.MainActivity.Companion.logd
+import androidx.camera.integration.antelope.cameracontrollers.Camera2CaptureSessionCallback
+import androidx.camera.integration.antelope.cameracontrollers.Camera2DeviceStateCallback
+import kotlinx.android.synthetic.main.activity_main.surface_preview
+import kotlinx.android.synthetic.main.activity_main.texture_preview
+import java.util.Arrays
+import java.util.Collections
+
+/** The camera API to use */
+enum class CameraAPI(private val api: String) {
+ /** Camera 1 API */
+ CAMERA1("Camera1"),
+ /** Camera 2 API */
+ CAMERA2("Camera2"),
+ /** Camera X API */
+ CAMERAX("CameraX")
+}
+
+/** The output capture size to request for captures */
+enum class ImageCaptureSize(private val size: String) {
+ /** Request captures to be the maximum supported size for this camera sensor */
+ MAX("Max"),
+ /** Request captures to be the minimum supported size for this camera sensor */
+ MIN("Min"),
+}
+
+/** The focus mechanism to request for captures */
+enum class FocusMode(private val mode: String) {
+ /** Auto-focus */
+ AUTO("Auto"),
+ /** Continuous auto-focus */
+ CONTINUOUS("Continuous"),
+ /** For fixed-focus lenses */
+ FIXED("Fixed")
+}
+
+/**
+ * For all the cameras associated with the device, populate the CameraParams values and add them to
+ * the companion object for the activity.
+ */
+fun initializeCameras(activity: MainActivity) {
+ val manager = activity.getSystemService(AppCompatActivity.CAMERA_SERVICE) as CameraManager
+ try {
+ val numCameras = manager.cameraIdList.size
+
+ for (cameraId in manager.cameraIdList) {
+ val tempCameraParams = CameraParams().apply {
+
+ val cameraChars = manager.getCameraCharacteristics(cameraId)
+ val cameraCapabilities =
+ cameraChars.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES)
+
+ // Multi-camera
+ for (capability in cameraCapabilities) {
+ if (capability ==
+ CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA) {
+ hasMulti = true
+ } else if (capability ==
+ CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR) {
+ hasManualControl = true
+ }
+ }
+
+ logd("Camera " + cameraId + " of " + numCameras)
+
+ id = cameraId
+ isOpen = false
+ hasFlash = cameraChars.get(CameraCharacteristics.FLASH_INFO_AVAILABLE) ?: false
+ isFront = CameraCharacteristics.LENS_FACING_FRONT ==
+ cameraChars.get(CameraCharacteristics.LENS_FACING)
+
+ isExternal = (Build.VERSION.SDK_INT >= 23 &&
+ CameraCharacteristics.LENS_FACING_EXTERNAL ==
+ cameraChars.get(CameraCharacteristics.LENS_FACING))
+
+ characteristics = cameraChars
+ focalLengths =
+ cameraChars.get(CameraCharacteristics.LENS_INFO_AVAILABLE_FOCAL_LENGTHS)
+ ?: FloatArray(0)
+ smallestFocalLength = smallestFocalLength(focalLengths)
+ minDeltaFromNormal = focalLengthMinDeltaFromNormal(focalLengths)
+
+ apertures = cameraChars.get(CameraCharacteristics.LENS_INFO_AVAILABLE_APERTURES)
+ ?: FloatArray(0)
+ largestAperture = largestAperture(apertures)
+ minFocusDistance =
+ cameraChars.get(CameraCharacteristics.LENS_INFO_MINIMUM_FOCUS_DISTANCE)
+ ?: MainActivity.FIXED_FOCUS_DISTANCE
+
+ for (focalLength in focalLengths) {
+ logd("In " + id + " found focalLength: " + focalLength)
+ }
+ logd("Smallest smallestFocalLength: " + smallestFocalLength)
+ logd("minFocusDistance: " + minFocusDistance)
+
+ for (aperture in apertures) {
+ logd("In " + id + " found aperture: " + aperture)
+ }
+ logd("Largest aperture: " + largestAperture)
+
+ if (hasManualControl) {
+ logd("Has Manual, minFocusDistance: " + minFocusDistance)
+ }
+
+ // Autofocus
+ hasAF = minFocusDistance != FIXED_FOCUS_DISTANCE // If camera is fixed focus, no AF
+
+ effects = cameraChars.get(CameraCharacteristics.CONTROL_AVAILABLE_EFFECTS)
+ ?: IntArray(0)
+ hasSepia = effects.contains(CameraMetadata.CONTROL_EFFECT_MODE_SEPIA)
+ hasMono = effects.contains(CameraMetadata.CONTROL_EFFECT_MODE_MONO)
+
+ if (hasSepia)
+ logd("WE HAVE Sepia!")
+ if (hasMono)
+ logd("WE HAVE Mono!")
+
+ val activeSensorRect: Rect = cameraChars.get(SENSOR_INFO_ACTIVE_ARRAY_SIZE)
+ megapixels = (activeSensorRect.width() * activeSensorRect.height()) / 1000000
+
+ camera2DeviceStateCallback =
+ Camera2DeviceStateCallback(this, activity, TestConfig())
+ camera2CaptureSessionCallback =
+ Camera2CaptureSessionCallback(activity, this, TestConfig())
+
+ previewSurfaceView = activity.surface_preview
+ cameraXPreviewTexture = activity.texture_preview
+
+ // TODO: As of 0.3.0 CameraX only has front and back cameras. Update in the future
+ val cameraXcameraID = if (id.equals("1"))
+ CameraX.LensFacing.BACK
+ else CameraX.LensFacing.FRONT
+
+ cameraXPreviewConfig = PreviewConfig.Builder()
+ .setLensFacing(cameraXcameraID)
+ .build()
+
+ cameraXCaptureConfig = ImageCaptureConfig.Builder()
+ .setLensFacing(cameraXcameraID)
+ .build()
+
+ imageAvailableListener =
+ ImageAvailableListener(activity, this, TestConfig())
+
+ if (Build.VERSION.SDK_INT >= 28) {
+ physicalCameras = cameraChars.physicalCameraIds
+ }
+
+ // Get Camera2 and CameraX image capture sizes
+ val map =
+ characteristics?.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)
+ if (map != null) {
+ cam2MaxSize = Collections.max(
+ Arrays.asList(*map.getOutputSizes(ImageFormat.JPEG)),
+ CompareSizesByArea())
+ cam2MinSize = Collections.min(
+ Arrays.asList(*map.getOutputSizes(ImageFormat.JPEG)),
+ CompareSizesByArea())
+
+ // Use minimum image size for preview
+ previewSurfaceView?.holder?.setFixedSize(cam2MinSize.width, cam2MinSize.height)
+ }
+
+ setupImageReader(activity, this, TestConfig())
+ }
+
+ cameraParams.put(cameraId, tempCameraParams)
+ } // For all camera devices
+ } catch (accessError: CameraAccessException) {
+ accessError.printStackTrace()
+ }
+}
+
+/**
+ * Convenience method to configure the ImageReaders required for Camera1 and Camera2 APIs.
+ *
+ * Uses JPEG image format, checks the current test configuration to determine the needed size.
+ */
+fun setupImageReader(activity: MainActivity, params: CameraParams, testConfig: TestConfig) {
+
+ // Only use ImageReader for Camera1 and Camera2
+ if (CameraAPI.CAMERAX != testConfig.api) {
+ params.imageAvailableListener = ImageAvailableListener(activity, params, testConfig)
+
+ val useLargest = testConfig.imageCaptureSize == ImageCaptureSize.MAX
+
+ val size = if (useLargest)
+ params.cam2MaxSize
+ else
+ params.cam2MinSize
+
+ params.imageReader?.close()
+ params.imageReader = ImageReader.newInstance(size.width, size.height,
+ ImageFormat.JPEG, 5)
+ params.imageReader?.setOnImageAvailableListener(
+ params.imageAvailableListener, params.backgroundHandler)
+ }
+}
+
+/** Finds the smallest focal length in the given array, useful for finding the widest angle lens */
+fun smallestFocalLength(focalLengths: FloatArray): Float = focalLengths.min()
+ ?: MainActivity.INVALID_FOCAL_LENGTH
+
+/** Finds the largest aperture in the array of focal lengths */
+fun largestAperture(apertures: FloatArray): Float = apertures.max()
+ ?: MainActivity.NO_APERTURE
+
+/** Finds the most "normal" focal length in the array of focal lengths */
+fun focalLengthMinDeltaFromNormal(focalLengths: FloatArray): Float =
+ focalLengths.minBy { Math.abs(it - MainActivity.NORMAL_FOCAL_LENGTH) }
+ ?: Float.MAX_VALUE
+
+/** Adds automatic flash to the given CaptureRequest.Builder */
+fun setAutoFlash(params: CameraParams, requestBuilder: CaptureRequest.Builder?) {
+ try {
+ if (params.hasFlash) {
+ requestBuilder?.set(CaptureRequest.CONTROL_AE_MODE,
+ CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH)
+
+ // Force flash always on
+// requestBuilder?.set(CaptureRequest.CONTROL_AE_MODE,
+// CaptureRequest.CONTROL_AE_MODE_ON_ALWAYS_FLASH)
+ }
+ } catch (e: Exception) {
+ // Do nothing
+ }
+}
+
+/**
+ * We have to take sensor orientation into account and rotate JPEG properly.
+ */
+fun getOrientation(params: CameraParams, rotation: Int): Int {
+ val orientations = SparseIntArray()
+ orientations.append(Surface.ROTATION_0, 90)
+ orientations.append(Surface.ROTATION_90, 0)
+ orientations.append(Surface.ROTATION_180, 270)
+ orientations.append(Surface.ROTATION_270, 180)
+
+ logd("Orientation: sensor: " +
+ params.characteristics?.get(CameraCharacteristics.SENSOR_ORIENTATION) +
+ " and current rotation: " + orientations.get(rotation))
+ val sensorRotation: Int =
+ params.characteristics?.get(CameraCharacteristics.SENSOR_ORIENTATION) ?: 0
+ return (orientations.get(rotation) + sensorRotation + 270) % 360
+}
diff --git a/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/CustomLifecycle.kt b/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/CustomLifecycle.kt
new file mode 100644
index 00000000000..4f3b0d4e9c8
--- /dev/null
+++ b/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/CustomLifecycle.kt
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2019 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.antelope
+
+import android.os.Handler
+import android.os.Looper
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.LifecycleRegistry
+import androidx.camera.integration.antelope.MainActivity.Companion.logd
+
+/**
+ * Camera X normally handles lifecycle events itself. Optimizations in the API make it difficult
+ * to perform a series of clean tests like Antelope does, so it requires its own custom lifecycle.
+ */
+class CustomLifecycle : LifecycleOwner {
+ private var lifecycleRegistry = LifecycleRegistry(this)
+ internal val mainHandler: Handler = Handler(Looper.getMainLooper())
+
+ init {
+ lifecycleRegistry.markState(Lifecycle.State.INITIALIZED)
+ lifecycleRegistry.markState(Lifecycle.State.CREATED)
+ }
+
+ override fun getLifecycle(): Lifecycle {
+ return lifecycleRegistry
+ }
+
+ fun start() {
+ if (Looper.myLooper() != mainHandler.looper) {
+ mainHandler.post { start() }
+ return
+ }
+
+ if (lifecycleRegistry.currentState != Lifecycle.State.CREATED) {
+ logd("CustomLifecycle start error: Prior state should be CREATED. Instead it is: " +
+ lifecycleRegistry.currentState)
+ } else {
+ try {
+ lifecycleRegistry.markState(Lifecycle.State.STARTED)
+ lifecycleRegistry.markState(Lifecycle.State.RESUMED)
+ } catch (e: IllegalArgumentException) {
+ logd("CustomLifecycle start error: unable to start " + e.message)
+ }
+ }
+ }
+
+ fun pauseAndStop() {
+ if (Looper.myLooper() != mainHandler.looper) {
+ mainHandler.post { pauseAndStop() }
+ return
+ }
+
+ if (lifecycleRegistry.currentState != Lifecycle.State.RESUMED) {
+ logd("CustomLifecycle pause error: Prior state should be RESUMED. Instead it is: " +
+ lifecycleRegistry.currentState)
+ } else {
+ try {
+ lifecycleRegistry.markState(Lifecycle.State.STARTED)
+ lifecycleRegistry.markState(Lifecycle.State.CREATED)
+ } catch (e: IllegalArgumentException) {
+ logd("CustomLifecycle pause error: unable to pause " + e.message)
+ }
+ }
+ }
+
+ fun finish() {
+ if (Looper.myLooper() != mainHandler.looper) {
+ mainHandler.post { finish() }
+ return
+ }
+
+ if (lifecycleRegistry.currentState != Lifecycle.State.CREATED) {
+ logd("CustomLifecycle finish error: Prior state should be CREATED. Instead it is: " +
+ lifecycleRegistry.currentState)
+ } else {
+ try {
+ lifecycleRegistry.markState(Lifecycle.State.DESTROYED)
+ } catch (e: IllegalArgumentException) {
+ logd("CustomLifecycle finish error: unable to finish " + e.message)
+ }
+ }
+ }
+
+ fun isFinished(): Boolean {
+ return (Lifecycle.State.DESTROYED == lifecycleRegistry.currentState)
+ }
+} \ No newline at end of file
diff --git a/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/DeviceInfo.kt b/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/DeviceInfo.kt
new file mode 100644
index 00000000000..e34dddcfbba
--- /dev/null
+++ b/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/DeviceInfo.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2019 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.antelope
+
+import androidx.appcompat.app.AppCompatActivity
+
+/**
+ * Convenience class to access device information
+ */
+class DeviceInfo(activity: AppCompatActivity) {
+ /** Detailed string with device and OS information */
+ val device: String = android.os.Build.MANUFACTURER + " " +
+ android.os.Build.BRAND + " " +
+ android.os.Build.DEVICE + " " +
+ android.os.Build.MODEL + " " +
+ android.os.Build.PRODUCT + " " +
+ "(" + android.os.Build.VERSION.RELEASE + android.os.Build.VERSION.INCREMENTAL + ") " +
+ "\nSDK: " + android.os.Build.VERSION.SDK_INT
+
+ /** Short string with device information */
+ val deviceShort: String = android.os.Build.DEVICE
+} \ No newline at end of file
diff --git a/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/ImageUtils.kt b/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/ImageUtils.kt
new file mode 100644
index 00000000000..69b8b896bfa
--- /dev/null
+++ b/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/ImageUtils.kt
@@ -0,0 +1,346 @@
+/*
+ * Copyright 2019 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.antelope
+
+import android.annotation.TargetApi
+import android.app.Activity
+import android.content.Intent
+import android.graphics.Bitmap
+import android.graphics.ImageFormat
+import android.graphics.Matrix
+import android.media.ImageReader
+import android.net.Uri
+import android.os.Build
+import android.os.Environment
+import android.widget.Toast
+import androidx.camera.core.ImageCapture
+import androidx.camera.core.ImageProxy
+import androidx.exifinterface.media.ExifInterface
+import androidx.camera.integration.antelope.MainActivity.Companion.PHOTOS_DIR
+import androidx.camera.integration.antelope.MainActivity.Companion.logd
+import androidx.camera.integration.antelope.cameracontrollers.CameraState
+import androidx.camera.integration.antelope.cameracontrollers.closeCameraX
+import androidx.camera.integration.antelope.cameracontrollers.closePreviewAndCamera
+import java.io.ByteArrayInputStream
+import java.io.File
+import java.io.FileOutputStream
+import java.io.IOException
+import java.text.SimpleDateFormat
+import java.util.Date
+import java.util.Locale
+
+/**
+ * ImageReader listener for use with Camera 2 API.
+ *
+ * Extract image and write to disk
+ */
+class ImageAvailableListener(
+ internal val activity: MainActivity,
+ internal var params: CameraParams,
+ internal val testConfig: TestConfig
+) : ImageReader.OnImageAvailableListener {
+
+ override fun onImageAvailable(reader: ImageReader) {
+ logd("onImageAvailable enter. Current test: " + testConfig.currentRunningTest +
+ " state: " + params.state)
+
+ // Only save 1 photo each time
+ if (CameraState.IMAGE_REQUESTED != params.state)
+ return
+ else
+ params.state = CameraState.UNINITIALIZED
+
+ val image = reader.acquireLatestImage()
+
+ when (image.format) {
+ ImageFormat.JPEG -> {
+ // Orientation
+ val rotation = activity.windowManager.defaultDisplay.rotation
+ val capturedImageRotation = getOrientation(params, rotation)
+
+ params.timer.imageReaderEnd = System.currentTimeMillis()
+ params.timer.imageSaveStart = System.currentTimeMillis()
+
+ val bytes = ByteArray(image.planes[0].buffer.remaining())
+ image.planes[0].buffer.get(bytes)
+
+ params.backgroundHandler?.post(ImageSaver(activity, bytes, capturedImageRotation,
+ params.isFront, params, testConfig))
+ }
+
+ // TODO: add RAW support
+ ImageFormat.RAW_SENSOR -> {
+ }
+
+ else -> {
+ }
+ }
+
+ image.close()
+ }
+}
+
+/**
+ * Asynchronously save ByteArray to disk
+ */
+class ImageSaver internal constructor(
+ private val activity: MainActivity,
+ private val bytes: ByteArray,
+ private val rotation: Int,
+ private val flip: Boolean,
+ private val params: CameraParams,
+ private val testConfig: TestConfig
+) : Runnable {
+
+ override fun run() {
+ logd("ImageSaver. ImageSaver is running, saving image to disk.")
+
+ // TODO: Once Android supports HDR+ detection add this in
+// if (isHDRPlus(bytes))
+// params.timer.isHDRPlus = true;
+
+ writeFile(activity, bytes)
+
+ params.timer.imageSaveEnd = System.currentTimeMillis()
+
+ // The test is over only if the capture call back has already been hit
+ // It is possible to be here before the callback is hit
+ if (0L != params.timer.captureEnd) {
+ if (TestType.MULTI_PHOTO_CHAIN == testConfig.currentRunningTest) {
+ testEnded(activity, params, testConfig)
+ } else {
+ logd("ImageSaver: photo saved, test is finished, closing the camera.")
+ testConfig.testFinished = true
+ closePreviewAndCamera(activity, params, testConfig)
+ }
+ }
+ }
+}
+
+/**
+ * Rotate a given Bitmap by degrees
+ */
+fun rotateBitmap(original: Bitmap, degrees: Float): Bitmap {
+ val matrix = Matrix()
+ matrix.postRotate(degrees)
+ return Bitmap.createBitmap(original, 0, 0, original.width, original.height,
+ matrix, true)
+}
+
+/**
+ * Scale a given Bitmap by scaleFactor
+ */
+fun scaleBitmap(activity: Activity, bitmap: Bitmap, scaleFactor: Float): Bitmap {
+ val scaledWidth = Math.round(bitmap.width * scaleFactor)
+ val scaledHeight = Math.round(bitmap.height * scaleFactor)
+
+ return Bitmap.createScaledBitmap(bitmap, scaledWidth, scaledHeight, true)
+}
+
+/**
+ * Flip a Bitmap horizontal
+ */
+fun horizontalFlip(activity: Activity, bitmap: Bitmap): Bitmap {
+ val matrix = Matrix()
+ matrix.preScale(-1.0f, 1.0f)
+ return Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true)
+}
+
+/**
+ * Generate a timestamp to append to saved filenames.
+ */
+fun generateTimestamp(): String {
+ val sdf = SimpleDateFormat("yyyy_MM_dd_HH_mm_ss_SSS", Locale.US)
+ return sdf.format(Date())
+}
+
+/**
+ * Actually write a byteArray file to disk. Assume the file is a jpg and use that extension
+ */
+fun writeFile(activity: MainActivity, bytes: ByteArray) {
+ val rawFile = File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM),
+ "Antelope" + generateTimestamp() + ".dng")
+
+ val jpgFile = File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM),
+ File.separatorChar + PHOTOS_DIR + File.separatorChar +
+ "Antelope" + generateTimestamp() + ".jpg")
+
+ val photosDir = File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM),
+ PHOTOS_DIR)
+
+ if (!photosDir.exists()) {
+ val createSuccess = photosDir.mkdir()
+ if (!createSuccess) {
+ Toast.makeText(activity, "DCIM/" + PHOTOS_DIR + " creation failed.",
+ Toast.LENGTH_SHORT).show()
+ logd("Photo storage directory DCIM/" + PHOTOS_DIR + " creation failed!!")
+ } else {
+ logd("Photo storage directory DCIM/" + PHOTOS_DIR + " did not exist. Created.")
+ }
+ }
+
+ var output: FileOutputStream? = null
+ try {
+ output = FileOutputStream(jpgFile)
+ output.write(bytes)
+ } catch (e: IOException) {
+ e.printStackTrace()
+ } finally {
+ if (null != output) {
+ try {
+ output.close()
+
+ if (!PrefHelper.getAutoDelete(activity)) {
+ // File is written, let media scanner know
+ val scannerIntent = Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE)
+ scannerIntent.data = Uri.fromFile(jpgFile)
+ activity.sendBroadcast(scannerIntent)
+
+ // File is written, now delete it.
+ // TODO: make sure this does not add extra latency
+ } else {
+ jpgFile.delete()
+ }
+ } catch (e: IOException) {
+ e.printStackTrace()
+ }
+ }
+ }
+ logd("writeFile: Completed.")
+}
+
+/**
+ * Delete all the photos generated by testing from the default Antelope PHOTOS_DIR
+ */
+fun deleteTestPhotos(activity: MainActivity) {
+ val photosDir = File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM),
+ PHOTOS_DIR)
+
+ if (photosDir.exists()) {
+
+ for (photo in photosDir.listFiles())
+ photo.delete()
+
+ // Files are deleted, let media scanner know
+ val scannerIntent = Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE)
+ scannerIntent.data = Uri.fromFile(photosDir)
+ activity.sendBroadcast(scannerIntent)
+
+ Toast.makeText(activity, "All test photos deleted", Toast.LENGTH_SHORT).show()
+ logd("All photos in storage directory DCIM/" + PHOTOS_DIR + " deleted.")
+ }
+}
+
+/**
+ * Try to detect if a saved image file has had HDR effects applied to it by examining the EXIF tag.
+ *
+ * Note: this does not currently work.
+ */
+@TargetApi(24)
+fun isHDRPlus(bytes: ByteArray?): Boolean {
+ if (24 <= Build.VERSION.SDK_INT) {
+ val bytestream = ByteArrayInputStream(bytes)
+ val exif = ExifInterface(bytestream)
+ val software: String = exif.getAttribute(ExifInterface.TAG_SOFTWARE) ?: ""
+ val makernote: String = exif.getAttribute(ExifInterface.TAG_MAKER_NOTE) ?: ""
+ logd("In isHDRPlus, software: " + software + ", makernote: " + makernote)
+ if (software.contains("HDR+") || makernote.contains("HDRP")) {
+ logd("Photo is HDR+: " + software + ", " + makernote)
+ return true
+ }
+ }
+ return false
+}
+
+/**
+ * ImageReader listener for use with Camera X API.
+ *
+ * Extract image and write to disk
+ */
+class CameraXImageAvailableListener(
+ internal val activity: MainActivity,
+ internal var params: CameraParams,
+ internal val testConfig: TestConfig
+) : ImageCapture.OnImageCapturedListener() {
+
+ /** Image was captured successfully */
+ override fun onCaptureSuccess(image: ImageProxy?, rotationDegrees: Int) {
+ logd("CameraXImageAvailableListener onCaptureSuccess. Current test: " +
+ testConfig.currentRunningTest)
+
+ when (image?.format) {
+ ImageFormat.JPEG -> {
+ params.timer.imageReaderEnd = System.currentTimeMillis()
+
+ // TODO As of CameraX 0.3.0 has a bug so capture session callbacks are never called.
+ // As a workaround for now we use this onCaptureSuccess callback as the only measure
+ // of capture timing (as opposed to capture callback + image ready for camera2
+ // Remove these lines when bug is fixed
+ // /////////////////////////////////////////////////////////////////////////////////
+ params.timer.captureEnd = System.currentTimeMillis()
+
+ params.timer.imageReaderStart = System.currentTimeMillis()
+ params.timer.imageReaderEnd = System.currentTimeMillis()
+
+ // End Remove lines ////////////////////////////////////////////////////////////////
+
+ // Orientation
+ val rotation = activity.windowManager.defaultDisplay.rotation
+ val capturedImageRotation = getOrientation(params, rotation)
+
+ params.timer.imageSaveStart = System.currentTimeMillis()
+
+ val bytes = ByteArray(image.planes[0].buffer.remaining())
+ image.planes[0].buffer.get(bytes)
+
+ params.backgroundHandler?.post(ImageSaver(activity, bytes, capturedImageRotation,
+ params.isFront, params, testConfig))
+ }
+
+ ImageFormat.RAW_SENSOR -> {
+ }
+
+ else -> {
+ }
+ }
+
+ image?.close()
+ }
+
+ /** Camera X was unable to capture a still image and threw an error */
+ override fun onError(
+ useCaseError: ImageCapture.UseCaseError?,
+ message: String?,
+ cause: Throwable?
+ ) {
+ logd("CameraX ImageCallback onError. Error: " + message)
+ params.timer.imageReaderEnd = System.currentTimeMillis()
+ params.timer.imageSaveStart = System.currentTimeMillis()
+ params.timer.imageSaveEnd = System.currentTimeMillis()
+
+ // The test is over only if the capture call back has already been hit
+ // It is possible to be here before the callback is hit
+ if (0L != params.timer.captureEnd) {
+ if (TestType.MULTI_PHOTO_CHAIN == testConfig.currentRunningTest) {
+ testEnded(activity, params, testConfig)
+ } else {
+ testConfig.testFinished = true
+ closeCameraX(activity, params, testConfig)
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/MainActivity.kt b/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/MainActivity.kt
new file mode 100644
index 00000000000..3d3d351fb67
--- /dev/null
+++ b/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/MainActivity.kt
@@ -0,0 +1,465 @@
+/*
+ * Copyright 2019 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.antelope
+
+import android.Manifest
+import android.content.ClipData
+import android.content.Context
+import android.content.pm.ActivityInfo
+import android.content.pm.PackageManager
+import android.content.res.Configuration
+import android.os.Bundle
+import android.os.Handler
+import android.os.HandlerThread
+import android.util.Log
+import android.view.Menu
+import android.view.MenuInflater
+import android.view.MenuItem
+import android.view.View
+import android.view.WindowManager
+import android.widget.Toast
+import androidx.appcompat.app.AppCompatActivity
+import androidx.lifecycle.Observer
+import androidx.lifecycle.ViewModelProviders
+import androidx.camera.integration.antelope.cameracontrollers.camera2Abort
+import androidx.camera.integration.antelope.cameracontrollers.cameraXAbort
+import androidx.camera.integration.antelope.cameracontrollers.closeAllCameras
+import androidx.core.app.ActivityCompat
+import androidx.core.content.ContextCompat
+import kotlinx.android.synthetic.main.activity_main.button_abort
+import kotlinx.android.synthetic.main.activity_main.button_multi
+import kotlinx.android.synthetic.main.activity_main.button_single
+import kotlinx.android.synthetic.main.activity_main.progress_test
+import kotlinx.android.synthetic.main.activity_main.scroll_log
+import kotlinx.android.synthetic.main.activity_main.surface_preview
+import kotlinx.android.synthetic.main.activity_main.text_log
+import kotlinx.android.synthetic.main.activity_main.texture_preview
+
+private const val REQUEST_CAMERA_PERMISSION = 1
+private const val REQUEST_FILE_WRITE_PERMISSION = 2
+
+/**
+ * Main Antelope Activity
+ */
+class MainActivity : AppCompatActivity() {
+
+ companion object {
+ /** Directory to save image files under sdcard/DCIM */
+ const val PHOTOS_DIR: String = "Antelope"
+ /** Directory to save .csv log files to under sdcard/Documents */
+ const val LOG_DIR: String = "Antelope"
+ /** Tag to include when using the logd function */
+ val LOG_TAG = "Antelope"
+
+ /** Define "normal" focal length as 50.0mm */
+ const val NORMAL_FOCAL_LENGTH: Float = 50f
+ /** No aperture reference */
+ const val NO_APERTURE: Float = 0f
+ /** Fixed-focus lenses have a value of 0 */
+ const val FIXED_FOCUS_DISTANCE: Float = 0f
+ /** Constant for invalid focal length */
+ val INVALID_FOCAL_LENGTH: Float = Float.MAX_VALUE
+ /** For single tests, percentage completion to show in progress bar when test is running */
+ const val PROGRESS_SINGLE_PERCENTAGE = 25
+
+ /** List of test results for current test run */
+ internal val testRun: ArrayList<TestResults> = ArrayList<TestResults>()
+ /** List of test configurations for a multiple test run */
+ internal val autoTestConfigs: ArrayList<TestConfig> = ArrayList()
+
+ /** Flag if a single test is running */
+ var isSingleTestRunning = false
+ /** Number of test remaining in a multiple test run */
+ var testsRemaining = 0
+
+ /** View model that contains state data for the application */
+ lateinit var camViewModel: CamViewModel
+
+ /** Hashmap of CameraParams for all cameras on the device */
+ lateinit var cameraParams: HashMap<String, CameraParams>
+ /** Convenience access to device information, OS build, etc. */
+ lateinit var deviceInfo: DeviceInfo
+
+ /** Array of human-readable information for each camera on this device */
+ val cameras: ArrayList<String> = ArrayList<String>()
+ /** Array of camera ids for this device */
+ val cameraIds: ArrayList<String> = ArrayList<String>()
+
+ /** Convenience wrapper for Log.d that can be toggled on/off */
+ fun logd(message: String) {
+ if (camViewModel.getShouldOutputLog().value ?: false)
+ Log.d(LOG_TAG, message)
+ }
+ }
+
+ /**
+ * Check camera permissions and set up UI
+ */
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_main)
+
+ camViewModel = ViewModelProviders.of(this).get(CamViewModel::class.java)
+ cameraParams = camViewModel.getCameraParams()
+ deviceInfo = DeviceInfo(this)
+
+ if (checkCameraPermissions()) {
+ initializeCameras(this)
+ setupCameraNames()
+ }
+
+ button_single.setOnClickListener {
+ val testDiag = SettingsDialog.newInstance(SettingsDialog.DIALOG_TYPE_SINGLE,
+ getString(R.string.settings_single_test_dialog_title),
+ cameras.toTypedArray(), cameraIds.toTypedArray())
+ testDiag.show(supportFragmentManager, SettingsDialog.DIALOG_TYPE_SINGLE)
+ }
+
+ button_multi.setOnClickListener {
+ val testDiag = SettingsDialog.newInstance(SettingsDialog.DIALOG_TYPE_MULTI,
+ getString(R.string.settings_multi_test_dialog_title),
+ cameras.toTypedArray(), cameraIds.toTypedArray())
+ testDiag.show(supportFragmentManager, SettingsDialog.DIALOG_TYPE_MULTI)
+ }
+
+ button_abort.setOnClickListener {
+ abortTests()
+ testsRemaining = 0
+ multiCounter = 0
+ toggleControls(true)
+ toggleRotationLock(false)
+ window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
+ setProgress(0)
+ showProgressBar(false)
+ updateLog("\nABORTED", true)
+ }
+
+ // Human readable report
+ val humanReadableReportObserver = object : Observer<String> {
+ override fun onChanged(newReport: String?) {
+ text_log.text = newReport ?: ""
+ }
+ }
+ camViewModel.getHumanReadableReport().observe(this, humanReadableReportObserver)
+ }
+
+ /**
+ * Set up options menu to allow debug logging and clearing cache'd data
+ */
+ override fun onCreateOptionsMenu(menu: Menu): Boolean {
+ val inflater: MenuInflater = menuInflater
+ inflater.inflate(R.menu.main_menu, menu)
+ if (camViewModel.getShouldOutputLog().value != null)
+ menu.getItem(0).isChecked = camViewModel.getShouldOutputLog().value!!
+ return true
+ }
+
+ /**
+ * Handle menu presses
+ */
+ override fun onOptionsItemSelected(item: MenuItem): Boolean {
+ return when (item.itemId) {
+ R.id.menu_logcat -> {
+ item.isChecked = !item.isChecked
+ camViewModel.getShouldOutputLog().value = item.isChecked
+ true
+ }
+ R.id.menu_delete_photos -> {
+ deleteTestPhotos(this)
+ true
+ }
+ R.id.menu_delete_logs -> {
+ deleteCSVFiles(this)
+ true
+ }
+ else -> super.onOptionsItemSelected(item)
+ }
+ }
+
+ /** Update the main scrollview text
+ *
+ * @param log The new text
+ * @param append Whether to append the new text or to replace the old
+ * @param copyToClipboard Whether or not to copy the text to the system clipboard
+ */
+ fun updateLog(log: String, append: Boolean = false, copyToClipboard: Boolean = true) {
+ runOnUiThread {
+ if (append)
+ camViewModel.getHumanReadableReport().value =
+ camViewModel.getHumanReadableReport().value + log
+ else
+ camViewModel.getHumanReadableReport().value = log
+ }
+
+ if (copyToClipboard) {
+ runOnUiThread {
+ // Copy to clipboard
+ val clipboard = getSystemService(Context.CLIPBOARD_SERVICE)
+ as android.content.ClipboardManager
+ val clip = ClipData.newPlainText("Log", log)
+ clipboard.primaryClip = clip
+ Toast.makeText(this, getString(R.string.log_copied),
+ Toast.LENGTH_SHORT).show()
+ }
+ }
+ }
+
+ /**
+ * Create human readable names for the camera devices
+ */
+ private fun setupCameraNames() {
+ cameras.clear()
+ cameraIds.clear()
+ for (param in cameraParams) {
+ var camera = ""
+
+ camera += param.value.id
+ cameraIds += param.value.id
+
+ if (param.value.isFront)
+ camera += " (Front)"
+ else if (param.value.isExternal)
+ camera += " (External)"
+ else
+ camera += " (Back)"
+
+ camera += " " + param.value.megapixels + "MP"
+
+ if (!param.value.hasAF)
+ camera += " fixed-focus"
+
+ camera += " (min FL: " + param.value.smallestFocalLength + "mm)"
+ cameras.add(camera)
+ }
+ }
+
+ /**
+ * Act on the result of a permissions request. If permission granted simply restart the activity
+ */
+ override fun onRequestPermissionsResult(
+ requestCode: Int,
+ permissions: Array<String>,
+ grantResults: IntArray
+ ) {
+
+ when (requestCode) {
+ REQUEST_CAMERA_PERMISSION -> {
+ if (grantResults.size > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
+ // We now have permission, restart the app
+ val intent = this.intent
+ finish()
+ startActivity(intent)
+ } else {
+ }
+ return
+ }
+ REQUEST_FILE_WRITE_PERMISSION -> {
+ // If request is cancelled, the result arrays are empty.
+ if (grantResults.size > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
+ // We now have permission, restart the app
+ val intent = this.intent
+ finish()
+ startActivity(intent)
+ } else {
+ }
+ return
+ }
+ }
+ }
+
+ /**
+ * Check if we have been granted the need camera and file-system permissions
+ */
+ fun checkCameraPermissions(): Boolean {
+ if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
+ !== PackageManager.PERMISSION_GRANTED) {
+
+ // No explanation needed; request the permission
+ ActivityCompat.requestPermissions(this,
+ arrayOf(Manifest.permission.CAMERA),
+ REQUEST_CAMERA_PERMISSION)
+ return false
+ } else if (ContextCompat.checkSelfPermission(this,
+ Manifest.permission.WRITE_EXTERNAL_STORAGE)
+ !== PackageManager.PERMISSION_GRANTED) {
+ // No explanation needed; request the permission
+ ActivityCompat.requestPermissions(this,
+ arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE),
+ REQUEST_FILE_WRITE_PERMISSION)
+ return false
+ }
+
+ return true
+ }
+
+ /** Start the background threads associated with the given camera device/params */
+ fun startBackgroundThread(params: CameraParams) {
+ if (params.backgroundThread == null) {
+ params.backgroundThread = HandlerThread(LOG_TAG).apply {
+ this.start()
+ params.backgroundHandler = Handler(this.looper)
+ }
+ }
+ }
+
+ /** Stop the background threads associated with the given camera device/params */
+ fun stopBackgroundThread(params: CameraParams) {
+ params.backgroundThread?.quitSafely()
+ try {
+ params.backgroundThread?.join()
+ params.backgroundThread = null
+ params.backgroundHandler = null
+ } catch (e: InterruptedException) {
+ logd("Interrupted while shutting background thread down: " + e)
+ }
+ }
+
+ /** Resume all background threads associated with any given camera devices/params */
+ override fun onResume() {
+ super.onResume()
+ for (tempCameraParams in cameraParams) {
+ startBackgroundThread(tempCameraParams.value)
+ }
+ }
+
+ /** Pause all background threads associated with any camera devices/params */
+ override fun onPause() {
+ for (tempCameraParams in cameraParams) {
+ stopBackgroundThread(tempCameraParams.value)
+ }
+ super.onPause()
+ }
+
+ /** Show/hide the progress bar during a test */
+ fun showProgressBar(visible: Boolean = true, percentage: Int = PROGRESS_SINGLE_PERCENTAGE) {
+ runOnUiThread {
+ if (visible) {
+ progress_test.progress = percentage
+ progress_test.visibility = View.VISIBLE
+ } else {
+ progress_test.progress = 0
+ progress_test.visibility = View.INVISIBLE
+ }
+ }
+ }
+
+ /** Enable/disable controls during a test run */
+ fun toggleControls(enabled: Boolean = true) {
+ runOnUiThread {
+ button_multi.isEnabled = enabled
+ button_single.isEnabled = enabled
+ button_single.isEnabled = enabled
+ button_abort.isEnabled = !enabled // note: inverse of others
+ }
+ }
+
+ /** Lock orientation during a test so the camera doesn't get re-initialized mid-capture */
+ fun toggleRotationLock(lockRotation: Boolean = true) {
+ if (lockRotation) {
+ val currentOrientation = resources.configuration.orientation
+ if (currentOrientation == Configuration.ORIENTATION_LANDSCAPE) {
+ requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE
+ } else {
+ requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT
+ }
+ } else {
+ requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_USER
+ }
+ }
+
+ /** Launch a single test based on the current configuration */
+ fun startSingleTest() {
+ testRun.clear()
+ testsRemaining = 1
+ isSingleTestRunning = true
+
+ val config = createSingleTestConfig(this)
+ setupUIForTest(config, false)
+
+ initializeTest(this, cameraParams.get(config.camera), config)
+ }
+
+ /** Launch a series of tests based on the current configuration */
+ fun startMultiTest() {
+ isSingleTestRunning = false
+ setupAutoTestRunner(this)
+ autoTestRunner(this)
+ }
+
+ /** After tests are completed, reset the UI to the initial state */
+ fun resetUIAfterTest() {
+ runOnUiThread {
+ toggleControls(true)
+ toggleRotationLock(false)
+ showProgressBar(false)
+ window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
+ }
+ }
+
+ /**
+ * Prepare the main UI for a test run. This includes showing/hiding the appropriate preview
+ * surface depending on if the test is Camera 1/2/X
+ */
+ internal fun setupUIForTest(testConfig: TestConfig, append: Boolean = true) {
+ with(testConfig) {
+ MainActivity.camViewModel.getCurrentAPI().postValue(this.api)
+ MainActivity.camViewModel.getCurrentImageCaptureSize().postValue(imageCaptureSize)
+ MainActivity.camViewModel.getCurrentCamera().postValue(camera.toInt())
+
+ if (FocusMode.FIXED == focusMode)
+ MainActivity.camViewModel.getCurrentFocusMode().postValue(FocusMode.AUTO)
+ else
+ MainActivity.camViewModel.getCurrentFocusMode().postValue(focusMode)
+
+ if (CameraAPI.CAMERAX == api) {
+ surface_preview.visibility = View.INVISIBLE
+ texture_preview.visibility = View.VISIBLE
+ } else {
+ surface_preview.visibility = View.VISIBLE
+ texture_preview.visibility = View.INVISIBLE
+ }
+
+ toggleControls(false)
+ toggleRotationLock(true)
+ updateLog("Running: " + testName + "\n", append, false)
+ scroll_log.fullScroll(View.FOCUS_DOWN)
+ window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
+ }
+ }
+
+ /**
+ * User has requested to abort the test. Close cameras and reset the UI.
+ */
+ fun abortTests() {
+ val currentConfig: TestConfig = createTestConfig("ABORT")
+
+ val currentCamera = camViewModel.getCurrentCamera().value ?: 0
+ val currentParams = cameraParams.get(currentCamera.toString())
+
+ when (currentConfig.api) {
+ CameraAPI.CAMERA1 -> closeAllCameras(this, currentConfig)
+ CameraAPI.CAMERAX -> {
+ if (null != currentParams)
+ cameraXAbort(this, currentParams, currentConfig)
+ }
+ CameraAPI.CAMERA2 -> {
+ if (null != currentParams)
+ camera2Abort(this, currentParams, currentConfig)
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/MeasureUtils.kt b/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/MeasureUtils.kt
new file mode 100644
index 00000000000..a85901e287b
--- /dev/null
+++ b/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/MeasureUtils.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2019 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.antelope
+
+import android.util.Log
+import android.util.Size
+import java.util.Collections
+import kotlin.collections.ArrayList
+
+/**
+ * Compares two `Size`s based on their areas.
+ */
+internal class CompareSizesByArea : Comparator<Size> {
+ override fun compare(lhs: Size, rhs: Size): Int {
+ // We cast here to ensure the multiplications won't overflow
+ return java.lang.Long.signum(lhs.width.toLong() * lhs.height -
+ rhs.width.toLong() * rhs.height)
+ }
+}
+
+/**
+ * Given `choices` of `Size`s supported by a camera, chooses the smallest one whose
+ * width and height are at least as large as the respective requested values.
+ * @param choices The list of sizes that the camera supports for the intended output class
+ * @param width The minimum desired width
+ * @param height The minimum desired height
+ * @return The optimal `Size`, or an arbitrary one if none were big enough
+ */
+internal fun chooseBigEnoughSize(choices: Array<Size>, width: Int, height: Int): Size {
+ // Collect the supported resolutions that are at least as big as the preview Surface
+ val bigEnough = ArrayList<Size>()
+ for (option in choices) {
+ if (option.width >= width && option.height >= height) {
+ bigEnough.add(option)
+ }
+ }
+ // Pick the smallest of those, assuming we found any
+ if (bigEnough.size > 0) {
+ return Collections.min(bigEnough, CompareSizesByArea())
+ } else {
+ Log.e(MainActivity.LOG_TAG, "Couldn't find any suitable preview size")
+ return choices[0]
+ }
+} \ No newline at end of file
diff --git a/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/MultiTestSettingsFragment.kt b/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/MultiTestSettingsFragment.kt
new file mode 100644
index 00000000000..b9b41665ff9
--- /dev/null
+++ b/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/MultiTestSettingsFragment.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2019 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.antelope
+
+import android.content.SharedPreferences
+import android.os.Bundle
+import androidx.preference.PreferenceFragmentCompat
+
+/**
+ * Fragment that shows the settings for the "Multiple tests" option
+ */
+class MultiTestSettingsFragment
+ : PreferenceFragmentCompat(), SharedPreferences.OnSharedPreferenceChangeListener {
+
+ override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
+ setPreferencesFromResource(R.xml.multi_test_settings, rootKey)
+ }
+
+ override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
+ }
+
+ override fun onResume() {
+ super.onResume()
+ preferenceManager.sharedPreferences.registerOnSharedPreferenceChangeListener(this)
+ }
+
+ override fun onPause() {
+ super.onPause()
+ preferenceManager.sharedPreferences.unregisterOnSharedPreferenceChangeListener(this)
+ }
+}
diff --git a/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/PrefHelper.kt b/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/PrefHelper.kt
new file mode 100644
index 00000000000..533b8704f1b
--- /dev/null
+++ b/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/PrefHelper.kt
@@ -0,0 +1,286 @@
+/*
+ * Copyright 2019 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.antelope
+
+import android.content.SharedPreferences
+import android.os.Build
+import android.preference.PreferenceManager
+import java.util.Collections
+import kotlin.collections.ArrayList
+import kotlin.collections.HashMap
+import kotlin.collections.HashSet
+import kotlin.collections.asList
+import kotlin.collections.iterator
+
+/**
+ * Convenience class to simplify getting values for Shared Preferences
+ */
+class PrefHelper {
+ companion object {
+ internal fun getAutoDelete(activity: MainActivity): Boolean {
+ var sharedPref: SharedPreferences =
+ PreferenceManager.getDefaultSharedPreferences(activity)
+ return sharedPref.getBoolean(activity.getString(R.string.settings_autodelete_key), true)
+ }
+
+ internal fun getNumTests(activity: MainActivity): Int {
+ var sharedPref: SharedPreferences =
+ PreferenceManager.getDefaultSharedPreferences(activity)
+ return sharedPref.getString(activity
+ .getString(R.string.settings_numtests_key), "30").toInt()
+ }
+
+ internal fun getPreviewBuffer(activity: MainActivity): Long {
+ var sharedPref: SharedPreferences =
+ PreferenceManager.getDefaultSharedPreferences(activity)
+ return sharedPref.getString(activity.getString(R.string.settings_previewbuffer_key),
+ "1500").toLong()
+ }
+
+ internal fun getAPIs(activity: MainActivity): ArrayList<CameraAPI> {
+ var sharedPref: SharedPreferences =
+ PreferenceManager.getDefaultSharedPreferences(activity)
+ val defApis: HashSet<String> =
+ HashSet(activity.resources.getStringArray(R.array.array_settings_api).asList())
+
+ val apiStrings: HashSet<String> =
+ sharedPref.getStringSet(activity.getString(R.string.settings_autotest_api_key),
+ defApis) as HashSet<String>
+
+ val apis: ArrayList<CameraAPI> = ArrayList()
+
+ for (apiString in apiStrings) {
+ when (apiString) {
+ "Camera1" -> apis.add(CameraAPI.CAMERA1)
+ "Camera2" -> apis.add(CameraAPI.CAMERA2)
+ "CameraX" -> apis.add(CameraAPI.CAMERAX)
+ }
+ }
+
+ Collections.sort(apis, ApiComparator())
+ return apis
+ }
+
+ internal fun getImageSizes(activity: MainActivity): ArrayList<ImageCaptureSize> {
+ var sharedPref: SharedPreferences =
+ PreferenceManager.getDefaultSharedPreferences(activity)
+ val defSizes: HashSet<String> = HashSet(activity.resources
+ .getStringArray(R.array.array_settings_imagesize).asList())
+
+ val sizeStrings: HashSet<String> =
+ sharedPref.getStringSet(activity
+ .getString(R.string.settings_autotest_imagesize_key), defSizes)
+ as HashSet<String>
+
+ val sizes: ArrayList<ImageCaptureSize> = ArrayList()
+
+ for (sizeString in sizeStrings) {
+ when (sizeString) {
+ "Min" -> sizes.add(ImageCaptureSize.MIN)
+ "Max" -> sizes.add(ImageCaptureSize.MAX)
+ }
+ }
+
+ Collections.sort(sizes, ImageSizeComparator())
+ return sizes
+ }
+
+ internal fun getFocusModes(activity: MainActivity): ArrayList<FocusMode> {
+ var sharedPref: SharedPreferences =
+ PreferenceManager.getDefaultSharedPreferences(activity)
+ val defModes: HashSet<String> =
+ HashSet(activity.resources.getStringArray(R.array.array_settings_focus).asList())
+
+ val modeStrings: HashSet<String> =
+ sharedPref.getStringSet(activity.getString(R.string.settings_autotest_focus_key),
+ defModes) as HashSet<String>
+
+ val modes: ArrayList<FocusMode> = ArrayList()
+
+ for (modeString in modeStrings) {
+ when (modeString) {
+ "Auto" -> modes.add(FocusMode.AUTO)
+ "Continuous" -> modes.add(FocusMode.CONTINUOUS)
+ "Fixed" -> modes.add(FocusMode.FIXED)
+ }
+ }
+
+ Collections.sort(modes, FocusModeComparator())
+ return modes
+ }
+
+ internal fun getOnlyLogical(activity: MainActivity): Boolean {
+ var sharedPref: SharedPreferences =
+ PreferenceManager.getDefaultSharedPreferences(activity)
+ return sharedPref.getBoolean(activity
+ .getString(R.string.settings_autotest_cameras_key), true)
+ }
+
+ internal fun getSwitchTest(activity: MainActivity): Boolean {
+ var sharedPref: SharedPreferences =
+ PreferenceManager.getDefaultSharedPreferences(activity)
+ return sharedPref
+ .getBoolean(activity.getString(R.string.settings_autotest_switchtest_key), true)
+ }
+
+ internal fun getSingleTestType(activity: MainActivity): String {
+ val sharedPref: SharedPreferences =
+ PreferenceManager.getDefaultSharedPreferences(activity)
+ return sharedPref
+ .getString(activity.getString(R.string.settings_single_test_type_key), "PHOTO")
+ }
+
+ internal fun getSingleTestFocus(activity: MainActivity): String {
+ val sharedPref: SharedPreferences =
+ PreferenceManager.getDefaultSharedPreferences(activity)
+ return sharedPref
+ .getString(activity.getString(R.string.settings_single_test_focus_key), "Auto")
+ }
+
+ internal fun getSingleTestImageSize(activity: MainActivity): String {
+ val sharedPref: SharedPreferences =
+ PreferenceManager.getDefaultSharedPreferences(activity)
+ return sharedPref
+ .getString(activity.getString(R.string.settings_single_test_imagesize_key), "Max")
+ }
+
+ internal fun getSingleTestCamera(activity: MainActivity): String {
+ val sharedPref: SharedPreferences =
+ PreferenceManager.getDefaultSharedPreferences(activity)
+ return sharedPref
+ .getString(activity.getString(R.string.settings_single_test_camera_key), "0")
+ }
+
+ internal fun getSingleTestApi(activity: MainActivity): String {
+ val sharedPref: SharedPreferences =
+ PreferenceManager.getDefaultSharedPreferences(activity)
+ return sharedPref
+ .getString(activity.getString(R.string.settings_single_test_api_key), "Camera2")
+ }
+
+ internal fun getCameraIds(
+ activity: MainActivity,
+ cameraParams: HashMap<String, CameraParams>
+ ): ArrayList<String> {
+
+ val cameraIds: ArrayList<String> = ArrayList()
+ val onlyLogical: Boolean = getOnlyLogical(activity)
+
+ // First we add all the cameras, then we either remove the logical ones or physical ones
+ for (params in cameraParams)
+ cameraIds.add(params.value.id)
+
+ // Before 28, no physical camera access
+ if (Build.VERSION.SDK_INT < 28)
+ return cameraIds
+
+ // For logical cameras, we remove all physical camera ids
+ if (onlyLogical) {
+ for (params in cameraParams) {
+ for (physicalId in params.value.physicalCameras)
+ cameraIds.remove(physicalId)
+ }
+
+ // For only physical, we check if it is backed by physical cameras, if so, remove it
+ // as the physical cameras should already be in the list
+ } else {
+ for (params in cameraParams) {
+ if (params.value.hasMulti)
+ cameraIds.remove(params.value.id)
+ }
+ }
+
+ Collections.sort(cameraIds)
+ return cameraIds
+ }
+
+ internal fun getLogicalCameraIds(
+ activity: MainActivity,
+ cameraParams: HashMap<String, CameraParams>
+ ): ArrayList<String> {
+
+ val cameraIds: ArrayList<String> = ArrayList()
+
+ // First we add all the cameras, then we either remove the logical ones or physical ones
+ for (params in cameraParams) {
+ cameraIds.add(params.value.id)
+ }
+ // Before 28, no physical camera access
+ if (Build.VERSION.SDK_INT < 28)
+ return cameraIds
+
+ // For logical cameras, we remove all physical camera ids
+ for (params in cameraParams) {
+ for (physicalId in params.value.physicalCameras) {
+ cameraIds.remove(physicalId)
+ }
+ }
+
+ Collections.sort(cameraIds)
+ return cameraIds
+ }
+ }
+
+ // Order: Camera2, CameraX, Camera1
+ internal class ApiComparator : Comparator<CameraAPI> {
+ override fun compare(api1: CameraAPI, api2: CameraAPI): Int {
+ if (api1 == api2)
+ return 0
+ if (api1 == CameraAPI.CAMERA2)
+ return -1
+ if (api2 == CameraAPI.CAMERA2)
+ return 1
+ if (api1 == CameraAPI.CAMERAX)
+ return -1
+ if (api2 == CameraAPI.CAMERAX)
+ return 1
+
+ // This should never happen
+ return -1
+ }
+ }
+
+ // Order: Max, Min
+ internal class ImageSizeComparator : Comparator<ImageCaptureSize> {
+ override fun compare(size1: ImageCaptureSize, size2: ImageCaptureSize): Int {
+ if (size1 == size2)
+ return 0
+ if (size1 == ImageCaptureSize.MAX)
+ return -1
+ if (size2 == ImageCaptureSize.MAX)
+ return 1
+
+ // This should never happen
+ return -1
+ }
+ }
+
+ // Order: Auto, Continuous
+ internal class FocusModeComparator : Comparator<FocusMode> {
+ override fun compare(mode1: FocusMode, mode2: FocusMode): Int {
+ if (mode1 == mode2)
+ return 0
+ if (mode1 == FocusMode.AUTO)
+ return -1
+ if (mode2 == FocusMode.AUTO)
+ return 1
+
+ // This should never happen
+ return -1
+ }
+ }
+} \ No newline at end of file
diff --git a/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/SettingsDialog.kt b/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/SettingsDialog.kt
new file mode 100644
index 00000000000..b7da33951e8
--- /dev/null
+++ b/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/SettingsDialog.kt
@@ -0,0 +1,151 @@
+/*
+ * Copyright 2019 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.antelope
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.fragment.app.DialogFragment
+import kotlinx.android.synthetic.main.settings_dialog.button_cancel
+import kotlinx.android.synthetic.main.settings_dialog.button_start
+
+/**
+ * DialogFragment that backs the configuration for both single tests and multiple tests
+ */
+internal class SettingsDialog : DialogFragment() {
+
+ override fun onStart() {
+ // If we show a dialog with a title, it doesn't take up the whole screen
+ // Adjust the window to take up the full screen
+ dialog?.window?.setLayout(ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT)
+ super.onStart()
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ // Set the dialog style so we get a title bar
+ setStyle(DialogFragment.STYLE_NORMAL, R.style.SettingsDialogTheme)
+ }
+
+ /** Set up the dialog depending on the dialog type */
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ val args = arguments
+ val type = args?.getString(DIALOG_TYPE)
+ val title = args?.getString(DIALOG_TITLE)
+ val cameraNames = args?.getStringArray(CAMERA_NAMES)
+ val cameraIds = args?.getStringArray(CAMERA_IDS)
+
+ dialog?.setTitle(title)
+
+ val dialogView = inflater.inflate(R.layout.settings_dialog, container, false)
+
+ if (null != cameraIds && null != cameraNames) {
+ when (type) {
+ DIALOG_TYPE_MULTI -> {
+ val settingsFragment = MultiTestSettingsFragment()
+ val childFragmentManager = childFragmentManager
+ val fragmentTransaction = childFragmentManager.beginTransaction()
+ fragmentTransaction.replace(R.id.scroll_settings_dialog, settingsFragment)
+ fragmentTransaction.commit()
+ }
+ else -> {
+ val settingsFragment = SingleTestSettingsFragment(cameraNames, cameraIds)
+ val childFragmentManager = childFragmentManager
+ val fragmentTransaction = childFragmentManager.beginTransaction()
+ fragmentTransaction.replace(R.id.scroll_settings_dialog, settingsFragment)
+ fragmentTransaction.commit()
+ }
+ }
+ }
+
+ return dialogView
+ }
+
+ /** When view is created, set up action buttons */
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+
+ val args = arguments
+ val type = args?.getString(DIALOG_TYPE)
+
+ when (type) {
+ DIALOG_TYPE_MULTI -> {
+ button_start.text = getString(R.string.settings_multi_go)
+ button_cancel.text = getString(R.string.settings_multi_cancel)
+
+ button_start.setOnClickListener {
+ (activity as MainActivity).startMultiTest()
+ this.dismiss()
+ }
+ button_cancel.setOnClickListener { this.dismiss() }
+ }
+ else -> {
+ button_start.text = getString(R.string.settings_single_go)
+ button_cancel.text = getString(R.string.settings_single_cancel)
+
+ button_start.setOnClickListener {
+ (activity as MainActivity).startSingleTest()
+ this.dismiss()
+ }
+ button_cancel.setOnClickListener { this.dismiss() }
+ }
+ }
+ }
+
+ companion object {
+ private const val DIALOG_TYPE = "DIALOG_TYPE"
+ private const val DIALOG_TITLE = "DIALOG_TITLE"
+ private const val CAMERA_NAMES = "CAMERA_NAMES"
+ private const val CAMERA_IDS = "CAMERA_IDS"
+
+ internal const val DIALOG_TYPE_SINGLE = "DIALOG_TYPE_SINGLE"
+ internal const val DIALOG_TYPE_MULTI = "DIALOG_TYPE_MULTI"
+
+ /**
+ * Create a new Settings dialog to configure a test run
+ *
+ * @param type Dialog type (DIALOG_TYPE_MULTI or DIALOG_TYPE_SINGLE)
+ * @param title Dialog title
+ * @param cameraNames Human readable array of camera names
+ * @param cameraIds Array of camera ids
+ */
+ fun newInstance(
+ type: String,
+ title: String,
+ cameraNames: Array<String>,
+ cameraIds: Array<String>
+ ): SettingsDialog {
+
+ val args = Bundle()
+ args.putString(DIALOG_TYPE, type)
+ args.putString(DIALOG_TITLE, title)
+ args.putStringArray(CAMERA_NAMES, cameraNames)
+ args.putStringArray(CAMERA_IDS, cameraIds)
+
+ val settingsDialog = SettingsDialog()
+ settingsDialog.arguments = args
+ return settingsDialog
+ }
+ }
+} \ No newline at end of file
diff --git a/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/SingleTestSettingsFragment.kt b/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/SingleTestSettingsFragment.kt
new file mode 100644
index 00000000000..6288ffb30ec
--- /dev/null
+++ b/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/SingleTestSettingsFragment.kt
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2019 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.antelope
+
+import android.content.SharedPreferences
+import android.os.Bundle
+import androidx.preference.ListPreference
+import androidx.preference.PreferenceFragmentCompat
+
+/**
+ * Fragment that shows the settings for the "Single test" option
+ */
+class SingleTestSettingsFragment(
+ internal val cameraNames: Array<String>,
+ internal val cameraIds: Array<String>
+) : PreferenceFragmentCompat(), SharedPreferences.OnSharedPreferenceChangeListener {
+
+ override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
+ setPreferencesFromResource(R.xml.single_test_settings, rootKey)
+
+ val cameraPref =
+ preferenceManager.findPreference<ListPreference>(
+ getString(R.string.settings_single_test_camera_key))
+ cameraPref?.entries = cameraNames
+ cameraPref?.entryValues = cameraIds
+ if (cameraIds.isNotEmpty())
+ cameraPref?.setDefaultValue(cameraIds[0])
+
+ if (null == cameraPref?.value)
+ cameraPref?.value = cameraIds[0]
+
+ // En/disable needed controls
+ toggleNumTests()
+ togglePreviewBuffer()
+ }
+
+ override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
+ if (key.equals(getString(R.string.settings_single_test_type_key))) {
+ toggleNumTests()
+ togglePreviewBuffer()
+ }
+ }
+
+ override fun onResume() {
+ super.onResume()
+ preferenceManager.sharedPreferences.registerOnSharedPreferenceChangeListener(this)
+ }
+
+ override fun onPause() {
+ super.onPause()
+ preferenceManager.sharedPreferences.unregisterOnSharedPreferenceChangeListener(this)
+ }
+
+ /** Some tests do not allow for multiple repetitions, If one of these selected,
+ * disable the number of tests control.
+ */
+ fun toggleNumTests() {
+ val typePref =
+ preferenceManager.findPreference<ListPreference>(
+ getString(R.string.settings_single_test_type_key))
+ val numberPref = preferenceManager
+ .findPreference<ListPreference>(getString(R.string.settings_numtests_key))
+ when (typePref?.value) {
+ "INIT", "PREVIEW", "SWITCH_CAMERA", "PHOTO" -> {
+ numberPref?.isEnabled = false
+ }
+ else -> {
+ numberPref?.isEnabled = true
+ }
+ }
+ }
+
+ /** Some tests do not require the preview stream to run, If one of these selected,
+ * disable the preview buffer control.
+ */
+ fun togglePreviewBuffer() {
+ val typePref =
+ preferenceManager
+ .findPreference<ListPreference>(getString(R.string.settings_single_test_type_key))
+ val previewPref =
+ preferenceManager
+ .findPreference<ListPreference>(getString(R.string.settings_previewbuffer_key))
+ when (typePref?.value) {
+ "INIT" -> {
+ previewPref?.isEnabled = false
+ }
+ else -> {
+ previewPref?.isEnabled = true
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/TestConfig.kt b/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/TestConfig.kt
new file mode 100644
index 00000000000..1e259893972
--- /dev/null
+++ b/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/TestConfig.kt
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2019 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.antelope
+
+/** The different types of tests Antelop can perform */
+enum class TestType {
+ /** No test */
+ NONE,
+ /** Open and close camera only*/
+ INIT,
+ /** Start preview stream */
+ PREVIEW,
+ /** Capture a single image */
+ PHOTO,
+ /** Capture multiple photos, closing/opening camera between each capture */
+ MULTI_PHOTO,
+ /** Capture multiple photos, leave camera open between captures */
+ MULTI_PHOTO_CHAIN,
+ /** Switch between two cameras one time. Preview stream only, no capture */
+ SWITCH_CAMERA,
+ /** Switch between two cameras multiple times, Preview only, no capture */
+ MULTI_SWITCH
+}
+
+/**
+ * Configuration and state variables for the current running test.
+ *
+ * Also contains the TestResults object to record the results.
+ */
+class TestConfig(
+ /** Name of test */
+ var testName: String = "",
+ /** Enum type of test */
+ var currentRunningTest: TestType = TestType.NONE,
+ /** API to use for test (Camera 1, 2, or X) */
+ var api: CameraAPI = CameraAPI.CAMERA2,
+ /** Size of capture to request */
+ var imageCaptureSize: ImageCaptureSize = ImageCaptureSize.MAX,
+ /** Auto-focus, Continuous focus, or Fixe-focus */
+ var focusMode: FocusMode = FocusMode.AUTO,
+ /** Camera ID */
+ var camera: String = "0",
+ /** Camera array to use for the switch camera test */
+ var switchTestCameras: Array<String> = arrayOf("0", "1"),
+ /** Convenience variable, currently active camera during switch test */
+ var switchTestCurrentCamera: String = "0",
+ /** Save the original camera for a switch test */
+ var switchTestRealCameraId: String = "0",
+ /** Semaphor for first onActive preview state */
+ var isFirstOnActive: Boolean = true,
+ /** Semaphor for first completed capture */
+ var isFirstOnCaptureComplete: Boolean = true,
+ /** Semaphor for when the test is completed */
+ var testFinished: Boolean = false,
+ /** Accumulate test results in this object */
+ var testResults: TestResults = TestResults()
+) {
+
+ /**
+ * Set up the TestResults object to reflect the test configuration
+ */
+ fun setupTestResults() {
+ testResults.testName = testName
+ testResults.testType = currentRunningTest
+ testResults.camera = camera
+ testResults.cameraId = camera
+ testResults.cameraAPI = api
+ testResults.imageCaptureSize = imageCaptureSize
+ testResults.focusMode = focusMode
+ }
+} \ No newline at end of file
diff --git a/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/TestResults.kt b/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/TestResults.kt
new file mode 100644
index 00000000000..70453d5c2fe
--- /dev/null
+++ b/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/TestResults.kt
@@ -0,0 +1,413 @@
+/*
+ * Copyright 2019 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.antelope
+
+import android.content.Intent
+import android.net.Uri
+import android.os.Environment
+import android.widget.Toast
+import com.google.common.math.Quantiles
+import com.google.common.math.Stats
+import androidx.camera.integration.antelope.MainActivity.Companion.logd
+import java.io.BufferedWriter
+import java.io.File
+import java.io.FileOutputStream
+import java.io.IOException
+import java.io.OutputStreamWriter
+import java.text.SimpleDateFormat
+import java.util.Calendar
+import java.util.Date
+import java.util.Locale
+
+/**
+ * Contains the results for a specific test. Most of the variables are arrays to accommodate tests
+ * with multiple repetitions (MULTI_PHOTO, MULTI_PHOTO_CHAINED, MULTI_SWITCH, etc.)
+ */
+class TestResults {
+ /** Name of test */
+ var testName: String = ""
+ /** Human readable camera name */
+ var camera: String = ""
+ /** Camera ID */
+ var cameraId: String = ""
+ /** Which API was used (1, 2, or X) */
+ var cameraAPI: CameraAPI = CameraAPI.CAMERA2
+ /** Image size that was requested */
+ var imageCaptureSize: ImageCaptureSize = ImageCaptureSize.MAX
+ /** Auto-focus, continuous focus, or fixed-foxus */
+ var focusMode: FocusMode = FocusMode.AUTO
+ /** Enum type of the test requested */
+ var testType: TestType = TestType.NONE
+ /** Time take to open camera */
+ var initialization: ArrayList<Long> = ArrayList<Long>()
+ /** Time taken for the preview to start */
+ var previewStart: ArrayList<Long> = ArrayList<Long>()
+ /** Time taken for the preview to run before next step in the test */
+ var previewFill: ArrayList<Long> = ArrayList<Long>()
+ /** Time taken to switch from first to second cameras */
+ var switchToSecond: ArrayList<Long> = ArrayList<Long>()
+ /** Time taken to switch from second to first cameras */
+ var switchToFirst: ArrayList<Long> = ArrayList<Long>()
+ /** Time taken for the auto-focus routine to complete */
+ var autofocus: ArrayList<Long> = ArrayList<Long>()
+ /** Time taken for image capture, not including auto-focus delay */
+ var captureNoAF: ArrayList<Long> = ArrayList<Long>()
+ /** Time taken for image capture, including auto-focus delay (if applicable) */
+ var capture: ArrayList<Long> = ArrayList<Long>()
+ /** Time taken after image is captured for it to be ready in the ImageReader */
+ var imageready: ArrayList<Long> = ArrayList<Long>()
+ /** Time taken for capture and the image to appear in the ImageReader */
+ var capturePlusImageReady: ArrayList<Long> = ArrayList<Long>()
+ /** Time taken to save image to disk */
+ var imagesave: ArrayList<Long> = ArrayList<Long>()
+ /** Time taken to close the preview stream */
+ var previewClose: ArrayList<Long> = ArrayList<Long>()
+ /** Time taken to close the camera */
+ var cameraClose: ArrayList<Long> = ArrayList<Long>()
+ /** Time taken for the entire test */
+ var total: ArrayList<Long> = ArrayList<Long>()
+ /** Time taken for the entire test not including filling the preview stream */
+ var totalNoPreview: ArrayList<Long> = ArrayList<Long>()
+ /** Was the image captured an HDR+ image? */
+ var isHDRPlus: ArrayList<Boolean> = ArrayList<Boolean>()
+
+ /**
+ * Format results into a human readable string
+ */
+ fun toString(activity: MainActivity, header: Boolean): String {
+ var output = ""
+
+ if (header) {
+ val dateFormatter = SimpleDateFormat("d MMM yyyy - kk'h'mm")
+ val cal: Calendar = Calendar.getInstance()
+ output += "DATE: " + dateFormatter.format(cal.time) +
+ " (Antelope " + getVersionName(activity) + ")\n"
+
+ output += "DEVICE: " + MainActivity.deviceInfo.device + "\n\n"
+ output += "CAMERAS:\n"
+ for (camera in MainActivity.cameras)
+ output += camera + "\n"
+ output += "\n"
+ }
+
+ output += testName + "\n"
+ output += "Camera: " + camera + "\n"
+ output += "API: " + cameraAPI + "\n"
+ output += "Focus Mode: " + focusMode + "\n"
+ output += "Image Capture Size: " + imageCaptureSize + "\n\n"
+
+ output += outputResultLine("Camera open", initialization)
+ output += outputResultLine("Preview start", previewStart)
+ output += outputResultLine("Preview buffer", previewFill)
+
+ when (focusMode) {
+ FocusMode.CONTINUOUS -> {
+ output += outputResultLine("Capture (continuous focus)", capture)
+ }
+ FocusMode.FIXED -> {
+ output += outputResultLine("Capture (fixed-focus)", capture)
+ }
+ else -> {
+ // CameraX doesn't allow us insight into autofocus
+ if (CameraAPI.CAMERAX == cameraAPI) {
+ output += outputResultLine("Capture incl. autofocus", capture)
+ } else {
+ output += outputResultLine("Autofocus", autofocus)
+ output += outputResultLine("Capture", captureNoAF)
+ output += outputResultLine("Capture incl. autofocus", capture)
+ }
+ }
+ }
+
+ output += outputResultLine("Image ready", imageready)
+ output += outputResultLine("Cap + img ready", capturePlusImageReady)
+ output += outputResultLine("Image save", imagesave)
+ output += outputResultLine("Switch to 2nd", switchToSecond)
+ output += outputResultLine("Switch to 1st", switchToFirst)
+ output += outputResultLine("Preview close", previewClose)
+ output += outputResultLine("Camera close", cameraClose)
+ output += outputBooleanResultLine("HDR+", isHDRPlus)
+ output += outputResultLine("Total", total)
+ output += outputResultLine("Total w/o preview buffer", totalNoPreview)
+
+ if (1 < capturePlusImageReady.size) {
+ val captureStats = Stats.of(capturePlusImageReady)
+ output += "Capture range: " + captureStats.min() + " - " + captureStats.max() + "\n"
+ output += "Capture mean: " + captureStats.mean() +
+ " (" + captureStats.count() + " captures)\n"
+ output += "Capture median: " + Quantiles.median().compute(capturePlusImageReady) + "\n"
+ output += "Capture standard deviation: " + captureStats.sampleStandardDeviation() + "\n"
+ }
+ output += "Total batch time: " + Stats.of(total).sum() + "\n\n\n"
+ return output
+ }
+
+ /**
+ * Format results to a comma-based .csv string
+ */
+ fun toCSV(activity: MainActivity, header: Boolean = true): String {
+ val numCommas = PrefHelper.getNumTests(activity)
+
+ var output = ""
+
+ if (header) {
+ val dateFormatter = SimpleDateFormat("d MMM yyyy - kk'h'mm")
+ val cal: Calendar = Calendar.getInstance()
+ output += "DATE: " + dateFormatter.format(cal.time) + " (Antelope " +
+ getVersionName(activity) + ")" + outputCommas(numCommas) + "\n"
+
+ output += "DEVICE: " + MainActivity.deviceInfo.device + outputCommas(numCommas) +
+ "\n" + outputCommas(numCommas) + "\n"
+ output += "CAMERAS: " + outputCommas(numCommas) + "\n"
+ for (camera in MainActivity.cameras)
+ output += camera + outputCommas(numCommas) + "\n"
+ output += outputCommas(numCommas) + "\n"
+ }
+
+ output += testName + outputCommas(numCommas) + outputCommas(numCommas) + "\n"
+ output += "Camera: " + camera + outputCommas(numCommas) + "\n"
+ output += "API: " + cameraAPI + outputCommas(numCommas) + "\n"
+ output += "Focus Mode: " + focusMode + outputCommas(numCommas) + "\n"
+ output += "Image Capture Size: " + imageCaptureSize + outputCommas(numCommas) + "\n" +
+ outputCommas(numCommas) + "\n"
+
+ output += outputResultLine("Camera open", initialization, numCommas, true)
+ output += outputResultLine("Preview start", previewStart, numCommas, true)
+ output += outputResultLine("Preview buffer", previewFill, numCommas, true)
+
+ when (focusMode) {
+ FocusMode.CONTINUOUS -> {
+ output += outputResultLine("Capture (continuous focus)", capture,
+ numCommas, true)
+ }
+ FocusMode.FIXED -> {
+ output += outputResultLine("Capture (fixed-focus)", capture,
+ numCommas, true)
+ }
+ else -> {
+ // CameraX doesn't allow us insight into autofocus
+ if (CameraAPI.CAMERAX == cameraAPI) {
+ output += outputResultLine("Capture incl. autofocus", capture,
+ numCommas, true)
+ } else {
+ output += outputResultLine("Autofocus", autofocus,
+ numCommas, true)
+ output += outputResultLine("Capture", captureNoAF,
+ numCommas, true)
+ output += outputResultLine("Capture incl. autofocus", capture,
+ numCommas, true)
+ }
+ }
+ }
+
+ output += outputResultLine("Image ready", imageready, numCommas, true)
+ output += outputResultLine("Cap + img ready", capturePlusImageReady,
+ numCommas, true)
+ output += outputResultLine("Image save", imagesave, numCommas, true)
+ output += outputResultLine("Switch to 2nd", switchToSecond, numCommas, true)
+ output += outputResultLine("Switch to 1st", switchToFirst, numCommas, true)
+ output += outputResultLine("Preview close", previewClose, numCommas, true)
+ output += outputResultLine("Camera close", cameraClose, numCommas, true)
+ output += outputBooleanResultLine("HDR+", isHDRPlus, numCommas, true)
+ output += outputResultLine("Total", total, numCommas, true)
+ output += outputResultLine("Total w/o preview buffer", totalNoPreview,
+ numCommas, true)
+
+ if (1 < capturePlusImageReady.size) {
+ val captureStats = Stats.of(capturePlusImageReady)
+ output += "Capture range:," + captureStats.min() + " - " + captureStats.max() +
+ outputCommas(numCommas) + "\n"
+ output += "Capture mean " + " (" + captureStats.count() + " captures):," +
+ Stats.of(capturePlusImageReady).mean() + outputCommas(numCommas) + "\n"
+ output += "Capture median:," + Quantiles.median().compute(capturePlusImageReady) +
+ outputCommas(numCommas) + "\n"
+ output += "Capture standard deviation:," +
+ captureStats.sampleStandardDeviation() + outputCommas(numCommas) + "\n"
+ }
+
+ output += "Total batch time:," + Stats.of(total).sum() + outputCommas(numCommas) + "\n"
+
+ output += outputCommas(numCommas) + "\n"
+ output += outputCommas(numCommas) + "\n"
+ output += outputCommas(numCommas) + "\n"
+
+ return output
+ }
+}
+
+/**
+ * Write all results to disk in a .csv file
+ *
+ * @param activity The main activity
+ * @param filePrefix The prefix for the .csv file
+ * @param csv The comma-based csv string
+ */
+fun writeCSV(activity: MainActivity, filePrefix: String, csv: String) {
+
+ val csvFile = File(Environment.getExternalStoragePublicDirectory(
+ Environment.DIRECTORY_DOCUMENTS),
+ File.separatorChar + MainActivity.LOG_DIR + File.separatorChar +
+ filePrefix + "_" + generateCSVTimestamp() + ".csv")
+
+ val csvDir = File(Environment.getExternalStoragePublicDirectory(
+ Environment.DIRECTORY_DOCUMENTS), MainActivity.LOG_DIR)
+ val docsDir = File(Environment.getExternalStoragePublicDirectory(
+ Environment.DIRECTORY_DOCUMENTS), "")
+
+ if (!docsDir.exists()) {
+ val createSuccess = docsDir.mkdir()
+ if (!createSuccess) {
+ Toast.makeText(activity, "Documents" + " creation failed.",
+ Toast.LENGTH_SHORT).show()
+ MainActivity.logd("Log storage directory Documents" + " creation failed!!")
+ } else {
+ MainActivity.logd("Log storage directory Documents" + " did not exist. Created.")
+ }
+ }
+
+ if (!csvDir.exists()) {
+ val createSuccess = csvDir.mkdir()
+ if (!createSuccess) {
+ Toast.makeText(activity, "Documents/" + MainActivity.LOG_DIR +
+ " creation failed.", Toast.LENGTH_SHORT).show()
+ MainActivity.logd("Log storage directory Documents/" +
+ MainActivity.LOG_DIR + " creation failed!!")
+ } else {
+ MainActivity.logd("Log storage directory Documents/" +
+ MainActivity.LOG_DIR + " did not exist. Created.")
+ }
+ }
+
+ val output = BufferedWriter(OutputStreamWriter(FileOutputStream(csvFile)))
+ try {
+ output.write(csv)
+ logd("CSV write completed successfully.")
+
+ // File is written, let media scanner know
+ val scannerIntent = Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE)
+ scannerIntent.data = Uri.fromFile(csvFile)
+ activity.sendBroadcast(scannerIntent)
+ } catch (e: IOException) {
+ logd("IOException vail on CSV write: " + e.printStackTrace())
+ } finally {
+ try {
+ output.close()
+ } catch (e: IOException) {
+ logd("IOException vail on CSV close: " + e.printStackTrace())
+ e.printStackTrace()
+ }
+ }
+}
+
+/**
+ * Delete all Antelope .csv files in the documents directory
+ */
+fun deleteCSVFiles(activity: MainActivity) {
+ val csvDir = File(Environment.getExternalStoragePublicDirectory(
+ Environment.DIRECTORY_DOCUMENTS), MainActivity.LOG_DIR)
+
+ if (csvDir.exists()) {
+
+ for (csv in csvDir.listFiles())
+ csv.delete()
+
+ // Files are deleted, let media scanner know
+ val scannerIntent = Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE)
+ scannerIntent.data = Uri.fromFile(csvDir)
+ activity.sendBroadcast(scannerIntent)
+
+ Toast.makeText(activity, "CSV logs deleted", Toast.LENGTH_SHORT).show()
+ logd("All csv logs in directory DOCUMENTS/" + MainActivity.LOG_DIR + " deleted.")
+ }
+}
+
+/**
+ * Generate a timestamp for csv filenames
+ */
+fun generateCSVTimestamp(): String {
+ val sdf = SimpleDateFormat("yyyy-MM-dd-HH'h'mm", Locale.US)
+ return sdf.format(Date())
+}
+
+/**
+ * Create a string consisting solely of the number of commas indicated
+ *
+ * Handy for properly formatting comma-based .csv files as the number of columns will depend on
+ * the user configurable number of test repetitions.
+ */
+fun outputCommas(numCommas: Int): String {
+ var output = ""
+ for (i in 1..numCommas)
+ output += ","
+ return output
+}
+
+/**
+ * For a list of Longs, output a comma separated .csv line
+ */
+fun outputResultLine(
+ name: String,
+ results: ArrayList<Long>,
+ numCommas: Int = 30,
+ isCSV: Boolean = false
+): String {
+ var output = ""
+
+ if (!results.isEmpty()) {
+ output += name + ": "
+ for ((index, result) in results.withIndex()) {
+ if (isCSV || (0 != index))
+ output += ","
+ output += result
+ }
+ if (isCSV)
+ output += outputCommas(numCommas - results.size)
+ output += "\n"
+ }
+
+ return output
+}
+
+/**
+ * For a list of Booleans, output a comma separated .csv line
+ */
+fun outputBooleanResultLine(
+ name: String,
+ results: ArrayList<Boolean>,
+ numCommas: Int = 30,
+ isCSV: Boolean = false
+): String {
+ var output = ""
+
+ // If every result is false, don't output this line at all
+ if (!results.isEmpty() && results.contains(true)) {
+ output += name + ": "
+ for ((index, result) in results.withIndex()) {
+ if (isCSV || (0 != index))
+ output += ","
+ if (result)
+ output += "HDR+"
+ else
+ output += " - "
+ }
+ if (isCSV)
+ output += outputCommas(numCommas - results.size)
+ output += "\n"
+ }
+
+ return output
+} \ No newline at end of file
diff --git a/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/TestUtils.kt b/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/TestUtils.kt
new file mode 100644
index 00000000000..fae19ae3b72
--- /dev/null
+++ b/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/TestUtils.kt
@@ -0,0 +1,554 @@
+/*
+ * Copyright 2019 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.antelope
+
+import com.google.common.math.Stats
+import androidx.camera.integration.antelope.MainActivity.Companion.cameraParams
+import androidx.camera.integration.antelope.MainActivity.Companion.isSingleTestRunning
+import androidx.camera.integration.antelope.MainActivity.Companion.logd
+import androidx.camera.integration.antelope.MainActivity.Companion.testsRemaining
+import java.text.SimpleDateFormat
+import java.util.Calendar
+import kotlin.collections.ArrayList
+import kotlin.math.roundToInt
+
+/**
+ * During a multiple-test run, this should be called after each test is completed. Record the result
+ * and call the automatic test runner to start the next test.
+ *
+ * If all test are completed, post result to screen and save the log.
+ */
+fun postTestResults(activity: MainActivity, testConfig: TestConfig) {
+ MainActivity.testRun.add(testConfig.testResults)
+ testsRemaining--
+
+ var log = ""
+ var csv = ""
+
+ if (0 >= testsRemaining) {
+ if (!isSingleTestRunning) {
+ log += testSummaryString(activity, MainActivity.testRun)
+ csv += testSummaryCSV(activity, MainActivity.testRun)
+ }
+
+ for ((index, result) in MainActivity.testRun.withIndex()) {
+ // TODO with test summary we can combine these two cases
+ if (0 == index) {
+ log += result.toString(activity, false)
+ csv += result.toCSV(activity, false)
+ } else {
+ log += result.toString(activity, false)
+ csv += result.toCSV(activity, false)
+ }
+ }
+
+ activity.resetUIAfterTest()
+ activity.updateLog(log)
+ writeCSV(activity, DeviceInfo(activity).deviceShort, csv)
+ } else {
+ autoTestRunner(activity)
+ }
+}
+
+/**
+ * Set up the TestConfig object for a single test
+ */
+fun createSingleTestConfig(activity: MainActivity): TestConfig {
+ val config = TestConfig()
+
+ config.apply {
+ when (PrefHelper.getSingleTestType(activity)) {
+ "INIT" -> {
+ testName = "Camera Open/Close"
+ currentRunningTest = TestType.INIT
+ }
+ "PREVIEW" -> {
+ testName = "Preview Start"
+ currentRunningTest = TestType.PREVIEW
+ }
+ "SWITCH_CAMERA" -> {
+ testName = "Switch Cameras"
+ currentRunningTest = TestType.SWITCH_CAMERA
+ }
+ "MULTI_SWITCH" -> {
+ testName = "Switch Cameras (Multiple)"
+ currentRunningTest = TestType.MULTI_SWITCH
+ }
+ "MULTI_PHOTO" -> {
+ testName = "Multiple Captures"
+ currentRunningTest = TestType.MULTI_PHOTO
+ }
+ "MULTI_PHOTO_CHAIN" -> {
+ testName = "Multiple Captures (Chained)"
+ currentRunningTest = TestType.MULTI_PHOTO_CHAIN
+ }
+ else -> {
+ testName = "Single Capture"
+ currentRunningTest = TestType.PHOTO
+ }
+ }
+
+ api = CameraAPI.valueOf(PrefHelper.getSingleTestApi(activity).toUpperCase())
+ focusMode = FocusMode.valueOf(PrefHelper.getSingleTestFocus(activity).toUpperCase())
+ imageCaptureSize =
+ ImageCaptureSize.valueOf(PrefHelper.getSingleTestImageSize(activity).toUpperCase())
+ camera = PrefHelper.getSingleTestCamera(activity)
+ config.setupTestResults()
+ }
+
+ return config
+}
+
+/**
+ * Create a test configuration given the test's name and the currently selected test values
+ */
+fun createTestConfig(testName: String): TestConfig {
+ val config = TestConfig(testName)
+ config.camera = MainActivity.camViewModel.getCurrentCamera().value.toString()
+ config.api = MainActivity.camViewModel.getCurrentAPI().value ?: CameraAPI.CAMERA2
+ config.imageCaptureSize =
+ MainActivity.camViewModel.getCurrentImageCaptureSize().value ?: ImageCaptureSize.MAX
+
+ // If we don't have auto-focus, we set the focus mode to FIXED
+ if (MainActivity.cameraParams.get(config.camera)?.hasAF ?: true)
+ config.focusMode = MainActivity.camViewModel.getCurrentFocusMode().value ?: FocusMode.AUTO
+ else
+ config.focusMode = FocusMode.FIXED
+
+ config.setupTestResults()
+
+ return config
+}
+
+/**
+ * For multiple tests, configure the list of TestConfigs to run
+ */
+fun setupAutoTestRunner(activity: MainActivity) {
+ MainActivity.autoTestConfigs.clear()
+ val cameras: ArrayList<String> = PrefHelper.getCameraIds(activity, MainActivity.cameraParams)
+ val logicalCameras: ArrayList<String> =
+ PrefHelper.getLogicalCameraIds(activity, MainActivity.cameraParams)
+ val apis: ArrayList<CameraAPI> = PrefHelper.getAPIs(activity)
+ val imageSizes: ArrayList<ImageCaptureSize> = PrefHelper.getImageSizes(activity)
+ val focusModes: ArrayList<FocusMode> = PrefHelper.getFocusModes(activity)
+ val testTypes: ArrayList<TestType> = ArrayList()
+ val doSwitchTest: Boolean = PrefHelper.getSwitchTest(activity)
+
+ testTypes.add(TestType.MULTI_PHOTO)
+ testTypes.add(TestType.MULTI_PHOTO_CHAIN)
+
+ if (doSwitchTest)
+ testTypes.add(TestType.MULTI_SWITCH)
+
+ MainActivity.testRun.clear()
+
+ for (camera in cameras) {
+ for (api in apis) {
+ // Camera1 does not have access to physical cameras, only logical 0 and 1
+ // Some devices have no camera or only 1 front-facing camera (like Chromebooks)
+ // so we need to make sure they exist
+ if ((CameraAPI.CAMERA1 == api) && !logicalCameras.contains(camera))
+ continue
+
+ // Currently CameraX only supports FRONT and BACK
+ if ((CameraAPI.CAMERAX == api) && !(camera.equals("0") || camera.equals("1")))
+ continue
+
+ for (imageSize in imageSizes) {
+ for (focusMode in focusModes) {
+ if (FocusMode.CONTINUOUS == focusMode) {
+ // If camera is fixed-focus, only run the AUTO test
+ if (!(MainActivity.cameraParams.get(camera)?.hasAF ?: true))
+ continue
+ }
+
+ for (testType in testTypes) {
+ // Camera1 does not have chaining capabilities
+ if ((CameraAPI.CAMERA1 == api) && (TestType.MULTI_PHOTO_CHAIN == testType))
+ continue
+
+ // For now we only test 0->1->0, just add this test for the first "camera"
+ // TODO: figure out a way to test different permutations
+ if ((TestType.MULTI_SWITCH == testType) && !camera.equals(cameras.first()))
+ continue
+
+ // Switch test doesn't do a capture don't repeat for all capture sizes
+ if (imageSize == ImageCaptureSize.MIN && testType == TestType.MULTI_SWITCH)
+ continue
+
+ // Switch test doesn't do a capture so don't repeat for all focus modes
+ if (focusMode != FocusMode.AUTO && testType == TestType.MULTI_SWITCH)
+ continue
+
+ // If this is a fixed focus lens, focusMode here has been set to auto,
+ // set it to fixed in the TestConfig
+ var realFocusMode: FocusMode = focusMode
+ if (!(MainActivity.cameraParams.get(camera)?.hasAF ?: true))
+ realFocusMode = FocusMode.FIXED
+
+ var testName = when (api) {
+ CameraAPI.CAMERA1 -> "Camera1"
+ CameraAPI.CAMERA2 -> "Camera2"
+ CameraAPI.CAMERAX -> "CameraX"
+ }
+
+ testName += " - "
+ testName += when (imageSize) {
+ ImageCaptureSize.MIN -> "Min"
+ ImageCaptureSize.MAX -> "Max"
+ }
+ testName += " image size - Camera device "
+
+ if (testType != TestType.MULTI_SWITCH) {
+ testName += camera
+ testName += " "
+ }
+
+ testName += when (realFocusMode) {
+ FocusMode.AUTO -> "(auto-focus)"
+ FocusMode.CONTINUOUS -> "(continuous focus)"
+ else -> "(fixed-focus)"
+ }
+
+ testName += " - "
+ testName += when (testType) {
+ TestType.MULTI_PHOTO -> "Multiple Captures"
+ TestType.MULTI_PHOTO_CHAIN -> "Multiple Captures (chained)"
+ TestType.MULTI_SWITCH -> "Switch Camera"
+ else -> "unknown test"
+ }
+
+ val testConfig =
+ TestConfig(testName, testType, api, imageSize, realFocusMode, camera)
+ testConfig.setupTestResults()
+ MainActivity.autoTestConfigs.add(testConfig)
+ }
+ }
+ }
+ }
+ }
+
+ // Add Test X of Y string to test names
+ for ((index, testConfig) in MainActivity.autoTestConfigs.withIndex()) {
+ testConfig.testName = "" + (index + 1) + " of " +
+ MainActivity.autoTestConfigs.size + ": " + testConfig.testName
+ }
+
+ testsRemaining = MainActivity.autoTestConfigs.size
+}
+
+/**
+ * Run the list of tests in autoTestConfigs
+ */
+fun autoTestRunner(activity: MainActivity) {
+ if (MainActivity.cameras.isEmpty()) {
+ testsRemaining = 0
+ return
+ }
+
+ // If something goes wrong or we are aborted, stop testing
+ if (0 == testsRemaining)
+ return
+
+ val currentTest: Int = MainActivity.autoTestConfigs.size - testsRemaining + 1
+ val currentConfig: TestConfig = MainActivity.autoTestConfigs.get(currentTest - 1)
+ MainActivity.logd("autoTestRun about to run: " + currentConfig.testName)
+
+ activity.runOnUiThread {
+ if (testsRemaining == MainActivity.autoTestConfigs.size)
+ activity.setupUIForTest(currentConfig, false)
+ else
+ activity.setupUIForTest(currentConfig, true)
+ }
+
+ multiCounter = 0
+ initializeTest(activity, cameraParams.get(currentConfig.camera), currentConfig)
+}
+
+/**
+ * For an array of TestResults, generate a high-level summary of the most important values.
+ *
+ * This mostly consists of values for default rear camera.
+ */
+fun testSummaryString(activity: MainActivity, allTestResults: ArrayList<TestResults>): String {
+ var output = ""
+
+ if (allTestResults.isEmpty())
+ return output
+
+ val mainCamera = getMainCamera(activity, allTestResults)
+
+ val c2Auto = findTest(allTestResults, mainCamera, CameraAPI.CAMERA2,
+ ImageCaptureSize.MAX, FocusMode.AUTO, TestType.MULTI_PHOTO)
+ val c2AutoChain = findTest(allTestResults, mainCamera, CameraAPI.CAMERA2,
+ ImageCaptureSize.MAX, FocusMode.AUTO, TestType.MULTI_PHOTO_CHAIN)
+ val c2Caf = findTest(allTestResults, mainCamera, CameraAPI.CAMERA2,
+ ImageCaptureSize.MAX, FocusMode.CONTINUOUS, TestType.MULTI_PHOTO)
+ val c2CafChain = findTest(allTestResults, mainCamera, CameraAPI.CAMERA2,
+ ImageCaptureSize.MAX, FocusMode.CONTINUOUS, TestType.MULTI_PHOTO_CHAIN)
+ val c2AutoMin = findTest(allTestResults, mainCamera, CameraAPI.CAMERA2,
+ ImageCaptureSize.MIN, FocusMode.AUTO, TestType.MULTI_PHOTO)
+ val c2Switch = findTest(allTestResults, mainCamera, CameraAPI.CAMERA2,
+ ImageCaptureSize.MAX, FocusMode.AUTO, TestType.MULTI_SWITCH)
+ val c1Auto = findTest(allTestResults, mainCamera, CameraAPI.CAMERA1,
+ ImageCaptureSize.MAX, FocusMode.AUTO, TestType.MULTI_PHOTO)
+ val c1Caf = findTest(allTestResults, mainCamera, CameraAPI.CAMERA1,
+ ImageCaptureSize.MAX, FocusMode.CONTINUOUS, TestType.MULTI_PHOTO)
+ val c1Switch = findTest(allTestResults, mainCamera, CameraAPI.CAMERA1,
+ ImageCaptureSize.MAX, FocusMode.AUTO, TestType.MULTI_SWITCH)
+ val cXAuto = findTest(allTestResults, mainCamera, CameraAPI.CAMERAX,
+ ImageCaptureSize.MAX, FocusMode.AUTO, TestType.MULTI_PHOTO)
+ val cXCaf = findTest(allTestResults, mainCamera, CameraAPI.CAMERAX,
+ ImageCaptureSize.MAX, FocusMode.CONTINUOUS, TestType.MULTI_PHOTO)
+ val cXSwitch = findTest(allTestResults, mainCamera, CameraAPI.CAMERAX,
+ ImageCaptureSize.MAX, FocusMode.AUTO, TestType.MULTI_SWITCH)
+
+ // Header
+ val dateFormatter = SimpleDateFormat("d MMM yyyy - kk'h'mm")
+ val cal: Calendar = Calendar.getInstance()
+ output += "DATE: " + dateFormatter.format(cal.time) + " (Antelope " +
+ getVersionName(activity) + ")\n"
+
+ output += "DEVICE: " + MainActivity.deviceInfo.device + "\n\n"
+ output += "CAMERAS:\n"
+ for (camera in MainActivity.cameras)
+ output += camera + "\n"
+ output += "\n"
+
+ // Test summary
+ output += "HIGH-LEVEL OVERVIEW:\n"
+
+ output += "Capture (Cam2): " + meanOfSumOfTwoArrays(c2Auto.capture, c2Auto.imageready) +
+ ", Cap chained (Cam2): " +
+ meanOfSumOfTwoArrays(c2AutoChain.capture, c2AutoChain.imageready) +
+ "\nCapture CAF (Cam2): " + meanOfSumOfTwoArrays(c2Caf.capture, c2Caf.imageready) +
+ ", Chained CAF (Cam2): " +
+ meanOfSumOfTwoArrays(c2CafChain.capture, c2CafChain.imageready) +
+ "\nCapture (Cam1): " + meanOfSumOfTwoArrays(c1Auto.capture, c1Auto.imageready) +
+ ", Cap CAF (Cam1): " + meanOfSumOfTwoArrays(c1Caf.capture, c1Caf.imageready) +
+ "\nCapture (CamX): " + meanOfSumOfTwoArrays(cXAuto.capture, cXAuto.imageready) +
+ ", Cap CAF (CamX): " + meanOfSumOfTwoArrays(cXCaf.capture, cXCaf.imageready) +
+ "\nSwitch 1->2 (Cam2): " +
+ mean(c2Switch.switchToSecond) + ", Switch 1->2 (Cam1): " + mean(c1Switch.switchToSecond) +
+ ", Switch 1->2 (CamX): " + mean(cXSwitch.switchToSecond) +
+ ", Switch 2->1 (Cam2): " +
+ mean(c2Switch.switchToFirst) + ", Switch 2->1 (Cam1): " + mean(c1Switch.switchToFirst) +
+ ", Switch 2->1 (CamX): " + mean(cXSwitch.switchToFirst) +
+ "\nCam2 Open: " + meanOfSumOfTwoArrays(c2Auto.initialization, c2Auto.previewStart) +
+ ", Cam1 Open: " + meanOfSumOfTwoArrays(c1Auto.initialization, c1Auto.previewStart) +
+ "\nCam2 Close: " + meanOfSumOfTwoArrays(c2Auto.previewClose, c2Auto.cameraClose) +
+ ", Cam1 Close: " + meanOfSumOfTwoArrays(c1Auto.previewClose, c1Auto.cameraClose) +
+ "\n∆ Min to Max Size: " + (numericalMean(c2Auto.capture) +
+ numericalMean(c2Auto.imageready) - numericalMean(c2AutoMin.capture) -
+ numericalMean(c2AutoMin.imageready)) +
+ ", Init->Image saved (Cam2): " + mean(c2Auto.totalNoPreview) +
+ "\n"
+
+ output += "\n"
+
+ return output
+}
+
+/**
+ * For an array of TestResults, generate a high-level summary of the most important values in a
+ * comma-separated .csv string.
+ *
+ * This mostly consists of values for default rear camera.
+ */
+fun testSummaryCSV(activity: MainActivity, allTestResults: ArrayList<TestResults>): String {
+ var output = ""
+
+ if (allTestResults.isEmpty())
+ return output
+
+ val mainCamera = getMainCamera(activity, allTestResults)
+
+ val c2Auto = findTest(allTestResults, mainCamera, CameraAPI.CAMERA2,
+ ImageCaptureSize.MAX, FocusMode.AUTO, TestType.MULTI_PHOTO)
+ val c2AutoChain = findTest(allTestResults, mainCamera, CameraAPI.CAMERA2,
+ ImageCaptureSize.MAX, FocusMode.AUTO, TestType.MULTI_PHOTO_CHAIN)
+ val c2Caf = findTest(allTestResults, mainCamera, CameraAPI.CAMERA2,
+ ImageCaptureSize.MAX, FocusMode.CONTINUOUS, TestType.MULTI_PHOTO)
+ val c2CafChain = findTest(allTestResults, mainCamera, CameraAPI.CAMERA2,
+ ImageCaptureSize.MAX, FocusMode.CONTINUOUS, TestType.MULTI_PHOTO_CHAIN)
+ val c2AutoMin = findTest(allTestResults, mainCamera, CameraAPI.CAMERA2,
+ ImageCaptureSize.MIN, FocusMode.AUTO, TestType.MULTI_PHOTO)
+ val c2Switch = findTest(allTestResults, mainCamera, CameraAPI.CAMERA2,
+ ImageCaptureSize.MAX, FocusMode.AUTO, TestType.MULTI_SWITCH)
+ val c1Auto = findTest(allTestResults, mainCamera, CameraAPI.CAMERA1,
+ ImageCaptureSize.MAX, FocusMode.AUTO, TestType.MULTI_PHOTO)
+ val c1Caf = findTest(allTestResults, mainCamera, CameraAPI.CAMERA1,
+ ImageCaptureSize.MAX, FocusMode.CONTINUOUS, TestType.MULTI_PHOTO)
+ val c1Switch = findTest(allTestResults, mainCamera, CameraAPI.CAMERA1,
+ ImageCaptureSize.MAX, FocusMode.AUTO, TestType.MULTI_SWITCH)
+ val cXAuto = findTest(allTestResults, mainCamera, CameraAPI.CAMERAX,
+ ImageCaptureSize.MAX, FocusMode.AUTO, TestType.MULTI_PHOTO)
+ val cXCaf = findTest(allTestResults, mainCamera, CameraAPI.CAMERAX,
+ ImageCaptureSize.MAX, FocusMode.CONTINUOUS, TestType.MULTI_PHOTO)
+ val cXSwitch = findTest(allTestResults, mainCamera, CameraAPI.CAMERAX,
+ ImageCaptureSize.MAX, FocusMode.AUTO, TestType.MULTI_SWITCH)
+
+ // Header
+ val dateFormatter = SimpleDateFormat("d MMM yyyy - kk'h'mm")
+ val cal: Calendar = Calendar.getInstance()
+ output += "DATE: " + dateFormatter.format(cal.time) + " (Antelope " +
+ getVersionName(activity) + ")" + "\n"
+
+ output += "DEVICE: " + MainActivity.deviceInfo.device + "\n" + "\n"
+ output += "CAMERAS: " + "\n"
+ for (camera in MainActivity.cameras)
+ output += camera + "\n"
+ output += "\n"
+
+ // Test summary
+ output += "HIGH-LEVEL OVERVIEW:\n"
+
+ output += ","
+ output += "Capture (Cam2)" + "," + "Cap chained (Cam2)" + "," +
+ "Capture CAF (Cam2)" + "," + "Chained CAF (Cam2)" + "," +
+ "Capture (Cam1)" + "," + "Cap CAF (Cam1)" + "," +
+ "Capture (CamX)" + "," + "Cap CAF (CamX)" + "," +
+ "Switch 1->2 (Cam2)" + "," + "Switch 1->2 (Cam1)" + "," + "Switch 1->2 (CamX)" + "," +
+ "Switch 2->1 (Cam2)" + "," + "Switch 2->1 (Cam1)" + "," + "Switch 2->1 (CamX)" + "," +
+ "Cam2 Open" + "," + "Cam1 Open" + "," +
+ "Cam2 Close" + "," + "Cam1 Close" + "," +
+ "∆ Min to Max Size" + "," +
+ "Init->Image saved (Cam2)" +
+ "\n"
+
+ output += ","
+ output += "" + meanOfSumOfTwoArrays(c2Auto.capture, c2Auto.imageready) + "," +
+ meanOfSumOfTwoArrays(c2AutoChain.capture, c2AutoChain.imageready)
+ output += "," + meanOfSumOfTwoArrays(c2Caf.capture, c2Caf.imageready) + "," +
+ meanOfSumOfTwoArrays(c2CafChain.capture, c2CafChain.imageready)
+ output += "," + meanOfSumOfTwoArrays(c1Auto.capture, c1Auto.imageready) + "," +
+ meanOfSumOfTwoArrays(c1Caf.capture, c1Caf.imageready)
+ output += "," + meanOfSumOfTwoArrays(cXAuto.capture, cXAuto.imageready) + "," +
+ meanOfSumOfTwoArrays(cXCaf.capture, cXCaf.imageready)
+ output += "," + mean(c2Switch.switchToSecond) + "," + mean(c1Switch.switchToSecond)
+ output += "," + mean(cXSwitch.switchToSecond)
+ output += "," + mean(c2Switch.switchToFirst) + "," + mean(c1Switch.switchToFirst)
+ output += "," + mean(cXSwitch.switchToFirst)
+ output += "," + meanOfSumOfTwoArrays(c2Auto.initialization, c2Auto.previewStart) + "," +
+ meanOfSumOfTwoArrays(c1Auto.initialization, c1Auto.previewStart)
+ output += "," + meanOfSumOfTwoArrays(c2Auto.previewClose, c2Auto.cameraClose) + "," +
+ meanOfSumOfTwoArrays(c1Auto.previewClose, c1Auto.cameraClose)
+ output += "," + (numericalMean(c2Auto.capture) + numericalMean(c2Auto.imageready) -
+ numericalMean(c2AutoMin.capture) - numericalMean(c2AutoMin.imageready))
+ output += "," + mean(c2Auto.totalNoPreview) + "\n"
+
+ output += "\n"
+ return output
+}
+
+/**
+ * Search an array of TestResults for the first test result that matches the given parameters
+ */
+fun findTest(
+ allTestResults: ArrayList<TestResults>,
+ camera: String,
+ api: CameraAPI,
+ imageCaptureSize: ImageCaptureSize,
+ focusMode: FocusMode,
+ testType: TestType
+): TestResults {
+
+ for (testResult in allTestResults) {
+ // Look for the matching test result
+ if (testResult.camera.equals(camera) &&
+ testResult.cameraAPI == api &&
+ testResult.imageCaptureSize == imageCaptureSize &&
+ (testResult.focusMode == focusMode || testResult.focusMode == FocusMode.FIXED) &&
+ testResult.testType == testType) {
+ return testResult
+ }
+ }
+
+ // Return empty test result
+ return TestResults()
+}
+
+/**
+ * The mean of a given array of longs, as a string
+ */
+fun mean(array: ArrayList<Long>): String {
+ if (array.isEmpty()) {
+ return "n/a"
+ } else
+ return Stats.meanOf(array).roundToInt().toString()
+}
+
+/**
+ * The mean of a given array of longs, as a double
+ */
+fun numericalMean(array: ArrayList<Long>): Double {
+ if (array.isEmpty())
+ return 0.0
+ else
+ return Stats.meanOf(array)
+}
+
+/**
+ * The mean of two arrays of longs added together, as a string
+ */
+fun meanOfSumOfTwoArrays(array1: ArrayList<Long>, array2: ArrayList<Long>): String {
+ if (array1.isEmpty() && array2.isEmpty()) {
+ return "n/a"
+ }
+ if (array1.isEmpty())
+ return mean(array2)
+ if (array2.isEmpty())
+ return mean(array1)
+ else
+ return (Stats.meanOf(array1) + Stats.meanOf(array2)).roundToInt().toString()
+}
+
+/**
+ * Find the "main" camera id, priority is: first rear facing physical, first rear-facing logical,
+ * first camera in the system.
+ */
+fun getMainCamera(activity: MainActivity, allTestResults: ArrayList<TestResults>): String {
+ val mainCamera = allTestResults.first().cameraId
+
+ // Return the first rear-facing camera
+ for (param in MainActivity.cameraParams) {
+ if (!param.value.isFront && !param.value.isExternal)
+
+ // If only logical cameras, first rear-facing is fine
+ if (PrefHelper.getOnlyLogical(activity)) {
+ logd("The MAIN camera id is:" + param.value.id)
+ return param.value.id
+
+ // Otherwise, make sure this is a physical camera
+ } else {
+ if (param.value.physicalCameras.contains(param.value.id)) {
+ logd("The MAIN camera id is:" + param.value.id)
+ return param.value.id
+ }
+ }
+ }
+
+ return mainCamera
+}
+
+/**
+ * Return the version name of the Activity
+ */
+fun getVersionName(activity: MainActivity): String {
+ val packageInfo = activity.packageManager.getPackageInfo(activity.packageName, 0)
+ return packageInfo.versionName
+} \ No newline at end of file
diff --git a/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/TimingTests.kt b/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/TimingTests.kt
new file mode 100644
index 00000000000..b16d784cede
--- /dev/null
+++ b/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/TimingTests.kt
@@ -0,0 +1,593 @@
+/*
+ * Copyright 2019 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.antelope
+
+import androidx.camera.integration.antelope.MainActivity.Companion.cameraParams
+import androidx.camera.integration.antelope.MainActivity.Companion.logd
+import androidx.camera.integration.antelope.cameracontrollers.camera1OpenCamera
+import androidx.camera.integration.antelope.cameracontrollers.camera2OpenCamera
+import androidx.camera.integration.antelope.cameracontrollers.cameraXOpenCamera
+import androidx.camera.integration.antelope.cameracontrollers.cameraXTakePicture
+import androidx.camera.integration.antelope.cameracontrollers.closeAllCameras
+import androidx.camera.integration.antelope.cameracontrollers.closePreviewAndCamera
+import androidx.camera.integration.antelope.cameracontrollers.initializeStillCapture
+
+// Keeps track of what iteration of a repeated test is occurring
+internal var multiCounter: Int = 0
+
+internal fun initializeTest(
+ activity: MainActivity,
+ params: CameraParams?,
+ config: TestConfig
+) {
+
+ if (null == params)
+ return
+
+ // Camera1 cannot directly access physical cameras. If we try, abort.
+ if ((CameraAPI.CAMERA1 == config.api) &&
+ !(PrefHelper.getLogicalCameraIds(activity, cameraParams).contains(config.camera))) {
+ activity.resetUIAfterTest()
+ activity.updateLog("ABORTED: Camera1 API cannot access camera with id:" + config.camera,
+ false, false)
+ return
+ }
+
+ when (config.currentRunningTest) {
+ TestType.INIT -> runInitTest(activity, params, config)
+ TestType.PREVIEW ->
+ runPreviewTest(activity, params, config)
+ TestType.SWITCH_CAMERA ->
+ runSwitchTest(activity, params, config)
+ TestType.MULTI_SWITCH ->
+ runMultiSwitchTest(activity, params, config)
+ TestType.PHOTO ->
+ runPhotoTest(activity, params, config)
+ TestType.MULTI_PHOTO ->
+ runMultiPhotoTest(activity, params, config)
+ TestType.MULTI_PHOTO_CHAIN ->
+ runMultiPhotoChainTest(activity, params, config)
+ TestType.NONE -> Unit
+ }
+}
+
+/**
+ * Run the INIT test
+ */
+internal fun runInitTest(
+ activity: MainActivity,
+ params: CameraParams,
+ config: TestConfig
+) {
+
+ logd("Running init test")
+ activity.startBackgroundThread(params)
+ activity.showProgressBar(true)
+
+ closeAllCameras(activity, config)
+
+ setupImageReader(activity, params, config)
+ params.timer = CameraTimer()
+ config.currentRunningTest = TestType.INIT
+ params.timer.testStart = System.currentTimeMillis()
+ beginTest(activity, params, config)
+}
+
+/**
+ * Run the SWITCH test
+ */
+internal fun runSwitchTest(activity: MainActivity, params: CameraParams, config: TestConfig) {
+ // For switch test, always go from default back camera to default front camera and back 0->1->0
+ // TODO: Can we handle different permutations of physical cameras?
+ if (!PrefHelper.getLogicalCameraIds(activity, cameraParams).contains("0") ||
+ !PrefHelper.getLogicalCameraIds(activity, cameraParams).contains("1")) {
+ activity.resetUIAfterTest()
+ activity.updateLog("ABORTED: Camera 0 and 1 needed for Switch test.",
+ false, false)
+ return
+ }
+
+ config.switchTestCameras = arrayOf("0", "1")
+ config.switchTestCurrentCamera = "0"
+
+ logd("Running switch test")
+ logd("Starting with camera: " + config.switchTestCurrentCamera)
+ activity.startBackgroundThread(params)
+ activity.showProgressBar(true)
+
+ closeAllCameras(activity, config)
+
+ setupImageReader(activity, params, config)
+ params.timer = CameraTimer()
+ config.currentRunningTest = TestType.SWITCH_CAMERA
+ params.timer.testStart = System.currentTimeMillis()
+ beginTest(activity, params, config)
+}
+
+/**
+ * Run the MULTI_SWITCH test
+ */
+internal fun runMultiSwitchTest(activity: MainActivity, params: CameraParams, config: TestConfig) {
+ // For switch test, always go from default back camera to default front camera and back 0->1->0
+ // TODO: Can we handle different permutations of physical cameras?
+ if (!PrefHelper.getLogicalCameraIds(activity, cameraParams).contains("0") ||
+ !PrefHelper.getLogicalCameraIds(activity, cameraParams).contains("1")) {
+ activity.resetUIAfterTest()
+ activity.updateLog("ABORTED: Camera 0 and 1 needed for Switch test.",
+ false, false)
+ return
+ }
+
+ config.switchTestCameras = arrayOf("0", "1")
+
+ if (0 == multiCounter) {
+ // New test
+ logd("Running multi switch test")
+ config.switchTestCurrentCamera = "0"
+ activity.startBackgroundThread(params)
+ activity.showProgressBar(true, 0)
+ multiCounter = PrefHelper.getNumTests(activity)
+ config.currentRunningTest = TestType.MULTI_SWITCH
+ config.testFinished = false
+ } else {
+ // Add previous result
+ params.timer.testEnd = System.currentTimeMillis()
+ activity.showProgressBar(true, precentageCompleted(activity, multiCounter))
+ logd("In Multi Switch Test. Counter: " + multiCounter)
+
+ config.testResults.initialization.add(params.timer.openEnd - params.timer.openStart)
+ config.testResults.previewStart.add(params.timer.previewEnd - params.timer.previewStart)
+ config.testResults.switchToSecond
+ .add(params.timer.switchToSecondEnd - params.timer.switchToSecondStart)
+ config.testResults.switchToFirst
+ .add(params.timer.switchToFirstEnd - params.timer.switchToFirstStart)
+ config.testResults.previewClose
+ .add(params.timer.previewCloseEnd - params.timer.previewCloseStart)
+ config.testResults.cameraClose
+ .add(params.timer.cameraCloseEnd - params.timer.cameraCloseStart)
+ config.testResults.total.add(params.timer.testEnd - params.timer.testStart)
+
+ config.testFinished = false
+ config.isFirstOnActive = true
+ }
+
+ setupImageReader(activity, params, config)
+ params.timer = CameraTimer()
+ params.timer.testStart = System.currentTimeMillis()
+ beginTest(activity, params, config)
+}
+
+/**
+ * Run the PREVIEW test
+ */
+internal fun runPreviewTest(activity: MainActivity, params: CameraParams, config: TestConfig) {
+ logd("Running preview test")
+ activity.startBackgroundThread(params)
+ activity.showProgressBar(true)
+
+ closeAllCameras(activity, config)
+
+ setupImageReader(activity, params, config)
+ params.timer = CameraTimer()
+ config.currentRunningTest = TestType.PREVIEW
+ params.timer.testStart = System.currentTimeMillis()
+ beginTest(activity, params, config)
+}
+
+/**
+ * Run the PHOTO (single capture) test
+ */
+internal fun runPhotoTest(activity: MainActivity, params: CameraParams, config: TestConfig) {
+ logd("Running photo test")
+ activity.startBackgroundThread(params)
+ activity.showProgressBar(true)
+
+ closeAllCameras(activity, config)
+
+ setupImageReader(activity, params, config)
+ params.timer = CameraTimer()
+ config.currentRunningTest = TestType.PHOTO
+ params.timer.testStart = System.currentTimeMillis()
+
+ logd("About to start photo test. " + config.currentRunningTest.toString())
+ beginTest(activity, params, config)
+}
+
+/**
+ * Run the MULTI_PHOTO (repeated capture) test
+ */
+internal fun runMultiPhotoTest(activity: MainActivity, params: CameraParams, config: TestConfig) {
+ if (0 == multiCounter) {
+ // New test
+ logd("Running multi photo test")
+ activity.startBackgroundThread(params)
+ activity.showProgressBar(true, 0)
+ multiCounter = PrefHelper.getNumTests(activity)
+ config.currentRunningTest = TestType.MULTI_PHOTO
+ logd("About to start multi photo test. multi_counter: " + multiCounter + " and test: " +
+ config.currentRunningTest.toString())
+ } else {
+ // Add previous result
+ params.timer.testEnd = System.currentTimeMillis()
+ activity.showProgressBar(true, precentageCompleted(activity, multiCounter))
+ logd("In Multi Photo Test. Counter: " + multiCounter)
+
+ config.testResults.initialization.add(params.timer.openEnd - params.timer.openStart)
+ config.testResults.previewStart.add(params.timer.previewEnd - params.timer.previewStart)
+ config.testResults.previewFill
+ .add(params.timer.previewFillEnd - params.timer.previewFillStart)
+ config.testResults.autofocus.add(params.timer.autofocusEnd - params.timer.autofocusStart)
+ config.testResults.captureNoAF.add((params.timer.captureEnd - params.timer.captureStart) -
+ (params.timer.autofocusEnd - params.timer.autofocusStart))
+ config.testResults.capture.add(params.timer.captureEnd - params.timer.captureStart)
+ config.testResults.imageready
+ .add(params.timer.imageReaderEnd - params.timer.imageReaderStart)
+ config.testResults.capturePlusImageReady
+ .add((params.timer.captureEnd - params.timer.captureStart) +
+ (params.timer.imageReaderEnd - params.timer.imageReaderStart))
+ config.testResults.imagesave
+ .add(params.timer.imageSaveEnd - params.timer.imageSaveStart)
+ config.testResults.isHDRPlus.add(params.timer.isHDRPlus)
+ config.testResults.previewClose
+ .add(params.timer.previewCloseEnd - params.timer.previewCloseStart)
+ config.testResults.cameraClose
+ .add(params.timer.cameraCloseEnd - params.timer.cameraCloseStart)
+ config.testResults.total.add(params.timer.testEnd - params.timer.testStart)
+ config.testResults.totalNoPreview.add((params.timer.testEnd - params.timer.testStart) -
+ (params.timer.previewFillEnd - params.timer.previewFillStart))
+ config.testFinished = false
+ config.isFirstOnActive = true
+ config.isFirstOnCaptureComplete = true
+ }
+
+ closeAllCameras(activity, config)
+
+ setupImageReader(activity, params, config)
+ params.timer = CameraTimer()
+ params.timer.testStart = System.currentTimeMillis()
+ beginTest(activity, params, config)
+}
+
+/**
+ * Run the MULTI_PHOTO_CHAIN (multiple captures, do not close camera) test
+ */
+internal fun runMultiPhotoChainTest(
+ activity: MainActivity,
+ params: CameraParams,
+ config: TestConfig
+) {
+
+ // Cannot chain with Camera 1, run default test
+ if (CameraAPI.CAMERA1 == config.api) {
+ logd("Cannot run Chain test with Camera 1, running regular multi-test instead")
+ config.currentRunningTest = TestType.MULTI_PHOTO
+ runMultiPhotoTest(activity, params, config)
+ return
+ }
+
+ if (0 == multiCounter) {
+ // New test
+ logd("Running multi photo (chain) test")
+ activity.startBackgroundThread(params)
+ activity.showProgressBar(true, 0)
+ multiCounter = PrefHelper.getNumTests(activity)
+ params.timer = CameraTimer()
+ params.timer.testStart = System.currentTimeMillis()
+ config.currentRunningTest = TestType.MULTI_PHOTO_CHAIN
+ logd("About to start multi chain test. multi_counter: " + multiCounter + " and test: " +
+ config.currentRunningTest.toString())
+
+ closeAllCameras(activity, config)
+
+ setupImageReader(activity, params, config)
+ beginTest(activity, params, config)
+ } else {
+ // Add previous result
+ activity.showProgressBar(true, precentageCompleted(activity, multiCounter))
+
+ if (config.api == CameraAPI.CAMERA1) {
+ beginTest(activity, params, config)
+ } else {
+
+ // Camera2 and CameraX
+ config.testResults.autofocus
+ .add(params.timer.autofocusEnd - params.timer.autofocusStart)
+ config.testResults.captureNoAF
+ .add((params.timer.captureEnd - params.timer.captureStart) -
+ (params.timer.autofocusEnd - params.timer.autofocusStart))
+ config.testResults.capture.add(params.timer.captureEnd - params.timer.captureStart)
+ config.testResults.imageready
+ .add(params.timer.imageReaderEnd - params.timer.imageReaderStart)
+ config.testResults.capturePlusImageReady
+ .add((params.timer.captureEnd - params.timer.captureStart) +
+ (params.timer.imageReaderEnd - params.timer.imageReaderStart))
+ config.testResults.imagesave
+ .add(params.timer.imageSaveEnd - params.timer.imageSaveStart)
+ config.testResults.isHDRPlus.add(params.timer.isHDRPlus)
+ config.testFinished = false
+
+ params.timer.clearImageTimers()
+ config.isFirstOnCaptureComplete = true
+
+ when (config.api) {
+ CameraAPI.CAMERA2 -> initializeStillCapture(activity, params, config)
+ CameraAPI.CAMERAX -> cameraXTakePicture(activity, params, config)
+ }
+ }
+ }
+}
+
+/**
+ * A test has ended. Depending on which test and if we are at the beginning, middle or end of a
+ * repeated test, record the results and repeat/return,
+ */
+internal fun testEnded(activity: MainActivity, params: CameraParams?, config: TestConfig) {
+ if (null == params)
+ return
+ logd("In testEnded. multi_counter: " + multiCounter + " and test: " +
+ config.currentRunningTest.toString())
+
+ when (config.currentRunningTest) {
+
+ TestType.INIT -> {
+ params.timer.testEnd = System.currentTimeMillis()
+ logd("Test ended")
+ config.testResults.initialization.add(params.timer.openEnd - params.timer.openStart)
+ config.testResults.cameraClose
+ .add(params.timer.cameraCloseEnd - params.timer.cameraCloseStart)
+ config.testResults.total.add(params.timer.testEnd - params.timer.testStart)
+ }
+
+ TestType.PREVIEW -> {
+ params.timer.testEnd = System.currentTimeMillis()
+ logd("Test ended")
+ config.testResults.initialization.add(params.timer.openEnd - params.timer.openStart)
+ config.testResults.previewStart.add(params.timer.previewEnd - params.timer.previewStart)
+ config.testResults.previewClose
+ .add(params.timer.previewCloseEnd - params.timer.previewCloseStart)
+ config.testResults.cameraClose
+ .add(params.timer.cameraCloseEnd - params.timer.cameraCloseStart)
+ config.testResults.total.add(params.timer.testEnd - params.timer.testStart)
+ }
+
+ TestType.SWITCH_CAMERA -> {
+ params.timer.testEnd = System.currentTimeMillis()
+ logd("Test ended")
+ config.testResults.initialization.add(params.timer.openEnd - params.timer.openStart)
+ config.testResults.previewStart.add(params.timer.previewEnd - params.timer.previewStart)
+ config.testResults.switchToSecond
+ .add(params.timer.switchToSecondEnd - params.timer.switchToSecondStart)
+ config.testResults.switchToFirst
+ .add(params.timer.switchToFirstEnd - params.timer.switchToFirstStart)
+ config.testResults.previewClose
+ .add(params.timer.previewCloseEnd - params.timer.previewCloseStart)
+ config.testResults.cameraClose
+ .add(params.timer.cameraCloseEnd - params.timer.cameraCloseStart)
+ config.testResults.total.add(params.timer.testEnd - params.timer.testStart)
+ }
+
+ TestType.MULTI_SWITCH -> {
+ val lastResult = params.timer.captureEnd - params.timer.captureStart
+
+ if (1 == multiCounter) {
+ params.timer.testEnd = System.currentTimeMillis()
+ config.testFinished = false // Reset flag
+
+ logd("Test ended")
+ activity.showProgressBar(true, precentageCompleted(activity, multiCounter))
+
+ config.testResults.initialization.add(params.timer.openEnd - params.timer.openStart)
+ config.testResults.previewStart
+ .add(params.timer.previewEnd - params.timer.previewStart)
+ config.testResults.switchToSecond
+ .add(params.timer.switchToSecondEnd - params.timer.switchToSecondStart)
+ config.testResults.switchToFirst
+ .add(params.timer.switchToFirstEnd - params.timer.switchToFirstStart)
+ config.testResults.previewClose
+ .add(params.timer.previewCloseEnd - params.timer.previewCloseStart)
+ config.testResults.cameraClose
+ .add(params.timer.cameraCloseEnd - params.timer.cameraCloseStart)
+ config.testResults.total.add(params.timer.testEnd - params.timer.testStart)
+
+ multiCounter = 0
+ } else {
+ logd("Switch " + (Math.abs(multiCounter - PrefHelper.getNumTests(activity)) + 1) +
+ " completed.")
+ multiCounter--
+ runMultiSwitchTest(activity, params, config)
+ return
+ }
+ }
+
+ TestType.PHOTO -> {
+ params.timer.testEnd = System.currentTimeMillis()
+ logd("Test ended")
+ config.testResults.initialization.add(params.timer.openEnd - params.timer.openStart)
+ config.testResults.previewStart.add(params.timer.previewEnd - params.timer.previewStart)
+ config.testResults.previewFill
+ .add(params.timer.previewFillEnd - params.timer.previewFillStart)
+ config.testResults.autofocus
+ .add(params.timer.autofocusEnd - params.timer.autofocusStart)
+ config.testResults.captureNoAF
+ .add((params.timer.captureEnd - params.timer.captureStart) -
+ (params.timer.autofocusEnd - params.timer.autofocusStart))
+ config.testResults.capture.add(params.timer.captureEnd - params.timer.captureStart)
+ config.testResults.imageready
+ .add(params.timer.imageReaderEnd - params.timer.imageReaderStart)
+ config.testResults.capturePlusImageReady
+ .add((params.timer.captureEnd - params.timer.captureStart) +
+ (params.timer.imageReaderEnd - params.timer.imageReaderStart))
+ config.testResults.imagesave
+ .add(params.timer.imageSaveEnd - params.timer.imageSaveStart)
+ config.testResults.isHDRPlus
+ .add(params.timer.isHDRPlus)
+ config.testResults.previewClose
+ .add(params.timer.previewCloseEnd - params.timer.previewCloseStart)
+ config.testResults.cameraClose
+ .add(params.timer.cameraCloseEnd - params.timer.cameraCloseStart)
+ config.testResults.total.add(params.timer.testEnd - params.timer.testStart)
+ config.testResults.totalNoPreview.add((params.timer.testEnd - params.timer.testStart) -
+ (params.timer.previewFillEnd - params.timer.previewFillStart))
+ }
+
+ TestType.MULTI_PHOTO -> {
+ val lastResult = params.timer.captureEnd - params.timer.captureStart
+
+ if (1 == multiCounter) {
+ params.timer.testEnd = System.currentTimeMillis()
+ config.testFinished = false // Reset flag
+
+ logd("Test ended")
+ activity.showProgressBar(true, precentageCompleted(activity, multiCounter))
+
+ config.testResults.initialization.add(params.timer.openEnd - params.timer.openStart)
+ config.testResults.previewStart
+ .add(params.timer.previewEnd - params.timer.previewStart)
+ config.testResults.previewFill
+ .add(params.timer.previewFillEnd - params.timer.previewFillStart)
+ config.testResults.autofocus
+ .add(params.timer.autofocusEnd - params.timer.autofocusStart)
+ config.testResults.captureNoAF
+ .add((params.timer.captureEnd - params.timer.captureStart) -
+ (params.timer.autofocusEnd - params.timer.autofocusStart))
+ config.testResults.capture.add(params.timer.captureEnd - params.timer.captureStart)
+ config.testResults.imageready
+ .add(params.timer.imageReaderEnd - params.timer.imageReaderStart)
+ config.testResults.capturePlusImageReady
+ .add((params.timer.captureEnd - params.timer.captureStart) +
+ (params.timer.imageReaderEnd - params.timer.imageReaderStart))
+ config.testResults.imagesave
+ .add(params.timer.imageSaveEnd - params.timer.imageSaveStart)
+ config.testResults.isHDRPlus.add(params.timer.isHDRPlus)
+ config.testResults.previewClose
+ .add(params.timer.previewCloseEnd - params.timer.previewCloseStart)
+ config.testResults.cameraClose
+ .add(params.timer.cameraCloseEnd - params.timer.cameraCloseStart)
+ config.testResults.total.add(params.timer.testEnd - params.timer.testStart)
+ config.testResults.totalNoPreview
+ .add((params.timer.testEnd - params.timer.testStart) -
+ (params.timer.previewFillEnd - params.timer.previewFillStart))
+
+ closeAllCameras(activity, config)
+
+ multiCounter = 0
+ } else {
+ logd("Capture " + (Math.abs(multiCounter - PrefHelper.getNumTests(activity)) + 1) +
+ " completed: " + lastResult + "ms")
+ multiCounter--
+ runMultiPhotoTest(activity, params, config)
+ return
+ }
+ }
+
+ TestType.MULTI_PHOTO_CHAIN -> {
+ val lastResult = params.timer.captureEnd - params.timer.captureStart
+
+ if (1 == multiCounter) {
+
+ // If this is a chain test, the camera may still be open
+ if (params.isOpen) {
+ config.testFinished = true
+ closePreviewAndCamera(activity, params, config)
+ return
+ }
+
+ params.timer.testEnd = System.currentTimeMillis()
+ logd("Test ended")
+ config.testFinished = false // Reset flag
+
+ activity.showProgressBar(true, precentageCompleted(activity, multiCounter))
+
+ config.testResults.initialization.add(params.timer.openEnd -
+ params.timer.openStart)
+ config.testResults.previewStart.add(params.timer.previewEnd -
+ params.timer.previewStart)
+ config.testResults.previewFill.add(params.timer.previewFillEnd -
+ params.timer.previewFillStart)
+ config.testResults.autofocus.add(params.timer.autofocusEnd -
+ params.timer.autofocusStart)
+ config.testResults.captureNoAF
+ .add((params.timer.captureEnd - params.timer.captureStart) -
+ (params.timer.autofocusEnd - params.timer.autofocusStart))
+ config.testResults.capture.add(params.timer.captureEnd - params.timer.captureStart)
+ config.testResults.imageready.add(params.timer.imageReaderEnd -
+ params.timer.imageReaderStart)
+ config.testResults.capturePlusImageReady.add((params.timer.captureEnd -
+ params.timer.captureStart) +
+ (params.timer.imageReaderEnd - params.timer.imageReaderStart))
+ config.testResults.imagesave.add(params.timer.imageSaveEnd -
+ params.timer.imageSaveStart)
+ config.testResults.isHDRPlus.add(params.timer.isHDRPlus)
+ config.testResults.previewClose.add(params.timer.previewCloseEnd -
+ params.timer.previewCloseStart)
+ config.testResults.cameraClose.add(params.timer.cameraCloseEnd -
+ params.timer.cameraCloseStart)
+ config.testResults.total.add(params.timer.testEnd - params.timer.testStart)
+ config.testResults.totalNoPreview
+ .add((params.timer.testEnd - params.timer.testStart) -
+ (params.timer.previewFillEnd - params.timer.previewFillStart))
+
+ closeAllCameras(activity, config)
+
+ multiCounter = 0
+ } else {
+ logd("Capture " + (Math.abs(multiCounter - PrefHelper.getNumTests(activity)) + 1) +
+ " completed: " + lastResult + "ms")
+ multiCounter--
+ runMultiPhotoChainTest(activity, params, config)
+ return
+ }
+ }
+ }
+
+ multiCounter = 0
+ postTestResults(activity, config)
+}
+
+/**
+ * Calculate the percentage of repeated tests that are complete
+ */
+fun precentageCompleted(activity: MainActivity, testCounter: Int): Int {
+ return (100 * (PrefHelper.getNumTests(activity) - testCounter)) /
+ PrefHelper.getNumTests(activity)
+}
+
+/**
+ * Test is configured, begin it based on the API in the test config
+ */
+internal fun beginTest(activity: MainActivity, params: CameraParams?, testConfig: TestConfig) {
+ if (null == params)
+ return
+
+ when (testConfig.api) {
+ CameraAPI.CAMERA1 -> {
+ // Camera 1 doesn't have its own threading built-in
+ val runnable = Runnable {
+ camera1OpenCamera(activity, params, testConfig)
+ }
+ params.backgroundHandler?.post(runnable)
+ }
+
+ CameraAPI.CAMERA2 -> {
+ camera2OpenCamera(activity, params, testConfig)
+ }
+
+ CameraAPI.CAMERAX -> {
+ cameraXOpenCamera(activity, params, testConfig)
+ }
+ }
+}
diff --git a/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/cameracontrollers/Camera1Controller.kt b/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/cameracontrollers/Camera1Controller.kt
new file mode 100644
index 00000000000..ca8bbfabef4
--- /dev/null
+++ b/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/cameracontrollers/Camera1Controller.kt
@@ -0,0 +1,306 @@
+/*
+ * Copyright 2019 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.antelope.cameracontrollers
+
+import android.hardware.Camera
+import android.util.Size
+import androidx.camera.integration.antelope.CameraParams
+import androidx.camera.integration.antelope.CompareSizesByArea
+import androidx.camera.integration.antelope.FocusMode
+import androidx.camera.integration.antelope.ImageCaptureSize
+import androidx.camera.integration.antelope.MainActivity
+import androidx.camera.integration.antelope.MainActivity.Companion.logd
+import androidx.camera.integration.antelope.PrefHelper
+import androidx.camera.integration.antelope.TestConfig
+import androidx.camera.integration.antelope.TestType
+import androidx.camera.integration.antelope.testEnded
+import androidx.camera.integration.antelope.writeFile
+import java.util.Collections
+
+internal var camera1: Camera? = null
+
+/**
+ * Opens the camera using the Camera1 API and measures the open time synchronously. For init tests,
+ * this is the only measurement needed so end the test. Otherwise, move on to open the camera
+ * preview.
+ */
+fun camera1OpenCamera(activity: MainActivity, params: CameraParams, testConfig: TestConfig) {
+ try {
+ logd("openCamera: " + params.id)
+ params.isOpen = true
+ params.timer.openStart = System.currentTimeMillis()
+
+ logd("Camera1Switch Open camera: " + testConfig.switchTestCurrentCamera.toInt())
+
+ if ((testConfig.currentRunningTest == TestType.SWITCH_CAMERA) ||
+ (testConfig.currentRunningTest == TestType.MULTI_SWITCH))
+ camera1 = Camera.open(testConfig.switchTestCurrentCamera.toInt())
+ else
+ camera1 = Camera.open(testConfig.camera.toInt())
+
+ params.timer.openEnd = System.currentTimeMillis()
+
+ // Due to the synchronous nature of Camera1, set up Camera1 specific parameters here
+ val camera1Params: Camera.Parameters? = camera1?.parameters
+ params.cam1AFSupported =
+ camera1Params?.supportedFocusModes?.contains(Camera.Parameters.FOCUS_MODE_AUTO)
+ ?: false
+
+ when (testConfig.currentRunningTest) {
+ TestType.INIT -> {
+ // Camera opened, we're done
+ testEnded(activity, params, testConfig)
+ }
+
+ else -> {
+ startCamera1Preview(activity, params, testConfig)
+ }
+ }
+ } catch (e: Exception) {
+ logd("camera1OpenCamera exception: " + params.id + ". Error: " + e.printStackTrace())
+ camera1 = null
+ }
+}
+
+/**
+ * Begin the preview using the Camera 1 API and synchronously measure the time to begin the stream.
+ */
+fun startCamera1Preview(activity: MainActivity, params: CameraParams, testConfig: TestConfig) {
+ val camera1Params: Camera.Parameters? = camera1?.parameters
+
+ params.cam1AFSupported =
+ camera1Params?.supportedFocusModes?.contains(Camera.Parameters.FOCUS_MODE_AUTO) ?: false
+
+ // Get Camera1 image capture sizes
+ // Cannot be done this before the camera is device opened
+ val cam1Sizes = camera1Params?.getSupportedPictureSizes()
+ if (null != cam1Sizes) {
+ val saneSizes: ArrayList<Size> = ArrayList()
+
+ for (size in cam1Sizes) {
+ saneSizes.add(Size(size.width, size.height))
+ }
+
+ params.cam1MaxSize = Collections.max(saneSizes, CompareSizesByArea())
+ params.cam1MinSize = Collections.min(saneSizes, CompareSizesByArea())
+ }
+
+ if (ImageCaptureSize.MIN == testConfig.imageCaptureSize)
+ camera1Params?.setPictureSize(params.cam1MinSize.width, params.cam1MinSize.height)
+ else
+ camera1Params?.setPictureSize(params.cam1MaxSize.width, params.cam1MaxSize.height)
+
+ if (params.cam1AFSupported) {
+ if (FocusMode.CONTINUOUS == testConfig.focusMode)
+ camera1Params?.focusMode = Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE
+ else
+ camera1Params?.focusMode = Camera.Parameters.FOCUS_MODE_AUTO
+ } else {
+ camera1Params?.focusMode = Camera.Parameters.FOCUS_MODE_FIXED
+ }
+
+ // After changing camera parameters, set them
+ camera1?.parameters = camera1Params
+
+ logd("startCamera1Preview: starting Camera1 preview.")
+
+ params.isPreviewing = true
+ params.timer.previewStart = System.currentTimeMillis()
+ camera1?.startPreview()
+ camera1?.setPreviewDisplay(params.previewSurfaceView?.holder)
+ params.timer.previewEnd = System.currentTimeMillis()
+
+ when (testConfig.currentRunningTest) {
+ TestType.PREVIEW -> {
+ testConfig.testFinished = true
+ closePreviewAndCamera(activity, params, testConfig)
+ }
+
+ TestType.SWITCH_CAMERA, TestType.MULTI_SWITCH -> {
+ logd("Camera1Switch preview running:")
+ if (testConfig.switchTestCurrentCamera == testConfig.switchTestCameras.get(0)) {
+ if (testConfig.testFinished) {
+ logd("Camera1Switch preview. On 1st camera, test finished. Closing 1st camera")
+ params.timer.switchToFirstEnd = System.currentTimeMillis()
+ Thread.sleep(PrefHelper.getPreviewBuffer(activity)) // Let preview run
+ closePreviewAndCamera(activity, params, testConfig)
+ } else {
+ logd("Camera1Switch preview. On 1st camera, Closing 1st camera, then open 2nd")
+ Thread.sleep(PrefHelper.getPreviewBuffer(activity)) // Let preview run
+ params.timer.switchToSecondStart = System.currentTimeMillis()
+ closePreviewAndCamera(activity, params, testConfig)
+ }
+ } else {
+ logd("Camera1Switch preview. On 2nd camera. Closing, ready to open first 1st " +
+ "camera")
+ params.timer.switchToSecondEnd = System.currentTimeMillis()
+ Thread.sleep(PrefHelper.getPreviewBuffer(activity)) // Let preview run
+ params.timer.switchToFirstStart = System.currentTimeMillis()
+ closePreviewAndCamera(activity, params, testConfig)
+ }
+ }
+
+ TestType.NONE -> {
+ closeAllCameras(activity, testConfig)
+ }
+
+ else -> {
+ camera1TakePicturePrep(activity, params, testConfig)
+ }
+ }
+}
+
+/**
+ * Set up timers and focus mode for taking a picture with the Camera 1 API. If auto-focus is
+ * requested, begin the auto-focus timer and asynchronously begin the auto-focus routine.
+ */
+fun camera1TakePicturePrep(activity: MainActivity, params: CameraParams, testConfig: TestConfig) {
+ if (params.timer.isFirstPhoto) {
+ logd("camera1TakePicturePrep: 1st photo in multi-chain test. Pausing for " +
+ PrefHelper.getPreviewBuffer(activity) + "ms to let preview run.")
+ params.timer.previewFillStart = System.currentTimeMillis()
+ Thread.sleep(PrefHelper.getPreviewBuffer(activity))
+ params.timer.previewFillEnd = System.currentTimeMillis()
+ params.timer.isFirstPhoto = false
+ }
+
+ params.timer.captureStart = System.currentTimeMillis()
+
+ if (params.cam1AFSupported &&
+ FocusMode.AUTO == testConfig.focusMode) {
+ MainActivity.logd("camera1TakePicturePrep: starting autofocus.")
+ params.timer.autofocusStart = System.currentTimeMillis()
+ camera1?.autoFocus(Camera1AutofocusCallback(activity, params, testConfig))
+ } else {
+ camera1TakePicture(activity, params, testConfig)
+ }
+}
+
+/**
+ * Initiate the capture request
+ */
+fun camera1TakePicture(activity: MainActivity, params: CameraParams, testConfig: TestConfig) {
+ val camera1JpegCallback = Camera1PictureCallback(activity, params, testConfig)
+
+ try {
+ MainActivity.logd("camera1TakePicture: capture start. ")
+ camera1?.takePicture(null, null, camera1JpegCallback)
+ } catch (e: RuntimeException) {
+ MainActivity.logd("camera1TakePicture: runtime exception: " + e.printStackTrace())
+ }
+}
+
+/**
+ * Close preview stream and camera device. If this is a switch test, begin the next step
+ */
+fun camera1CloseCamera(activity: MainActivity, params: CameraParams?, testConfig: TestConfig) {
+ if (params == null)
+ return
+
+ if (params.isPreviewing) {
+ params.timer.previewCloseStart = System.currentTimeMillis()
+ camera1?.stopPreview()
+ params.timer.previewCloseEnd = System.currentTimeMillis()
+ params.isPreviewing = false
+ }
+
+ params.timer.cameraCloseStart = System.currentTimeMillis()
+ camera1?.release()
+ params.timer.cameraCloseEnd = System.currentTimeMillis()
+ params.isOpen = false
+
+ logd("Camera 1 Close camera: camera released.")
+
+ if (testConfig.testFinished) {
+ logd("Camera 1 Close camera: Test finished, returning")
+ testEnded(activity, params, testConfig)
+ return
+ }
+
+ when (testConfig.currentRunningTest) {
+ TestType.SWITCH_CAMERA, TestType.MULTI_SWITCH -> {
+ logd("Camera1Switch Close camera")
+ // First camera closed, now start the second
+ if (testConfig.switchTestCurrentCamera == testConfig.switchTestCameras.get(0)) {
+ testConfig.switchTestCurrentCamera = testConfig.switchTestCameras.get(1)
+ logd("Camera1Switch Close camera 1st camera is closed, opening the second")
+ camera1OpenCamera(activity, params, testConfig)
+ }
+
+ // Second camera closed, now start the first
+ else if (testConfig.switchTestCurrentCamera == testConfig.switchTestCameras.get(1)) {
+ logd("Camera1Switch Close camera 2nd camera is closed, opening the first")
+ testConfig.switchTestCurrentCamera = testConfig.switchTestCameras.get(0)
+ testConfig.testFinished = true
+ camera1OpenCamera(activity, params, testConfig)
+ }
+ }
+ else -> {
+ Unit // no-op
+ }
+ }
+}
+
+/**
+ * Auto-focus is complete, record the elapsed time and request the capture
+ */
+class Camera1AutofocusCallback internal constructor(
+ internal val activity: MainActivity,
+ internal val params: CameraParams,
+ internal val testConfig: TestConfig
+) : Camera.AutoFocusCallback {
+
+ override fun onAutoFocus(p0: Boolean, p1: Camera?) {
+ MainActivity.logd("camera1AutofocusCallback: autofocus complete.")
+ params.timer.autofocusEnd = System.currentTimeMillis()
+ camera1TakePicture(activity, params, testConfig)
+ }
+}
+
+/**
+ * Image capture has completed. Record the time taken, synchronously write file to disk and measure
+ * the time required. This test run is finished, call to finalize test or continue the test run.
+ */
+class Camera1PictureCallback internal constructor(
+ internal val activity: MainActivity,
+ internal val params: CameraParams,
+ internal val testConfig: TestConfig
+) : Camera.PictureCallback {
+
+ override fun onPictureTaken(bytes: ByteArray?, p1: Camera?) {
+
+ params.timer.captureEnd = System.currentTimeMillis()
+
+ // With the Camera1 API, calling takePicture() stops the preview. In order to make sure the
+ // close timings are comparable across APIs, restart it here.
+ camera1?.startPreview()
+
+ logd("in Camera1PictureCallback onPictureTaken")
+
+ params.timer.imageReaderStart = System.currentTimeMillis()
+ params.timer.imageReaderEnd = System.currentTimeMillis()
+ params.timer.imageSaveStart = System.currentTimeMillis()
+
+ if (null != bytes)
+ writeFile(activity, bytes)
+
+ params.timer.imageSaveEnd = System.currentTimeMillis()
+
+ testConfig.testFinished = true
+ closePreviewAndCamera(activity, params, testConfig)
+ }
+} \ No newline at end of file
diff --git a/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/cameracontrollers/Camera2CaptureCallback.kt b/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/cameracontrollers/Camera2CaptureCallback.kt
new file mode 100644
index 00000000000..84c94ac7f42
--- /dev/null
+++ b/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/cameracontrollers/Camera2CaptureCallback.kt
@@ -0,0 +1,134 @@
+/*
+ * Copyright 2019 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.antelope.cameracontrollers
+
+import android.hardware.camera2.CameraCaptureSession
+import android.hardware.camera2.CaptureFailure
+import android.hardware.camera2.CaptureRequest
+import android.hardware.camera2.CaptureResult
+import android.hardware.camera2.TotalCaptureResult
+import android.view.Surface
+import androidx.annotation.NonNull
+import androidx.camera.integration.antelope.CameraParams
+import androidx.camera.integration.antelope.MainActivity
+import androidx.camera.integration.antelope.TestConfig
+import androidx.camera.integration.antelope.TestType
+import androidx.camera.integration.antelope.testEnded
+
+/**
+ * Image capture callback for Camera 2 API. Tracks state of an image capture request.
+ */
+class Camera2CaptureCallback(
+ internal val activity: MainActivity,
+ internal val params: CameraParams,
+ internal val testConfig: TestConfig
+) : CameraCaptureSession.CaptureCallback() {
+
+ override fun onCaptureSequenceAborted(session: CameraCaptureSession?, sequenceId: Int) {
+ MainActivity.logd("captureStillPicture captureCallback: Sequence aborted. Current test: " +
+ testConfig.currentRunningTest.toString())
+ super.onCaptureSequenceAborted(session, sequenceId)
+ }
+
+ override fun onCaptureFailed(
+ session: CameraCaptureSession?,
+ request: CaptureRequest?,
+ failure: CaptureFailure?
+ ) {
+
+ if (!params.isOpen) {
+ return
+ }
+
+ MainActivity.logd("captureStillPicture captureCallback: Capture Failed. Failure: " +
+ failure?.reason + " Current test: " + testConfig.currentRunningTest.toString())
+
+ // The session failed. Let's just try again (yay infinite loops)
+ closePreviewAndCamera(activity, params, testConfig)
+ camera2OpenCamera(activity, params, testConfig)
+ super.onCaptureFailed(session, request, failure)
+ }
+
+ override fun onCaptureStarted(
+ session: CameraCaptureSession?,
+ request: CaptureRequest?,
+ timestamp: Long,
+ frameNumber: Long
+ ) {
+ MainActivity.logd("captureStillPicture captureCallback: Capture Started. Current test: " +
+ testConfig.currentRunningTest.toString() + ", frame number: " + frameNumber)
+ super.onCaptureStarted(session, request, timestamp, frameNumber)
+ }
+
+ override fun onCaptureProgressed(
+ session: CameraCaptureSession?,
+ request: CaptureRequest?,
+ partialResult: CaptureResult?
+ ) {
+ MainActivity.logd("captureStillPicture captureCallback: Capture progressed. " +
+ "Current test: " + testConfig.currentRunningTest.toString())
+ super.onCaptureProgressed(session, request, partialResult)
+ }
+
+ override fun onCaptureBufferLost(
+ session: CameraCaptureSession?,
+ request: CaptureRequest?,
+ target: Surface?,
+ frameNumber: Long
+ ) {
+ MainActivity.logd("captureStillPicture captureCallback: Buffer lost. Current test: " +
+ testConfig.currentRunningTest.toString())
+ super.onCaptureBufferLost(session, request, target, frameNumber)
+ }
+
+ override fun onCaptureCompleted(
+ @NonNull session: CameraCaptureSession,
+ @NonNull request: CaptureRequest,
+ @NonNull result: TotalCaptureResult
+ ) {
+
+ if (!params.isOpen) {
+ return
+ }
+
+ MainActivity.logd("Camera2 onCaptureCompleted. CaptureEnd. Current test: " +
+ testConfig.currentRunningTest.toString())
+
+ params.timer.captureEnd = System.currentTimeMillis()
+
+ params.captureRequestBuilder?.removeTarget(params.imageReader?.surface)
+
+ // ImageReader might get the image before this callback is called, if so, the test is done
+ if (0L != params.timer.imageSaveEnd) {
+ params.timer.imageReaderStart = params.timer.imageReaderEnd // No ImageReader delay
+ MainActivity.logd("Camera2 onCaptureCompleted. Image already saved. " +
+ "Ending Test and closing camera.")
+
+ if (TestType.MULTI_PHOTO_CHAIN == testConfig.currentRunningTest) {
+ testEnded(activity, params, testConfig)
+ } else {
+ testConfig.testFinished = true
+ closePreviewAndCamera(activity, params, testConfig)
+ }
+
+ // Otherwise the test isn't done until the image appears in the reader
+ } else {
+ MainActivity.logd("Camera2 onCaptureCompleted. Still waiting on imageReader.")
+ params.timer.imageReaderStart = System.currentTimeMillis()
+ }
+ }
+} \ No newline at end of file
diff --git a/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/cameracontrollers/Camera2CaptureSessionCallback.kt b/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/cameracontrollers/Camera2CaptureSessionCallback.kt
new file mode 100644
index 00000000000..458eaa233ba
--- /dev/null
+++ b/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/cameracontrollers/Camera2CaptureSessionCallback.kt
@@ -0,0 +1,208 @@
+/*
+ * Copyright 2019 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.antelope.cameracontrollers
+
+import android.hardware.camera2.CameraCaptureSession
+import android.hardware.camera2.CaptureFailure
+import android.hardware.camera2.CaptureRequest
+import android.hardware.camera2.CaptureResult
+import android.hardware.camera2.TotalCaptureResult
+import androidx.annotation.NonNull
+import androidx.camera.integration.antelope.CameraParams
+import androidx.camera.integration.antelope.MainActivity
+import androidx.camera.integration.antelope.TestConfig
+
+/**
+ * Callbacks that track an image capture session, including progress of auto-focus and
+ * auto-exposure routines.
+ *
+ * In general these callbacks encompass the intermediate states of the camera after a preview stream
+ * is running but before an actual image capture is performed.
+ */
+class Camera2CaptureSessionCallback(
+ internal val activity: MainActivity,
+ internal var params: CameraParams,
+ internal var testConfig: TestConfig
+) : CameraCaptureSession.CaptureCallback() {
+
+ override fun onCaptureSequenceCompleted(
+ session: CameraCaptureSession?,
+ sequenceId: Int,
+ frameNumber: Long
+ ) {
+ MainActivity.logd("Camera2CaptureSessionCallback : Capture sequence COMPLETED")
+ super.onCaptureSequenceCompleted(session, sequenceId, frameNumber)
+ }
+
+ override fun onCaptureSequenceAborted(session: CameraCaptureSession?, sequenceId: Int) {
+ MainActivity.logd("Camera2CaptureSessionCallback : Capture sequence ABORTED")
+ super.onCaptureSequenceAborted(session, sequenceId)
+ }
+
+ override fun onCaptureFailed(
+ session: CameraCaptureSession?,
+ request: CaptureRequest?,
+ failure: CaptureFailure?
+ ) {
+ MainActivity.logd("Camera2CaptureSessionCallback : Capture sequence FAILED - " +
+ failure?.reason)
+
+ if (!params.isOpen) {
+ return
+ }
+
+ // There has been a device failure, a restart might help
+ closePreviewAndCamera(activity, params, testConfig)
+ camera2OpenCamera(activity, params, testConfig)
+ }
+
+ private fun process(result: CaptureResult) {
+ // MainActivity.logd("Camera2CaptureSessionCallback in process")
+ if (!params.isOpen) {
+ return
+ }
+
+ when (params.state) {
+ // Preview is running normally. Nothing to do
+ CameraState.PREVIEW_RUNNING -> {
+ // MainActivity.logd("Camera2CaptureSessionCallback : PREVIEW_RUNNING
+ }
+
+ // We are waiting for AF and AE to converge, check if this has happened
+ CameraState.WAITING_FOCUS_LOCK -> {
+ val afState = result.get(CaptureResult.CONTROL_AF_STATE)
+ MainActivity.logd("Camera2CaptureSessionCallback: STATE_WAITING_LOCK, afstate == " +
+ afState + ", frame number: " + result.frameNumber)
+
+ when (afState) {
+ null -> {
+ MainActivity.logd("Camera2CaptureSessionCallback: STATE_WAITING_LOCK, " +
+ "afState == null, Calling captureStillPicture!")
+ params.state = CameraState.IMAGE_REQUESTED
+ captureStillPicture(activity, params, testConfig)
+ }
+
+ // Waiting for a focus lock but the AF mechanism is inactive. Hopefully after
+ // waiting a few frames it will start up
+ CaptureResult.CONTROL_AF_STATE_INACTIVE -> {
+ // CONTROL_AF_STATE_INACTIVE should be a short-lived state (2-3 frames). On
+ // some devices this can be longer or get stuck indefinitely. If AF has not
+ // started after 50 frames, just run the capture.
+ if (params.autoFocusStuckCounter++ > 50) {
+ MainActivity.logd("Camera2CaptureSessionCallback : " +
+ "STATE_WAITING_LOCK, AF is stuck! Calling captureStillPicture!")
+ params.state = CameraState.IMAGE_REQUESTED
+ captureStillPicture(activity, params, testConfig)
+ }
+ }
+
+ CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED,
+ CaptureResult.CONTROL_AF_STATE_PASSIVE_FOCUSED,
+ CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED -> {
+ // AF is locked, check AE. Note CONTROL_AE_STATE can be null on some devices
+ val aeState = result.get(CaptureResult.CONTROL_AE_STATE)
+ if (aeState == null ||
+ aeState == CaptureResult.CONTROL_AE_STATE_CONVERGED) {
+ MainActivity.logd("Camera2CaptureSessionCallback : " +
+ "STATE_WAITING_LOCK, AF and AE converged! " +
+ "Calling captureStillPicture!")
+ params.state = CameraState.IMAGE_REQUESTED
+ captureStillPicture(activity, params, testConfig)
+ } else {
+ // AF is locked but not AE
+ runPrecaptureSequence(activity, params, testConfig)
+ }
+ }
+
+ CaptureResult.CONTROL_AF_STATE_PASSIVE_SCAN,
+ CaptureResult.CONTROL_AF_STATE_PASSIVE_UNFOCUSED,
+ CaptureResult.CONTROL_AF_STATE_ACTIVE_SCAN -> {
+ Unit // no-op, keep waiting for lock
+ }
+ }
+ }
+
+ // Waiting on AE metering
+ CameraState.WAITING_EXPOSURE_LOCK -> {
+ val aeState = result.get(CaptureResult.CONTROL_AE_STATE)
+
+ // No aeState on this device, just do the capture
+ if (aeState == null) {
+ MainActivity.logd("Camera2CaptureSessionCallback : STATE_WAITING_PRECAPTURE, " +
+ "aeState == null, Calling captureStillPicture!")
+ params.state = CameraState.IMAGE_REQUESTED
+ captureStillPicture(activity, params, testConfig)
+ } else when (aeState) {
+ // Still metering, keep waiting
+ CaptureResult.CONTROL_AE_STATE_PRECAPTURE,
+ CaptureResult.CONTROL_AE_STATE_SEARCHING
+ -> Unit // no-op
+
+ // AE converged, double check AF is good
+ CaptureResult.CONTROL_AE_STATE_CONVERGED,
+ CaptureResult.CONTROL_AE_STATE_LOCKED
+ -> params.state = CameraState.WAITING_FOCUS_LOCK
+
+ // If we need a flash, begin the capture
+ // If AE is INACTIVE, it's in an unusual state (AF locked but AE starting up),
+ // just do the capture to avoid getting stuck.
+ CaptureResult.CONTROL_AE_STATE_FLASH_REQUIRED,
+ CaptureResult.CONTROL_AE_STATE_INACTIVE -> {
+ MainActivity.logd("Camera2CaptureSessionCallback : " +
+ "STATE_WAITING_PRECAPTURE, aeState: " + aeState +
+ ", AE stuck or needs flash, calling captureStillPicture!")
+ params.state = CameraState.IMAGE_REQUESTED
+ captureStillPicture(activity, params, testConfig)
+ }
+ }
+ }
+ }
+ }
+
+ /** Unused but retained here as it can be useful for debugging */
+ override fun onCaptureStarted(
+ session: CameraCaptureSession,
+ request: CaptureRequest,
+ timestamp: Long,
+ frameNumber: Long
+ ) {
+ // MainActivity.logd("Camera2CaptureSessionCallback captureCallback: Capture Started.")
+ super.onCaptureStarted(session, request, timestamp, frameNumber)
+ }
+
+ /** Both onCaptureProgressed and onCaptureComplete call through to the processing function */
+ override fun onCaptureProgressed(
+ @NonNull session: CameraCaptureSession,
+ @NonNull request: CaptureRequest,
+ @NonNull partialResult: CaptureResult
+ ) {
+ // MainActivity.logd("Camera2CaptureSessionCallback captureCallback: onCaptureProgressed, " +
+ // "partial result frame number: " + partialResult.frameNumber)
+ process(partialResult)
+ }
+
+ /** Both onCaptureProgressed and onCaptureComplete call through to the processing function */
+ override fun onCaptureCompleted(
+ @NonNull session: CameraCaptureSession,
+ @NonNull request: CaptureRequest,
+ @NonNull result: TotalCaptureResult
+ ) {
+ // MainActivity.logd("Camera2CaptureSessionCallback captureCallback: onCaptureCompleted." +
+ // " Total result frame number: " + result.frameNumber)
+ process(result)
+ }
+} \ No newline at end of file
diff --git a/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/cameracontrollers/Camera2Controller.kt b/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/cameracontrollers/Camera2Controller.kt
new file mode 100644
index 00000000000..97212167de6
--- /dev/null
+++ b/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/cameracontrollers/Camera2Controller.kt
@@ -0,0 +1,306 @@
+/*
+ * Copyright 2019 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.antelope.cameracontrollers
+
+import android.content.Context
+import android.hardware.camera2.CameraAccessException
+import android.hardware.camera2.CameraDevice
+import android.hardware.camera2.CameraManager
+import android.hardware.camera2.CameraMetadata
+import android.hardware.camera2.CaptureRequest
+import androidx.camera.integration.antelope.CameraParams
+import androidx.camera.integration.antelope.FocusMode
+import androidx.camera.integration.antelope.MainActivity
+import androidx.camera.integration.antelope.MainActivity.Companion.logd
+import androidx.camera.integration.antelope.PrefHelper
+import androidx.camera.integration.antelope.TestConfig
+import androidx.camera.integration.antelope.TestType
+import androidx.camera.integration.antelope.getOrientation
+import androidx.camera.integration.antelope.setAutoFlash
+import java.lang.Thread.sleep
+import java.util.Arrays
+
+/** State of the camera during an image capture - */
+internal enum class CameraState {
+ UNINITIALIZED,
+ PREVIEW_RUNNING,
+ WAITING_FOCUS_LOCK,
+ WAITING_EXPOSURE_LOCK,
+ IMAGE_REQUESTED
+}
+
+/**
+ * Opens the camera using the Camera 2 API and starts the open counter. The open call will complete
+ * in the DeviceStateCallback asynchronously. For switch tests, the camera id will be swizzling so
+ * the original camera id is saved.
+ */
+fun camera2OpenCamera(activity: MainActivity, params: CameraParams?, testConfig: TestConfig) {
+ if (null == params)
+ return
+
+ val manager = activity.getSystemService(Context.CAMERA_SERVICE) as CameraManager
+ try {
+ // TODO make the switch test methodology more robust and handle physical cameras
+ if ((testConfig.currentRunningTest == TestType.SWITCH_CAMERA) ||
+ (testConfig.currentRunningTest == TestType.MULTI_SWITCH)) {
+ testConfig.switchTestRealCameraId = params.id // Save the original camera ID
+ params.id = testConfig.switchTestCurrentCamera
+ }
+
+ // Might be a new test, update callbacks to match the test config
+ params.camera2DeviceStateCallback = Camera2DeviceStateCallback(params, activity, testConfig)
+ params.camera2CaptureSessionCallback =
+ Camera2CaptureSessionCallback(activity, params, testConfig)
+
+ params.timer.openStart = System.currentTimeMillis()
+ logd("openCamera: " + params.id + " running test: " +
+ testConfig.currentRunningTest.toString())
+
+ manager.openCamera(params.id, params.camera2DeviceStateCallback, params.backgroundHandler)
+ } catch (e: CameraAccessException) {
+ logd("openCamera CameraAccessException: " + params.id)
+ e.printStackTrace()
+ } catch (e: SecurityException) {
+ logd("openCamera SecurityException: " + params.id)
+ e.printStackTrace()
+ }
+}
+
+/**
+ * Setup the camera preview session and output surface.
+ */
+fun createCameraPreviewSession(
+ activity: MainActivity,
+ params: CameraParams,
+ testConfig: TestConfig
+) {
+
+ logd("In createCameraPreviewSession.")
+ if (!params.isOpen) {
+ return
+ }
+
+ try {
+ val surface = params.previewSurfaceView?.holder?.surface
+ if (null == surface)
+ return
+
+ val imageSurface = params.imageReader?.surface
+ if (null == imageSurface)
+ return
+
+ params.captureRequestBuilder =
+ params.device?.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW)
+
+ params.captureRequestBuilder?.removeTarget(params.previewSurfaceView?.holder?.surface)
+ params.captureRequestBuilder?.addTarget(surface)
+
+ params.device?.createCaptureSession(Arrays.asList(surface, imageSurface),
+ Camera2PreviewSessionStateCallback(activity, params, testConfig), null)
+ } catch (e: CameraAccessException) {
+ MainActivity.logd("createCameraPreviewSession CameraAccessException: " + e.message)
+ e.printStackTrace()
+ } catch (e: IllegalStateException) {
+ MainActivity.logd("createCameraPreviewSession IllegalStateException: " + e.message)
+ e.printStackTrace()
+ }
+}
+
+/**
+ * Set up timers for a still capture. The preview stream is allowed to run here in order to fill the
+ * pipeline with images to simulate more realistic camera conditions.
+ */
+fun initializeStillCapture(activity: MainActivity, params: CameraParams, testConfig: TestConfig) {
+ logd("TakePicture: capture start.")
+
+ if (!params.isOpen) {
+ return
+ }
+
+ if (params.timer.isFirstPhoto) {
+ params.timer.isFirstPhoto = false
+ }
+
+ logd("Camera2 initializeStillCapture: 1st photo in a multi-photo test. " +
+ "Pausing for " + PrefHelper.getPreviewBuffer(activity) + "ms to let preview run.")
+ params.timer.previewFillStart = System.currentTimeMillis()
+ sleep(PrefHelper.getPreviewBuffer(activity))
+ params.timer.previewFillEnd = System.currentTimeMillis()
+
+ params.timer.captureStart = System.currentTimeMillis()
+ params.timer.autofocusStart = System.currentTimeMillis()
+ lockFocus(activity, params, testConfig)
+}
+
+/**
+ * Initiate the auto-focus routine if required.
+ */
+fun lockFocus(activity: MainActivity, params: CameraParams, testConfig: TestConfig) {
+ logd("In lockFocus.")
+ if (!params.isOpen) {
+ return
+ }
+
+ try {
+ if (null != params.device) {
+ setAutoFlash(params, params.captureRequestBuilder)
+ params.captureRequestBuilder?.addTarget(params.imageReader?.surface)
+
+ // If this lens can focus, we need to start a focus search and wait for focus lock
+ if (params.hasAF &&
+ FocusMode.AUTO == testConfig.focusMode) {
+ logd("In lockFocus. About to request focus lock and call capture.")
+
+ params.captureRequestBuilder?.set(CaptureRequest.CONTROL_AF_MODE,
+ CaptureRequest.CONTROL_AF_MODE_AUTO)
+ params.captureRequestBuilder?.set(CaptureRequest.CONTROL_AF_TRIGGER,
+ CameraMetadata.CONTROL_AF_TRIGGER_CANCEL)
+ params.camera2CaptureSession?.capture(params.captureRequestBuilder?.build(),
+ params.camera2CaptureSessionCallback, params.backgroundHandler)
+
+ params.captureRequestBuilder?.set(CaptureRequest.CONTROL_AF_MODE,
+ CaptureRequest.CONTROL_AF_MODE_AUTO)
+ params.captureRequestBuilder?.set(CaptureRequest.CONTROL_AF_TRIGGER,
+ CameraMetadata.CONTROL_AF_TRIGGER_START)
+
+ params.state = CameraState.WAITING_FOCUS_LOCK
+
+ params.autoFocusStuckCounter = 0
+ params.camera2CaptureSession?.capture(params.captureRequestBuilder?.build(),
+ params.camera2CaptureSessionCallback, params.backgroundHandler)
+ } else {
+ // If no auto-focus requested, go ahead to the still capture routine
+ logd("In lockFocus. Fixed focus or continuous focus, calling captureStillPicture.")
+ params.state = CameraState.IMAGE_REQUESTED
+ captureStillPicture(activity, params, testConfig)
+ }
+ }
+ } catch (e: CameraAccessException) {
+ e.printStackTrace()
+ }
+}
+
+/**
+ * Request pre-capture auto-exposure (AE) metering
+ */
+fun runPrecaptureSequence(activity: MainActivity, params: CameraParams, testConfig: TestConfig) {
+ if (!params.isOpen) {
+ return
+ }
+
+ try {
+ if (null != params.device) {
+ setAutoFlash(params, params.captureRequestBuilder)
+ params.captureRequestBuilder?.set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER,
+ CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_START)
+
+ params.state = CameraState.WAITING_EXPOSURE_LOCK
+ params.camera2CaptureSession?.capture(params.captureRequestBuilder?.build(),
+ params.camera2CaptureSessionCallback, params.backgroundHandler)
+ }
+ } catch (e: CameraAccessException) {
+ e.printStackTrace()
+ }
+}
+
+/**
+ * Make a still capture request. At this point, AF and AE should be converged or unnecessary.
+ */
+fun captureStillPicture(activity: MainActivity, params: CameraParams, testConfig: TestConfig) {
+ if (!params.isOpen) {
+ return
+ }
+
+ try {
+ logd("In captureStillPicture. Current test: " + testConfig.currentRunningTest.toString())
+
+ if (null != params.device) {
+ params.timer.autofocusEnd = System.currentTimeMillis()
+
+ params.captureRequestBuilder =
+ params.device?.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE)
+ params.captureRequestBuilder?.addTarget(params.imageReader?.surface)
+
+ when (testConfig.focusMode) {
+ FocusMode.CONTINUOUS -> {
+ params.captureRequestBuilder?.set(CaptureRequest.CONTROL_AF_MODE,
+ CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE)
+ }
+ FocusMode.AUTO -> {
+ params.captureRequestBuilder?.set(CaptureRequest.CONTROL_AF_TRIGGER,
+ CameraMetadata.CONTROL_AF_TRIGGER_IDLE)
+ }
+ FocusMode.FIXED -> {
+ }
+ }
+
+ // Disable HDR+ for Pixel devices
+ // This is a hack, Pixel devices do not have Sepia mode, but this forces HDR+ off
+ if (android.os.Build.MANUFACTURER.equals("Google")) {
+ // params.captureRequestBuilder?.set(CaptureRequest.CONTROL_EFFECT_MODE,
+ // CaptureRequest.CONTROL_EFFECT_MODE_SEPIA)
+ }
+
+ // Orientation
+ val rotation = activity.windowManager.defaultDisplay.rotation
+ val capturedImageRotation = getOrientation(params, rotation)
+ params.captureRequestBuilder
+ ?.set(CaptureRequest.JPEG_ORIENTATION, capturedImageRotation)
+
+ // Flash
+ setAutoFlash(params, params.captureRequestBuilder)
+
+ val captureCallback = Camera2CaptureCallback(activity, params, testConfig)
+ params.camera2CaptureSession?.capture(params.captureRequestBuilder?.build(),
+ captureCallback, params.backgroundHandler)
+ }
+ } catch (e: CameraAccessException) {
+ e.printStackTrace()
+ } catch (e: IllegalStateException) {
+ logd("captureStillPicture IllegalStateException, aborting: " + e.message)
+ }
+}
+
+/**
+ * Close preview stream and camera device. If this was a switch test, restore the camera id
+ */
+fun camera2CloseCamera(activity: MainActivity, params: CameraParams?, testConfig: TestConfig) {
+ if (params == null)
+ return
+
+ MainActivity.logd("closePreviewAndCamera: " + params.id)
+ if (params.isPreviewing) {
+ params.timer.previewCloseStart = System.currentTimeMillis()
+ params.camera2CaptureSession?.close()
+ } else {
+ params.timer.cameraCloseStart = System.currentTimeMillis()
+ params.device?.close()
+ }
+
+ if ((testConfig.currentRunningTest == TestType.SWITCH_CAMERA) ||
+ (testConfig.currentRunningTest == TestType.MULTI_SWITCH)) {
+ params.id = testConfig.switchTestRealCameraId // Restore the actual camera ID
+ }
+}
+
+/**
+ * An abort request has been received. Abandon everything
+ */
+fun camera2Abort(activity: MainActivity, params: CameraParams, testConfig: TestConfig) {
+ params.camera2CaptureSession?.abortCaptures()
+ activity.stopBackgroundThread(params)
+} \ No newline at end of file
diff --git a/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/cameracontrollers/Camera2DeviceStateCallback.kt b/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/cameracontrollers/Camera2DeviceStateCallback.kt
new file mode 100644
index 00000000000..340b424254d
--- /dev/null
+++ b/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/cameracontrollers/Camera2DeviceStateCallback.kt
@@ -0,0 +1,143 @@
+/*
+ * Copyright 2019 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.antelope.cameracontrollers
+
+import android.hardware.camera2.CameraDevice
+import androidx.annotation.NonNull
+import androidx.camera.integration.antelope.CameraParams
+import androidx.camera.integration.antelope.MainActivity
+import androidx.camera.integration.antelope.TestConfig
+import androidx.camera.integration.antelope.TestType
+import androidx.camera.integration.antelope.testEnded
+
+/**
+ * Callbacks that track the state of the camera device using the Camera 2 API.
+ */
+class Camera2DeviceStateCallback(
+ internal var params: CameraParams,
+ internal var activity: MainActivity,
+ internal var testConfig: TestConfig
+) : CameraDevice.StateCallback() {
+
+ /**
+ * Camera device has opened successfully, record timing and initiate the preview stream.
+ */
+ override fun onOpened(@NonNull cameraDevice: CameraDevice) {
+ params.timer.openEnd = System.currentTimeMillis()
+ MainActivity.logd("In CameraStateCallback onOpened: " + cameraDevice.id +
+ " current test: " + testConfig.currentRunningTest.toString())
+ params.isOpen = true
+ params.device = cameraDevice
+
+ when (testConfig.currentRunningTest) {
+ TestType.INIT -> {
+ // Camera opened, we're done
+ testConfig.testFinished = true
+ closePreviewAndCamera(activity, params, testConfig)
+ }
+
+ else -> {
+ params.timer.previewStart = System.currentTimeMillis()
+ createCameraPreviewSession(activity, params, testConfig)
+ }
+ }
+ }
+
+ /**
+ * Camera device has been closed, recording close timing.
+ *
+ * If this is a switch test, swizzle camera ids and move to the next step of the test.
+ */
+ override fun onClosed(camera: CameraDevice?) {
+ MainActivity.logd("In CameraStateCallback onClosed. Camera: " + params.id +
+ " is closed. testFinished: " + testConfig.testFinished)
+ params.isOpen = false
+
+ if (testConfig.testFinished) {
+ params.timer.cameraCloseEnd = System.currentTimeMillis()
+ testConfig.testFinished = false
+ testEnded(activity, params, testConfig)
+ return
+ }
+
+ if ((testConfig.currentRunningTest == TestType.SWITCH_CAMERA) ||
+ (testConfig.currentRunningTest == TestType.MULTI_SWITCH)) {
+
+ // First camera closed, now start the second
+ if (testConfig.switchTestCurrentCamera == testConfig.switchTestCameras.get(0)) {
+ testConfig.switchTestCurrentCamera = testConfig.switchTestCameras.get(1)
+ camera2OpenCamera(activity, params, testConfig)
+ }
+
+ // Second camera closed, now start the first
+ else if (testConfig.switchTestCurrentCamera == testConfig.switchTestCameras.get(1)) {
+ testConfig.switchTestCurrentCamera = testConfig.switchTestCameras.get(0)
+ testConfig.testFinished = true
+ camera2OpenCamera(activity, params, testConfig)
+ }
+ }
+
+ super.onClosed(camera)
+ }
+
+ /**
+ * Camera has been disconnected. Whatever was happening, it won't work now.
+ */
+ override fun onDisconnected(@NonNull cameraDevice: CameraDevice) {
+ MainActivity.logd("In CameraStateCallback onDisconnected: " + params.id)
+ if (!params.isOpen) {
+ return
+ }
+
+ testConfig.testFinished = false // Whatever we are doing will fail now, try to exit
+ closePreviewAndCamera(activity, params, testConfig)
+ }
+
+ /**
+ * Camera device has thrown an error. Try to recover or fail gracefully.
+ */
+ override fun onError(@NonNull cameraDevice: CameraDevice, error: Int) {
+ MainActivity.logd("In CameraStateCallback onError: " + cameraDevice.id + " error: " + error)
+ if (!params.isOpen) {
+ return
+ }
+
+ when (error) {
+ CameraDevice.StateCallback.ERROR_MAX_CAMERAS_IN_USE -> {
+ // Let's try to close an open camera and re-open this one
+ MainActivity.logd("In CameraStateCallback too many cameras open, closing one...")
+ closeACamera(activity, testConfig)
+ camera2OpenCamera(activity, params, testConfig)
+ }
+
+ CameraDevice.StateCallback.ERROR_CAMERA_DEVICE -> {
+ MainActivity.logd("Fatal camera error, close and try to re-initialize...")
+ closePreviewAndCamera(activity, params, testConfig)
+ camera2OpenCamera(activity, params, testConfig)
+ }
+
+ CameraDevice.StateCallback.ERROR_CAMERA_IN_USE -> {
+ MainActivity.logd("This camera is already open... doing nothing")
+ }
+
+ else -> {
+ testConfig.testFinished = false // Whatever we are doing will fail, just try to exit
+ closePreviewAndCamera(activity, params, testConfig)
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/cameracontrollers/Camera2PreviewSessionStateCallback.kt b/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/cameracontrollers/Camera2PreviewSessionStateCallback.kt
new file mode 100644
index 00000000000..5c3037b94ab
--- /dev/null
+++ b/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/cameracontrollers/Camera2PreviewSessionStateCallback.kt
@@ -0,0 +1,152 @@
+/*
+ * Copyright 2019 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.antelope.cameracontrollers
+
+import android.hardware.camera2.CameraAccessException
+import android.hardware.camera2.CameraCaptureSession
+import android.hardware.camera2.CaptureRequest
+import androidx.annotation.NonNull
+import androidx.camera.integration.antelope.CameraParams
+import androidx.camera.integration.antelope.FocusMode
+import androidx.camera.integration.antelope.MainActivity
+import androidx.camera.integration.antelope.PrefHelper
+import androidx.camera.integration.antelope.TestConfig
+import androidx.camera.integration.antelope.TestType
+import androidx.camera.integration.antelope.setAutoFlash
+
+/**
+ * Callbacks that track the state of a preview capture session.
+ */
+class Camera2PreviewSessionStateCallback(
+ internal val activity: MainActivity,
+ internal val params: CameraParams,
+ internal val testConfig: TestConfig
+) : CameraCaptureSession.StateCallback() {
+
+ /**
+ * Preview session is open and frames are coming through. If the test is preview only, record
+ * results and close the camera, if a switch or image capture test, proceed to the next step.
+ *
+ */
+ override fun onActive(session: CameraCaptureSession?) {
+ if (!params.isOpen) {
+ return
+ }
+
+ params.timer.previewEnd = System.currentTimeMillis()
+ params.isPreviewing = true
+
+ when (testConfig.currentRunningTest) {
+ TestType.PREVIEW -> {
+ testConfig.testFinished = true
+ closePreviewAndCamera(activity, params, testConfig)
+ }
+
+ TestType.SWITCH_CAMERA, TestType.MULTI_SWITCH -> {
+ if (testConfig.switchTestCurrentCamera == testConfig.switchTestCameras.get(0)) {
+ if (testConfig.testFinished) {
+ params.timer.switchToFirstEnd = System.currentTimeMillis()
+ Thread.sleep(PrefHelper.getPreviewBuffer(activity)) // Let preview run
+ closePreviewAndCamera(activity, params, testConfig)
+ } else {
+ Thread.sleep(PrefHelper.getPreviewBuffer(activity)) // Let preview run
+ params.timer.switchToSecondStart = System.currentTimeMillis()
+ closePreviewAndCamera(activity, params, testConfig)
+ }
+ } else {
+ params.timer.switchToSecondEnd = System.currentTimeMillis()
+ Thread.sleep(PrefHelper.getPreviewBuffer(activity)) // Let preview run
+ params.timer.switchToFirstStart = System.currentTimeMillis()
+ closePreviewAndCamera(activity, params, testConfig)
+ }
+ }
+
+ else -> {
+ initializeStillCapture(activity, params, testConfig)
+ }
+ }
+ super.onActive(session)
+ }
+
+ /**
+ * Preview session has been configured, set up preview parameters and request that the preview
+ * capture begin.
+ */
+ override fun onConfigured(@NonNull cameraCaptureSession: CameraCaptureSession) {
+ if (!params.isOpen) {
+ return
+ }
+
+ MainActivity.logd("In onConfigured: CaptureSession configured!")
+
+ try {
+ when (testConfig.focusMode) {
+ FocusMode.AUTO -> {
+ params.captureRequestBuilder?.set(CaptureRequest.CONTROL_AF_MODE,
+ CaptureRequest.CONTROL_AF_MODE_AUTO)
+ }
+ FocusMode.CONTINUOUS -> {
+ params.captureRequestBuilder?.set(CaptureRequest.CONTROL_AF_MODE,
+ CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE)
+ }
+ FocusMode.FIXED -> {
+ params.captureRequestBuilder?.set(CaptureRequest.CONTROL_AF_MODE,
+ CaptureRequest.CONTROL_AF_MODE_AUTO)
+ }
+ }
+
+ // Enable flash automatically when necessary.
+ setAutoFlash(params, params.captureRequestBuilder)
+
+ params.camera2CaptureSession = cameraCaptureSession
+ params.state = CameraState.PREVIEW_RUNNING
+
+ // Request that the camera preview begins
+ cameraCaptureSession.setRepeatingRequest(params.captureRequestBuilder?.build(),
+ params.camera2CaptureSessionCallback, params.backgroundHandler)
+ } catch (e: CameraAccessException) {
+ MainActivity.logd("Create Capture Session error: " + params.id)
+ e.printStackTrace()
+ } catch (e: IllegalStateException) {
+ MainActivity.logd("createCameraPreviewSession onConfigured, IllegalStateException," +
+ " aborting: " + e)
+ }
+ }
+
+ /**
+ * Configuration of the preview stream failed, try again.
+ */
+ override fun onConfigureFailed(@NonNull cameraCaptureSession: CameraCaptureSession) {
+ if (!params.isOpen) {
+ return
+ }
+
+ MainActivity.logd("Camera preview initialization failed. Trying again")
+ createCameraPreviewSession(activity, params, testConfig)
+ }
+
+ /**
+ * Preview session has been closed. Record close timing and proceed to close camera.
+ */
+ override fun onClosed(session: CameraCaptureSession) {
+ params.timer.previewCloseEnd = System.currentTimeMillis()
+ params.isPreviewing = false
+ params.timer.cameraCloseStart = System.currentTimeMillis()
+ params.device?.close()
+ super.onClosed(session)
+ }
+} \ No newline at end of file
diff --git a/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/cameracontrollers/CameraXCaptureSessionCallback.kt b/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/cameracontrollers/CameraXCaptureSessionCallback.kt
new file mode 100644
index 00000000000..76af9de2828
--- /dev/null
+++ b/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/cameracontrollers/CameraXCaptureSessionCallback.kt
@@ -0,0 +1,137 @@
+/*
+ * Copyright 2019 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.antelope.cameracontrollers
+
+import android.hardware.camera2.CameraCaptureSession
+import android.hardware.camera2.CaptureFailure
+import android.hardware.camera2.CaptureRequest
+import android.hardware.camera2.CaptureResult
+import android.hardware.camera2.TotalCaptureResult
+import android.view.Surface
+import androidx.annotation.NonNull
+import androidx.camera.integration.antelope.CameraParams
+import androidx.camera.integration.antelope.MainActivity
+import androidx.camera.integration.antelope.MainActivity.Companion.logd
+import androidx.camera.integration.antelope.TestConfig
+import androidx.camera.integration.antelope.TestType
+import androidx.camera.integration.antelope.testEnded
+
+/**
+ * Callbacks that track an image capture session
+ */
+class CameraXCaptureSessionCallback(
+ internal val activity: MainActivity,
+ internal val params: CameraParams,
+ internal val testConfig: TestConfig
+) : CameraCaptureSession.CaptureCallback() {
+
+ /** Capture has been aborted. */
+ override fun onCaptureSequenceAborted(session: CameraCaptureSession?, sequenceId: Int) {
+ MainActivity.logd("CameraX captureCallback: Sequence aborted. Current test: " +
+ testConfig.currentRunningTest.toString())
+ super.onCaptureSequenceAborted(session, sequenceId)
+ }
+
+ /** Capture has failed, try to restart */
+ override fun onCaptureFailed(
+ session: CameraCaptureSession?,
+ request: CaptureRequest?,
+ failure: CaptureFailure?
+ ) {
+ MainActivity.logd("CameraX captureStillPicture captureCallback: Capture Failed. Failure: " +
+ failure?.reason + " Current test: " + testConfig.currentRunningTest.toString())
+ closeCameraX(activity, params, testConfig)
+ cameraXOpenCamera(activity, params, testConfig)
+ }
+
+ /** Unused but retained here as it can be useful for debugging */
+ override fun onCaptureStarted(
+ session: CameraCaptureSession?,
+ request: CaptureRequest?,
+ timestamp: Long,
+ frameNumber: Long
+ ) {
+ // MainActivity.logd("CameraX captureStillPicture captureCallback: Capture Started.")
+ super.onCaptureStarted(session, request, timestamp, frameNumber)
+ }
+
+ /** Unused but retained here as it can be useful for debugging */
+ override fun onCaptureProgressed(
+ session: CameraCaptureSession?,
+ request: CaptureRequest?,
+ partialResult: CaptureResult?
+ ) {
+ // MainActivity.logd("CameraX captureStillPicture captureCallback: Capture progressed.")
+ super.onCaptureProgressed(session, request, partialResult)
+ }
+
+ /** Unused but retained here as it can be useful for debugging */
+ override fun onCaptureBufferLost(
+ session: CameraCaptureSession?,
+ request: CaptureRequest?,
+ target: Surface?,
+ frameNumber: Long
+ ) {
+ // MainActivity.logd("CameraX captureStillPicture captureCallback: Buffer lost.")
+ super.onCaptureBufferLost(session, request, target, frameNumber)
+ }
+
+ /**
+ * Still capture has completed. Record timing and proceed to next test or finish.
+ */
+ override fun onCaptureCompleted(
+ @NonNull session: CameraCaptureSession,
+ @NonNull request: CaptureRequest,
+ @NonNull result: TotalCaptureResult
+ ) {
+
+ if (params.cameraXLifecycle.isFinished()) {
+ cameraXAbort(activity, params, testConfig)
+ return
+ }
+
+ logd("CameraX onCaptureCompleted!! " + request.tag)
+ // Prevent duplicate captures from being triggered
+ if (testConfig.isFirstOnCaptureComplete) {
+ testConfig.isFirstOnCaptureComplete = false
+ } else {
+ return
+ }
+
+ params.timer.captureEnd = System.currentTimeMillis()
+ MainActivity.logd("CameraX StillCapture completed. CaptureEnd. Current test: " +
+ testConfig.currentRunningTest.toString())
+
+ // ImageReader might get the image before this callback is called, if so, the test is done
+ if (0L != params.timer.imageSaveEnd) {
+ params.timer.imageReaderStart = params.timer.imageReaderEnd // No ImageReader delay
+ MainActivity.logd("StillCapture completed. Ending Test. Current test: " +
+ testConfig.currentRunningTest.toString())
+
+ if (TestType.MULTI_PHOTO_CHAIN == testConfig.currentRunningTest) {
+ testEnded(activity, params, testConfig)
+ } else {
+ testConfig.testFinished = true
+ closeCameraX(activity, params, testConfig)
+ }
+ } else {
+ MainActivity.logd("StillCapture completed. Waiting on imageReader. Current test: " +
+ testConfig.currentRunningTest.toString())
+ params.timer.imageReaderStart = System.currentTimeMillis()
+ }
+ }
+} \ No newline at end of file
diff --git a/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/cameracontrollers/CameraXController.kt b/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/cameracontrollers/CameraXController.kt
new file mode 100644
index 00000000000..87df30d6b2d
--- /dev/null
+++ b/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/cameracontrollers/CameraXController.kt
@@ -0,0 +1,305 @@
+/*
+ * Copyright 2019 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.antelope.cameracontrollers
+
+import androidx.lifecycle.LifecycleOwner
+import android.graphics.SurfaceTexture
+import android.hardware.camera2.CameraCaptureSession
+import android.hardware.camera2.CameraDevice
+import android.hardware.camera2.CaptureRequest
+import android.view.ViewGroup
+import androidx.camera.integration.antelope.CameraParams
+import androidx.camera.camera2.Camera2Config
+import androidx.camera.core.CameraX
+import androidx.camera.core.ImageCapture
+import androidx.camera.core.ImageCaptureConfig
+import androidx.camera.core.Preview
+import androidx.camera.core.PreviewConfig
+import androidx.camera.integration.antelope.CameraXImageAvailableListener
+import androidx.camera.integration.antelope.CustomLifecycle
+import androidx.camera.integration.antelope.FocusMode
+import androidx.camera.integration.antelope.MainActivity
+import androidx.camera.integration.antelope.MainActivity.Companion.logd
+import androidx.camera.integration.antelope.PrefHelper
+import androidx.camera.integration.antelope.TestConfig
+import androidx.camera.integration.antelope.TestType
+
+/**
+ * Opens the camera using the Camera X API and starts the open counter. The open call will complete
+ * in the DeviceStateCallback asynchronously. For switch tests, the camera id will be swizzling so
+ * the original camera id is saved.
+ *
+ * CameraX manages its lifecycle internally, for the purpose of repeated testing, Antelope uses a
+ * custom lifecycle to allow for starting new tests cleanly which is started here.
+ *
+ * All the needed Cmaera X use cases should be bound before starting the lifecycle. Depending on
+ * the test, bind either the preview case, or both the preview and image capture case.
+ */
+internal fun cameraXOpenCamera(
+ activity: MainActivity,
+ params: CameraParams,
+ testConfig: TestConfig
+) {
+
+ try {
+ // TODO make the switch test methodology more robust and handle physical cameras
+ // Currently we swap out the ids behind the scenes
+ // This requires to save the actual camera id for after the test
+ if ((testConfig.currentRunningTest == TestType.SWITCH_CAMERA) ||
+ (testConfig.currentRunningTest == TestType.MULTI_SWITCH)) {
+ testConfig.switchTestRealCameraId = params.id // Save the actual camera ID
+ params.id = testConfig.switchTestCurrentCamera
+ }
+
+ params.cameraXDeviceStateCallback = CameraXDeviceStateCallback(params, activity, testConfig)
+ params.cameraXPreviewSessionStateCallback =
+ CameraXPreviewSessionStateCallback(activity, params, testConfig)
+
+ if (params.cameraXDeviceStateCallback != null &&
+ params.cameraXPreviewSessionStateCallback != null) {
+ params.cameraXPreviewConfig =
+ cameraXPreviewUseCaseBuilder(params.id, testConfig.focusMode,
+ params.cameraXDeviceStateCallback!!,
+ params.cameraXPreviewSessionStateCallback!!)
+ }
+
+ if (!params.cameraXLifecycle.isFinished()) {
+ logd("Lifecycle not finished, finishing it.")
+ params.cameraXLifecycle.pauseAndStop()
+ params.cameraXLifecycle.finish()
+ }
+ params.cameraXLifecycle = CustomLifecycle()
+
+ val lifecycleOwner: LifecycleOwner = params.cameraXLifecycle
+ val previewUseCase = Preview(params.cameraXPreviewConfig)
+
+ // Set preview to observe the surface texture
+ activity.runOnUiThread {
+ previewUseCase.setOnPreviewOutputUpdateListener {
+ viewFinderOutput: Preview.PreviewOutput? ->
+ if (viewFinderOutput?.surfaceTexture != null) {
+ if (!isCameraSurfaceTextureReleased(viewFinderOutput.surfaceTexture)) {
+ // View swizzling required to for the view hierarchy to update correctly
+ val viewGroup = params.cameraXPreviewTexture?.parent as ViewGroup
+ viewGroup.removeView(params.cameraXPreviewTexture)
+ viewGroup.addView(params.cameraXPreviewTexture)
+ params.cameraXPreviewTexture?.surfaceTexture =
+ viewFinderOutput.surfaceTexture
+ }
+ }
+ }
+ }
+
+ when (testConfig.currentRunningTest) {
+ // Only the preview is required
+ TestType.PREVIEW,
+ TestType.SWITCH_CAMERA,
+ TestType.MULTI_SWITCH -> {
+ params.timer.openStart = System.currentTimeMillis()
+ activity.runOnUiThread {
+ CameraX.bindToLifecycle(lifecycleOwner, previewUseCase)
+ params.cameraXLifecycle.start()
+ }
+ }
+ else -> {
+ // Both preview and image capture are needed
+ params.cameraXCaptureSessionCallback =
+ CameraXCaptureSessionCallback(activity, params, testConfig)
+
+ if (params.cameraXDeviceStateCallback != null &&
+ params.cameraXCaptureSessionCallback != null) {
+ params.cameraXCaptureConfig =
+ cameraXImageCaptureUseCaseBuilder(params.id, testConfig.focusMode,
+ params.cameraXDeviceStateCallback!!,
+ params.cameraXCaptureSessionCallback!!)
+ }
+
+ params.cameraXImageCaptureUseCase = ImageCapture(params.cameraXCaptureConfig)
+
+ params.timer.openStart = System.currentTimeMillis()
+ activity.runOnUiThread {
+ CameraX.bindToLifecycle(lifecycleOwner, previewUseCase,
+ params.cameraXImageCaptureUseCase)
+ params.cameraXLifecycle.start()
+ }
+ }
+ }
+ } catch (e: Exception) {
+ MainActivity.logd("cameraXOpenCamera exception: " + params.id)
+ e.printStackTrace()
+ }
+}
+
+/**
+ * End Camera X custom lifecycle, unbind use cases, and start timing the camera close.
+ */
+internal fun closeCameraX(activity: MainActivity, params: CameraParams, testConfig: TestConfig) {
+ logd("In closecameraX, camera: " + params.id + ", test: " + testConfig.currentRunningTest)
+
+ params.timer.cameraCloseStart = System.currentTimeMillis()
+
+ if (!params.cameraXLifecycle.isFinished()) {
+ params.cameraXLifecycle.pauseAndStop()
+ params.cameraXLifecycle.finish()
+
+ // CameraX calls need to be on the main thread
+ activity.run {
+ CameraX.unbindAll()
+ }
+ }
+ if ((testConfig.currentRunningTest == TestType.SWITCH_CAMERA) ||
+ (testConfig.currentRunningTest == TestType.MULTI_SWITCH)) {
+ params.id = testConfig.switchTestRealCameraId // Restore the actual camera ID
+ }
+
+ params.isOpen = false
+}
+
+/**
+ * Proceed to take and measure a still image capture.
+ */
+internal fun cameraXTakePicture(
+ activity: MainActivity,
+ params: CameraParams,
+ testConfig: TestConfig
+) {
+ if (params.cameraXLifecycle.isFinished()) {
+ cameraXAbort(activity, params, testConfig)
+ return
+ }
+
+ logd("CameraX TakePicture: capture start.")
+
+ // Pause in multi-captures to make sure HDR routines don't get overloaded
+ logd("CameraX TakePicture. Pausing for " +
+ PrefHelper.getPreviewBuffer(activity) + "ms to let preview run.")
+ params.timer.previewFillStart = System.currentTimeMillis()
+ Thread.sleep(PrefHelper.getPreviewBuffer(activity))
+ params.timer.previewFillEnd = System.currentTimeMillis()
+
+ params.timer.captureStart = System.currentTimeMillis()
+ params.timer.autofocusStart = System.currentTimeMillis()
+ params.timer.autofocusEnd = System.currentTimeMillis()
+
+ logd("Capture timer started: " + params.timer.captureStart)
+ activity.runOnUiThread {
+ params.cameraXImageCaptureUseCase
+ .takePicture(CameraXImageAvailableListener(activity, params, testConfig))
+ }
+}
+
+/**
+ * An abort request has been received. Abandon everything
+ */
+internal fun cameraXAbort(activity: MainActivity, params: CameraParams, testConfig: TestConfig) {
+ closeCameraX(activity, params, testConfig)
+ return
+}
+
+/**
+ * Try to determine if a SurfaceTexture is released.
+ *
+ * Prior to SDK 26 there was not built in mechanism for this. This method relies on expected
+ * exceptions being thrown if a released SurfaceTexture is updated.
+ */
+private fun isCameraSurfaceTextureReleased(texture: SurfaceTexture): Boolean {
+ var released = false
+
+ if (26 <= android.os.Build.VERSION.SDK_INT) {
+ released = texture.isReleased
+ } else {
+ // WARNING: This relies on some implementation details of the SurfaceTexture native code.
+ // If the SurfaceTexture is released, we should get a RuntimeException. If not, we should
+ // get an IllegalStateException since we are not in the same EGL context as the camera.
+ var exception: Exception? = null
+ try {
+ texture.updateTexImage()
+ } catch (e: IllegalStateException) {
+ logd("in isCameraSurfaceTextureReleased: IllegalStateException: " + e.message)
+ exception = e
+ released = false
+ } catch (e: RuntimeException) {
+ logd("in isCameraSurfaceTextureReleased: RuntimeException: " + e.message)
+ exception = e
+ released = true
+ }
+
+ if (!released && exception == null) {
+ throw RuntimeException("Unable to determine if SurfaceTexture is released")
+ }
+ }
+
+ logd("The camera texture is: " + if (released) "RELEASED" else "NOT RELEASED")
+
+ return released
+}
+
+/**
+ * Setup the Camera X preview use case
+ */
+private fun cameraXPreviewUseCaseBuilder(
+ id: String,
+ focusMode: FocusMode,
+ deviceStateCallback: CameraDevice.StateCallback,
+ sessionCaptureStateCallback: CameraCaptureSession.StateCallback
+): PreviewConfig {
+
+ // TODO: As of 0.3.0 CameraX can only use front and back cameras. Update in future versions
+ val cameraXcameraID = if (id.equals("0")) CameraX.LensFacing.BACK else CameraX.LensFacing.FRONT
+ val configBuilder = PreviewConfig.Builder()
+ .setLensFacing(cameraXcameraID)
+ Camera2Config.Extender(configBuilder)
+ .setDeviceStateCallback(deviceStateCallback)
+ .setSessionStateCallback(sessionCaptureStateCallback)
+ .setCaptureRequestOption(CaptureRequest.CONTROL_AF_MODE,
+ when (focusMode) {
+ FocusMode.AUTO -> CaptureRequest.CONTROL_AF_MODE_AUTO
+ FocusMode.CONTINUOUS -> CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE
+ FocusMode.FIXED -> CaptureRequest.CONTROL_AF_MODE_AUTO
+ })
+ return configBuilder.build()
+}
+
+/**
+ * Setup the Camera X image capture use case
+ */
+private fun cameraXImageCaptureUseCaseBuilder(
+ id: String,
+ focusMode: FocusMode,
+ deviceStateCallback:
+ CameraDevice.StateCallback,
+ sessionCaptureCallback: CameraCaptureSession.CaptureCallback
+): ImageCaptureConfig {
+
+ // TODO: As of 0.3.0 CameraX can only use front and back cameras. Update in future versions
+ val cameraXcameraID = if (id.equals("0")) CameraX.LensFacing.BACK else CameraX.LensFacing.FRONT
+
+ val configBuilder = ImageCaptureConfig.Builder()
+ .setLensFacing(cameraXcameraID)
+ .setCaptureMode(ImageCapture.CaptureMode.MAX_QUALITY)
+ Camera2Config.Extender(configBuilder)
+ .setDeviceStateCallback(deviceStateCallback)
+ .setSessionCaptureCallback(sessionCaptureCallback)
+ .setCaptureRequestOption(CaptureRequest.CONTROL_AF_MODE,
+ when (focusMode) {
+ FocusMode.AUTO -> CaptureRequest.CONTROL_AF_MODE_AUTO
+ FocusMode.CONTINUOUS -> CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE
+ FocusMode.FIXED -> CaptureRequest.CONTROL_AF_MODE_AUTO
+ })
+
+ return configBuilder.build()
+} \ No newline at end of file
diff --git a/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/cameracontrollers/CameraXDeviceStateCallback.kt b/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/cameracontrollers/CameraXDeviceStateCallback.kt
new file mode 100644
index 00000000000..13be3e706a2
--- /dev/null
+++ b/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/cameracontrollers/CameraXDeviceStateCallback.kt
@@ -0,0 +1,133 @@
+/*
+ * Copyright 2019 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.antelope.cameracontrollers
+
+import android.hardware.camera2.CameraDevice
+import androidx.annotation.NonNull
+import androidx.camera.integration.antelope.CameraParams
+import androidx.camera.integration.antelope.MainActivity
+import androidx.camera.integration.antelope.TestConfig
+import androidx.camera.integration.antelope.TestType
+import androidx.camera.integration.antelope.testEnded
+
+/**
+ * Callbacks that track the state of the camera device using the Camera X API.
+ */
+class CameraXDeviceStateCallback(
+ internal var params: CameraParams,
+ internal var activity: MainActivity,
+ internal var testConfig: TestConfig
+) : CameraDevice.StateCallback() {
+
+ /**
+ * Camera device has opened successfully, record timing and initiate the preview stream.
+ */
+ override fun onOpened(@NonNull cameraDevice: CameraDevice) {
+ MainActivity.logd("In CameraXStateCallback onOpened: " + cameraDevice.id +
+ " current test: " + testConfig.currentRunningTest.toString())
+
+ params.timer.openEnd = System.currentTimeMillis()
+ params.isOpen = true
+ params.device = cameraDevice
+
+ when (testConfig.currentRunningTest) {
+ TestType.INIT -> {
+ // Camera opened, we're done
+ testConfig.testFinished = true
+ closeCameraX(activity, params, testConfig)
+ }
+
+ else -> {
+ params.timer.previewStart = System.currentTimeMillis()
+ }
+ }
+ }
+
+ /**
+ * Camera device has been closed, recording close timing.
+ *
+ * If this is a switch test, swizzle camera ids and move to the next step of the test.
+ */
+ override fun onClosed(camera: CameraDevice?) {
+ MainActivity.logd("In CameraXStateCallback onClosed.")
+
+ if (testConfig.testFinished) {
+ params.timer.cameraCloseEnd = System.currentTimeMillis()
+ testConfig.testFinished = false
+ testEnded(activity, params, testConfig)
+ return
+ }
+
+ if ((testConfig.currentRunningTest == TestType.SWITCH_CAMERA) ||
+ (testConfig.currentRunningTest == TestType.MULTI_SWITCH)) {
+
+ // First camera closed, now start the second
+ if (testConfig.switchTestCurrentCamera == testConfig.switchTestCameras.get(0)) {
+ testConfig.switchTestCurrentCamera = testConfig.switchTestCameras.get(1)
+ cameraXOpenCamera(activity, params, testConfig)
+ }
+
+ // Second camera closed, now start the first
+ else if (testConfig.switchTestCurrentCamera == testConfig.switchTestCameras.get(1)) {
+ testConfig.switchTestCurrentCamera = testConfig.switchTestCameras.get(0)
+ testConfig.testFinished = true
+ cameraXOpenCamera(activity, params, testConfig)
+ }
+ }
+ }
+
+ /**
+ * Camera has been disconnected. Whatever was happening, it won't work now.
+ */
+ override fun onDisconnected(@NonNull cameraDevice: CameraDevice) {
+ MainActivity.logd("In CameraXStateCallback onDisconnected: " + params.id)
+ testConfig.testFinished = false // Whatever we are doing will fail now, try to exit
+ closeCameraX(activity, params, testConfig)
+ }
+
+ /**
+ * Camera device has thrown an error. Try to recover or fail gracefully.
+ */
+ override fun onError(@NonNull cameraDevice: CameraDevice, error: Int) {
+ MainActivity.logd("In CameraXStateCallback onError: " +
+ cameraDevice.id + " and error: " + error)
+
+ when (error) {
+ CameraDevice.StateCallback.ERROR_MAX_CAMERAS_IN_USE -> {
+ // Let's try to close an open camera and re-open this one
+ MainActivity.logd("In CameraXStateCallback too many cameras open, closing one...")
+ closeACamera(activity, testConfig)
+ cameraXOpenCamera(activity, params, testConfig)
+ }
+
+ CameraDevice.StateCallback.ERROR_CAMERA_DEVICE -> {
+ MainActivity.logd("Fatal camerax error, close and try to re-initialize...")
+ closeCameraX(activity, params, testConfig)
+ cameraXOpenCamera(activity, params, testConfig)
+ }
+
+ CameraDevice.StateCallback.ERROR_CAMERA_IN_USE -> {
+ MainActivity.logd("This camera is already open... doing nothing")
+ }
+
+ else -> {
+ testConfig.testFinished = false // Whatever we are doing will fail now, try to exit
+ closeCameraX(activity, params, testConfig)
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/cameracontrollers/CameraXPreviewSessionStateCallback.kt b/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/cameracontrollers/CameraXPreviewSessionStateCallback.kt
new file mode 100644
index 00000000000..e011f80366f
--- /dev/null
+++ b/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/cameracontrollers/CameraXPreviewSessionStateCallback.kt
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2019 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.antelope.cameracontrollers
+
+import android.hardware.camera2.CameraCaptureSession
+import androidx.annotation.NonNull
+import androidx.camera.integration.antelope.CameraParams
+import androidx.camera.integration.antelope.MainActivity
+import androidx.camera.integration.antelope.PrefHelper
+import androidx.camera.integration.antelope.TestConfig
+import androidx.camera.integration.antelope.TestType
+
+/**
+ * Callbacks that track the state of a preview capture session.
+ */
+class CameraXPreviewSessionStateCallback(
+ internal var activity: MainActivity,
+ internal var params: CameraParams,
+ internal var testConfig: TestConfig
+) : CameraCaptureSession.StateCallback() {
+
+ /**
+ * Preview session is open and frames are coming through. If the test is preview only, record
+ * results and close the camera, if a switch or image capture test, proceed to the next step.
+ *
+ */
+ override fun onActive(session: CameraCaptureSession?) {
+ if (params.cameraXLifecycle.isFinished()) {
+ cameraXAbort(activity, params, testConfig)
+ return
+ }
+
+ // Prevent duplicate captures from being triggered if running a capture test
+ if (testConfig.currentRunningTest != TestType.MULTI_SWITCH &&
+ testConfig.currentRunningTest != TestType.SWITCH_CAMERA) {
+ if (testConfig.isFirstOnActive) {
+ testConfig.isFirstOnActive = false
+ } else {
+ return
+ }
+ }
+
+ params.timer.previewEnd = System.currentTimeMillis()
+
+ when (testConfig.currentRunningTest) {
+ TestType.PREVIEW -> {
+ testConfig.testFinished = true
+ closeCameraX(activity, params, testConfig)
+ }
+
+ TestType.SWITCH_CAMERA, TestType.MULTI_SWITCH -> {
+ if (testConfig.switchTestCurrentCamera == testConfig.switchTestCameras.get(0)) {
+ if (testConfig.testFinished) {
+ params.timer.switchToFirstEnd = System.currentTimeMillis()
+ Thread.sleep(PrefHelper.getPreviewBuffer(activity)) // Let preview run
+ closePreviewAndCamera(activity, params, testConfig)
+ } else {
+ Thread.sleep(PrefHelper.getPreviewBuffer(activity)) // Let preview run
+ params.timer.switchToSecondStart = System.currentTimeMillis()
+ closePreviewAndCamera(activity, params, testConfig)
+ }
+ } else {
+ params.timer.switchToSecondEnd = System.currentTimeMillis()
+ Thread.sleep(PrefHelper.getPreviewBuffer(activity)) // Let preview run
+ params.timer.switchToFirstStart = System.currentTimeMillis()
+ closePreviewAndCamera(activity, params, testConfig)
+ }
+ }
+
+ else -> {
+ cameraXTakePicture(activity, params, testConfig)
+ }
+ }
+ if (null != session)
+ super.onActive(session)
+ }
+
+ /**
+ * Preview session has been configured. Camera X handles the next step.
+ */
+ override fun onConfigured(@NonNull cameraCaptureSession: CameraCaptureSession) {
+ MainActivity.logd("In onConfigured: CaptureSession configured!")
+ }
+
+ /**
+ * Configuration of the preview stream failed, try again.
+ */
+ override fun onConfigureFailed(@NonNull cameraCaptureSession: CameraCaptureSession) {
+ MainActivity.logd("CameraX preview initialization failed. Closing camera.")
+ closeCameraX(activity, params, testConfig)
+ }
+
+ /**
+ * Preview session has been closed. Camera X handles the next step.
+ */
+ override fun onClosed(session: CameraCaptureSession) {
+ MainActivity.logd("In CameraXPreviewSessionStateCallback onClosed.")
+ }
+} \ No newline at end of file
diff --git a/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/cameracontrollers/Common.kt b/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/cameracontrollers/Common.kt
new file mode 100644
index 00000000000..589e0df9f49
--- /dev/null
+++ b/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/cameracontrollers/Common.kt
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2019 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.antelope.cameracontrollers
+
+import androidx.camera.integration.antelope.CameraAPI
+import androidx.camera.integration.antelope.CameraParams
+import androidx.camera.integration.antelope.MainActivity
+import androidx.camera.integration.antelope.TestConfig
+
+/**
+ * Cross-API function to close the currently active preview stream and camera device
+ */
+fun closePreviewAndCamera(activity: MainActivity, params: CameraParams?, testConfig: TestConfig) {
+ if (null == params)
+ return
+
+ when (testConfig.api) {
+ CameraAPI.CAMERA1 -> camera1CloseCamera(activity, params, testConfig)
+ CameraAPI.CAMERA2 -> camera2CloseCamera(activity, params, testConfig)
+ CameraAPI.CAMERAX -> closeCameraX(activity, params, testConfig)
+ }
+}
+
+/**
+ * Convenience method to close all cameras on a device.
+ */
+fun closeAllCameras(activity: MainActivity, testConfig: TestConfig) {
+ MainActivity.logd("Closing all cameras.")
+ for (tempCameraParams: CameraParams in MainActivity.cameraParams.values) {
+ closePreviewAndCamera(activity, tempCameraParams, testConfig)
+ }
+}
+
+/**
+ * Close the first open camera on the device. This can be used if a ERROR_MAX_CAMERAS_IN_USE is
+ * occurring.
+ */
+fun closeACamera(activity: MainActivity, testConfig: TestConfig) {
+ var closedACamera = false
+ MainActivity.logd("In closeACamera, looking for open camera.")
+ for (tempCameraParams: CameraParams in MainActivity.cameraParams.values) {
+ if (tempCameraParams.isOpen) {
+ MainActivity.logd("In closeACamera, found open camera, closing: " + tempCameraParams.id)
+ closedACamera = true
+ closePreviewAndCamera(activity, tempCameraParams, testConfig)
+ break
+ }
+ }
+
+ // We couldn't find an open camera, let's close everything
+ if (!closedACamera) {
+ closeAllCameras(activity, testConfig)
+ }
+} \ No newline at end of file
diff --git a/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/timing/BaseActivity.java b/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/timing/BaseActivity.java
deleted file mode 100644
index 9ca268603a4..00000000000
--- a/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/timing/BaseActivity.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (C) 2019 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.timing;
-
-import android.os.Bundle;
-
-import androidx.appcompat.app.AppCompatActivity;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * An activity used to run performance test case.
- *
- * <p>To run performance test case, please implement this Activity. Camerax Use Case can be
- * implement in prepareUseCase and runUseCase. For performance result, you can set currentTimeMillis
- * to startTime and store the execution time into totalTime. At the end of test case, please call
- * onUseCaseFinish() to notify the lock.
- */
-public abstract class BaseActivity extends AppCompatActivity {
- public static final long MICROS_IN_SECOND = TimeUnit.SECONDS.toMillis(1);
- public static final long PREVIEW_FILL_BUFFER_TIME = 1500;
- private static final String TAG = "BaseActivity";
- public long startTime;
- public long totalTime;
- public long openCameraStartTime;
- public long openCameraTotalTime;
- public long startRreviewTime;
- public long startPreviewTotalTime;
- public long previewFrameRate;
- public long closeCameraStartTime;
- public long closeCameraTotalTime;
- public String imageResolution;
- public CountDownLatch latch;
-
- /**
- * Prepares the use case.
- */
- public abstract void prepareUseCase();
-
- /**
- * Activates use case so it will receive data from camera.
- *
- * @throws InterruptedException on fatal errors.
- */
- public abstract void runUseCase() throws InterruptedException;
-
- /**
- * Called when the test case finishes.
- * <p>Could be called in CameraDevice's state callbacks.
- */
- public void onUseCaseFinish() {
- latch.countDown();
- }
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- latch = new CountDownLatch(1);
- }
-}
diff --git a/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/timing/CustomLifecycle.java b/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/timing/CustomLifecycle.java
deleted file mode 100644
index fd5308a08f6..00000000000
--- a/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/timing/CustomLifecycle.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (C) 2019 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.timing;
-
-import android.os.Handler;
-import android.os.Looper;
-
-import androidx.annotation.NonNull;
-import androidx.lifecycle.Lifecycle;
-import androidx.lifecycle.LifecycleOwner;
-import androidx.lifecycle.LifecycleRegistry;
-
-/** A customized lifecycle owner which obeys the lifecycle transition rules. */
-public final class CustomLifecycle implements LifecycleOwner {
- private final LifecycleRegistry mLifecycleRegistry;
- private final Handler mMainHandler = new Handler(Looper.getMainLooper());
-
- public CustomLifecycle() {
- mLifecycleRegistry = new LifecycleRegistry(this);
- mLifecycleRegistry.setCurrentState(Lifecycle.State.INITIALIZED);
- mLifecycleRegistry.setCurrentState(Lifecycle.State.CREATED);
- }
-
- @NonNull
- @Override
- public Lifecycle getLifecycle() {
- return mLifecycleRegistry;
- }
-
- /**
- * Called when activity resumes.
- */
- public void doOnResume() {
- if (Looper.getMainLooper() != Looper.myLooper()) {
- mMainHandler.post(new Runnable() {
- @Override
- public void run() {
- CustomLifecycle.this.doOnResume();
- }
- });
- return;
- }
- mLifecycleRegistry.setCurrentState(Lifecycle.State.RESUMED);
- }
-
- /**
- * Called when activity is destroyed.
- */
- public void doDestroyed() {
- if (Looper.getMainLooper() != Looper.myLooper()) {
- mMainHandler.post(new Runnable() {
- @Override
- public void run() {
- CustomLifecycle.this.doDestroyed();
- }
- });
- return;
- }
- mLifecycleRegistry.setCurrentState(Lifecycle.State.DESTROYED);
- }
-}
diff --git a/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/timing/TakePhotoActivity.java b/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/timing/TakePhotoActivity.java
deleted file mode 100644
index dca2bd21844..00000000000
--- a/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/timing/TakePhotoActivity.java
+++ /dev/null
@@ -1,280 +0,0 @@
-/*
- * Copyright (C) 2019 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.timing;
-
-import android.graphics.SurfaceTexture;
-import android.hardware.camera2.CameraCaptureSession;
-import android.hardware.camera2.CameraDevice;
-import android.os.Bundle;
-import android.util.Log;
-import android.view.TextureView;
-import android.view.TextureView.SurfaceTextureListener;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.Button;
-
-import androidx.camera.camera2.Camera2Config;
-import androidx.camera.core.CameraX;
-import androidx.camera.core.CameraX.LensFacing;
-import androidx.camera.core.ImageCapture;
-import androidx.camera.core.ImageCapture.CaptureMode;
-import androidx.camera.core.ImageCaptureConfig;
-import androidx.camera.core.ImageProxy;
-import androidx.camera.core.Preview;
-import androidx.camera.core.PreviewConfig;
-
-/** This Activity is used to run image capture performance test in mobileharness. */
-public class TakePhotoActivity extends BaseActivity {
-
- private static final String TAG = "TakePhotoActivity";
- // How many sample frames we should use to calculate framerate.
- private static final int FRAMERATE_SAMPLE_WINDOW = 5;
- private static final String EXTRA_CAPTURE_MODE = "capture_mode";
- private static final String EXTRA_CAMERA_FACING = "camera_facing";
- private static final String CAMERA_FACING_FRONT = "FRONT";
- private static final String CAMERA_FACING_BACK = "BACK";
- private final String mDefaultCameraFacing = CAMERA_FACING_BACK;
- private final CameraDevice.StateCallback mDeviceStateCallback =
- new CameraDevice.StateCallback() {
-
- @Override
- public void onOpened(CameraDevice cameraDevice) {
- openCameraTotalTime = System.currentTimeMillis() - openCameraStartTime;
- Log.d(TAG, "[onOpened] openCameraTotalTime: " + openCameraTotalTime);
- startRreviewTime = System.currentTimeMillis();
- }
-
- @Override
- public void onClosed(CameraDevice camera) {
- super.onClosed(camera);
- closeCameraTotalTime = System.currentTimeMillis() - closeCameraStartTime;
- Log.d(TAG, "[onClosed] closeCameraTotalTime: " + closeCameraTotalTime);
- onUseCaseFinish();
- }
-
- @Override
- public void onDisconnected(CameraDevice cameraDevice) {
- }
-
- @Override
- public void onError(CameraDevice cameraDevice, int i) {
- Log.e(TAG, "[onError] open camera failed, error code: " + i);
- }
- };
- private final CameraCaptureSession.StateCallback mCaptureSessionStateCallback =
- new CameraCaptureSession.StateCallback() {
-
- @Override
- public void onActive(CameraCaptureSession session) {
- super.onActive(session);
- startPreviewTotalTime = System.currentTimeMillis() - startRreviewTime;
- Log.d(TAG, "[onActive] previewStartTotalTime: " + startPreviewTotalTime);
- }
-
- @Override
- public void onConfigured(CameraCaptureSession cameraCaptureSession) {
- Log.d(TAG, "[onConfigured] CaptureSession configured!");
- }
-
- @Override
- public void onConfigureFailed(CameraCaptureSession cameraCaptureSession) {
- Log.e(TAG, "[onConfigureFailed] CameraX preview initialization failed.");
- }
- };
- /** The default cameraId to use. */
- private LensFacing mCurrentCameraLensFacing = LensFacing.BACK;
- private ImageCapture mImageCapture;
- private Preview mPreview;
- private int mFrameCount;
- private long mPreviewSampleStartTime;
- private CaptureMode mCaptureMode = CaptureMode.MIN_LATENCY;
- private CustomLifecycle mCustomLifecycle;
-
- @Override
- public void runUseCase() throws InterruptedException {
-
- // Length of time to let the preview stream run before capturing the first image.
- // This can help ensure capture latency is real latency and not merely the device
- // filling the buffer.
- Thread.sleep(PREVIEW_FILL_BUFFER_TIME);
-
- startTime = System.currentTimeMillis();
- mImageCapture.takePicture(
- new ImageCapture.OnImageCapturedListener() {
- @Override
- public void onCaptureSuccess(ImageProxy image, int rotationDegrees) {
- totalTime = System.currentTimeMillis() - startTime;
- if (image != null) {
- imageResolution = image.getWidth() + "x" + image.getHeight();
- } else {
- Log.e(TAG, "[onCaptureSuccess] image is null");
- }
- }
- });
- }
-
- @Override
- public void prepareUseCase() {
- createPreview();
- createImageCapture();
- }
-
- void createPreview() {
- PreviewConfig.Builder configBuilder =
- new PreviewConfig.Builder()
- .setLensFacing(mCurrentCameraLensFacing)
- .setTargetName("Preview");
-
- new Camera2Config.Extender(configBuilder)
- .setDeviceStateCallback(mDeviceStateCallback)
- .setSessionStateCallback(mCaptureSessionStateCallback);
-
- mPreview = new Preview(configBuilder.build());
- openCameraStartTime = System.currentTimeMillis();
-
- mPreview.setOnPreviewOutputUpdateListener(
- new Preview.OnPreviewOutputUpdateListener() {
- @Override
- public void onUpdated(Preview.PreviewOutput previewOutput) {
- TextureView textureView = TakePhotoActivity.this.findViewById(
- R.id.textureView);
- ViewGroup viewGroup = (ViewGroup) textureView.getParent();
- viewGroup.removeView(textureView);
- viewGroup.addView(textureView);
- textureView.setSurfaceTexture(previewOutput.getSurfaceTexture());
- textureView.setSurfaceTextureListener(
- new SurfaceTextureListener() {
- @Override
- public void onSurfaceTextureAvailable(
- SurfaceTexture surfaceTexture, int i, int i1) {
- }
-
- @Override
- public void onSurfaceTextureSizeChanged(
- SurfaceTexture surfaceTexture, int i, int i1) {
- }
-
- @Override
- public boolean onSurfaceTextureDestroyed(
- SurfaceTexture surfaceTexture) {
- return false;
- }
-
- @Override
- public void onSurfaceTextureUpdated(
- SurfaceTexture surfaceTexture) {
- Log.d(TAG, "[onSurfaceTextureUpdated]");
- if (0 == totalTime) {
- return;
- }
-
- if (0 == mFrameCount) {
- mPreviewSampleStartTime = System.currentTimeMillis();
- } else if (FRAMERATE_SAMPLE_WINDOW == mFrameCount) {
- final long duration =
- System.currentTimeMillis()
- - mPreviewSampleStartTime;
- previewFrameRate =
- (MICROS_IN_SECOND
- * FRAMERATE_SAMPLE_WINDOW
- / duration);
- closeCameraStartTime = System.currentTimeMillis();
- mCustomLifecycle.doDestroyed();
- }
- mFrameCount++;
- }
- });
- }
- });
-
- CameraX.bindToLifecycle(mCustomLifecycle, mPreview);
- }
-
- void createImageCapture() {
- ImageCaptureConfig config =
- new ImageCaptureConfig.Builder()
- .setTargetName("ImageCapture")
- .setLensFacing(mCurrentCameraLensFacing)
- .setCaptureMode(mCaptureMode)
- .build();
-
- mImageCapture = new ImageCapture(config);
- CameraX.bindToLifecycle(mCustomLifecycle, mImageCapture);
-
- final Button button = this.findViewById(R.id.Picture);
- button.setOnClickListener(
- new View.OnClickListener() {
- @Override
- public void onClick(View view) {
- startTime = System.currentTimeMillis();
- mImageCapture.takePicture(
- new ImageCapture.OnImageCapturedListener() {
- @Override
- public void onCaptureSuccess(
- ImageProxy image, int rotationDegrees) {
- totalTime = System.currentTimeMillis() - startTime;
- if (image != null) {
- imageResolution =
- image.getWidth() + "x" + image.getHeight();
- } else {
- Log.e(TAG, "[onCaptureSuccess] image is null");
- }
- }
- });
- }
- });
- }
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
-
- final Bundle bundle = getIntent().getExtras();
- if (bundle != null) {
- final String captureModeString = bundle.getString(EXTRA_CAPTURE_MODE);
- if (captureModeString != null) {
- mCaptureMode = CaptureMode.valueOf(captureModeString.toUpperCase());
- }
- final String cameraLensFacing = bundle.getString(EXTRA_CAMERA_FACING);
- if (cameraLensFacing != null) {
- setupCamera(cameraLensFacing);
- } else {
- setupCamera(mDefaultCameraFacing);
- }
- }
- mCustomLifecycle = new CustomLifecycle();
- prepareUseCase();
- }
-
- @Override
- protected void onResume() {
- super.onResume();
- mCustomLifecycle.doOnResume();
- }
-
- void setupCamera(String cameraFacing) {
- Log.d(TAG, "Camera Facing: " + cameraFacing);
- if (CAMERA_FACING_BACK.equalsIgnoreCase(cameraFacing)) {
- mCurrentCameraLensFacing = LensFacing.BACK;
- } else if (CAMERA_FACING_FRONT.equalsIgnoreCase(cameraFacing)) {
- mCurrentCameraLensFacing = LensFacing.FRONT;
- } else {
- throw new RuntimeException("Invalid lens facing: " + cameraFacing);
- }
- }
-}
diff --git a/camera/integration-tests/timingtestapp/src/main/res/drawable-v24/ic_launcher_foreground.xml b/camera/integration-tests/timingtestapp/src/main/res/drawable-v24/ic_launcher_foreground.xml
new file mode 100644
index 00000000000..18c76bbcaf0
--- /dev/null
+++ b/camera/integration-tests/timingtestapp/src/main/res/drawable-v24/ic_launcher_foreground.xml
@@ -0,0 +1,50 @@
+<!--
+ ~ Copyright 2019 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.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:aapt="http://schemas.android.com/aapt"
+ android:width="108dp"
+ android:height="108dp"
+ android:viewportWidth="108"
+ android:viewportHeight="108">
+ <path
+ android:fillType="evenOdd"
+ android:pathData="M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z"
+ android:strokeWidth="1"
+ android:strokeColor="#00000000">
+ <aapt:attr name="android:fillColor">
+ <gradient
+ android:endX="78.5885"
+ android:endY="90.9159"
+ android:startX="48.7653"
+ android:startY="61.0927"
+ android:type="linear">
+ <item
+ android:color="#44000000"
+ android:offset="0.0" />
+ <item
+ android:color="#00000000"
+ android:offset="1.0" />
+ </gradient>
+ </aapt:attr>
+ </path>
+ <path
+ android:fillColor="#FFFFFF"
+ android:fillType="nonZero"
+ android:pathData="M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z"
+ android:strokeWidth="1"
+ android:strokeColor="#00000000" />
+</vector>
diff --git a/camera/integration-tests/timingtestapp/src/main/res/drawable/ic_launcher_background.xml b/camera/integration-tests/timingtestapp/src/main/res/drawable/ic_launcher_background.xml
new file mode 100644
index 00000000000..cc5236921eb
--- /dev/null
+++ b/camera/integration-tests/timingtestapp/src/main/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,185 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright 2019 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.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="108dp"
+ android:height="108dp"
+ android:viewportWidth="108"
+ android:viewportHeight="108">
+ <path
+ android:fillColor="#008577"
+ android:pathData="M0,0h108v108h-108z" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M9,0L9,108"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M19,0L19,108"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M29,0L29,108"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M39,0L39,108"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M49,0L49,108"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M59,0L59,108"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M69,0L69,108"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M79,0L79,108"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M89,0L89,108"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M99,0L99,108"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,9L108,9"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,19L108,19"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,29L108,29"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,39L108,39"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,49L108,49"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,59L108,59"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,69L108,69"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,79L108,79"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,89L108,89"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,99L108,99"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M19,29L89,29"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M19,39L89,39"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M19,49L89,49"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M19,59L89,59"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M19,69L89,69"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M19,79L89,79"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M29,19L29,89"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M39,19L39,89"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M49,19L49,89"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M59,19L59,89"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M69,19L69,89"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M79,19L79,89"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+</vector>
diff --git a/camera/integration-tests/timingtestapp/src/main/res/drawable/rounded_frame.xml b/camera/integration-tests/timingtestapp/src/main/res/drawable/rounded_frame.xml
new file mode 100644
index 00000000000..13478759023
--- /dev/null
+++ b/camera/integration-tests/timingtestapp/src/main/res/drawable/rounded_frame.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright 2019 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.
+ -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+ <solid android:color="@android:color/transparent" />
+ <stroke
+ android:width="2dp"
+ android:color="@color/colorAccent" />
+ <corners android:radius="5dp" />
+ <padding
+ android:bottom="10dp"
+ android:left="10dp"
+ android:right="10dp"
+ android:top="10dp" />
+</shape> \ No newline at end of file
diff --git a/camera/integration-tests/timingtestapp/src/main/res/layout-land/activity_main.xml b/camera/integration-tests/timingtestapp/src/main/res/layout-land/activity_main.xml
new file mode 100644
index 00000000000..9de951f4697
--- /dev/null
+++ b/camera/integration-tests/timingtestapp/src/main/res/layout-land/activity_main.xml
@@ -0,0 +1,155 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright 2019 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:id="@+id/constraint_main"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ tools:context=".MainActivity">
+
+ <androidx.constraintlayout.widget.Guideline
+ android:id="@+id/guideline_horizontal"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ app:layout_constraintGuide_percent="0.60" />
+
+ <androidx.constraintlayout.widget.Guideline
+ android:id="@+id/guideline_vertical"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ app:layout_constraintGuide_percent="0.65" />
+
+ <androidx.constraintlayout.widget.Guideline
+ android:id="@+id/guideline_progress"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ app:layout_constraintGuide_percent="0.96" />
+
+ <ScrollView
+ android:id="@+id/scroll_log"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:layout_marginStart="8dp"
+ android:layout_marginTop="8dp"
+ android:layout_marginEnd="8dp"
+ android:layout_marginBottom="8dp"
+ android:background="@drawable/rounded_frame"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toEndOf="@id/guideline_vertical"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <TextView
+ android:id="@+id/text_log"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:ems="10"
+ android:text="@string/log_initial" />
+ </LinearLayout>
+ </ScrollView>
+
+ <ProgressBar
+ android:id="@+id/progress_test"
+ style="@android:style/Widget.DeviceDefault.Light.ProgressBar.Horizontal"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="8dp"
+ android:layout_marginTop="8dp"
+ android:layout_marginEnd="8dp"
+ android:layout_marginBottom="8dp"
+ android:indeterminate="false"
+ android:visibility="invisible"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="@id/guideline_vertical"
+ app:layout_constraintTop_toBottomOf="@+id/guideline_progress" />
+
+ <androidx.camera.integration.antelope.AutoFitSurfaceView
+ android:id="@+id/surface_preview"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:layout_marginStart="16dp"
+ android:layout_marginTop="8dp"
+ android:layout_marginEnd="8dp"
+ android:layout_marginBottom="8dp"
+ app:layout_constraintBottom_toTopOf="@id/button_single"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="@id/guideline_vertical"
+ app:layout_constraintTop_toTopOf="parent" />
+
+ <androidx.camera.integration.antelope.AutoFitTextureView
+ android:id="@+id/texture_preview"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:layout_marginStart="16dp"
+ android:layout_marginTop="8dp"
+ android:layout_marginEnd="8dp"
+ android:layout_marginBottom="8dp"
+ android:visibility="invisible"
+ app:layout_constraintBottom_toTopOf="@id/button_single"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="@id/guideline_vertical"
+ app:layout_constraintTop_toTopOf="parent" />
+
+
+ <Button
+ android:id="@+id/button_single"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="8dp"
+ android:layout_marginEnd="8dp"
+ android:includeFontPadding="false"
+ android:minHeight="20dp"
+ android:text="@string/button_single_test"
+ app:layout_constraintBottom_toTopOf="@+id/button_multi"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="@+id/button_multi" />
+
+ <Button
+ android:id="@+id/button_multi"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="8dp"
+ android:layout_marginEnd="8dp"
+ android:includeFontPadding="false"
+ android:minHeight="20dp"
+ android:text="@string/button_multi_test"
+ app:layout_constraintBottom_toTopOf="@id/button_abort"
+ app:layout_constraintEnd_toEndOf="parent" />
+
+ <Button
+ android:id="@+id/button_abort"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_marginEnd="8dp"
+ android:includeFontPadding="false"
+ android:minHeight="20dp"
+ android:text="@string/button_abort"
+ app:layout_constraintBottom_toTopOf="@id/guideline_progress"
+ app:layout_constraintEnd_toEndOf="parent" />
+
+
+</androidx.constraintlayout.widget.ConstraintLayout> \ No newline at end of file
diff --git a/camera/integration-tests/timingtestapp/src/main/res/layout/activity_main.xml b/camera/integration-tests/timingtestapp/src/main/res/layout/activity_main.xml
index 3dc76a981bd..96152090f0c 100644
--- a/camera/integration-tests/timingtestapp/src/main/res/layout/activity_main.xml
+++ b/camera/integration-tests/timingtestapp/src/main/res/layout/activity_main.xml
@@ -1,52 +1,155 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2019 The Android Open Source Project
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright 2019 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.
+ -->
-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:id="@+id/constraint_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
- tools:context="androidx.camera.integration.timing">
+ tools:context=".MainActivity">
+
+ <androidx.constraintlayout.widget.Guideline
+ android:id="@+id/guideline_horizontal"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ app:layout_constraintGuide_percent="0.60" />
- <TextureView
- android:id="@+id/textureView"
+ <ScrollView
+ android:id="@+id/scroll_log"
android:layout_width="0dp"
android:layout_height="0dp"
+ android:layout_marginStart="8dp"
+ android:layout_marginTop="8dp"
+ android:layout_marginEnd="8dp"
+ android:layout_marginBottom="8dp"
+ android:background="@drawable/rounded_frame"
+ app:layout_constraintBottom_toTopOf="@id/guideline_horizontal"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <TextView
+ android:id="@+id/text_log"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:ems="10"
+ android:text="@string/log_initial" />
+ </LinearLayout>
+ </ScrollView>
+
+ <ProgressBar
+ android:id="@+id/progress_test"
+ style="@android:style/Widget.DeviceDefault.Light.ProgressBar.Horizontal"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="8dp"
+ android:layout_marginTop="8dp"
+ android:layout_marginEnd="8dp"
+ android:layout_marginBottom="8dp"
+ android:indeterminate="false"
+ android:visibility="invisible"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toTopOf="parent" />
+ app:layout_constraintTop_toBottomOf="@+id/guideline_progress" />
+
+ <androidx.camera.integration.antelope.AutoFitSurfaceView
+ android:id="@+id/surface_preview"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:layout_marginStart="16dp"
+ android:layout_marginTop="8dp"
+ android:layout_marginEnd="8dp"
+ android:layout_marginBottom="8dp"
+ app:layout_constraintBottom_toTopOf="@id/guideline_progress"
+ app:layout_constraintEnd_toStartOf="@id/guideline_vertical"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@+id/guideline_horizontal" />
+
+ <androidx.camera.integration.antelope.AutoFitTextureView
+ android:id="@+id/texture_preview"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:layout_marginStart="16dp"
+ android:layout_marginTop="8dp"
+ android:layout_marginEnd="8dp"
+ android:layout_marginBottom="8dp"
+ android:visibility="invisible"
+ app:layout_constraintBottom_toTopOf="@id/guideline_progress"
+ app:layout_constraintEnd_toStartOf="@id/guideline_vertical"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@+id/guideline_horizontal" />
<androidx.constraintlayout.widget.Guideline
- android:id="@+id/takepicture"
+ android:id="@+id/guideline_progress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:orientation="vertical"
- app:layout_constraintGuide_begin="0dp"
- app:layout_constraintGuide_percent="0.1" />
+ android:orientation="horizontal"
+ app:layout_constraintGuide_percent="0.96" />
+
<Button
- android:id="@+id/Picture"
- android:layout_width="wrap_content"
+ android:id="@+id/button_single"
+ android:layout_width="0dp"
android:layout_height="wrap_content"
- android:scaleType="fitXY"
- android:text="Picture"
- app:layout_constraintBottom_toBottomOf="parent"
+ android:layout_marginTop="8dp"
+ android:layout_marginEnd="8dp"
+ android:includeFontPadding="false"
+ android:minHeight="20dp"
+ android:text="@string/button_single_test"
app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintHorizontal_bias="0.0"
- app:layout_constraintStart_toStartOf="@+id/takepicture"
- app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintVertical_bias="1.0" />
-</androidx.constraintlayout.widget.ConstraintLayout>
+ app:layout_constraintStart_toStartOf="@+id/button_multi"
+ app:layout_constraintTop_toBottomOf="@+id/guideline_horizontal" />
+
+ <Button
+ android:id="@+id/button_multi"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="8dp"
+ android:layout_marginEnd="8dp"
+ android:includeFontPadding="false"
+ android:minHeight="20dp"
+ android:text="@string/button_multi_test"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintTop_toBottomOf="@+id/button_single" />
+
+ <Button
+ android:id="@+id/button_abort"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_marginEnd="8dp"
+ android:includeFontPadding="false"
+ android:minHeight="20dp"
+ android:text="@string/button_abort"
+ app:layout_constraintBottom_toTopOf="@id/guideline_progress"
+ app:layout_constraintEnd_toEndOf="parent" />
+
+ <androidx.constraintlayout.widget.Guideline
+ android:id="@+id/guideline_vertical"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ app:layout_constraintGuide_percent="0.5" />
+
+
+</androidx.constraintlayout.widget.ConstraintLayout> \ No newline at end of file
diff --git a/camera/integration-tests/timingtestapp/src/main/res/layout/settings_dialog.xml b/camera/integration-tests/timingtestapp/src/main/res/layout/settings_dialog.xml
new file mode 100644
index 00000000000..d3e5d000d3d
--- /dev/null
+++ b/camera/integration-tests/timingtestapp/src/main/res/layout/settings_dialog.xml
@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright 2019 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"
+ android:id="@+id/constraint_settings_dialog"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <ScrollView
+ android:id="@+id/scroll_settings_dialog"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_marginStart="8dp"
+ android:layout_marginTop="8dp"
+ android:layout_marginEnd="8dp"
+ android:scrollbarFadeDuration="0"
+ app:layout_constraintBottom_toTopOf="@id/button_start"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent">
+
+ </ScrollView>
+
+ <Button
+ android:id="@+id/button_start"
+ style="@style/Widget.AppCompat.Button.ButtonBar.AlertDialog"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginEnd="8dp"
+ android:layout_marginBottom="8dp"
+ android:text="@string/settings_single_go"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toEndOf="parent" />
+
+ <Button
+ android:id="@+id/button_cancel"
+ style="@style/Widget.AppCompat.Button.ButtonBar.AlertDialog"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginEnd="8dp"
+ android:layout_marginBottom="8dp"
+ android:text="@string/settings_single_cancel"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toStartOf="@+id/button_start"
+ app:layout_constraintTop_toTopOf="@+id/button_start" />
+</androidx.constraintlayout.widget.ConstraintLayout> \ No newline at end of file
diff --git a/camera/integration-tests/timingtestapp/src/main/res/menu/main_menu.xml b/camera/integration-tests/timingtestapp/src/main/res/menu/main_menu.xml
new file mode 100644
index 00000000000..0b000f91ebd
--- /dev/null
+++ b/camera/integration-tests/timingtestapp/src/main/res/menu/main_menu.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright 2019 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.
+ -->
+
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+ <item
+ android:id="@+id/menu_logcat"
+ android:checkable="true"
+ android:title="@string/menu_logcat" />
+ <item
+ android:id="@+id/menu_delete_photos"
+ android:title="@string/menu_delete_photos" />
+ <item
+ android:id="@+id/menu_delete_logs"
+ android:title="@string/menu_delete_logs" />
+</menu> \ No newline at end of file
diff --git a/camera/integration-tests/timingtestapp/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/camera/integration-tests/timingtestapp/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 00000000000..25efb7bcf6b
--- /dev/null
+++ b/camera/integration-tests/timingtestapp/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright 2019 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.
+ -->
+
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+ <background android:drawable="@drawable/ic_launcher_background" />
+ <foreground android:drawable="@mipmap/ic_launcher_foreground" />
+</adaptive-icon> \ No newline at end of file
diff --git a/camera/integration-tests/timingtestapp/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/camera/integration-tests/timingtestapp/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 00000000000..25efb7bcf6b
--- /dev/null
+++ b/camera/integration-tests/timingtestapp/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright 2019 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.
+ -->
+
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+ <background android:drawable="@drawable/ic_launcher_background" />
+ <foreground android:drawable="@mipmap/ic_launcher_foreground" />
+</adaptive-icon> \ No newline at end of file
diff --git a/camera/integration-tests/timingtestapp/src/main/res/mipmap-hdpi/ic_launcher.png b/camera/integration-tests/timingtestapp/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 00000000000..8db53615434
--- /dev/null
+++ b/camera/integration-tests/timingtestapp/src/main/res/mipmap-hdpi/ic_launcher.png
Binary files differ
diff --git a/camera/integration-tests/timingtestapp/src/main/res/mipmap-hdpi/ic_launcher_foreground.png b/camera/integration-tests/timingtestapp/src/main/res/mipmap-hdpi/ic_launcher_foreground.png
new file mode 100644
index 00000000000..c9fb04df7e1
--- /dev/null
+++ b/camera/integration-tests/timingtestapp/src/main/res/mipmap-hdpi/ic_launcher_foreground.png
Binary files differ
diff --git a/camera/integration-tests/timingtestapp/src/main/res/mipmap-hdpi/ic_launcher_round.png b/camera/integration-tests/timingtestapp/src/main/res/mipmap-hdpi/ic_launcher_round.png
new file mode 100644
index 00000000000..a462e63b025
--- /dev/null
+++ b/camera/integration-tests/timingtestapp/src/main/res/mipmap-hdpi/ic_launcher_round.png
Binary files differ
diff --git a/camera/integration-tests/timingtestapp/src/main/res/mipmap-mdpi/ic_launcher.png b/camera/integration-tests/timingtestapp/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 00000000000..fba050b0221
--- /dev/null
+++ b/camera/integration-tests/timingtestapp/src/main/res/mipmap-mdpi/ic_launcher.png
Binary files differ
diff --git a/camera/integration-tests/timingtestapp/src/main/res/mipmap-mdpi/ic_launcher_foreground.png b/camera/integration-tests/timingtestapp/src/main/res/mipmap-mdpi/ic_launcher_foreground.png
new file mode 100644
index 00000000000..45ff1c30223
--- /dev/null
+++ b/camera/integration-tests/timingtestapp/src/main/res/mipmap-mdpi/ic_launcher_foreground.png
Binary files differ
diff --git a/camera/integration-tests/timingtestapp/src/main/res/mipmap-mdpi/ic_launcher_round.png b/camera/integration-tests/timingtestapp/src/main/res/mipmap-mdpi/ic_launcher_round.png
new file mode 100644
index 00000000000..a7299aa2fd5
--- /dev/null
+++ b/camera/integration-tests/timingtestapp/src/main/res/mipmap-mdpi/ic_launcher_round.png
Binary files differ
diff --git a/camera/integration-tests/timingtestapp/src/main/res/mipmap-xhdpi/ic_launcher.png b/camera/integration-tests/timingtestapp/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 00000000000..6ffe739e07f
--- /dev/null
+++ b/camera/integration-tests/timingtestapp/src/main/res/mipmap-xhdpi/ic_launcher.png
Binary files differ
diff --git a/camera/integration-tests/timingtestapp/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png b/camera/integration-tests/timingtestapp/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png
new file mode 100644
index 00000000000..2a09046539d
--- /dev/null
+++ b/camera/integration-tests/timingtestapp/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png
Binary files differ
diff --git a/camera/integration-tests/timingtestapp/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/camera/integration-tests/timingtestapp/src/main/res/mipmap-xhdpi/ic_launcher_round.png
new file mode 100644
index 00000000000..17901edb85b
--- /dev/null
+++ b/camera/integration-tests/timingtestapp/src/main/res/mipmap-xhdpi/ic_launcher_round.png
Binary files differ
diff --git a/camera/integration-tests/timingtestapp/src/main/res/mipmap-xxhdpi/ic_launcher.png b/camera/integration-tests/timingtestapp/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 00000000000..e45eb2fc377
--- /dev/null
+++ b/camera/integration-tests/timingtestapp/src/main/res/mipmap-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/camera/integration-tests/timingtestapp/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png b/camera/integration-tests/timingtestapp/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png
new file mode 100644
index 00000000000..12891cd9229
--- /dev/null
+++ b/camera/integration-tests/timingtestapp/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png
Binary files differ
diff --git a/camera/integration-tests/timingtestapp/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/camera/integration-tests/timingtestapp/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
new file mode 100644
index 00000000000..f1ffb895743
--- /dev/null
+++ b/camera/integration-tests/timingtestapp/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
Binary files differ
diff --git a/camera/integration-tests/timingtestapp/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/camera/integration-tests/timingtestapp/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 00000000000..cf9ffa64c5d
--- /dev/null
+++ b/camera/integration-tests/timingtestapp/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Binary files differ
diff --git a/camera/integration-tests/timingtestapp/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/camera/integration-tests/timingtestapp/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png
new file mode 100644
index 00000000000..ff76659db41
--- /dev/null
+++ b/camera/integration-tests/timingtestapp/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png
Binary files differ
diff --git a/camera/integration-tests/timingtestapp/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/camera/integration-tests/timingtestapp/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
new file mode 100644
index 00000000000..e757795076a
--- /dev/null
+++ b/camera/integration-tests/timingtestapp/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
Binary files differ
diff --git a/camera/integration-tests/timingtestapp/src/main/res/values/arrays.xml b/camera/integration-tests/timingtestapp/src/main/res/values/arrays.xml
new file mode 100644
index 00000000000..872ac892082
--- /dev/null
+++ b/camera/integration-tests/timingtestapp/src/main/res/values/arrays.xml
@@ -0,0 +1,83 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright 2019 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.
+ -->
+
+<resources>
+ <string-array name="array_numtests">
+ <item>5</item>
+ <item>10</item>
+ <item>15</item>
+ <item>30</item>
+ <item>50</item>
+ <item>100</item>
+ <item>250</item>
+ <item>500</item>
+ </string-array>
+
+ <string-array name="array_previewbuffer">
+ <item>250</item>
+ <item>500</item>
+ <item>750</item>
+ <item>1000</item>
+ <item>1500</item>
+ <item>2000</item>
+ <item>2500</item>
+ <item>5000</item>
+ </string-array>
+
+ <string-array name="array_settings_api">
+ <item>Camera1</item>
+ <item>Camera2</item>
+ <item>CameraX</item>
+ </string-array>
+
+ <!-- Don't turn CameraX on by default until it is more stable -->
+ <string-array name="array_settings_api_defaults">
+ <item>Camera1</item>
+ <item>Camera2</item>
+ <item>CameraX</item>
+ </string-array>
+
+ <string-array name="array_settings_imagesize">
+ <item>Min</item>
+ <item>Max</item>
+ </string-array>
+
+ <string-array name="array_settings_focus">
+ <item>Auto</item>
+ <item>Continuous</item>
+ </string-array>
+
+ <string-array name="array_single_test_types">
+ <item>Camera Open/Close</item>
+ <item>Preview Start</item>
+ <item>Switch Cameras</item>
+ <item>Switch Cameras (Multiple)</item>
+ <item>Single Capture</item>
+ <item>Multiple Captures</item>
+ <item>Multiple Captures (Chained)</item>
+ </string-array>
+
+ <string-array name="array_single_test_type_values">
+ <item>INIT</item>
+ <item>PREVIEW</item>
+ <item>SWITCH_CAMERA</item>
+ <item>MULTI_SWITCH</item>
+ <item>PHOTO</item>
+ <item>MULTI_PHOTO</item>
+ <item>MULTI_PHOTO_CHAIN</item>
+ </string-array>
+
+</resources> \ No newline at end of file
diff --git a/camera/integration-tests/timingtestapp/src/main/res/values/colors.xml b/camera/integration-tests/timingtestapp/src/main/res/values/colors.xml
new file mode 100644
index 00000000000..b956ca4d80d
--- /dev/null
+++ b/camera/integration-tests/timingtestapp/src/main/res/values/colors.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright 2019 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.
+ -->
+
+<resources>
+ <color name="colorPrimary">#0088BB</color>
+ <color name="colorPrimaryDark">#00574B</color>
+ <color name="colorAccent">#55DD11FF</color>
+</resources>
diff --git a/camera/integration-tests/timingtestapp/src/main/res/values/ic_launcher_background.xml b/camera/integration-tests/timingtestapp/src/main/res/values/ic_launcher_background.xml
new file mode 100644
index 00000000000..09803e6e421
--- /dev/null
+++ b/camera/integration-tests/timingtestapp/src/main/res/values/ic_launcher_background.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright 2019 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.
+ -->
+
+<resources>
+ <color name="ic_launcher_background">#A617A1</color>
+</resources> \ No newline at end of file
diff --git a/camera/integration-tests/timingtestapp/src/main/res/values/strings.xml b/camera/integration-tests/timingtestapp/src/main/res/values/strings.xml
index 2df05657350..3af8838b542 100644
--- a/camera/integration-tests/timingtestapp/src/main/res/values/strings.xml
+++ b/camera/integration-tests/timingtestapp/src/main/res/values/strings.xml
@@ -1,17 +1,118 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2019 The Android Open Source Project
+<!--
+ ~ Copyright 2019 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.
+ -->
-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
+<resources>
+ <string name="log_initial">Android Camera Performance Tool</string>
+
+ <string name="app_name">Antelope</string>
+ <string name="label_camera1">Camera1</string>
+ <string name="label_camera2">Camera2</string>
+ <string name="label_camerax">CameraX</string>
+ <string name="label_size_max">Max image size</string>
+ <string name="label_size_min">Min image size</string>
+ <string name="label_focus_auto">Auto-focus</string>
+ <string name="label_focus_continuous">Continuous focus</string>
+ <string name="label_camera">Camera:</string>
+ <string name="button_single_test">Single Test</string>
+ <string name="button_multi_test">Multiple Tests</string>
+ <string name="button_abort">Abort</string>
+ <string name="label_log">Log: </string>
+ <string name="log_copied">Log copied to the clipboard</string>
+
+ <string name="menu_logcat">Output logcat</string>
+ <string name="menu_delete_photos">Delete images</string>
+ <string name="menu_delete_logs">Delete CSV logs</string>
+
+ <string name="settings_autodelete_key">settings_autodelete</string>
+ <string name="settings_autodelete_title">Auto-delete images</string>
+ <string name="settings_autodelete_summary">Automatically delete images immediately after test runs</string>
+ <string name="settings_autodelete_summary_off">Do not automatically delete images immediately after test runs, images will be stored in the Antelope directory of your device\'s photos area</string>
+
+ <string name="settings_numtests_key">settings_numtests</string>
+ <string name="settings_numtests_title">Number of repetitions</string>
+ <string name="settings_numtests_summary">Number of repetitions to do for repeated tests</string>
+
+ <string name="settings_previewbuffer_key">settings_previewbuffer</string>
+ <string name="settings_previewbuffer_title">Preview buffer time (ms)</string>
+ <string name="settings_previewbuffer_summary">Length of time to allow the preview buffer to run before trying to capture an image. Longer values will ensure the hardware frame buffer is full to offer accurate capture-only latency.</string>
+
+ <string name="settings_autotest_header">Auto-test settings</string>
+ <string name="settings_autotest_header_key">settings_autotest_header_key</string>
+
+ <string name="settings_autotest_api_key">settings_autotest_api</string>
+ <string name="settings_autotest_api_title">APIs</string>
+ <string name="settings_autotest_api_summary">APIs to include in test</string>
+
+ <string name="settings_autotest_imagesize_key">settings_autotest_imagesize</string>
+ <string name="settings_autotest_imagesize_title">Image Capture Sizes</string>
+ <string name="settings_autotest_imagesize_summary">Capture sizes to include in test</string>
+
+ <string name="settings_autotest_focus_key">settings_autotest_focus</string>
+ <string name="settings_autotest_focus_title">Focus Modes</string>
+ <string name="settings_autotest_focus_summary">Focus modes to include in test</string>
+
+ <string name="settings_autotest_switchtest_key">settings_autotest_switchtest</string>
+ <string name="settings_autotest_switchtest_title">Perform Switch Test</string>
+ <string name="settings_autotest_switchtest_summary_on">Time switching from primary->secondary->primary camera.</string>
+ <string name="settings_autotest_switchtest_summmary_off">Do not perform switch test.</string>
- http://www.apache.org/licenses/LICENSE-2.0
+ <string name="settings_autotest_cameras_key">settings_autotest_cameras</string>
+ <string name="settings_autotest_cameras_title">Only Logical Cameras</string>
+ <string name="settings_autotest_cameras_summary_on">Only test logical cameras (default front and back camera).</string>
+ <string name="settings_autotest_cameras_summary_off">Test all physical cameras on the device.</string>
-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.
+ <!-- Single test settings -->
+ <string name="settings_single_test_dialog_title">Single Test</string>
+
+ <string name="settings_single_test_type_key">settings_single_test_type</string>
+ <string name="settings_single_test_type_title">Test Type</string>
+
+ <string name="settings_single_test_api_key">settings_single_test_api</string>
+ <string name="settings_single_test_api_title">API</string>
+
+ <string name="settings_single_test_focus_key">settings_single_test_focus</string>
+ <string name="settings_single_test_focus_title">Focus Mode</string>
+
+ <string name="settings_single_test_imagesize_key">settings_single_test_imagesize</string>
+ <string name="settings_single_test_imagesize_title">Image Capture Size</string>
+
+ <string name="settings_single_test_camera_key">settings_single_test_camera</string>
+ <string name="settings_single_test_camera_title">Camera</string>
+
+ <string name="settings_single_go">Begin Test</string>
+ <string name="settings_single_cancel">Cancel</string>
+ <string name="settings_multi_go">Begin Testing</string>
+ <string name="settings_multi_cancel">Cancel</string>
+
+ <!-- Multi Test settings -->
+ <string name="settings_multi_test_dialog_title">Auto Test</string>
+
+
+ <!--
+ <string name="settings__key">settings_</string>
+ <string name="settings__title"></string>
+ <string name="settings__summary"></string>
+
+ <string name="settings__key">settings_</string>
+ <string name="settings__title"></string>
+ <string name="settings__summary"></string>
-->
-<resources>
+ <!--
+ <string name="settings__key">settings_</string>
+ <string name="settings__title"></string>
+ <string name="settings__summary"></string>
+ -->
</resources>
diff --git a/camera/integration-tests/timingtestapp/src/main/res/values/style.xml b/camera/integration-tests/timingtestapp/src/main/res/values/style.xml
deleted file mode 100644
index 7503cc0b615..00000000000
--- a/camera/integration-tests/timingtestapp/src/main/res/values/style.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2019 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.
--->
-<resources>
- <!-- Base application theme. -->
- <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
- <!-- Customize your theme here. -->
- </style>
-</resources>
diff --git a/camera/integration-tests/timingtestapp/src/main/res/values/styles.xml b/camera/integration-tests/timingtestapp/src/main/res/values/styles.xml
new file mode 100644
index 00000000000..9d7ebc15f79
--- /dev/null
+++ b/camera/integration-tests/timingtestapp/src/main/res/values/styles.xml
@@ -0,0 +1,30 @@
+<!--
+ ~ Copyright 2019 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.
+ -->
+
+<resources>
+
+ <!-- Base application theme. -->
+ <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
+ <!-- Customize your theme here. -->
+ <item name="colorPrimary">@color/colorPrimary</item>
+ <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
+ <item name="colorAccent">@color/colorAccent</item>
+ </style>
+
+ <style name="SettingsDialogTheme" parent="@style/Theme.AppCompat.Light.Dialog">
+ <item name="android:windowNoTitle">false</item>
+ </style>
+</resources>
diff --git a/camera/integration-tests/timingtestapp/src/main/res/xml/multi_test_settings.xml b/camera/integration-tests/timingtestapp/src/main/res/xml/multi_test_settings.xml
new file mode 100644
index 00000000000..dccf2231f2e
--- /dev/null
+++ b/camera/integration-tests/timingtestapp/src/main/res/xml/multi_test_settings.xml
@@ -0,0 +1,130 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright 2019 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.
+ -->
+
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto">
+
+ <!-- Doesn't work as expected
+ <SeekBarPreference
+ android:id="@+id/seekbar_numtests"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:key="@string/settings_numtests_key"
+ android:title="@string/settings_numtests_title"
+ android:summary="@string/settings_numtests_summary"
+ android:defaultValue="30"
+ android:max="500"
+ app:min="5"
+ app:showSeekBarValue="true"
+ app:adjustable="true"
+ app:seekBarIncrement="5"
+ />
+
+ <SeekBarPreference
+ android:id="@+id/seekbar_numtests"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:key="@string/settings_previewbuffer_key"
+ android:title="@string/settings_previewbuffer_title"
+ android:summary="@string/settings_previewbuffer_summary"
+ android:defaultValue="1500"
+ android:max="5000"
+ app:min="250"
+ app:showSeekBarValue="true"
+ app:adjustable="true"
+ app:seekBarIncrement="250"
+ />
+-->
+
+ <MultiSelectListPreference
+ android:id="@+id/multiselect_apis"
+ android:defaultValue="@array/array_settings_api_defaults"
+ android:entries="@array/array_settings_api"
+ android:entryValues="@array/array_settings_api"
+ android:key="@string/settings_autotest_api_key"
+ android:summary="@string/settings_autotest_api_summary"
+ android:title="@string/settings_autotest_api_title"
+ app:iconSpaceReserved="false" />
+
+ <MultiSelectListPreference
+ android:id="@+id/multiselect_imagesize"
+ android:defaultValue="@array/array_settings_imagesize"
+ android:entries="@array/array_settings_imagesize"
+ android:entryValues="@array/array_settings_imagesize"
+ android:key="@string/settings_autotest_imagesize_key"
+ android:summary="@string/settings_autotest_imagesize_summary"
+ android:title="@string/settings_autotest_imagesize_title"
+ app:iconSpaceReserved="false" />
+
+ <MultiSelectListPreference
+ android:id="@+id/multiselect_focus"
+ android:defaultValue="@array/array_settings_focus"
+ android:entries="@array/array_settings_focus"
+ android:entryValues="@array/array_settings_focus"
+ android:key="@string/settings_autotest_focus_key"
+ android:summary="@string/settings_autotest_focus_summary"
+ android:title="@string/settings_autotest_focus_title"
+ app:iconSpaceReserved="false" />
+
+ <CheckBoxPreference
+ android:id="@+id/checkbox_switchtest"
+ android:defaultValue="true"
+ android:key="@string/settings_autotest_switchtest_key"
+ android:summaryOff="@string/settings_autotest_switchtest_summmary_off"
+ android:summaryOn="@string/settings_autotest_switchtest_summary_on"
+ android:title="@string/settings_autotest_switchtest_title"
+ app:iconSpaceReserved="false" />
+
+ <CheckBoxPreference
+ android:id="@+id/checkbox_logical_cameras"
+ android:defaultValue="true"
+ android:key="@string/settings_autotest_cameras_key"
+ android:summaryOff="@string/settings_autotest_cameras_summary_off"
+ android:summaryOn="@string/settings_autotest_cameras_summary_on"
+ android:title="@string/settings_autotest_cameras_title"
+ app:iconSpaceReserved="false" />
+
+
+ <ListPreference
+ android:id="@+id/list_numtests"
+ android:defaultValue="30"
+ android:entries="@array/array_numtests"
+ android:entryValues="@array/array_numtests"
+ android:key="@string/settings_numtests_key"
+ android:summary="%s"
+ android:title="@string/settings_numtests_title"
+ app:iconSpaceReserved="false" />
+
+ <ListPreference
+ android:id="@+id/list_previewbuffer"
+ android:defaultValue="1500"
+ android:entries="@array/array_previewbuffer"
+ android:entryValues="@array/array_previewbuffer"
+ android:key="@string/settings_previewbuffer_key"
+ android:summary="%s"
+ android:title="@string/settings_previewbuffer_title"
+ app:iconSpaceReserved="false" />
+
+ <CheckBoxPreference
+ android:id="@+id/checkbox_autodelete"
+ android:defaultValue="true"
+ android:key="@string/settings_autodelete_key"
+ android:summaryOff="@string/settings_autodelete_summary_off"
+ android:summaryOn="@string/settings_autodelete_summary"
+ android:title="@string/settings_autodelete_title"
+ app:iconSpaceReserved="false" />
+
+</PreferenceScreen> \ No newline at end of file
diff --git a/camera/integration-tests/timingtestapp/src/main/res/xml/single_test_settings.xml b/camera/integration-tests/timingtestapp/src/main/res/xml/single_test_settings.xml
new file mode 100644
index 00000000000..0a22ccb5c9c
--- /dev/null
+++ b/camera/integration-tests/timingtestapp/src/main/res/xml/single_test_settings.xml
@@ -0,0 +1,98 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright 2019 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.
+ -->
+
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto">
+
+ <ListPreference
+ android:id="@+id/list_single_test_type"
+ android:defaultValue="PHOTO"
+ android:entries="@array/array_single_test_types"
+ android:entryValues="@array/array_single_test_type_values"
+ android:key="@string/settings_single_test_type_key"
+ android:summary="%s"
+ android:title="@string/settings_single_test_type_title"
+ app:iconSpaceReserved="false" />
+
+ <ListPreference
+ android:id="@+id/list_single_test_api"
+ android:defaultValue="Camera2"
+ android:entries="@array/array_settings_api"
+ android:entryValues="@array/array_settings_api"
+ android:key="@string/settings_single_test_api_key"
+ android:summary="%s"
+ android:title="@string/settings_single_test_api_title"
+ app:iconSpaceReserved="false" />
+
+ <ListPreference
+ android:id="@+id/list_single_test_focus"
+ android:defaultValue="Auto"
+ android:entries="@array/array_settings_focus"
+ android:entryValues="@array/array_settings_focus"
+ android:key="@string/settings_single_test_focus_key"
+ android:summary="%s"
+ android:title="@string/settings_single_test_focus_title"
+ app:iconSpaceReserved="false" />
+
+ <ListPreference
+ android:id="@+id/list_single_test_imagesize"
+ android:defaultValue="Max"
+ android:entries="@array/array_settings_imagesize"
+ android:entryValues="@array/array_settings_imagesize"
+ android:key="@string/settings_single_test_imagesize_key"
+ android:summary="%s"
+ android:title="@string/settings_single_test_imagesize_title"
+ app:iconSpaceReserved="false" />
+
+ <ListPreference
+ android:id="@+id/list_single_test_camera"
+ android:key="@string/settings_single_test_camera_key"
+ android:summary="%s"
+ android:title="@string/settings_single_test_camera_title"
+ app:iconSpaceReserved="false" />
+
+ <ListPreference
+ android:id="@+id/list_numtests"
+ android:defaultValue="30"
+ android:entries="@array/array_numtests"
+ android:entryValues="@array/array_numtests"
+ android:key="@string/settings_numtests_key"
+ android:summary="%s"
+ android:title="@string/settings_numtests_title"
+ app:iconSpaceReserved="false" />
+ <!-- android:summary="@string/settings_numtests_summary" -->
+ <!-- android:summary="@string/settings_previewbuffer_summary" -->
+
+ <ListPreference
+ android:id="@+id/list_previewbuffer"
+ android:defaultValue="1500"
+ android:entries="@array/array_previewbuffer"
+ android:entryValues="@array/array_previewbuffer"
+ android:key="@string/settings_previewbuffer_key"
+ android:summary="%s"
+ android:title="@string/settings_previewbuffer_title"
+ app:iconSpaceReserved="false" />
+
+ <CheckBoxPreference
+ android:id="@+id/checkbox_autodelete"
+ android:defaultValue="true"
+ android:key="@string/settings_autodelete_key"
+ android:summaryOff="@string/settings_autodelete_summary_off"
+ android:summaryOn="@string/settings_autodelete_summary"
+ android:title="@string/settings_autodelete_title"
+ app:iconSpaceReserved="false" />
+
+</PreferenceScreen> \ No newline at end of file