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.statusbar.notification.row.ui.viewbinder
18 
19 import android.util.Log
20 import android.view.MotionEvent
21 import android.view.View
22 import android.view.View.OnTouchListener
23 import androidx.lifecycle.Lifecycle
24 import androidx.lifecycle.repeatOnLifecycle
25 import com.android.systemui.Gefingerpoken
26 import com.android.systemui.lifecycle.repeatWhenAttached
27 import com.android.systemui.plugins.FalsingManager
28 import com.android.systemui.statusbar.notification.row.ActivatableNotificationView
29 import com.android.systemui.statusbar.notification.row.ui.viewmodel.ActivatableNotificationViewModel
30 import kotlinx.coroutines.awaitCancellation
31 import kotlinx.coroutines.launch
32 
33 /** Binds an [ActivatableNotificationView] to its [view model][ActivatableNotificationViewModel]. */
34 object ActivatableNotificationViewBinder {
35 
36     fun bind(
37         viewModel: ActivatableNotificationViewModel,
38         view: ActivatableNotificationView,
39         falsingManager: FalsingManager,
40     ) {
41         ExpandableOutlineViewBinder.bind(viewModel, view)
42         val touchHandler = TouchHandler(view, falsingManager)
43         view.repeatWhenAttached {
44             repeatOnLifecycle(Lifecycle.State.STARTED) {
45                 launch {
46                     viewModel.isTouchable.collect { isTouchable ->
47                         touchHandler.isTouchEnabled = isTouchable
48                     }
49                 }
50                 view.registerListenersWhileAttached(touchHandler)
51             }
52         }
53     }
54 
55     private suspend fun ActivatableNotificationView.registerListenersWhileAttached(
56         touchHandler: TouchHandler,
57     ): Unit =
58         try {
59             setOnTouchListener(touchHandler)
60             setTouchHandler(touchHandler)
61             awaitCancellation()
62         } finally {
63             setTouchHandler(null)
64             setOnTouchListener(null)
65         }
66 }
67 
68 private class TouchHandler(
69     private val view: ActivatableNotificationView,
70     private val falsingManager: FalsingManager,
71 ) : Gefingerpoken, OnTouchListener {
72 
73     var isTouchEnabled = false
74 
onTouchnull75     override fun onTouch(v: View, ev: MotionEvent): Boolean {
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             return falsingManager.isFalseTap(FalsingManager.LOW_PENALTY).also {
86                 if (it) {
87                     Log.d(v::class.simpleName ?: TAG, "capturing false tap")
88                 }
89             }
90         }
91         return false
92     }
93 
onInterceptTouchEventnull94     override fun onInterceptTouchEvent(ev: MotionEvent): Boolean = false
95 
96     /** Use [onTouch] instead. */
97     override fun onTouchEvent(ev: MotionEvent): Boolean = false
98 
99     companion object {
100         private const val TAG = "ActivatableNotificationViewBinder"
101     }
102 }
103 
104