• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2023 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 android.tools.device.helpers
18 
19 import android.graphics.Rect
20 import android.tools.common.PlatformConsts
21 import android.tools.common.Rotation
22 import android.tools.common.datatypes.Region
23 import android.tools.common.traces.surfaceflinger.Display
24 import android.tools.common.traces.wm.DisplayContent
25 import android.tools.device.traces.getCurrentStateDump
26 import android.tools.device.traces.parsers.toAndroidRect
27 import android.util.LruCache
28 import androidx.test.platform.app.InstrumentationRegistry
29 
30 object WindowUtils {
31 
32     private val displayBoundsCache = LruCache<Rotation, Region>(1)
33     private val instrumentation = InstrumentationRegistry.getInstrumentation()
34 
35     /** Helper functions to retrieve system window sizes and positions. */
36     private val context = instrumentation.context
37 
38     private val resources
39         get() = context.getResources()
40 
41     /** Get the display bounds */
42     val displayBounds: Rect
43         get() {
44             val currState = getCurrentStateDump(clearCacheAfterParsing = false)
45             return currState.layerState.physicalDisplay?.layerStackSpace?.toAndroidRect() ?: Rect()
46         }
47 
48     /** Gets the current display rotation */
49     val displayRotation: Rotation
50         get() {
51             val currState = getCurrentStateDump(clearCacheAfterParsing = false)
52 
53             return currState.wmState.getRotation(PlatformConsts.DEFAULT_DISPLAY)
54         }
55 
56     /**
57      * Get the display bounds when the device is at a specific rotation
58      *
59      * @param requestedRotation Device rotation
60      */
getDisplayBoundsnull61     fun getDisplayBounds(requestedRotation: Rotation): Region {
62         return displayBoundsCache[requestedRotation]
63             ?: let {
64                 val displayIsRotated = displayRotation.isRotated()
65                 val requestedDisplayIsRotated = requestedRotation.isRotated()
66 
67                 // if the current orientation changes with the requested rotation,
68                 // flip height and width of display bounds.
69                 val displayBounds = displayBounds
70                 val retval: Region
71                 if (displayIsRotated != requestedDisplayIsRotated) {
72                     retval = Region.from(0, 0, displayBounds.height(), displayBounds.width())
73                 } else {
74                     retval = Region.from(0, 0, displayBounds.width(), displayBounds.height())
75                 }
76                 displayBoundsCache.put(requestedRotation, retval)
77                 return retval
78             }
79     }
80     /** Gets the status bar height with a specific display cutout. */
getExpectedStatusBarHeightnull81     private fun getExpectedStatusBarHeight(displayContent: DisplayContent): Int {
82         val cutout = displayContent.cutout
83         val defaultSize = status_bar_height_default
84         val safeInsetTop = cutout?.insets?.top ?: 0
85         val waterfallInsetTop = cutout?.waterfallInsets?.top ?: 0
86         // The status bar height should be:
87         // Max(top cutout size, (status bar default height + waterfall top size))
88         return safeInsetTop.coerceAtLeast(defaultSize + waterfallInsetTop)
89     }
90 
91     /**
92      * Gets the expected status bar position for a specific display
93      *
94      * @param display the main display
95      */
getExpectedStatusBarPositionnull96     fun getExpectedStatusBarPosition(display: DisplayContent): Region {
97         val height = getExpectedStatusBarHeight(display)
98         return Region.from(0, 0, display.displayRect.width, height)
99     }
100 
101     /**
102      * Gets the expected navigation bar position for a specific display
103      *
104      * @param display the main display
105      */
getNavigationBarPositionnull106     fun getNavigationBarPosition(display: Display): Region {
107         return getNavigationBarPosition(display, isGesturalNavigationEnabled)
108     }
109 
110     /**
111      * Gets the expected navigation bar position for a specific display
112      *
113      * @param display the main display
114      * @param isGesturalNavigation whether gestural navigation is enabled
115      */
getNavigationBarPositionnull116     fun getNavigationBarPosition(display: Display, isGesturalNavigation: Boolean): Region {
117         val navBarWidth = getDimensionPixelSize("navigation_bar_width")
118         val displayHeight = display.layerStackSpace.height
119         val displayWidth = display.layerStackSpace.width
120         val requestedRotation = display.transform.getRotation()
121         val navBarHeight = getNavigationBarFrameHeight(requestedRotation, isGesturalNavigation)
122 
123         return when {
124             // nav bar is at the bottom of the screen
125             !requestedRotation.isRotated() || isGesturalNavigation ->
126                 Region.from(0, displayHeight - navBarHeight, displayWidth, displayHeight)
127             // nav bar is on the right side
128             requestedRotation == Rotation.ROTATION_90 ->
129                 Region.from(displayWidth - navBarWidth, 0, displayWidth, displayHeight)
130             // nav bar is on the left side
131             requestedRotation == Rotation.ROTATION_270 ->
132                 Region.from(0, 0, navBarWidth, displayHeight)
133             else -> error("Unknown rotation $requestedRotation")
134         }
135     }
136 
137     /**
138      * Estimate the navigation bar position at a specific rotation
139      *
140      * @param requestedRotation Device rotation
141      */
estimateNavigationBarPositionnull142     fun estimateNavigationBarPosition(requestedRotation: Rotation): Region {
143         val displayBounds = displayBounds
144         val displayWidth: Int
145         val displayHeight: Int
146         if (!requestedRotation.isRotated()) {
147             displayWidth = displayBounds.width()
148             displayHeight = displayBounds.height()
149         } else {
150             // swap display dimensions in landscape or seascape mode
151             displayWidth = displayBounds.height()
152             displayHeight = displayBounds.width()
153         }
154         val navBarWidth = getDimensionPixelSize("navigation_bar_width")
155         val navBarHeight =
156             getNavigationBarFrameHeight(requestedRotation, isGesturalNavigation = false)
157 
158         return when {
159             // nav bar is at the bottom of the screen
160             !requestedRotation.isRotated() || isGesturalNavigationEnabled ->
161                 Region.from(0, displayHeight - navBarHeight, displayWidth, displayHeight)
162             // nav bar is on the right side
163             requestedRotation == Rotation.ROTATION_90 ->
164                 Region.from(displayWidth - navBarWidth, 0, displayWidth, displayHeight)
165             // nav bar is on the left side
166             requestedRotation == Rotation.ROTATION_270 ->
167                 Region.from(0, 0, navBarWidth, displayHeight)
168             else -> error("Unknown rotation $requestedRotation")
169         }
170     }
171 
172     /** Checks if the device uses gestural navigation */
173     val isGesturalNavigationEnabled: Boolean
174         get() {
175             val resourceId =
176                 resources.getIdentifier("config_navBarInteractionMode", "integer", "android")
177             return resources.getInteger(resourceId) == 2 /* NAV_BAR_MODE_GESTURAL */
178         }
179 
getDimensionPixelSizenull180     fun getDimensionPixelSize(resourceName: String): Int {
181         val resourceId = resources.getIdentifier(resourceName, "dimen", "android")
182         return resources.getDimensionPixelSize(resourceId)
183     }
184 
185     /** Gets the navigation bar frame height */
getNavigationBarFrameHeightnull186     fun getNavigationBarFrameHeight(rotation: Rotation, isGesturalNavigation: Boolean): Int {
187         return if (rotation.isRotated()) {
188             if (isGesturalNavigation) {
189                 getDimensionPixelSize("navigation_bar_frame_height")
190             } else {
191                 getDimensionPixelSize("navigation_bar_height_landscape")
192             }
193         } else {
194             getDimensionPixelSize("navigation_bar_frame_height")
195         }
196     }
197 
198     private val status_bar_height_default: Int
199         get() {
200             val resourceId =
201                 resources.getIdentifier("status_bar_height_default", "dimen", "android")
202             return resources.getDimensionPixelSize(resourceId)
203         }
204 
205     val quick_qs_offset_height: Int
206         get() {
207             val resourceId = resources.getIdentifier("quick_qs_offset_height", "dimen", "android")
208             return resources.getDimensionPixelSize(resourceId)
209         }
210 
211     /** Split screen divider inset height */
212     val dockedStackDividerInset: Int
213         get() {
214             val resourceId =
215                 resources.getIdentifier("docked_stack_divider_insets", "dimen", "android")
216             return resources.getDimensionPixelSize(resourceId)
217         }
218 }
219