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