1 /* <lambda>null2 * 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 com.android.systemui.shade 18 19 import android.content.Context 20 import android.content.res.Configuration 21 import android.graphics.Rect 22 import android.os.PowerManager 23 import android.util.ArraySet 24 import android.view.GestureDetector 25 import android.view.MotionEvent 26 import android.view.View 27 import android.view.ViewGroup 28 import android.view.WindowInsets 29 import android.widget.FrameLayout 30 import androidx.activity.OnBackPressedDispatcher 31 import androidx.activity.OnBackPressedDispatcherOwner 32 import androidx.activity.setViewTreeOnBackPressedDispatcherOwner 33 import androidx.compose.ui.platform.ComposeView 34 import androidx.core.view.updateMargins 35 import androidx.lifecycle.Lifecycle 36 import androidx.lifecycle.LifecycleEventObserver 37 import androidx.lifecycle.LifecycleObserver 38 import androidx.lifecycle.LifecycleOwner 39 import androidx.lifecycle.LifecycleRegistry 40 import androidx.lifecycle.lifecycleScope 41 import androidx.lifecycle.repeatOnLifecycle 42 import com.android.app.tracing.coroutines.launchTraced as launch 43 import com.android.compose.theme.PlatformTheme 44 import com.android.internal.annotations.VisibleForTesting 45 import com.android.keyguard.UserActivityNotifier 46 import com.android.systemui.Flags 47 import com.android.systemui.ambient.touch.TouchMonitor 48 import com.android.systemui.ambient.touch.dagger.AmbientTouchComponent 49 import com.android.systemui.communal.dagger.Communal 50 import com.android.systemui.communal.domain.interactor.CommunalInteractor 51 import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor 52 import com.android.systemui.communal.ui.compose.CommunalContainer 53 import com.android.systemui.communal.ui.compose.CommunalContent 54 import com.android.systemui.communal.ui.viewmodel.CommunalViewModel 55 import com.android.systemui.communal.util.CommunalColors 56 import com.android.systemui.communal.util.UserTouchActivityNotifier 57 import com.android.systemui.dagger.SysUISingleton 58 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor 59 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor 60 import com.android.systemui.keyguard.shared.model.Edge 61 import com.android.systemui.keyguard.shared.model.KeyguardState 62 import com.android.systemui.lifecycle.repeatWhenAttached 63 import com.android.systemui.log.LogBuffer 64 import com.android.systemui.log.core.Logger 65 import com.android.systemui.log.dagger.CommunalTouchLog 66 import com.android.systemui.media.controls.ui.controller.KeyguardMediaController 67 import com.android.systemui.res.R 68 import com.android.systemui.scene.shared.flag.SceneContainerFlag 69 import com.android.systemui.scene.shared.model.SceneDataSourceDelegator 70 import com.android.systemui.shade.domain.interactor.ShadeInteractor 71 import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController 72 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController 73 import com.android.systemui.util.kotlin.BooleanFlowOperators.anyOf 74 import com.android.systemui.util.kotlin.Quad 75 import com.android.systemui.util.kotlin.collectFlow 76 import java.util.function.Consumer 77 import javax.inject.Inject 78 import kotlinx.coroutines.flow.Flow 79 import kotlinx.coroutines.flow.combine 80 81 /** 82 * Controller that's responsible for the glanceable hub container view and its touch handling. 83 * 84 * This will be used until the glanceable hub is integrated into Flexiglass. 85 */ 86 @SysUISingleton 87 class GlanceableHubContainerController 88 @Inject 89 constructor( 90 private val communalInteractor: CommunalInteractor, 91 private val communalSettingsInteractor: CommunalSettingsInteractor, 92 private val communalViewModel: CommunalViewModel, 93 private val keyguardInteractor: KeyguardInteractor, 94 private val keyguardTransitionInteractor: KeyguardTransitionInteractor, 95 private val shadeInteractor: ShadeInteractor, 96 private val powerManager: PowerManager, 97 private val communalColors: CommunalColors, 98 private val ambientTouchComponentFactory: AmbientTouchComponent.Factory, 99 private val communalContent: CommunalContent, 100 @Communal private val dataSourceDelegator: SceneDataSourceDelegator, 101 private val notificationStackScrollLayoutController: NotificationStackScrollLayoutController, 102 private val keyguardMediaController: KeyguardMediaController, 103 private val lockscreenSmartspaceController: LockscreenSmartspaceController, 104 private val userTouchActivityNotifier: UserTouchActivityNotifier, 105 @CommunalTouchLog logBuffer: LogBuffer, 106 private val userActivityNotifier: UserActivityNotifier, 107 ) : LifecycleOwner { 108 private val logger = Logger(logBuffer, TAG) 109 110 private class CommunalWrapper( 111 context: Context, 112 private val communalSettingsInteractor: CommunalSettingsInteractor, 113 ) : FrameLayout(context) { 114 private val consumers: MutableSet<Consumer<Boolean>> = ArraySet() 115 116 override fun requestDisallowInterceptTouchEvent(disallowIntercept: Boolean) { 117 consumers.forEach { it.accept(disallowIntercept) } 118 super.requestDisallowInterceptTouchEvent(disallowIntercept) 119 } 120 121 fun dispatchTouchEvent( 122 ev: MotionEvent?, 123 disallowInterceptConsumer: Consumer<Boolean>?, 124 ): Boolean { 125 disallowInterceptConsumer?.apply { consumers.add(this) } 126 127 try { 128 return super.dispatchTouchEvent(ev) 129 } finally { 130 consumers.clear() 131 } 132 } 133 134 override fun onApplyWindowInsets(windowInsets: WindowInsets): WindowInsets { 135 if ( 136 !communalSettingsInteractor.isV2FlagEnabled() || 137 resources.configuration.orientation != Configuration.ORIENTATION_LANDSCAPE 138 ) { 139 return super.onApplyWindowInsets(windowInsets) 140 } 141 val type = WindowInsets.Type.displayCutout() 142 val insets = windowInsets.getInsets(type) 143 144 // Reset horizontal margins added by window insets, so hub can be edge to edge. 145 if (insets.left > 0 || insets.right > 0) { 146 val lp = layoutParams as LayoutParams 147 lp.updateMargins(0, lp.topMargin, 0, lp.bottomMargin) 148 } 149 return WindowInsets.CONSUMED 150 } 151 } 152 153 /** The container view for the hub. This will not be initialized until [initView] is called. */ 154 private var communalContainerView: View? = null 155 156 /** Wrapper around the communal container to intercept touch events */ 157 private var communalContainerWrapper: CommunalWrapper? = null 158 159 /** 160 * This lifecycle is used to control when the [touchMonitor] listens to touches. The lifecycle 161 * should only be [Lifecycle.State.RESUMED] when the hub is showing and not covered by anything, 162 * such as the notification shade or bouncer. 163 */ 164 private var lifecycleRegistry: LifecycleRegistry = LifecycleRegistry(this) 165 166 /** 167 * This [TouchMonitor] listens for top and bottom swipe gestures globally when the hub is open. 168 * When a top or bottom swipe is detected, they will be intercepted and used to open the 169 * notification shade/bouncer. 170 */ 171 private var touchMonitor: TouchMonitor? = null 172 173 /** 174 * True if we are currently tracking a touch intercepted by the hub, either because the hub is 175 * open or being opened. 176 */ 177 private var isTrackingHubTouch = false 178 179 /** 180 * True if a touch gesture on the lock screen has been consumed by the shade/bouncer and thus 181 * should be ignored by the hub. 182 * 183 * This is necessary on the lock screen as gestures on an empty spot go through special touch 184 * handling logic in [NotificationShadeWindowViewController] that decides if they should go to 185 * the shade or bouncer. Once the shade or bouncer are moving, we don't get the typical cancel 186 * event so to play nice, we ignore touches once we see the shade or bouncer are opening. 187 */ 188 private var touchTakenByKeyguardGesture = false 189 190 /** 191 * True if the hub UI is fully open, meaning it should receive touch input. 192 * 193 * Tracks [CommunalInteractor.isCommunalShowing]. 194 */ 195 private var hubShowing = false 196 197 /** 198 * True if we're transitioning to or from edit mode 199 * 200 * We block all touches and gestures when edit mode is open to prevent funky transition issues 201 * when entering and exiting edit mode because we delay exiting the hub scene when entering edit 202 * mode and enter the hub scene early when exiting edit mode to make for a smoother transition. 203 * Gestures during these transitions can result in broken and unexpected UI states. 204 * 205 * Tracks [CommunalInteractor.editActivityShowing] and the [KeyguardState.GONE] to 206 * [KeyguardState.GLANCEABLE_HUB] transition. 207 */ 208 private var inEditModeTransition = false 209 210 /** 211 * True if either the primary or alternate bouncer are open, meaning the hub should not receive 212 * any touch input. 213 */ 214 private var anyBouncerShowing = false 215 216 /** 217 * True if the shade is fully expanded and the user is not interacting with it anymore, meaning 218 * the hub should not receive any touch input. 219 * 220 * We need to not pause the touch handling lifecycle as soon as the shade opens because if the 221 * user swipes down, then back up without lifting their finger, the lifecycle will be paused 222 * then resumed, and resuming force-stops all active touch sessions. This means the shade will 223 * not receive the end of the gesture and will be stuck open. 224 * 225 * Based on [ShadeInteractor.isAnyFullyExpanded] and [ShadeInteractor.isUserInteracting]. 226 */ 227 private var shadeShowingAndConsumingTouches = false 228 229 /** 230 * True anytime the shade is processing user touches, regardless of expansion state. 231 * 232 * Based on [ShadeInteractor.isUserInteracting]. 233 */ 234 private var shadeConsumingTouches = false 235 236 /** 237 * True if the shade is showing at all. 238 * 239 * Inverse of [ShadeInteractor.isShadeFullyCollapsed] 240 */ 241 private var shadeShowing = false 242 243 /** True if the keyguard transition state is finished on [KeyguardState.LOCKSCREEN]. */ 244 private var onLockscreen = false 245 246 /** 247 * True if the shade ever fully expands and the user isn't interacting with it (aka finger on 248 * screen dragging). In this case, the shade should handle all touch events until it has fully 249 * collapsed. 250 */ 251 private var userNotInteractiveAtShadeFullyExpanded = false 252 253 /** 254 * True if the device is dreaming, in which case we shouldn't do anything for top/bottom swipes 255 * and just let the dream overlay's touch handling deal with them. 256 * 257 * Tracks [KeyguardInteractor.isDreaming]. 258 */ 259 private var isDreaming = false 260 261 /** True if we should allow swiping open the glanceable hub. */ 262 private var swipeToHubEnabled = false 263 264 /** Observes and logs state when the lifecycle that controls the [touchMonitor] updates. */ 265 private val touchLifecycleLogger: LifecycleObserver = LifecycleEventObserver { _, event -> 266 logger.d({ 267 "Touch handler lifecycle changed to $str1. hubShowing: $bool1, " + 268 "shadeShowingAndConsumingTouches: $bool2, " + 269 "anyBouncerShowing: $bool3, inEditModeTransition: $bool4" 270 }) { 271 str1 = event.toString() 272 bool1 = hubShowing 273 bool2 = shadeShowingAndConsumingTouches 274 bool3 = anyBouncerShowing 275 bool4 = inEditModeTransition 276 } 277 } 278 279 /** Returns a flow that tracks whether communal hub is available. */ 280 fun communalAvailable(): Flow<Boolean> = 281 anyOf(communalInteractor.isCommunalAvailable, communalInteractor.editModeOpen) 282 283 /** 284 * Creates the container view containing the glanceable hub UI. 285 * 286 * @throws RuntimeException if the view is already initialized 287 */ 288 fun initView(context: Context): View { 289 return initView( 290 ComposeView(context).apply { 291 repeatWhenAttached { 292 lifecycleScope.launch { 293 repeatOnLifecycle(Lifecycle.State.CREATED) { 294 setViewTreeOnBackPressedDispatcherOwner( 295 object : OnBackPressedDispatcherOwner { 296 override val onBackPressedDispatcher = 297 OnBackPressedDispatcher().apply { 298 setOnBackInvokedDispatcher( 299 viewRootImpl.onBackInvokedDispatcher 300 ) 301 } 302 303 override val lifecycle: Lifecycle = 304 this@repeatWhenAttached.lifecycle 305 } 306 ) 307 308 setContent { 309 PlatformTheme { 310 CommunalContainer( 311 viewModel = communalViewModel, 312 colors = communalColors, 313 dataSourceDelegator = dataSourceDelegator, 314 content = communalContent, 315 ) 316 } 317 } 318 } 319 } 320 } 321 } 322 ) 323 } 324 325 private fun resetTouchMonitor() { 326 touchMonitor?.apply { 327 destroy() 328 touchMonitor = null 329 } 330 } 331 332 /** Override for testing. */ 333 @VisibleForTesting 334 internal fun initView(containerView: View): View { 335 SceneContainerFlag.assertInLegacyMode() 336 if (communalContainerView != null) { 337 throw RuntimeException("Communal view has already been initialized") 338 } 339 340 resetTouchMonitor() 341 342 touchMonitor = 343 ambientTouchComponentFactory.create(this, HashSet(), TAG).getTouchMonitor().apply { 344 init() 345 } 346 347 lifecycleRegistry.addObserver(touchLifecycleLogger) 348 lifecycleRegistry.currentState = Lifecycle.State.CREATED 349 350 communalContainerView = containerView 351 352 if (!Flags.hubmodeFullscreenVerticalSwipeFix()) { 353 val topEdgeSwipeRegionWidth = 354 containerView.resources.getDimensionPixelSize( 355 R.dimen.communal_top_edge_swipe_region_height 356 ) 357 val bottomEdgeSwipeRegionWidth = 358 containerView.resources.getDimensionPixelSize( 359 R.dimen.communal_bottom_edge_swipe_region_height 360 ) 361 362 // BouncerSwipeTouchHandler has a larger gesture area than we want, set an exclusion 363 // area so 364 // the gesture area doesn't overlap with widgets. 365 // TODO(b/323035776): adjust gesture area for portrait mode 366 containerView.repeatWhenAttached { 367 // Run when the touch handling lifecycle is RESUMED, meaning the hub is visible and 368 // not 369 // occluded. 370 lifecycleRegistry.repeatOnLifecycle(Lifecycle.State.RESUMED) { 371 containerView.systemGestureExclusionRects = 372 listOf( 373 // Only allow swipe up to bouncer and swipe down to shade in the very 374 // top/bottom to avoid conflicting with widgets in the hub grid. 375 Rect( 376 0, 377 topEdgeSwipeRegionWidth, 378 containerView.right, 379 containerView.bottom - bottomEdgeSwipeRegionWidth, 380 ) 381 ) 382 383 logger.d({ "Insets updated: $str1" }) { 384 str1 = containerView.systemGestureExclusionRects.toString() 385 } 386 } 387 } 388 } 389 390 // Listen to bouncer visibility directly as these flows become true as soon as any portion 391 // of the bouncers are visible when the transition starts. The keyguard transition state 392 // only changes once transitions are fully finished, which would mean touches during a 393 // transition to the bouncer would be incorrectly intercepted by the hub. 394 collectFlow( 395 containerView, 396 anyOf( 397 keyguardInteractor.primaryBouncerShowing, 398 keyguardInteractor.alternateBouncerShowing, 399 ), 400 { 401 anyBouncerShowing = it 402 if (hubShowing) { 403 logger.d({ "New value for anyBouncerShowing: $bool1" }) { bool1 = it } 404 } 405 updateTouchHandlingState() 406 }, 407 ) 408 collectFlow( 409 containerView, 410 keyguardTransitionInteractor.isFinishedIn(KeyguardState.LOCKSCREEN), 411 { onLockscreen = it }, 412 ) 413 collectFlow( 414 containerView, 415 communalInteractor.isCommunalVisible, 416 { 417 hubShowing = it 418 updateTouchHandlingState() 419 }, 420 ) 421 collectFlow( 422 containerView, 423 // When leaving edit mode, editActivityShowing is true until the edit mode activity 424 // finishes itself and the device locks, after which isInTransition will be true until 425 // we're fully on the hub. 426 anyOf( 427 communalInteractor.editActivityShowing, 428 keyguardTransitionInteractor.isInTransition( 429 Edge.create(KeyguardState.GONE, KeyguardState.GLANCEABLE_HUB) 430 ), 431 ), 432 { 433 inEditModeTransition = it 434 updateTouchHandlingState() 435 }, 436 ) 437 collectFlow( 438 containerView, 439 combine( 440 shadeInteractor.isAnyFullyExpanded, 441 shadeInteractor.isUserInteracting, 442 shadeInteractor.isShadeFullyCollapsed, 443 shadeInteractor.isQsExpanded, 444 ::Quad, 445 ), 446 { (isFullyExpanded, isUserInteracting, isShadeFullyCollapsed, isQsExpanded) -> 447 shadeConsumingTouches = isUserInteracting 448 shadeShowing = isQsExpanded || !isShadeFullyCollapsed 449 val expandedAndNotInteractive = isFullyExpanded && !isUserInteracting 450 451 // If we ever are fully expanded and not interacting, capture this state as we 452 // should not handle touches until we fully collapse again 453 userNotInteractiveAtShadeFullyExpanded = 454 !isShadeFullyCollapsed && 455 (userNotInteractiveAtShadeFullyExpanded || expandedAndNotInteractive) 456 457 // If the shade reaches full expansion without interaction, then we should allow it 458 // to consume touches rather than handling it here until it disappears. 459 shadeShowingAndConsumingTouches = 460 (userNotInteractiveAtShadeFullyExpanded || expandedAndNotInteractive).also { 461 if (it != shadeShowingAndConsumingTouches && hubShowing) { 462 logger.d({ "New value for shadeShowingAndConsumingTouches: $bool1" }) { 463 bool1 = it 464 } 465 } 466 } 467 updateTouchHandlingState() 468 }, 469 ) 470 collectFlow(containerView, keyguardInteractor.isDreaming, { isDreaming = it }) 471 collectFlow(containerView, communalViewModel.swipeToHubEnabled, { swipeToHubEnabled = it }) 472 473 communalContainerWrapper = 474 CommunalWrapper(containerView.context, communalSettingsInteractor) 475 communalContainerWrapper?.addView(communalContainerView) 476 logger.d("Hub container initialized") 477 return communalContainerWrapper!! 478 } 479 480 /** 481 * Updates the lifecycle stored by the [lifecycleRegistry] to control when the [touchMonitor] 482 * should listen for and intercept top and bottom swipes. 483 * 484 * Also clears gesture exclusion zones when the hub is occluded or gone. 485 */ 486 private fun updateTouchHandlingState() { 487 // Only listen to gestures when we're settled in the hub keyguard state and the shade 488 // bouncer are not showing on top. 489 val shouldInterceptGestures = 490 hubShowing && 491 !(shadeShowingAndConsumingTouches || anyBouncerShowing || inEditModeTransition) 492 if (shouldInterceptGestures) { 493 lifecycleRegistry.currentState = Lifecycle.State.RESUMED 494 } else { 495 // Hub is either occluded or no longer showing, turn off touch handling. 496 lifecycleRegistry.currentState = Lifecycle.State.STARTED 497 498 // Clear exclusion rects if the hub is not showing or is covered, so we don't interfere 499 // with back gestures when the bouncer or shade. We do this here instead of with 500 // repeatOnLifecycle as repeatOnLifecycle does not run when going from RESUMED back to 501 // STARTED, only when going from CREATED to STARTED. 502 communalContainerView!!.systemGestureExclusionRects = emptyList() 503 } 504 } 505 506 /** Removes the container view from its parent. */ 507 fun disposeView() { 508 SceneContainerFlag.assertInLegacyMode() 509 communalContainerView?.let { 510 (it.parent as ViewGroup).removeView(it) 511 lifecycleRegistry.currentState = Lifecycle.State.CREATED 512 communalContainerView = null 513 } 514 515 communalContainerWrapper?.let { 516 (it.parent as ViewGroup).removeView(it) 517 communalContainerWrapper = null 518 } 519 520 lifecycleRegistry.removeObserver(touchLifecycleLogger) 521 522 resetTouchMonitor() 523 524 logger.d("Hub container disposed") 525 } 526 527 /** 528 * Notifies the hub container of a touch event. Returns true if it's determined that the touch 529 * should go to the hub container and no one else. 530 * 531 * Special handling is needed because the hub container sits at the lowest z-order in 532 * [NotificationShadeWindowView] and would not normally receive touches. We also cannot use a 533 * [GestureDetector] as the hub container's SceneTransitionLayout is a Compose view that expects 534 * to be fully in control of its own touch handling. 535 */ 536 fun onTouchEvent(ev: MotionEvent): Boolean { 537 SceneContainerFlag.assertInLegacyMode() 538 539 if (communalContainerView == null) { 540 // Return early so we don't log unnecessarily and fill up our LogBuffer. 541 return false 542 } 543 544 // In the case that we are handling full swipes on the lockscreen, are on the lockscreen, 545 // and the touch is within the horizontal notification band on the screen, do not process 546 // the touch. 547 val touchOnNotifications = 548 !notificationStackScrollLayoutController.isBelowLastNotification(ev.x, ev.y) 549 val touchOnUmo = keyguardMediaController.isWithinMediaViewBounds(ev.x.toInt(), ev.y.toInt()) 550 val touchOnSmartspace = 551 lockscreenSmartspaceController.isWithinSmartspaceBounds(ev.x.toInt(), ev.y.toInt()) 552 val glanceableHubV2 = communalSettingsInteractor.isV2FlagEnabled() 553 if ( 554 !hubShowing && 555 (touchOnNotifications || touchOnUmo || touchOnSmartspace || !swipeToHubEnabled) 556 ) { 557 logger.d({ 558 "Lockscreen touch ignored: touchOnNotifications: $bool1, touchOnUmo: $bool2, " + 559 "touchOnSmartspace: $bool3, glanceableHubV2: $bool4" 560 }) { 561 bool1 = touchOnNotifications 562 bool2 = touchOnUmo 563 bool3 = touchOnSmartspace 564 bool4 = glanceableHubV2 565 } 566 return false 567 } 568 569 return handleTouchEventOnCommunalView(ev) 570 } 571 572 private fun handleTouchEventOnCommunalView(ev: MotionEvent): Boolean { 573 val isDown = ev.actionMasked == MotionEvent.ACTION_DOWN 574 val isUp = ev.actionMasked == MotionEvent.ACTION_UP 575 val isMove = ev.actionMasked == MotionEvent.ACTION_MOVE 576 val isCancel = ev.actionMasked == MotionEvent.ACTION_CANCEL 577 578 val hubOccluded = anyBouncerShowing || shadeConsumingTouches || shadeShowing 579 580 if ((isDown || isMove) && !hubOccluded) { 581 if (isDown) { 582 logger.d({ 583 "Touch started. x: $int1, y: $int2, hubShowing: $bool1, isDreaming: $bool2, " + 584 "onLockscreen: $bool3" 585 }) { 586 int1 = ev.x.toInt() 587 int2 = ev.y.toInt() 588 bool1 = hubShowing 589 bool2 = isDreaming 590 bool3 = onLockscreen 591 } 592 } 593 isTrackingHubTouch = true 594 } 595 596 if (isTrackingHubTouch) { 597 // On the lock screen, our touch handlers are not active and we rely on the NSWVC's 598 // touch handling for gestures on blank areas, which can go up to show the bouncer or 599 // down to show the notification shade. We see the touches first and they are not 600 // consumed and cancelled like on the dream or hub so we have to gracefully ignore them 601 // if the shade or bouncer are handling them. This issue only applies to touches on the 602 // keyguard itself, once the bouncer or shade are fully open, our logic stops us from 603 // taking touches. 604 touchTakenByKeyguardGesture = 605 (onLockscreen && (shadeConsumingTouches || anyBouncerShowing)).also { 606 if (it != touchTakenByKeyguardGesture && it) { 607 logger.d( 608 "Lock screen touch consumed by shade or bouncer, ignoring " + 609 "subsequent touches" 610 ) 611 } 612 } 613 if (isUp || isCancel) { 614 logger.d({ 615 val endReason = if (bool1) "up" else "cancel" 616 "Touch ended with $endReason. x: $int1, y: $int2, " + 617 "shadeConsumingTouches: $bool2, anyBouncerShowing: $bool3" 618 }) { 619 int1 = ev.x.toInt() 620 int2 = ev.y.toInt() 621 bool1 = isUp 622 bool2 = shadeConsumingTouches 623 bool3 = anyBouncerShowing 624 } 625 isTrackingHubTouch = false 626 627 // Clear out touch taken state to ensure the up/cancel event still gets dispatched 628 // to the hub. This is necessary as the hub always receives at least the initial 629 // down even if the shade or bouncer end up handling the touch. 630 touchTakenByKeyguardGesture = false 631 } 632 return dispatchTouchEvent(ev) 633 } 634 635 return false 636 } 637 638 /** 639 * Dispatches the touch event to the communal container and sends a user activity event to reset 640 * the screen timeout. 641 */ 642 private fun dispatchTouchEvent(ev: MotionEvent): Boolean { 643 if (inEditModeTransition) { 644 // Consume but ignore touches while we're transitioning to or from edit mode so that the 645 // user can't trigger another transition, such as by swiping the hub away, tapping a 646 // widget, or opening the shade/bouncer. Doing any of these while transitioning can 647 // result in broken states. 648 return true 649 } 650 var handled = hubShowing 651 try { 652 if (!touchTakenByKeyguardGesture) { 653 communalContainerWrapper?.dispatchTouchEvent(ev) { 654 if (it) { 655 handled = true 656 } 657 } 658 } 659 return handled 660 } finally { 661 if (handled) { 662 userTouchActivityNotifier.notifyActivity(ev) 663 } 664 } 665 } 666 667 override val lifecycle: Lifecycle 668 get() = lifecycleRegistry 669 670 companion object { 671 private const val TAG = "GlanceableHubContainer" 672 } 673 } 674