• 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.graphics.Insets
19 import android.graphics.Region
20 import android.view.InsetsFrameProvider
21 import android.view.InsetsFrameProvider.SOURCE_DISPLAY
22 import android.view.InsetsFrameProvider.SOURCE_FRAME
23 import android.view.InsetsState
24 import android.view.InsetsState.ITYPE_BOTTOM_MANDATORY_GESTURES
25 import android.view.InsetsState.ITYPE_BOTTOM_TAPPABLE_ELEMENT
26 import android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR
27 import android.view.InsetsState.ITYPE_LEFT_GESTURES
28 import android.view.InsetsState.ITYPE_RIGHT_GESTURES
29 import android.view.ViewTreeObserver
30 import android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME
31 import android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION
32 import android.view.WindowManager
33 import android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD
34 import android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION
35 import com.android.internal.policy.GestureNavigationSettingsObserver
36 import com.android.launcher3.AbstractFloatingView
37 import com.android.launcher3.AbstractFloatingView.TYPE_TASKBAR_OVERLAY_PROXY
38 import com.android.launcher3.DeviceProfile
39 import com.android.launcher3.R
40 import com.android.launcher3.anim.AlphaUpdateListener
41 import com.android.launcher3.taskbar.TaskbarControllers.LoggableTaskbarController
42 import java.io.PrintWriter
43 
44 /** Handles the insets that Taskbar provides to underlying apps and the IME. */
45 class TaskbarInsetsController(val context: TaskbarActivityContext) : LoggableTaskbarController {
46 
47     /** The bottom insets taskbar provides to the IME when IME is visible. */
48     val taskbarHeightForIme: Int = context.resources.getDimensionPixelSize(R.dimen.taskbar_ime_size)
49     private val touchableRegion: Region = Region()
50     private val deviceProfileChangeListener = { _: DeviceProfile ->
51         onTaskbarWindowHeightOrInsetsChanged()
52     }
53     private val gestureNavSettingsObserver =
54         GestureNavigationSettingsObserver(
55             context.mainThreadHandler,
56             context,
57             this::onTaskbarWindowHeightOrInsetsChanged
58         )
59 
60     // Initialized in init.
61     private lateinit var controllers: TaskbarControllers
62     private lateinit var windowLayoutParams: WindowManager.LayoutParams
63 
64     fun init(controllers: TaskbarControllers) {
65         this.controllers = controllers
66         windowLayoutParams = context.windowLayoutParams
67         windowLayoutParams.insetsRoundedCornerFrame = true
68         onTaskbarWindowHeightOrInsetsChanged()
69 
70         context.addOnDeviceProfileChangeListener(deviceProfileChangeListener)
71         gestureNavSettingsObserver.registerForCallingUser()
72     }
73 
74     fun onDestroy() {
75         context.removeOnDeviceProfileChangeListener(deviceProfileChangeListener)
76         gestureNavSettingsObserver.unregister()
77     }
78 
79     fun onTaskbarWindowHeightOrInsetsChanged() {
80         if (context.isGestureNav) {
81             setProvidesInsetsTypes(
82                     windowLayoutParams,
83                     intArrayOf(
84                             ITYPE_EXTRA_NAVIGATION_BAR,
85                             ITYPE_BOTTOM_TAPPABLE_ELEMENT,
86                             ITYPE_BOTTOM_MANDATORY_GESTURES,
87                             ITYPE_LEFT_GESTURES,
88                             ITYPE_RIGHT_GESTURES,
89                     ),
90                     intArrayOf(
91                             SOURCE_FRAME,
92                             SOURCE_FRAME,
93                             SOURCE_FRAME,
94                             SOURCE_DISPLAY,
95                             SOURCE_DISPLAY
96                     )
97             )
98         } else {
99             setProvidesInsetsTypes(
100                     windowLayoutParams,
101                     intArrayOf(
102                             ITYPE_EXTRA_NAVIGATION_BAR,
103                             ITYPE_BOTTOM_TAPPABLE_ELEMENT,
104                             ITYPE_BOTTOM_MANDATORY_GESTURES
105                     ),
106                     intArrayOf(
107                             SOURCE_FRAME,
108                             SOURCE_FRAME,
109                             SOURCE_FRAME
110                     )
111             )
112         }
113 
114         val touchableHeight = controllers.taskbarStashController.touchableHeight
115         touchableRegion.set(
116             0,
117             windowLayoutParams.height - touchableHeight,
118             context.deviceProfile.widthPx,
119             windowLayoutParams.height
120         )
121         val contentHeight = controllers.taskbarStashController.contentHeightToReportToApps
122         val tappableHeight = controllers.taskbarStashController.tappableHeightToReportToApps
123         val res = context.resources
124 
125         for (provider in windowLayoutParams.providedInsets) {
126             if (
127                 provider.type == ITYPE_EXTRA_NAVIGATION_BAR ||
128                     provider.type == ITYPE_BOTTOM_MANDATORY_GESTURES
129             ) {
130                 provider.insetsSize = getInsetsByNavMode(contentHeight)
131             } else if (provider.type == ITYPE_BOTTOM_TAPPABLE_ELEMENT) {
132                 provider.insetsSize = getInsetsByNavMode(tappableHeight)
133             } else if (provider.type == ITYPE_LEFT_GESTURES) {
134                 provider.insetsSize =
135                     Insets.of(
136                         gestureNavSettingsObserver.getLeftSensitivityForCallingUser(res),
137                         0,
138                         0,
139                         0
140                     )
141             } else if (provider.type == ITYPE_RIGHT_GESTURES) {
142                 provider.insetsSize =
143                     Insets.of(
144                         0,
145                         0,
146                         gestureNavSettingsObserver.getRightSensitivityForCallingUser(res),
147                         0
148                     )
149             }
150         }
151 
152         val imeInsetsSize = getInsetsByNavMode(taskbarHeightForIme)
153         val insetsSizeOverride =
154             arrayOf(
155                 InsetsFrameProvider.InsetsSizeOverride(TYPE_INPUT_METHOD, imeInsetsSize),
156             )
157         // Use 0 tappableElement insets for the VoiceInteractionWindow when gesture nav is enabled.
158         val visInsetsSizeForGestureNavTappableElement = getInsetsByNavMode(0)
159         val insetsSizeOverrideForGestureNavTappableElement =
160             arrayOf(
161                 InsetsFrameProvider.InsetsSizeOverride(TYPE_INPUT_METHOD, imeInsetsSize),
162                 InsetsFrameProvider.InsetsSizeOverride(
163                     TYPE_VOICE_INTERACTION,
164                     visInsetsSizeForGestureNavTappableElement
165                 ),
166             )
167         for (provider in windowLayoutParams.providedInsets) {
168             if (context.isGestureNav && provider.type == ITYPE_BOTTOM_TAPPABLE_ELEMENT) {
169                 provider.insetsSizeOverrides = insetsSizeOverrideForGestureNavTappableElement
170             } else if (provider.type != ITYPE_LEFT_GESTURES
171                     && provider.type != ITYPE_RIGHT_GESTURES) {
172                 // We only override insets at the bottom of the screen
173                 provider.insetsSizeOverrides = insetsSizeOverride
174             }
175         }
176         context.notifyUpdateLayoutParams()
177     }
178 
179     /**
180      * @return [Insets] where the [bottomInset] is either used as a bottom inset or
181      *
182      * ```
183      *         right/left inset if using 3 button nav
184      * ```
185      */
186     private fun getInsetsByNavMode(bottomInset: Int): Insets {
187         val devicePortrait = !context.deviceProfile.isLandscape
188         if (!TaskbarManager.isPhoneButtonNavMode(context) || devicePortrait) {
189             // Taskbar or portrait phone mode
190             return Insets.of(0, 0, 0, bottomInset)
191         }
192 
193         // TODO(b/230394142): seascape
194         return Insets.of(0, 0, bottomInset, 0)
195     }
196 
197     /**
198      * Sets {@param providesInsetsTypes} as the inset types provided by {@param params}.
199      *
200      * @param params The window layout params.
201      * @param providesInsetsTypes The inset types we would like this layout params to provide.
202      */
203     fun setProvidesInsetsTypes(
204         params: WindowManager.LayoutParams,
205         providesInsetsTypes: IntArray,
206         providesInsetsSources: IntArray
207     ) {
208         params.providedInsets = arrayOfNulls<InsetsFrameProvider>(providesInsetsTypes.size)
209         for (i in providesInsetsTypes.indices) {
210             params.providedInsets[i] =
211                 InsetsFrameProvider(providesInsetsTypes[i], providesInsetsSources[i], null, null)
212         }
213     }
214 
215     /**
216      * Called to update the touchable insets.
217      *
218      * @see InternalInsetsInfo.setTouchableInsets
219      */
220     fun updateInsetsTouchability(insetsInfo: ViewTreeObserver.InternalInsetsInfo) {
221         insetsInfo.touchableRegion.setEmpty()
222         // Always have nav buttons be touchable
223         controllers.navbarButtonsViewController.addVisibleButtonsRegion(
224             context.dragLayer,
225             insetsInfo.touchableRegion
226         )
227         var insetsIsTouchableRegion = true
228         if (context.dragLayer.alpha < AlphaUpdateListener.ALPHA_CUTOFF_THRESHOLD) {
229             // Let touches pass through us.
230             insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION)
231         } else if (
232             controllers.navbarButtonsViewController.isImeVisible &&
233                 controllers.taskbarStashController.isStashed()
234         ) {
235             insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION)
236         } else if (!controllers.uiController.isTaskbarTouchable) {
237             // Let touches pass through us.
238             insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION)
239         } else if (controllers.taskbarDragController.isSystemDragInProgress) {
240             // Let touches pass through us.
241             insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION)
242         } else if (AbstractFloatingView.hasOpenView(context, TYPE_TASKBAR_OVERLAY_PROXY)) {
243             // Let touches pass through us if icons are hidden.
244             if (controllers.taskbarViewController.areIconsVisible()) {
245                 insetsInfo.touchableRegion.set(touchableRegion)
246             }
247             insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION)
248         } else if (
249             controllers.taskbarViewController.areIconsVisible() ||
250                 AbstractFloatingView.hasOpenView(context, AbstractFloatingView.TYPE_ALL) ||
251                 context.isNavBarKidsModeActive
252         ) {
253             // Taskbar has some touchable elements, take over the full taskbar area
254             insetsInfo.setTouchableInsets(
255                 if (context.isTaskbarWindowFullscreen) {
256                     TOUCHABLE_INSETS_FRAME
257                 } else {
258                     insetsInfo.touchableRegion.set(touchableRegion)
259                     TOUCHABLE_INSETS_REGION
260                 }
261             )
262             insetsIsTouchableRegion = false
263         } else {
264             insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION)
265         }
266         context.excludeFromMagnificationRegion(insetsIsTouchableRegion)
267     }
268 
269     override fun dumpLogs(prefix: String, pw: PrintWriter) {
270         pw.println(prefix + "TaskbarInsetsController:")
271         pw.println("$prefix\twindowHeight=${windowLayoutParams.height}")
272         for (provider in windowLayoutParams.providedInsets) {
273             pw.print(
274                 "$prefix\tprovidedInsets: (type=" +
275                     InsetsState.typeToString(provider.type) +
276                     " insetsSize=" +
277                     provider.insetsSize
278             )
279             if (provider.insetsSizeOverrides != null) {
280                 pw.print(" insetsSizeOverrides={")
281                 for ((i, overrideSize) in provider.insetsSizeOverrides.withIndex()) {
282                     if (i > 0) pw.print(", ")
283                     pw.print(overrideSize)
284                 }
285                 pw.print("})")
286             }
287             pw.println()
288         }
289     }
290 }
291