1 /*
2  * Copyright 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package androidx.camera.camera2.pipe.integration.impl
18 
19 import android.content.Context
20 import android.graphics.Point
21 import android.hardware.display.DisplayManager
22 import android.hardware.display.DisplayManager.DisplayListener
23 import android.os.Handler
24 import android.os.Looper
25 import android.util.Size
26 import android.view.Display
27 import androidx.camera.camera2.pipe.integration.compat.workaround.DisplaySizeCorrector
28 import androidx.camera.camera2.pipe.integration.compat.workaround.MaxPreviewSize
29 import androidx.camera.core.internal.utils.SizeUtil
30 import javax.inject.Inject
31 import javax.inject.Singleton
32 
33 @Suppress("DEPRECATION") // getRealSize
34 @Singleton
35 public class DisplayInfoManager @Inject constructor(context: Context) {
36     private val maxPreviewSize = MaxPreviewSize()
37     private val displaySizeCorrector = DisplaySizeCorrector()
38 
39     public companion object {
40         private val MAX_PREVIEW_SIZE = Size(1920, 1080)
41         /** This is the smallest size from a device which had issue reported to CameraX. */
42         private val ABNORMAL_DISPLAY_SIZE_THRESHOLD: Size = Size(320, 240)
43         /**
44          * The fallback display size for the case that the retrieved display size is abnormally
45          * small and no correct display size can be retrieved from DisplaySizeCorrector.
46          */
47         private val FALLBACK_DISPLAY_SIZE: Size = Size(640, 480)
48         private var lazyMaxDisplay: Display? = null
49         private var lazyPreviewSize: Size? = null
50 
invalidateLazyFieldsnull51         internal fun invalidateLazyFields() {
52             lazyMaxDisplay = null
53             lazyPreviewSize = null
54         }
55 
<lambda>null56         internal val displayListener by lazy {
57             object : DisplayListener {
58                 override fun onDisplayAdded(displayId: Int) {
59                     invalidateLazyFields()
60                 }
61 
62                 override fun onDisplayRemoved(displayId: Int) {
63                     invalidateLazyFields()
64                 }
65 
66                 override fun onDisplayChanged(displayId: Int) {
67                     invalidateLazyFields()
68                 }
69             }
70         }
71     }
72 
<lambda>null73     private val displayManager: DisplayManager by lazy {
74         (context.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager).also {
75             it.registerDisplayListener(displayListener, Handler(Looper.getMainLooper()))
76         }
77     }
78 
79     public val defaultDisplay: Display
80         get() = getMaxSizeDisplay()
81 
82     private var previewSize: Size? = null
83 
84     /** Update the preview size according to current display size. */
refreshnull85     public fun refresh() {
86         previewSize = calculatePreviewSize()
87     }
88 
89     /**
90      * PREVIEW refers to the best size match to the device's screen resolution, or to 1080p
91      * (1920x1080), whichever is smaller.
92      */
getPreviewSizenull93     public fun getPreviewSize(): Size {
94         // Use cached value to speed up since this would be called multiple times.
95         if (previewSize != null) {
96             return previewSize as Size
97         }
98         previewSize = calculatePreviewSize()
99         return previewSize as Size
100     }
101 
getMaxSizeDisplaynull102     private fun getMaxSizeDisplay(): Display {
103         lazyMaxDisplay?.let {
104             return it
105         }
106 
107         val displays = displayManager.displays
108 
109         var maxDisplayWhenStateNotOff: Display? = null
110         var maxDisplaySizeWhenStateNotOff = -1
111 
112         var maxDisplay: Display? = null
113         var maxDisplaySize = -1
114 
115         for (display: Display in displays) {
116             val displaySize = Point()
117             // TODO(b/230400472): Use WindowManager#getCurrentWindowMetrics(). Display#getRealSize()
118             //  is deprecated since API level 31.
119             display.getRealSize(displaySize)
120 
121             if (displaySize.x * displaySize.y > maxDisplaySize) {
122                 maxDisplaySize = displaySize.x * displaySize.y
123                 maxDisplay = display
124             }
125             if (display.state != Display.STATE_OFF) {
126                 if (displaySize.x * displaySize.y > maxDisplaySizeWhenStateNotOff) {
127                     maxDisplaySizeWhenStateNotOff = displaySize.x * displaySize.y
128                     maxDisplayWhenStateNotOff = display
129                 }
130             }
131         }
132 
133         lazyMaxDisplay = maxDisplayWhenStateNotOff ?: maxDisplay
134 
135         return checkNotNull(lazyMaxDisplay) { "No displays found from ${displayManager.displays}!" }
136     }
137 
138     /** Calculates the device's screen resolution, or MAX_PREVIEW_SIZE, whichever is smaller. */
calculatePreviewSizenull139     private fun calculatePreviewSize(): Size {
140         lazyPreviewSize?.let {
141             return it
142         }
143 
144         var displayViewSize = getCorrectedDisplaySize()
145         if (SizeUtil.isSmallerByArea(MAX_PREVIEW_SIZE, displayViewSize)) {
146             displayViewSize = MAX_PREVIEW_SIZE
147         }
148         return maxPreviewSize.getMaxPreviewResolution(displayViewSize).also { lazyPreviewSize = it }
149     }
150 
getCorrectedDisplaySizenull151     private fun getCorrectedDisplaySize(): Size {
152         val displaySize = Point()
153         defaultDisplay.getRealSize(displaySize)
154         var displayViewSize = Size(displaySize.x, displaySize.y)
155 
156         // Checks whether the display size is abnormally small.
157         if (SizeUtil.isSmallerByArea(displayViewSize, ABNORMAL_DISPLAY_SIZE_THRESHOLD)) {
158             // Gets the display size from DisplaySizeCorrector if the display size retrieved from
159             // DisplayManager is abnormally small. Falls back the display size to 640x480 if
160             // DisplaySizeCorrector doesn't contain the device's display size info.
161             displayViewSize = displaySizeCorrector.displaySize ?: FALLBACK_DISPLAY_SIZE
162         }
163 
164         // Flips the size to landscape orientation
165         if (displayViewSize.height > displayViewSize.width) {
166             displayViewSize =
167                 Size(/* width= */ displayViewSize.height, /* height= */ displayViewSize.width)
168         }
169 
170         return displayViewSize
171     }
172 }
173