Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,14 @@

package androidx.camera.camera2.compat

import android.content.Context
import android.hardware.camera2.CameraCaptureSession
import android.hardware.camera2.CaptureRequest
import android.os.Build
import android.util.Size
import android.view.Display
import android.view.Surface
import android.view.WindowManager
import androidx.annotation.RequiresApi

@RequiresApi(Build.VERSION_CODES.N)
Expand All @@ -36,6 +40,20 @@ internal object Api24Compat {
}
}

@RequiresApi(Build.VERSION_CODES.R)
internal object Api30Compat {
@JvmStatic
fun getDisplaySize(context: Context, display: Display): Size {
val displayContext = context.createDisplayContext(display)
val windowManager = displayContext.getSystemService(WindowManager::class.java)
val bounds =
checkNotNull(windowManager) { "WindowManager is not available." }
.maximumWindowMetrics
.bounds
return Size(bounds.width(), bounds.height())
}
}

@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
internal object Api34Compat {
@JvmStatic
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* Copyright 2026 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.camera2.compat

import android.content.Context
import android.graphics.Point
import android.os.Build
import android.util.Size
import android.view.Display

/** Helper for methods of [Display] that are backward compatible. */
public object DisplayCompat {

/**
* Gets the real size of the display.
*
* Uses [Api30Compat.getDisplaySize] on API 30+ to avoid using the deprecated
* [Display.getRealSize] method.
*/
@JvmStatic
@Suppress("DEPRECATION")
public fun getDisplaySize(context: Context, display: Display): Size {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
Api30Compat.getDisplaySize(context, display)
} else {
val displaySize = Point()
display.getRealSize(displaySize)
Size(displaySize.x, displaySize.y)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,14 @@
package androidx.camera.camera2.impl

import android.content.Context
import android.graphics.Point
import android.hardware.display.DisplayManager
import android.hardware.display.DisplayManager.DisplayListener
import android.os.Handler
import android.os.Looper
import android.util.Size
import android.view.Display
import androidx.annotation.VisibleForTesting
import androidx.camera.camera2.compat.DisplayCompat.getDisplaySize
import androidx.camera.camera2.compat.workaround.DisplaySizeCorrector
import androidx.camera.camera2.compat.workaround.MaxPreviewSize
import androidx.camera.core.impl.utils.ContextUtil
Expand All @@ -40,8 +40,7 @@ import androidx.camera.core.internal.utils.SizeUtil
* the display configuration changes. The next call to [getDisplays] or [getPreviewSize] will then
* fetch the fresh data.
*/
@Suppress("DEPRECATION") // getRealSize
public class DisplayInfoManager private constructor(context: Context) {
public class DisplayInfoManager private constructor(private val context: Context) {
private val maxPreviewSize = MaxPreviewSize()
private val displaySizeCorrector = DisplaySizeCorrector()

Expand Down Expand Up @@ -179,18 +178,16 @@ public class DisplayInfoManager private constructor(context: Context) {
var maxDisplaySize = -1

for (display: Display in displays) {
val displaySize = Point()
// TODO(b/230400472): Use WindowManager#getCurrentWindowMetrics(). Display#getRealSize()
// is deprecated since API level 31.
display.getRealSize(displaySize)
val displaySize = getDisplaySize(context, display)
val area = displaySize.width * displaySize.height

if (displaySize.x * displaySize.y > maxDisplaySize) {
maxDisplaySize = displaySize.x * displaySize.y
if (area > maxDisplaySize) {
maxDisplaySize = area
maxDisplay = display
}
if (display.state != Display.STATE_OFF) {
if (displaySize.x * displaySize.y > maxDisplaySizeWhenStateNotOff) {
maxDisplaySizeWhenStateNotOff = displaySize.x * displaySize.y
if (area > maxDisplaySizeWhenStateNotOff) {
maxDisplaySizeWhenStateNotOff = area
maxDisplayWhenStateNotOff = display
}
}
Expand All @@ -216,9 +213,7 @@ public class DisplayInfoManager private constructor(context: Context) {
}

private fun getCorrectedDisplaySize(): Size {
val displaySize = Point()
getMaxSizeDisplay(false).getRealSize(displaySize)
var displayViewSize = Size(displaySize.x, displaySize.y)
var displayViewSize = getDisplaySize(context, getMaxSizeDisplay(false))

// Checks whether the display size is abnormally small.
if (SizeUtil.isSmallerByArea(displayViewSize, ABNORMAL_DISPLAY_SIZE_THRESHOLD)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ import androidx.camera.camera2.pipe.testing.FakeCameraBackend
import androidx.camera.camera2.pipe.testing.FakeCameraDevices
import androidx.camera.camera2.pipe.testing.FakeCameraMetadata
import androidx.camera.camera2.pipe.testing.HighEndDeviceTemplate
import androidx.camera.camera2.testing.TestShadowWindowManager
import androidx.camera.core.CameraSelector
import androidx.camera.core.CameraSelector.LensFacing
import androidx.camera.core.CameraX
Expand Down Expand Up @@ -167,7 +168,7 @@ import org.robolectric.util.ReflectionHelpers
@Suppress("DEPRECATION")
@RunWith(RobolectricTestRunner::class)
@DoNotInstrument
@Config(sdk = [Config.ALL_SDKS])
@Config(sdk = [Config.ALL_SDKS], shadows = [TestShadowWindowManager::class])
class SupportedSurfaceCombinationTest {
private val streamUseCaseOption: androidx.camera.core.impl.Config.Option<Long> =
androidx.camera.core.impl.Config.Option.create(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,14 @@
package androidx.camera.camera2.impl

import android.content.Context
import android.graphics.Point
import android.hardware.display.DisplayManager
import android.os.Build
import android.util.Size
import android.view.Display
import android.view.WindowManager
import androidx.camera.camera2.adapter.RobolectricCameraPipeTestRunner
import androidx.camera.camera2.compat.DisplayCompat
import androidx.camera.camera2.testing.TestShadowWindowManager
import androidx.test.core.app.ApplicationProvider
import com.google.common.truth.Truth.assertThat
import org.junit.After
Expand All @@ -39,30 +40,27 @@ import org.robolectric.shadows.ShadowDisplayManager
import org.robolectric.shadows.ShadowDisplayManager.removeDisplay
import org.robolectric.util.ReflectionHelpers

@Suppress("DEPRECATION") // getRealSize
@RunWith(RobolectricCameraPipeTestRunner::class)
@DoNotInstrument
@Config(sdk = [Config.ALL_SDKS])
@Config(sdk = [Config.ALL_SDKS], shadows = [TestShadowWindowManager::class])
class DisplayInfoManagerTest {
private val context = ApplicationProvider.getApplicationContext<Context>()
private val displayInfoManager =
DisplayInfoManager.run {
// DisplayInfoManager is used in multiple classes which may be initiated in many other
// classes i.e. it may be used through many tests indirectly. So, we call
// releaseInstance once before getInstance too so that the first test in this class can
// also start with a clean slate.
releaseInstance()
getInstance(ApplicationProvider.getApplicationContext())
getInstance(context)
}

private fun addDisplay(width: Int, height: Int, state: Int = Display.STATE_ON): Int {
val displayStr = String.format("w%ddp-h%ddp", width, height)
val displayId = ShadowDisplayManager.addDisplay(displayStr)

if (state != Display.STATE_ON) {
val displayManager =
(ApplicationProvider.getApplicationContext() as Context).getSystemService(
Context.DISPLAY_SERVICE
) as DisplayManager
val displayManager = context.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
(Shadow.extract(displayManager.getDisplay(displayId)) as ShadowDisplay).setState(state)
}

Expand All @@ -77,16 +75,11 @@ class DisplayInfoManagerTest {
@Test
fun defaultDisplayIsDeviceDisplay_whenOneDisplay() {
// Arrange
val displayManager =
(ApplicationProvider.getApplicationContext() as Context).getSystemService(
Context.DISPLAY_SERVICE
) as DisplayManager
val currentDisplaySize = Point()
displayManager.displays[0].getRealSize(currentDisplaySize)
val displayManager = context.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
val currentDisplaySize = DisplayCompat.getDisplaySize(context, displayManager.displays[0])

// Act
val size = Point()
displayInfoManager.getMaxSizeDisplay().getRealSize(size)
val size = DisplayCompat.getDisplaySize(context, displayInfoManager.getMaxSizeDisplay())

// Assert
assertEquals(currentDisplaySize, size)
Expand All @@ -99,11 +92,10 @@ class DisplayInfoManagerTest {
addDisplay(480, 640)

// Act
val size = Point()
displayInfoManager.getMaxSizeDisplay().getRealSize(size)
val size = DisplayCompat.getDisplaySize(context, displayInfoManager.getMaxSizeDisplay())

// Assert
assertEquals(Point(2000, 3000), size)
assertEquals(Size(2000, 3000), size)
}

@Test
Expand All @@ -114,11 +106,10 @@ class DisplayInfoManagerTest {
removeDisplay(id)

// Act
val size = Point()
displayInfoManager.getMaxSizeDisplay().getRealSize(size)
val size = DisplayCompat.getDisplaySize(context, displayInfoManager.getMaxSizeDisplay())

// Assert
assertEquals(Point(480, 640), size)
assertEquals(Size(480, 640), size)
}

@Test
Expand All @@ -130,11 +121,10 @@ class DisplayInfoManagerTest {
displayInfoManager.getMaxSizeDisplay()
addDisplay(2000, 3000)

val size = Point()
displayInfoManager.getMaxSizeDisplay().getRealSize(size)
val size = DisplayCompat.getDisplaySize(context, displayInfoManager.getMaxSizeDisplay())

// Assert
assertEquals(Point(2000, 3000), size)
assertEquals(Size(2000, 3000), size)
}

@Test
Expand All @@ -146,11 +136,10 @@ class DisplayInfoManagerTest {
addDisplay(200, 300, Display.STATE_OFF)

// Act
val size = Point()
displayInfoManager.getMaxSizeDisplay().getRealSize(size)
val size = DisplayCompat.getDisplaySize(context, displayInfoManager.getMaxSizeDisplay())

// Assert
assertEquals(Point(480, 640), size)
assertEquals(Size(480, 640), size)
}

@Test
Expand All @@ -162,11 +151,10 @@ class DisplayInfoManagerTest {
addDisplay(200, 300, Display.STATE_OFF)

// Act
val size = Point()
displayInfoManager.getMaxSizeDisplay().getRealSize(size)
val size = DisplayCompat.getDisplaySize(context, displayInfoManager.getMaxSizeDisplay())

// Assert
assertEquals(Point(480, 640), size)
assertEquals(Size(480, 640), size)
}

@Test
Expand All @@ -178,11 +166,10 @@ class DisplayInfoManagerTest {
removeDisplay(0)

// Act
val size = Point()
displayInfoManager.getMaxSizeDisplay().getRealSize(size)
val size = DisplayCompat.getDisplaySize(context, displayInfoManager.getMaxSizeDisplay())

// Assert
assertEquals(Point(2000, 3000), size)
assertEquals(Size(2000, 3000), size)
}

@Test(expected = IllegalStateException::class)
Expand All @@ -191,8 +178,7 @@ class DisplayInfoManagerTest {
removeDisplay(0)

// Act
val size = Point()
displayInfoManager.getMaxSizeDisplay().getRealSize(size)
DisplayCompat.getDisplaySize(context, displayInfoManager.getMaxSizeDisplay())
}

@Test
Expand Down Expand Up @@ -227,37 +213,33 @@ class DisplayInfoManagerTest {
assertEquals(Size(1920, 1080), displayInfoManager.getPreviewSize())
}

@Suppress("DEPRECATION") // WindowManager.defaultDisplay
@Test
fun canReturnFallbackPreviewSize640x480_displaySmallerThan320x240() {
// Arrange
val windowManager =
ApplicationProvider.getApplicationContext<Context>()
.getSystemService(Context.WINDOW_SERVICE) as WindowManager
val windowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
Shadows.shadowOf(windowManager.defaultDisplay).setRealWidth(16)
Shadows.shadowOf(windowManager.defaultDisplay).setRealHeight(16)

// Act & Assert
DisplayInfoManager.releaseInstance()
val displayInfoManager =
DisplayInfoManager.getInstance(ApplicationProvider.getApplicationContext())
val displayInfoManager = DisplayInfoManager.getInstance(context)
assertThat(displayInfoManager.getPreviewSize()).isEqualTo(Size(640, 480))
}

@Suppress("DEPRECATION") // WindowManager.defaultDisplay
@Test
fun canReturnCorrectPreviewSize_fromDisplaySizeCorrector() {
// Arrange
val windowManager =
ApplicationProvider.getApplicationContext<Context>()
.getSystemService(Context.WINDOW_SERVICE) as WindowManager
val windowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
Shadows.shadowOf(windowManager.defaultDisplay).setRealWidth(16)
Shadows.shadowOf(windowManager.defaultDisplay).setRealHeight(16)

ReflectionHelpers.setStaticField(Build::class.java, "MODEL", "SM-A127F")

// Act & Assert
DisplayInfoManager.releaseInstance()
val displayInfoManager =
DisplayInfoManager.getInstance(ApplicationProvider.getApplicationContext())
val displayInfoManager = DisplayInfoManager.getInstance(context)
assertThat(displayInfoManager.getPreviewSize()).isEqualTo(Size(1600, 720))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import androidx.camera.camera2.adapter.RobolectricCameraPipeTestRunner
import androidx.camera.camera2.pipe.testing.FakeCameraMetadata
import androidx.camera.camera2.pipe.testing.HighEndDeviceTemplate
import androidx.camera.camera2.testing.FakeCameraProperties
import androidx.camera.camera2.testing.TestShadowWindowManager
import androidx.camera.core.impl.StreamSpec
import androidx.test.core.app.ApplicationProvider
import com.google.common.truth.Truth.assertThat
Expand All @@ -41,7 +42,7 @@ import org.robolectric.util.ReflectionHelpers

@RunWith(RobolectricCameraPipeTestRunner::class)
@DoNotInstrument
@Config(sdk = [Config.ALL_SDKS])
@Config(sdk = [Config.ALL_SDKS], shadows = [TestShadowWindowManager::class])
class MeteringRepeatingTest {
companion object {
val dummyZeroSizeStreamSpec = StreamSpec.builder(Size(0, 0)).build()
Expand Down
Loading
Loading