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