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 = SysUICutoutProviderImpl(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 = SysUICutoutProviderImpl(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 = SysUICutoutProviderImpl(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 = SysUICutoutProviderImpl(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 = SysUICutoutProviderImpl(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 = SysUICutoutProviderImpl(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 = SysUICutoutProviderImpl(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(Rect(/* left= */ 440, /* top= */ 10, /* right= */ 490, /* bottom= */ 110)) 133 } 134 135 @Test cutoutInfo_rotation90_returnsRotatedProtectionBoundsnull136 fun cutoutInfo_rotation90_returnsRotatedProtectionBounds() { 137 val provider = 138 setUpProviderWithCameraProtection( 139 displayWidth = 500, 140 displayHeight = 1000, 141 rotation = Surface.ROTATION_90, 142 protectionBounds = 143 Rect(/* left= */ 440, /* top= */ 10, /* right= */ 490, /* bottom= */ 110), 144 ) 145 146 val sysUICutout = provider.cutoutInfoForCurrentDisplayAndRotation()!! 147 148 assertThat(sysUICutout.cameraProtection!!.bounds) 149 .isEqualTo(Rect(/* left= */ 10, /* top= */ 10, /* right= */ 110, /* bottom= */ 60)) 150 } 151 152 @Test cutoutInfo_withRotation_doesNotMutateOriginalBoundsnull153 fun cutoutInfo_withRotation_doesNotMutateOriginalBounds() { 154 val displayNaturalWidth = 500 155 val displayNaturalHeight = 1000 156 val originalProtectionBounds = 157 Rect(/* left= */ 440, /* top= */ 10, /* right= */ 490, /* bottom= */ 110) 158 // Safe copy as we don't know at which layer the mutation could happen 159 val originalProtectionBoundsCopy = Rect(originalProtectionBounds) 160 val display = 161 createDisplay( 162 uniqueId = OUTER_DISPLAY_UNIQUE_ID, 163 rotation = Surface.ROTATION_180, 164 width = displayNaturalWidth, 165 height = displayNaturalHeight, 166 ) 167 fakeProtectionLoader.addOuterCameraProtection( 168 displayUniqueId = OUTER_DISPLAY_UNIQUE_ID, 169 bounds = originalProtectionBounds, 170 ) 171 val provider = 172 SysUICutoutProviderImpl(context.createDisplayContext(display), fakeProtectionLoader) 173 174 // Here we get the rotated bounds once 175 provider.cutoutInfoForCurrentDisplayAndRotation() 176 177 // Rotate display back to original rotation 178 whenever(display.rotation).thenReturn(Surface.ROTATION_0) 179 180 // Now the bounds should match the original ones. We are checking for mutation since Rect 181 // is mutable and has many methods that mutate the instance, and it is easy to do it by 182 // mistake. 183 val sysUICutout = provider.cutoutInfoForCurrentDisplayAndRotation()!! 184 assertThat(sysUICutout.cameraProtection!!.bounds).isEqualTo(originalProtectionBoundsCopy) 185 } 186 187 @Test cutoutInfo_rotation180_returnsRotatedProtectionBoundsnull188 fun cutoutInfo_rotation180_returnsRotatedProtectionBounds() { 189 val provider = 190 setUpProviderWithCameraProtection( 191 displayWidth = 500, 192 displayHeight = 1000, 193 rotation = Surface.ROTATION_180, 194 protectionBounds = 195 Rect(/* left= */ 440, /* top= */ 10, /* right= */ 490, /* bottom= */ 110), 196 ) 197 198 val sysUICutout = provider.cutoutInfoForCurrentDisplayAndRotation()!! 199 200 assertThat(sysUICutout.cameraProtection!!.bounds) 201 .isEqualTo(Rect(/* left= */ 10, /* top= */ 890, /* right= */ 60, /* bottom= */ 990)) 202 } 203 204 @Test cutoutInfo_rotation270_returnsRotatedProtectionBoundsnull205 fun cutoutInfo_rotation270_returnsRotatedProtectionBounds() { 206 val provider = 207 setUpProviderWithCameraProtection( 208 displayWidth = 500, 209 displayHeight = 1000, 210 rotation = Surface.ROTATION_270, 211 protectionBounds = 212 Rect(/* left= */ 440, /* top= */ 10, /* right= */ 490, /* bottom= */ 110), 213 ) 214 215 val sysUICutout = provider.cutoutInfoForCurrentDisplayAndRotation()!! 216 217 assertThat(sysUICutout.cameraProtection!!.bounds) 218 .isEqualTo(Rect(/* left= */ 890, /* top= */ 440, /* right= */ 990, /* bottom= */ 490)) 219 } 220 setUpProviderWithCameraProtectionnull221 private fun setUpProviderWithCameraProtection( 222 displayWidth: Int, 223 displayHeight: Int, 224 rotation: Int = Surface.ROTATION_0, 225 protectionBounds: Rect, 226 ): SysUICutoutProvider { 227 val display = 228 createDisplay( 229 uniqueId = OUTER_DISPLAY_UNIQUE_ID, 230 rotation = rotation, 231 width = 232 if (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180) { 233 displayWidth 234 } else { 235 displayHeight 236 }, 237 height = 238 if (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180) 239 displayHeight 240 else displayWidth, 241 ) 242 fakeProtectionLoader.addOuterCameraProtection( 243 displayUniqueId = OUTER_DISPLAY_UNIQUE_ID, 244 bounds = protectionBounds, 245 ) 246 return SysUICutoutProviderImpl(context.createDisplayContext(display), fakeProtectionLoader) 247 } 248 249 companion object { 250 private const val OUTER_DISPLAY_UNIQUE_ID = "outer" 251 private val OUTER_DISPLAY = createDisplay(uniqueId = OUTER_DISPLAY_UNIQUE_ID) 252 createDisplaynull253 private fun createDisplay( 254 width: Int = 500, 255 height: Int = 1000, 256 @Rotation rotation: Int = Surface.ROTATION_0, 257 uniqueId: String? = "uniqueId", 258 cutout: DisplayCutout? = mock<DisplayCutout>(), 259 ) = 260 mock<Display> { 261 whenever(this.getDisplayInfo(any())).thenAnswer { 262 val displayInfo = it.arguments[0] as DisplayInfo 263 displayInfo.rotation = rotation 264 displayInfo.logicalWidth = width 265 displayInfo.logicalHeight = height 266 return@thenAnswer true 267 } 268 // Setting width and height to smaller values to simulate real behavior of this API 269 // not always returning the real display size 270 whenever(this.width).thenReturn(width - 5) 271 whenever(this.height).thenReturn(height - 10) 272 whenever(this.rotation).thenReturn(rotation) 273 whenever(this.displayAdjustments).thenReturn(DisplayAdjustments()) 274 whenever(this.cutout).thenReturn(cutout) 275 whenever(this.uniqueId).thenReturn(uniqueId) 276 } 277 } 278 } 279