1 /* 2 * Copyright (C) 2024 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 com.android.systemui 18 19 import android.graphics.Rect 20 import android.view.Display 21 import android.view.DisplayAdjustments 22 import android.view.DisplayCutout 23 import android.view.DisplayInfo 24 import android.view.Surface 25 import android.view.Surface.Rotation 26 import androidx.test.ext.junit.runners.AndroidJUnit4 27 import androidx.test.filters.SmallTest 28 import com.android.systemui.util.mockito.any 29 import com.android.systemui.util.mockito.mock 30 import com.android.systemui.util.mockito.whenever 31 import com.google.common.truth.Truth.assertThat 32 import org.junit.Test 33 import org.junit.runner.RunWith 34 35 @SmallTest 36 @RunWith(AndroidJUnit4::class) 37 class SysUICutoutProviderTest : SysuiTestCase() { 38 39 private val fakeProtectionLoader = FakeCameraProtectionLoader(context) 40 41 @Test cutoutInfoForCurrentDisplay_noCutout_returnsNullnull42 fun cutoutInfoForCurrentDisplay_noCutout_returnsNull() { 43 val noCutoutDisplay = createDisplay(cutout = null) 44 val noCutoutDisplayContext = context.createDisplayContext(noCutoutDisplay) 45 val provider = SysUICutoutProvider(noCutoutDisplayContext, fakeProtectionLoader) 46 47 val sysUICutout = provider.cutoutInfoForCurrentDisplayAndRotation() 48 49 assertThat(sysUICutout).isNull() 50 } 51 52 @Test cutoutInfoForCurrentDisplay_returnsCutoutnull53 fun cutoutInfoForCurrentDisplay_returnsCutout() { 54 val cutoutDisplay = createDisplay() 55 val cutoutDisplayContext = context.createDisplayContext(cutoutDisplay) 56 val provider = SysUICutoutProvider(cutoutDisplayContext, fakeProtectionLoader) 57 58 val sysUICutout = provider.cutoutInfoForCurrentDisplayAndRotation()!! 59 60 assertThat(sysUICutout.cutout).isEqualTo(cutoutDisplay.cutout) 61 } 62 63 @Test cutoutInfoForCurrentDisplay_noAssociatedProtection_returnsNoProtectionnull64 fun cutoutInfoForCurrentDisplay_noAssociatedProtection_returnsNoProtection() { 65 val cutoutDisplay = createDisplay() 66 val cutoutDisplayContext = context.createDisplayContext(cutoutDisplay) 67 val provider = SysUICutoutProvider(cutoutDisplayContext, fakeProtectionLoader) 68 69 val sysUICutout = provider.cutoutInfoForCurrentDisplayAndRotation()!! 70 71 assertThat(sysUICutout.cameraProtection).isNull() 72 } 73 74 @Test cutoutInfoForCurrentDisplay_outerDisplay_protectionAssociated_returnsProtectionnull75 fun cutoutInfoForCurrentDisplay_outerDisplay_protectionAssociated_returnsProtection() { 76 fakeProtectionLoader.addOuterCameraProtection(displayUniqueId = OUTER_DISPLAY_UNIQUE_ID) 77 val outerDisplayContext = context.createDisplayContext(OUTER_DISPLAY) 78 val provider = SysUICutoutProvider(outerDisplayContext, fakeProtectionLoader) 79 80 val sysUICutout = provider.cutoutInfoForCurrentDisplayAndRotation()!! 81 82 assertThat(sysUICutout.cameraProtection).isNotNull() 83 } 84 85 @Test cutoutInfoForCurrentDisplay_outerDisplay_protectionNotAvailable_returnsNullProtectionnull86 fun cutoutInfoForCurrentDisplay_outerDisplay_protectionNotAvailable_returnsNullProtection() { 87 fakeProtectionLoader.clearProtectionInfoList() 88 val outerDisplayContext = context.createDisplayContext(OUTER_DISPLAY) 89 val provider = SysUICutoutProvider(outerDisplayContext, fakeProtectionLoader) 90 91 val sysUICutout = provider.cutoutInfoForCurrentDisplayAndRotation()!! 92 93 assertThat(sysUICutout.cameraProtection).isNull() 94 } 95 96 @Test cutoutInfoForCurrentDisplay_displayWithNullId_protectionsWithNoId_returnsNullProtectionnull97 fun cutoutInfoForCurrentDisplay_displayWithNullId_protectionsWithNoId_returnsNullProtection() { 98 fakeProtectionLoader.addOuterCameraProtection(displayUniqueId = "") 99 val displayContext = context.createDisplayContext(createDisplay(uniqueId = null)) 100 val provider = SysUICutoutProvider(displayContext, fakeProtectionLoader) 101 102 val sysUICutout = provider.cutoutInfoForCurrentDisplayAndRotation()!! 103 104 assertThat(sysUICutout.cameraProtection).isNull() 105 } 106 107 @Test cutoutInfoForCurrentDisplay_displayWithEmptyId_protectionsWithNoId_returnsNullProtectionnull108 fun cutoutInfoForCurrentDisplay_displayWithEmptyId_protectionsWithNoId_returnsNullProtection() { 109 fakeProtectionLoader.addOuterCameraProtection(displayUniqueId = "") 110 val displayContext = context.createDisplayContext(createDisplay(uniqueId = "")) 111 val provider = SysUICutoutProvider(displayContext, fakeProtectionLoader) 112 113 val sysUICutout = provider.cutoutInfoForCurrentDisplayAndRotation()!! 114 115 assertThat(sysUICutout.cameraProtection).isNull() 116 } 117 118 @Test cutoutInfo_rotation0_returnsOriginalProtectionBoundsnull119 fun cutoutInfo_rotation0_returnsOriginalProtectionBounds() { 120 val provider = 121 setUpProviderWithCameraProtection( 122 displayWidth = 500, 123 displayHeight = 1000, 124 rotation = Surface.ROTATION_0, 125 protectionBounds = 126 Rect(/* left = */ 440, /* top = */ 10, /* right = */ 490, /* bottom = */ 110) 127 ) 128 129 val sysUICutout = provider.cutoutInfoForCurrentDisplayAndRotation()!! 130 131 assertThat(sysUICutout.cameraProtection!!.bounds) 132 .isEqualTo( 133 Rect(/* left = */ 440, /* top = */ 10, /* right = */ 490, /* bottom = */ 110) 134 ) 135 } 136 137 @Test cutoutInfo_rotation90_returnsRotatedProtectionBoundsnull138 fun cutoutInfo_rotation90_returnsRotatedProtectionBounds() { 139 val provider = 140 setUpProviderWithCameraProtection( 141 displayWidth = 500, 142 displayHeight = 1000, 143 rotation = Surface.ROTATION_90, 144 protectionBounds = 145 Rect(/* left = */ 440, /* top = */ 10, /* right = */ 490, /* bottom = */ 110) 146 ) 147 148 val sysUICutout = provider.cutoutInfoForCurrentDisplayAndRotation()!! 149 150 assertThat(sysUICutout.cameraProtection!!.bounds) 151 .isEqualTo(Rect(/* left = */ 10, /* top = */ 10, /* right = */ 110, /* bottom = */ 60)) 152 } 153 154 @Test cutoutInfo_withRotation_doesNotMutateOriginalBoundsnull155 fun cutoutInfo_withRotation_doesNotMutateOriginalBounds() { 156 val displayNaturalWidth = 500 157 val displayNaturalHeight = 1000 158 val originalProtectionBounds = 159 Rect(/* left = */ 440, /* top = */ 10, /* right = */ 490, /* bottom = */ 110) 160 // Safe copy as we don't know at which layer the mutation could happen 161 val originalProtectionBoundsCopy = Rect(originalProtectionBounds) 162 val display = 163 createDisplay( 164 uniqueId = OUTER_DISPLAY_UNIQUE_ID, 165 rotation = Surface.ROTATION_180, 166 width = displayNaturalWidth, 167 height = displayNaturalHeight, 168 ) 169 fakeProtectionLoader.addOuterCameraProtection( 170 displayUniqueId = OUTER_DISPLAY_UNIQUE_ID, 171 bounds = originalProtectionBounds 172 ) 173 val provider = 174 SysUICutoutProvider(context.createDisplayContext(display), fakeProtectionLoader) 175 176 // Here we get the rotated bounds once 177 provider.cutoutInfoForCurrentDisplayAndRotation() 178 179 // Rotate display back to original rotation 180 whenever(display.rotation).thenReturn(Surface.ROTATION_0) 181 182 // Now the bounds should match the original ones. We are checking for mutation since Rect 183 // is mutable and has many methods that mutate the instance, and it is easy to do it by 184 // mistake. 185 val sysUICutout = provider.cutoutInfoForCurrentDisplayAndRotation()!! 186 assertThat(sysUICutout.cameraProtection!!.bounds).isEqualTo(originalProtectionBoundsCopy) 187 } 188 189 @Test cutoutInfo_rotation180_returnsRotatedProtectionBoundsnull190 fun cutoutInfo_rotation180_returnsRotatedProtectionBounds() { 191 val provider = 192 setUpProviderWithCameraProtection( 193 displayWidth = 500, 194 displayHeight = 1000, 195 rotation = Surface.ROTATION_180, 196 protectionBounds = 197 Rect(/* left = */ 440, /* top = */ 10, /* right = */ 490, /* bottom = */ 110) 198 ) 199 200 val sysUICutout = provider.cutoutInfoForCurrentDisplayAndRotation()!! 201 202 assertThat(sysUICutout.cameraProtection!!.bounds) 203 .isEqualTo(Rect(/* left = */ 10, /* top = */ 890, /* right = */ 60, /* bottom = */ 990)) 204 } 205 206 @Test cutoutInfo_rotation270_returnsRotatedProtectionBoundsnull207 fun cutoutInfo_rotation270_returnsRotatedProtectionBounds() { 208 val provider = 209 setUpProviderWithCameraProtection( 210 displayWidth = 500, 211 displayHeight = 1000, 212 rotation = Surface.ROTATION_270, 213 protectionBounds = 214 Rect(/* left = */ 440, /* top = */ 10, /* right = */ 490, /* bottom = */ 110) 215 ) 216 217 val sysUICutout = provider.cutoutInfoForCurrentDisplayAndRotation()!! 218 219 assertThat(sysUICutout.cameraProtection!!.bounds) 220 .isEqualTo( 221 Rect(/* left = */ 890, /* top = */ 440, /* right = */ 990, /* bottom = */ 490) 222 ) 223 } 224 setUpProviderWithCameraProtectionnull225 private fun setUpProviderWithCameraProtection( 226 displayWidth: Int, 227 displayHeight: Int, 228 rotation: Int = Surface.ROTATION_0, 229 protectionBounds: Rect, 230 ): SysUICutoutProvider { 231 val display = 232 createDisplay( 233 uniqueId = OUTER_DISPLAY_UNIQUE_ID, 234 rotation = rotation, 235 width = 236 if (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180) { 237 displayWidth 238 } else { 239 displayHeight 240 }, 241 height = 242 if (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180) 243 displayHeight 244 else displayWidth, 245 ) 246 fakeProtectionLoader.addOuterCameraProtection( 247 displayUniqueId = OUTER_DISPLAY_UNIQUE_ID, 248 bounds = protectionBounds 249 ) 250 return SysUICutoutProvider(context.createDisplayContext(display), fakeProtectionLoader) 251 } 252 253 companion object { 254 private const val OUTER_DISPLAY_UNIQUE_ID = "outer" 255 private val OUTER_DISPLAY = createDisplay(uniqueId = OUTER_DISPLAY_UNIQUE_ID) 256 createDisplaynull257 private fun createDisplay( 258 width: Int = 500, 259 height: Int = 1000, 260 @Rotation rotation: Int = Surface.ROTATION_0, 261 uniqueId: String? = "uniqueId", 262 cutout: DisplayCutout? = mock<DisplayCutout>() 263 ) = 264 mock<Display> { 265 whenever(this.getDisplayInfo(any())).thenAnswer { 266 val displayInfo = it.arguments[0] as DisplayInfo 267 displayInfo.rotation = rotation 268 displayInfo.logicalWidth = width 269 displayInfo.logicalHeight = height 270 return@thenAnswer true 271 } 272 // Setting width and height to smaller values to simulate real behavior of this API 273 // not always returning the real display size 274 whenever(this.width).thenReturn(width - 5) 275 whenever(this.height).thenReturn(height - 10) 276 whenever(this.rotation).thenReturn(rotation) 277 whenever(this.displayAdjustments).thenReturn(DisplayAdjustments()) 278 whenever(this.cutout).thenReturn(cutout) 279 whenever(this.uniqueId).thenReturn(uniqueId) 280 } 281 } 282 } 283