1 /* 2 * 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.statusbar.notification.row.ui.viewbinder 18 19 import android.view.MotionEvent 20 import android.view.View 21 import android.view.View.OnTouchListener 22 import androidx.lifecycle.Lifecycle 23 import androidx.lifecycle.repeatOnLifecycle 24 import com.android.systemui.Gefingerpoken 25 import com.android.systemui.lifecycle.repeatWhenAttached 26 import com.android.systemui.plugins.FalsingManager 27 import com.android.systemui.statusbar.notification.row.ActivatableNotificationView 28 import com.android.systemui.statusbar.notification.row.ui.viewmodel.ActivatableNotificationViewModel 29 import kotlinx.coroutines.awaitCancellation 30 import kotlinx.coroutines.launch 31 32 /** Binds an [ActivatableNotificationView] to its [view model][ActivatableNotificationViewModel]. */ 33 object ActivatableNotificationViewBinder { 34 35 fun bind( 36 viewModel: ActivatableNotificationViewModel, 37 view: ActivatableNotificationView, 38 falsingManager: FalsingManager, 39 ) { 40 ExpandableOutlineViewBinder.bind(viewModel, view) 41 val touchHandler = TouchHandler(view, falsingManager) 42 view.repeatWhenAttached { 43 repeatOnLifecycle(Lifecycle.State.STARTED) { 44 launch { 45 viewModel.isTouchable.collect { isTouchable -> 46 touchHandler.isTouchEnabled = isTouchable 47 } 48 } 49 view.registerListenersWhileAttached(touchHandler) 50 } 51 } 52 } 53 54 private suspend fun ActivatableNotificationView.registerListenersWhileAttached( 55 touchHandler: TouchHandler, 56 ): Unit = 57 try { 58 setOnTouchListener(touchHandler) 59 setTouchHandler(touchHandler) 60 awaitCancellation() 61 } finally { 62 setTouchHandler(null) 63 setOnTouchListener(null) 64 } 65 } 66 67 private class TouchHandler( 68 private val view: ActivatableNotificationView, 69 private val falsingManager: FalsingManager, 70 ) : Gefingerpoken, OnTouchListener { 71 72 var isTouchEnabled = false 73 74 override fun onTouch(v: View, ev: MotionEvent): Boolean { 75 val result = false 76 if (ev.action == MotionEvent.ACTION_UP) { 77 view.setLastActionUpTime(ev.eventTime) 78 } 79 // With a11y, just do nothing. 80 if (!isTouchEnabled) { 81 return false 82 } 83 if (ev.action == MotionEvent.ACTION_UP) { 84 // If this is a false tap, capture the even so it doesn't result in a click. 85 val falseTap: Boolean = falsingManager.isFalseTap(FalsingManager.LOW_PENALTY) 86 if (!falseTap && v is ActivatableNotificationView) { 87 v.onTap() 88 } 89 return falseTap 90 } 91 return result 92 } 93 94 override fun onInterceptTouchEvent(ev: MotionEvent): Boolean = false 95 96 /** Use [onTouch] instead. */ 97 override fun onTouchEvent(ev: MotionEvent): Boolean = false 98 } 99