Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
a15dce7
Cleanup some remaining SceneCore RestrictTo usages
mrichards Apr 9, 2026
ed39492
Update SceneCore's PlaneOrientation & PlaneSemanticType
mrichards Apr 7, 2026
a627c96
Fix Animation State text display in GltfModelAnimationActivity
Apr 9, 2026
48b62e6
Bump version for Glance Wear for upcoming release.
Apr 10, 2026
52c4d1a
Upgrade to AGP 9.2.0-alpha07
liutikas Apr 6, 2026
b4b333e
Fix Icon caching bug by invalidating draw cache when painter is changed
TrubnikovDmitriy Apr 10, 2026
a19ee43
Add ArCoreTestRule and suite of Test classes
Feb 6, 2026
fb344ed
Merge "Cleanup some remaining SceneCore RestrictTo usages" into andro…
Apr 10, 2026
9edcb32
Merge "Fix Icon caching bug by invalidating draw cache when painter i…
Apr 10, 2026
72f9e1e
Use MAX_PIPELINE_DEPTH for calculating ImageReader capacity margins
lnishan Apr 9, 2026
add0ab1
Merge "Update SceneCore's PlaneOrientation & PlaneSemanticType" into …
mrichards Apr 10, 2026
8f2be20
Revert "Use layout bounds for accessibility sibling occlusion subtrac…
Apr 10, 2026
699b177
Merge "Use MAX_PIPELINE_DEPTH for calculating ImageReader capacity ma…
lnishan Apr 10, 2026
8f1825c
Merge "Add ArCoreTestRule and suite of Test classes" into androidx-main
Apr 10, 2026
a2a31ed
Merge "Revert "Use layout bounds for accessibility sibling occlusion …
Apr 10, 2026
f8e163f
Update PlaneTest.kt to use ArCoreTestRule
Feb 9, 2026
32c7615
Merge "Bump version for Glance Wear for upcoming release." into andro…
Apr 10, 2026
f8a0537
[3/7] Lifecycle Manager Removal - Play Services Runtime
Mar 20, 2026
6c72517
Merge "Upgrade to AGP 9.2.0-alpha07" into androidx-main
liutikas Apr 10, 2026
d8c58d3
Migrate scenecore-testing Fakes to internal folders - deprecates old …
Apr 10, 2026
d4af32c
Merge "[3/7] Lifecycle Manager Removal - Play Services Runtime" into …
Apr 11, 2026
09af62c
Merge "Update PlaneTest.kt to use ArCoreTestRule" into androidx-main
Apr 11, 2026
142de10
Merge "Migrate scenecore-testing Fakes to internal folders - deprecat…
Apr 11, 2026
7c8fcf3
Merge "Fix Animation State text display in GltfModelAnimationActivity…
Apr 11, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 13 additions & 13 deletions buildSrc-tests/lint-baseline.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<issues format="6" by="lint 9.2.0-alpha03" type="baseline" client="gradle" dependencies="false" name="AGP (9.2.0-alpha03)" variant="all" version="9.2.0-alpha03">
<issues format="6" by="lint 9.2.0-alpha07" type="baseline" client="gradle" dependencies="false" name="AGP (9.2.0-alpha07)" variant="all" version="9.2.0-alpha07">

<issue
id="EagerGradleConfiguration"
Expand Down Expand Up @@ -301,53 +301,53 @@
<issue
id="GradleConfigurationCacheBroadInputs"
message="Use Project.providers.environmentVariable instead of getenv"
errorLine1=" if (System.getenv().containsKey(&quot;SSH_CLIENT&quot;) &amp;&amp; !System.getenv().containsKey(&quot;DISPLAY&quot;)) {"
errorLine2=" ~~~~~~">
errorLine1=" !System.getenv().containsKey(&quot;ANDROIDX_PROJECTS&quot;) &amp;&amp;"
errorLine2=" ~~~~~~">
<location
file="${:buildSrc-tests*main*MAIN*sourceProvider*0*javaDir*4}/androidx/build/studio/StudioTask.kt"/>
</issue>

<issue
id="GradleConfigurationCacheBroadInputs"
message="Use Project.providers.environmentVariable instead of getenv"
errorLine1=" if (System.getenv().containsKey(&quot;SSH_CLIENT&quot;) &amp;&amp; !System.getenv().containsKey(&quot;DISPLAY&quot;)) {"
errorLine2=" ~~~~~~">
errorLine1=" !System.getenv().containsKey(&quot;PROJECT_PREFIX&quot;)"
errorLine2=" ~~~~~~">
<location
file="${:buildSrc-tests*main*MAIN*sourceProvider*0*javaDir*4}/androidx/build/studio/StudioTask.kt"/>
</issue>

<issue
id="GradleConfigurationCacheBroadInputs"
message="Use Project.providers.environmentVariable instead of getenv"
errorLine1=" val canonicalSdkPath = File(System.getenv(&quot;HOME&quot;), relativeSdkPath)"
errorLine2=" ~~~~~~">
errorLine1=" System.getenv().containsKey(&quot;SSH_CLIENT&quot;) &amp;&amp; !System.getenv().containsKey(&quot;DISPLAY&quot;)"
errorLine2=" ~~~~~~">
<location
file="${:buildSrc-tests*main*MAIN*sourceProvider*0*javaDir*4}/androidx/build/studio/StudioTask.kt"/>
</issue>

<issue
id="GradleConfigurationCacheBroadInputs"
message="Use Project.providers.environmentVariable instead of getenv"
errorLine1=" !System.getenv().containsKey(&quot;ANDROIDX_PROJECTS&quot;) &amp;&amp;"
errorLine2=" ~~~~~~">
errorLine1=" System.getenv().containsKey(&quot;SSH_CLIENT&quot;) &amp;&amp; !System.getenv().containsKey(&quot;DISPLAY&quot;)"
errorLine2=" ~~~~~~">
<location
file="${:buildSrc-tests*main*MAIN*sourceProvider*0*javaDir*4}/androidx/build/studio/StudioTask.kt"/>
</issue>

<issue
id="GradleConfigurationCacheBroadInputs"
message="Use Project.providers.environmentVariable instead of getenv"
errorLine1=" !System.getenv().containsKey(&quot;PROJECT_PREFIX&quot;)"
errorLine2=" ~~~~~~">
errorLine1=" return if (System.getenv(&quot;QT_QPA_PLATFORM&quot;) == &quot;wayland&quot;) {"
errorLine2=" ~~~~~~">
<location
file="${:buildSrc-tests*main*MAIN*sourceProvider*0*javaDir*4}/androidx/build/studio/StudioTask.kt"/>
</issue>

<issue
id="GradleConfigurationCacheBroadInputs"
message="Use Project.providers.environmentVariable instead of getenv"
errorLine1=" return if (System.getenv(&quot;QT_QPA_PLATFORM&quot;) == &quot;wayland&quot;) {"
errorLine2=" ~~~~~~">
errorLine1=" val canonicalSdkPath = File(System.getenv(&quot;HOME&quot;), relativeSdkPath)"
errorLine2=" ~~~~~~">
<location
file="${:buildSrc-tests*main*MAIN*sourceProvider*0*javaDir*4}/androidx/build/studio/StudioTask.kt"/>
</issue>
Expand Down
20 changes: 1 addition & 19 deletions camera/camera-camera2-pipe/lint-baseline.xml
Original file line number Diff line number Diff line change
@@ -1,23 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<issues format="6" by="lint 9.0.0-alpha08" type="baseline" client="gradle" dependencies="false" name="AGP (9.0.0-alpha08)" variant="all" version="9.0.0-alpha08">

<issue
id="UseSdkSuppress"
message="Don&apos;t use @RequiresApi from tests; use @SdkSuppress on `cameraDeviceSetupCompatIsCached` instead"
errorLine1=" @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/test/java/androidx/camera/camera2/pipe/compat/Camera2DeviceCacheTest.kt"/>
</issue>

<issue
id="UseSdkSuppress"
message="Don&apos;t use @RequiresApi from tests; use @SdkSuppress on `cameraDeviceSetupWrapperIsCached` instead"
errorLine1=" @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/test/java/androidx/camera/camera2/pipe/compat/Camera2DeviceCacheTest.kt"/>
</issue>
<issues format="6" by="lint 9.2.0-alpha07" type="baseline" client="gradle" dependencies="false" name="AGP (9.2.0-alpha07)" variant="all" version="9.2.0-alpha07">

<issue
id="ObsoleteSdkInt"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import androidx.camera.camera2.pipe.CameraController
import androidx.camera.camera2.pipe.CameraGraph
import androidx.camera.camera2.pipe.CameraGraphId
import androidx.camera.camera2.pipe.CameraMetadata
import androidx.camera.camera2.pipe.CameraPipe
import androidx.camera.camera2.pipe.CameraSurfaceManager
import androidx.camera.camera2.pipe.Parameters
import androidx.camera.camera2.pipe.Request
Expand All @@ -46,6 +47,8 @@ import androidx.camera.camera2.pipe.internal.CameraGraphRequestListenersImpl
import androidx.camera.camera2.pipe.internal.FrameCaptureQueue
import androidx.camera.camera2.pipe.internal.FrameDistributor
import androidx.camera.camera2.pipe.internal.GraphSessionLock
import androidx.camera.camera2.pipe.media.ImageReaderImageSources
import androidx.camera.camera2.pipe.media.ImageSources
import dagger.Binds
import dagger.Module
import dagger.Provides
Expand Down Expand Up @@ -202,6 +205,18 @@ internal abstract class SharedCameraGraphModules {
}

@CameraGraphScope @Provides fun provideSystemClockOffsets() = SystemClockOffsets.estimate()

@CameraGraphScope
@Provides
fun configureImageSources(
imageReaderImageSources: ImageReaderImageSources,
cameraPipeConfig: CameraPipe.Config,
): ImageSources {
if (cameraPipeConfig.imageSources != null) {
return cameraPipeConfig.imageSources
}
return imageReaderImageSources
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,6 @@ import androidx.camera.camera2.pipe.core.TimeSource
import androidx.camera.camera2.pipe.internal.CameraBackendsImpl
import androidx.camera.camera2.pipe.internal.CameraDevicesImpl
import androidx.camera.camera2.pipe.internal.CameraPipeLifetime
import androidx.camera.camera2.pipe.media.ImageReaderImageSources
import androidx.camera.camera2.pipe.media.ImageSources
import androidx.camera.featurecombinationquery.CameraDeviceSetupCompatFactory
import dagger.Binds
import dagger.Component
Expand Down Expand Up @@ -188,17 +186,6 @@ internal abstract class CameraPipeModule {
)
}

@Provides
fun configureImageSources(
imageReaderImageSources: ImageReaderImageSources,
cameraPipeConfig: CameraPipe.Config,
): ImageSources {
if (cameraPipeConfig.imageSources != null) {
return cameraPipeConfig.imageSources
}
return imageReaderImageSources
}

@Singleton @Provides fun provideCameraSurfaceManager() = CameraSurfaceManager()

@Singleton
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,18 +106,6 @@ private constructor(
}

public companion object {
// See: b/172464059
//
// The ImageReader has an internal limit of 64 images by design, but depending on the device
// specific camera HAL (Which can be different per device) there is an additional number of
// images that are reserved by the Camera HAL which reduces this number. If, for example,
// the HAL reserves 8 images, you have a maximum of 56 (64 - 8).
//
// One of the worst cases observed is the HAL reserving 10 images, which gives a maximum
// capacity of 54 (64 - 10). For safety and compatibility reasons, set the maximum capacity
// to be 54, which leaves headroom for an app configured limit of 50.
internal const val IMAGEREADER_MAX_CAPACITY = 54

/**
* Create and configure a new ImageReader instance as an [ImageReaderWrapper].
*
Expand All @@ -138,11 +126,6 @@ private constructor(
require(width > 0) { "Width ($width) must be > 0" }
require(height > 0) { "Height ($height) must be > 0" }
require(capacity > 0) { "Capacity ($capacity) must be > 0" }
require(capacity <= IMAGEREADER_MAX_CAPACITY) {
"Capacity for creating new ImageSources is restricted to " +
"$IMAGEREADER_MAX_CAPACITY. Android has undocumented internal limits that " +
"are different depending on which device the ImageReader is created on."
}

// Warnings for unsupported features:
if (usageFlags != null && Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
Expand Down Expand Up @@ -337,12 +320,6 @@ public class AndroidMultiResolutionImageReader(
plaformApiCompat: PlatformApiCompat?,
): ImageReaderWrapper {
require(capacity > 0) { "Capacity ($capacity) must be > 0" }
require(capacity <= AndroidImageReader.IMAGEREADER_MAX_CAPACITY) {
"Capacity for creating new ImageSources is restricted to " +
"${AndroidImageReader.IMAGEREADER_MAX_CAPACITY}. Android has undocumented " +
"internal limits that are different depending on which device the " +
"MultiResolutionImageReader is created on."
}
if (enableConcurrentOutputs) {
require(plaformApiCompat?.isMultiResolutionConcurrentReadersEnabled() == true) {
"Concurrent MultiResolutionImageReaders are not supported on this device"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ import androidx.camera.camera2.pipe.InputStreamId
import androidx.camera.camera2.pipe.StreamFormat
import androidx.camera.camera2.pipe.compat.Api29Compat
import androidx.camera.camera2.pipe.core.Log
import androidx.camera.camera2.pipe.media.AndroidImageReader.Companion.IMAGEREADER_MAX_CAPACITY
import kotlin.reflect.KClass
import kotlinx.atomicfu.atomic

Expand Down Expand Up @@ -101,11 +100,6 @@ private constructor(
handler: Handler,
): ImageWriterWrapper {
require(maxImages > 0) { "Max images ($maxImages) must be > 0" }
require(maxImages <= IMAGEREADER_MAX_CAPACITY) {
"Max images for ImageWriters is restricted to " +
"$IMAGEREADER_MAX_CAPACITY to prevent overloading downstream " +
"consumer components."
}
// Create and configure a new ImageWriter
val imageWriter =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && format != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,28 +16,43 @@

package androidx.camera.camera2.pipe.media

import android.hardware.camera2.CameraCharacteristics
import android.media.ImageReader
import android.os.Build
import android.view.Surface
import androidx.camera.camera2.pipe.CameraMetadata
import androidx.camera.camera2.pipe.CameraPipe
import androidx.camera.camera2.pipe.CameraStream
import androidx.camera.camera2.pipe.ImageSourceConfig
import androidx.camera.camera2.pipe.OutputId
import androidx.camera.camera2.pipe.StreamId
import androidx.camera.camera2.pipe.core.Log
import androidx.camera.camera2.pipe.core.Threads
import androidx.camera.camera2.pipe.media.AndroidImageReader.Companion.IMAGEREADER_MAX_CAPACITY
import androidx.camera.camera2.pipe.media.ImageReaderImageSource.Companion.IMAGE_SOURCE_CAPACITY
import androidx.camera.camera2.pipe.media.OutputImage.Companion.toLogString
import javax.inject.Inject
import kotlin.reflect.KClass
import kotlinx.atomicfu.atomic

internal class ImageReaderImageSources
@Inject
constructor(private val threads: Threads, cameraPipeConfig: CameraPipe.Config) : ImageSources {
constructor(
private val threads: Threads,
cameraPipeConfig: CameraPipe.Config,
cameraMetadata: CameraMetadata,
) : ImageSources {
private val platformApiCompat = cameraPipeConfig.platformApiCompat

// See: b/172464059
//
// The ImageReader has an internal limit of 64 images by design, but depending on the device
// specific camera HAL (Which can be different per device) there is an additional number of
// images that are reserved by the Camera HAL which reduces this number. If, for example,
// the HAL reserves 8 images, you have a maximum of 56 (64 - 8).
private val maxImageReaderCapacity by lazy {
ImageReaderImageSource.BUFFER_QUEUE_MAX_CAPACITY -
checkNotNull(cameraMetadata[CameraCharacteristics.REQUEST_PIPELINE_MAX_DEPTH])
}

override fun createImageSource(
cameraStream: CameraStream,
imageSourceConfig: ImageSourceConfig,
Expand All @@ -62,11 +77,6 @@ constructor(private val threads: Threads, cameraPipeConfig: CameraPipe.Config) :
): ImageSource {
require(cameraStream.outputs.isNotEmpty()) { "$cameraStream must have outputs." }
require(capacity > 0) { "Capacity ($capacity) must be > 0" }
require(capacity <= IMAGE_SOURCE_CAPACITY) {
"Capacity for creating new ImageReaderImageSources is restricted to " +
"$IMAGE_SOURCE_CAPACITY. Android has undocumented internal limits that can vary " +
"per device."
}
if (enableConcurrentOutputs) {
check(cameraStream.outputs.size > 1) {
"Cannot enable concurrent outputs for a single output camera stream."
Expand All @@ -76,13 +86,7 @@ constructor(private val threads: Threads, cameraPipeConfig: CameraPipe.Config) :
val handlerProvider = { threads.camera2Handler }
val executorProvider = { threads.lightweightExecutor }

// Increase the internal capacity of the ImageReader so that the final capacity of the
// ImageSource matches the requested capacity.
//
// As an example, if the consumer requests "40", the ImageReader will be created with
// a capacity of "42", which will allow the consumer to hold exactly 40 images without
// stalling the camera pipeline.
val imageReaderCapacity = capacity + ImageReaderImageSource.IMAGE_SOURCE_CAPACITY_MARGIN
val imageReaderCapacity = clampImageReaderCapacity(capacity, maxImageReaderCapacity)

if (cameraStream.outputs.size == 1) {
val output = cameraStream.outputs.single()
Expand Down Expand Up @@ -146,6 +150,22 @@ constructor(private val threads: Threads, cameraPipeConfig: CameraPipe.Config) :
// but it was not possible to create it due to the SDK the code is running on.
throw IllegalStateException("Failed to create an ImageSource for $cameraStream!")
}

private fun clampImageReaderCapacity(desired: Int, maxImageReaderCapacity: Int): Int {
// Increase the internal capacity of the ImageReader so that the final capacity of the
// ImageSource matches the requested capacity.
//
// As an example, if the consumer requests "40", the ImageReader will be created with
// a capacity of "42", which will allow the consumer to hold exactly 40 images without
// stalling the camera pipeline.
return maxOf(
1 + ImageReaderImageSource.IMAGE_SOURCE_CAPACITY_MARGIN,
minOf(
desired + ImageReaderImageSource.IMAGE_SOURCE_CAPACITY_MARGIN,
maxImageReaderCapacity,
),
)
}
}

/** An ImageReaderImageSource implements an [ImageSource] using an [ImageReader] */
Expand All @@ -154,9 +174,8 @@ public class ImageReaderImageSource(
private val maxImages: Int,
) : ImageSource {
public companion object {
public const val BUFFER_QUEUE_MAX_CAPACITY: Int = 64
public const val IMAGE_SOURCE_CAPACITY_MARGIN: Int = 2
public const val IMAGE_SOURCE_CAPACITY: Int =
IMAGEREADER_MAX_CAPACITY - IMAGE_SOURCE_CAPACITY_MARGIN

public fun create(imageReader: ImageReaderWrapper): ImageSource {
// Reduce the maxImages of the ImageSource relative to the ImageReader to ensure there
Expand Down
11 changes: 1 addition & 10 deletions camera/camera-testing/lint-baseline.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<issues format="6" by="lint 9.0.0-beta02" type="baseline" client="gradle" dependencies="false" name="AGP (9.0.0-beta02)" variant="all" version="9.0.0-beta02">
<issues format="6" by="lint 9.2.0-alpha07" type="baseline" client="gradle" dependencies="false" name="AGP (9.2.0-alpha07)" variant="all" version="9.2.0-alpha07">

<issue
id="MissingPermission"
Expand Down Expand Up @@ -73,15 +73,6 @@
file="src/main/java/androidx/camera/testing/impl/CoreAppTestUtil.java"/>
</issue>

<issue
id="ObsoleteSdkInt"
message="Unnecessary; `SDK_INT` is never &lt; 23"
errorLine1=" if (Build.VERSION.SDK_INT &lt; Build.VERSION_CODES.M) {"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/camera/testing/impl/CoreAppTestUtil.java"/>
</issue>

<issue
id="ObsoleteSdkInt"
message="Unnecessary; `SDK_INT` is always >= 23"
Expand Down
Loading