• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 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 package com.android.launcher3.taskbar
17 
18 import android.inputmethodservice.InputMethodService.ENABLE_HIDE_IME_CAPTION_BAR
19 import android.graphics.Insets
20 import android.graphics.Region
21 import android.os.Binder
22 import android.os.IBinder
23 import android.view.Gravity
24 import android.view.InsetsFrameProvider
25 import android.view.InsetsFrameProvider.SOURCE_DISPLAY
26 import android.view.InsetsSource.FLAG_INSETS_ROUNDED_CORNER
27 import android.view.InsetsSource.FLAG_SUPPRESS_SCRIM
28 import android.view.ViewTreeObserver
29 import android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME
30 import android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION
31 import android.view.WindowInsets
32 import android.view.WindowInsets.Type.mandatorySystemGestures
33 import android.view.WindowInsets.Type.navigationBars
34 import android.view.WindowInsets.Type.systemGestures
35 import android.view.WindowInsets.Type.tappableElement
36 import android.view.WindowManager
37 import android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD
38 import android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION
39 import androidx.core.graphics.toRegion
40 import com.android.internal.policy.GestureNavigationSettingsObserver
41 import com.android.launcher3.DeviceProfile
42 import com.android.launcher3.R
43 import com.android.launcher3.anim.AlphaUpdateListener
44 import com.android.launcher3.taskbar.TaskbarControllers.LoggableTaskbarController
45 import com.android.launcher3.util.DisplayController
46 import java.io.PrintWriter
47 import kotlin.jvm.optionals.getOrNull
48 
49 /** Handles the insets that Taskbar provides to underlying apps and the IME. */
50 class TaskbarInsetsController(val context: TaskbarActivityContext) : LoggableTaskbarController {
51 
52     companion object {
53         private const val INDEX_LEFT = 0
54         private const val INDEX_RIGHT = 1
55     }
56 
57     /** The bottom insets taskbar provides to the IME when IME is visible. */
58     val taskbarHeightForIme: Int = context.resources.getDimensionPixelSize(R.dimen.taskbar_ime_size)
59     private val touchableRegion: Region = Region()
60     private val insetsOwner: IBinder = Binder()
61     private val deviceProfileChangeListener = { _: DeviceProfile ->
62         onTaskbarOrBubblebarWindowHeightOrInsetsChanged()
63     }
64     private val gestureNavSettingsObserver =
65         GestureNavigationSettingsObserver(
66             context.mainThreadHandler,
67             context,
68             this::onTaskbarOrBubblebarWindowHeightOrInsetsChanged
69         )
70 
71     // Initialized in init.
72     private lateinit var controllers: TaskbarControllers
73     private lateinit var windowLayoutParams: WindowManager.LayoutParams
74 
75     fun init(controllers: TaskbarControllers) {
76         this.controllers = controllers
77         windowLayoutParams = context.windowLayoutParams
78         onTaskbarOrBubblebarWindowHeightOrInsetsChanged()
79 
80         context.addOnDeviceProfileChangeListener(deviceProfileChangeListener)
81         gestureNavSettingsObserver.registerForCallingUser()
82     }
83 
84     fun onDestroy() {
85         context.removeOnDeviceProfileChangeListener(deviceProfileChangeListener)
86         gestureNavSettingsObserver.unregister()
87     }
88 
89     fun onTaskbarOrBubblebarWindowHeightOrInsetsChanged() {
90         val tappableHeight = controllers.taskbarStashController.tappableHeightToReportToApps
91         // We only report tappableElement height for unstashed, persistent taskbar,
92         // which is also when we draw the rounded corners above taskbar.
93         val insetsRoundedCornerFlag =
94             if (tappableHeight > 0) {
95                 FLAG_INSETS_ROUNDED_CORNER
96             } else {
97                 0
98             }
99 
100         windowLayoutParams.providedInsets = getProvidedInsets(insetsRoundedCornerFlag)
101         if (!context.isGestureNav) {
102             if (windowLayoutParams.paramsForRotation != null) {
103                 for (layoutParams in windowLayoutParams.paramsForRotation) {
104                     layoutParams.providedInsets = getProvidedInsets(insetsRoundedCornerFlag)
105                 }
106             }
107         }
108 
109         val taskbarTouchableHeight = controllers.taskbarStashController.touchableHeight
110         val bubblesTouchableHeight =
111             if (controllers.bubbleControllers.isPresent) {
112                 controllers.bubbleControllers.get().bubbleStashController.touchableHeight
113             } else {
114                 0
115             }
116         val touchableHeight = Math.max(taskbarTouchableHeight, bubblesTouchableHeight)
117 
118         if (
119             controllers.bubbleControllers.isPresent &&
120                 controllers.bubbleControllers.get().bubbleStashController.isBubblesShowingOnHome
121         ) {
122             val iconBounds =
123                 controllers.bubbleControllers.get().bubbleBarViewController.bubbleBarBounds
124             touchableRegion.set(
125                 iconBounds.left,
126                 iconBounds.top,
127                 iconBounds.right,
128                 iconBounds.bottom
129             )
130         } else {
131             touchableRegion.set(
132                 0,
133                 windowLayoutParams.height - touchableHeight,
134                 context.deviceProfile.widthPx,
135                 windowLayoutParams.height
136             )
137         }
138 
139         val gravity = windowLayoutParams.gravity
140         for (provider in windowLayoutParams.providedInsets) {
141             setProviderInsets(provider, gravity)
142         }
143 
144         if (windowLayoutParams.paramsForRotation != null) {
145             // Add insets for navbar rotated params
146             for (layoutParams in windowLayoutParams.paramsForRotation) {
147                 for (provider in layoutParams.providedInsets) {
148                     setProviderInsets(provider, layoutParams.gravity)
149                 }
150             }
151         }
152 
153         context.notifyUpdateLayoutParams()
154     }
155 
156     /**
157      * The inset types and number of insets provided have to match for both gesture nav and button
158      * nav. The values and the order of the elements in array are allowed to differ.
159      * Reason being WM does not allow types and number of insets changing for a given window once it
160      * is added into the hierarchy for performance reasons.
161      */
162     private fun getProvidedInsets(insetsRoundedCornerFlag: Int): Array<InsetsFrameProvider> {
163         val navBarsFlag =
164                 (if (context.isGestureNav) FLAG_SUPPRESS_SCRIM else 0) or insetsRoundedCornerFlag
165         return arrayOf(
166                 InsetsFrameProvider(insetsOwner, 0, navigationBars())
167                         .setFlags(
168                                 navBarsFlag,
169                                 FLAG_SUPPRESS_SCRIM or FLAG_INSETS_ROUNDED_CORNER
170                         ),
171                 InsetsFrameProvider(insetsOwner, 0, tappableElement()),
172                 InsetsFrameProvider(insetsOwner, 0, mandatorySystemGestures()),
173                 InsetsFrameProvider(insetsOwner, INDEX_LEFT, systemGestures())
174                         .setSource(SOURCE_DISPLAY),
175                 InsetsFrameProvider(insetsOwner, INDEX_RIGHT, systemGestures())
176                         .setSource(SOURCE_DISPLAY)
177         )
178     }
179 
180     private fun setProviderInsets(provider: InsetsFrameProvider, gravity: Int) {
181         val contentHeight = controllers.taskbarStashController.contentHeightToReportToApps
182         val tappableHeight = controllers.taskbarStashController.tappableHeightToReportToApps
183         val res = context.resources
184         if (provider.type == navigationBars() || provider.type == mandatorySystemGestures()) {
185             provider.insetsSize = getInsetsForGravity(contentHeight, gravity)
186         } else if (provider.type == tappableElement()) {
187             provider.insetsSize = getInsetsForGravity(tappableHeight, gravity)
188         } else if (provider.type == systemGestures() && provider.index == INDEX_LEFT) {
189             val leftIndexInset =
190                     if (context.isThreeButtonNav) 0
191                     else gestureNavSettingsObserver.getLeftSensitivityForCallingUser(res)
192             provider.insetsSize = Insets.of(leftIndexInset, 0, 0, 0)
193         } else if (provider.type == systemGestures() && provider.index == INDEX_RIGHT) {
194             val rightIndexInset =
195                     if (context.isThreeButtonNav) 0
196                     else gestureNavSettingsObserver.getRightSensitivityForCallingUser(res)
197             provider.insetsSize = Insets.of(0, 0, rightIndexInset, 0)
198         }
199 
200 
201         // When in gesture nav, report the stashed height to the IME, to allow hiding the
202         // IME navigation bar.
203         val imeInsetsSize = if (ENABLE_HIDE_IME_CAPTION_BAR && context.isGestureNav) {
204             getInsetsForGravity(controllers.taskbarStashController.stashedHeight, gravity);
205         } else {
206             getInsetsForGravity(taskbarHeightForIme, gravity)
207         }
208         val imeInsetsSizeOverride =
209                 arrayOf(
210                         InsetsFrameProvider.InsetsSizeOverride(TYPE_INPUT_METHOD, imeInsetsSize),
211                 )
212         // Use 0 tappableElement insets for the VoiceInteractionWindow when gesture nav is enabled.
213         val visInsetsSizeForTappableElement =
214                 if (context.isGestureNav) getInsetsForGravity(0, gravity)
215                 else getInsetsForGravity(tappableHeight, gravity)
216         val insetsSizeOverrideForTappableElement =
217                 arrayOf(
218                         InsetsFrameProvider.InsetsSizeOverride(TYPE_INPUT_METHOD, imeInsetsSize),
219                         InsetsFrameProvider.InsetsSizeOverride(
220                                 TYPE_VOICE_INTERACTION,
221                                 visInsetsSizeForTappableElement
222                         ),
223                 )
224         if ((context.isGestureNav || TaskbarManager.FLAG_HIDE_NAVBAR_WINDOW)
225                 && provider.type == tappableElement()) {
226             provider.insetsSizeOverrides = insetsSizeOverrideForTappableElement
227         } else if (provider.type != systemGestures()) {
228             // We only override insets at the bottom of the screen
229             provider.insetsSizeOverrides = imeInsetsSizeOverride
230         }
231     }
232 
233     /**
234      * @return [Insets] where the [inset] is either used as a bottom inset or
235      * right/left inset if using 3 button nav
236      */
237     private fun getInsetsForGravity(inset: Int, gravity: Int): Insets {
238         if ((gravity and Gravity.BOTTOM) == Gravity.BOTTOM) {
239             // Taskbar or portrait phone mode
240             return Insets.of(0, 0, 0, inset)
241         }
242 
243         // TODO(b/230394142): seascape
244         val isSeascape = (gravity and Gravity.START) == Gravity.START
245         val leftInset = if (isSeascape) inset else 0
246         val rightInset = if (isSeascape) 0 else inset
247         return Insets.of(leftInset , 0, rightInset, 0)
248     }
249 
250     /**
251      * Called to update the touchable insets.
252      *
253      * @see ViewTreeObserver.InternalInsetsInfo.setTouchableInsets
254      */
255     fun updateInsetsTouchability(insetsInfo: ViewTreeObserver.InternalInsetsInfo) {
256         insetsInfo.touchableRegion.setEmpty()
257         // Always have nav buttons be touchable
258         controllers.navbarButtonsViewController.addVisibleButtonsRegion(
259             context.dragLayer,
260             insetsInfo.touchableRegion
261         )
262         val bubbleBarVisible =
263             controllers.bubbleControllers.isPresent &&
264                 controllers.bubbleControllers.get().bubbleBarViewController.isBubbleBarVisible()
265         var insetsIsTouchableRegion = true
266         if (context.dragLayer.alpha < AlphaUpdateListener.ALPHA_CUTOFF_THRESHOLD) {
267             // Let touches pass through us.
268             insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION)
269         } else if (
270             controllers.navbarButtonsViewController.isImeVisible &&
271                 controllers.taskbarStashController.isStashed
272         ) {
273             insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION)
274         } else if (!controllers.uiController.isTaskbarTouchable) {
275             // Let touches pass through us.
276             insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION)
277         } else if (controllers.taskbarDragController.isSystemDragInProgress) {
278             // Let touches pass through us.
279             insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION)
280         } else if (context.isTaskbarWindowFullscreen) {
281             // Intercept entire fullscreen window.
282             insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_FRAME)
283             insetsIsTouchableRegion = false
284         } else if (
285             controllers.taskbarViewController.areIconsVisible() ||
286                 context.isNavBarKidsModeActive ||
287                 bubbleBarVisible
288         ) {
289             // Taskbar has some touchable elements, take over the full taskbar area
290             if (
291                 controllers.uiController.isInOverview &&
292                     DisplayController.isTransientTaskbar(context)
293             ) {
294                 val region =
295                     controllers.taskbarActivityContext.dragLayer.lastDrawnTransientRect.toRegion()
296                 val bubbleBarBounds =
297                     controllers.bubbleControllers.getOrNull()?.let { bubbleControllers ->
298                         if (!bubbleControllers.bubbleStashController.isBubblesShowingOnOverview) {
299                             return@let null
300                         }
301                         if (!bubbleControllers.bubbleBarViewController.isBubbleBarVisible) {
302                             return@let null
303                         }
304                         bubbleControllers.bubbleBarViewController.bubbleBarBounds
305                     }
306 
307                 // Include the bounds of the bubble bar in the touchable region if they exist.
308                 if (bubbleBarBounds != null) {
309                     region.op(bubbleBarBounds, Region.Op.UNION)
310                 }
311                 insetsInfo.touchableRegion.set(region)
312             } else {
313                 insetsInfo.touchableRegion.set(touchableRegion)
314             }
315             insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION)
316             insetsIsTouchableRegion = false
317         } else {
318             insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION)
319         }
320         context.excludeFromMagnificationRegion(insetsIsTouchableRegion)
321     }
322 
323     override fun dumpLogs(prefix: String, pw: PrintWriter) {
324         pw.println(prefix + "TaskbarInsetsController:")
325         pw.println("$prefix\twindowHeight=${windowLayoutParams.height}")
326         for (provider in windowLayoutParams.providedInsets) {
327             pw.print(
328                 "$prefix\tprovidedInsets: (type=" +
329                     WindowInsets.Type.toString(provider.type) +
330                     " insetsSize=" +
331                     provider.insetsSize
332             )
333             if (provider.insetsSizeOverrides != null) {
334                 pw.print(" insetsSizeOverrides={")
335                 for ((i, overrideSize) in provider.insetsSizeOverrides.withIndex()) {
336                     if (i > 0) pw.print(", ")
337                     pw.print(overrideSize)
338                 }
339                 pw.print("})")
340             }
341             pw.println()
342         }
343     }
344 }
345