• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 2024 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.navigationbar.gestural.domain
18 
19 import com.android.systemui.dagger.qualifiers.Application
20 import com.android.systemui.dagger.qualifiers.Background
21 import com.android.systemui.dagger.qualifiers.Main
22 import com.android.systemui.navigationbar.gestural.data.respository.GestureRepository
23 import com.android.systemui.shared.system.ActivityManagerWrapper
24 import com.android.systemui.shared.system.TaskStackChangeListener
25 import com.android.systemui.shared.system.TaskStackChangeListeners
26 import com.android.systemui.util.kotlin.emitOnStart
27 import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
28 import javax.inject.Inject
29 import kotlin.coroutines.CoroutineContext
30 import kotlinx.coroutines.CoroutineDispatcher
31 import kotlinx.coroutines.CoroutineScope
32 import kotlinx.coroutines.channels.awaitClose
33 import kotlinx.coroutines.flow.MutableStateFlow
34 import kotlinx.coroutines.flow.asStateFlow
35 import kotlinx.coroutines.flow.combine
36 import kotlinx.coroutines.flow.distinctUntilChanged
37 import kotlinx.coroutines.flow.flowOn
38 import kotlinx.coroutines.flow.mapLatest
39 import com.android.app.tracing.coroutines.launchTraced as launch
40 import kotlinx.coroutines.withContext
41 
42 /**
43  * {@link GestureInteractor} helps interact with gesture-related logic, including accessing the
44  * underlying {@link GestureRepository}.
45  */
46 class GestureInteractor
47 @Inject
48 constructor(
49     private val gestureRepository: GestureRepository,
50     @Main private val mainDispatcher: CoroutineDispatcher,
51     @Background private val backgroundCoroutineContext: CoroutineContext,
52     @Application private val scope: CoroutineScope,
53     private val activityManagerWrapper: ActivityManagerWrapper,
54     private val taskStackChangeListeners: TaskStackChangeListeners,
55 ) {
56     enum class Scope {
57         Local,
58         Global,
59     }
60 
61     private val _localGestureBlockedMatchers = MutableStateFlow<Set<TaskMatcher>>(setOf())
62 
63     private val _topActivity =
64         conflatedCallbackFlow {
65                 val taskListener =
66                     object : TaskStackChangeListener {
67                         override fun onTaskStackChanged() {
68                             trySend(Unit)
69                         }
70                     }
71 
72                 taskStackChangeListeners.registerTaskStackListener(taskListener)
73                 awaitClose { taskStackChangeListeners.unregisterTaskStackListener(taskListener) }
74             }
75             .flowOn(mainDispatcher)
76             .emitOnStart()
77             .mapLatest { getTopActivity() }
78             .distinctUntilChanged()
79 
80     private suspend fun getTopActivity(): TaskInfo? =
81         withContext(backgroundCoroutineContext) {
82             activityManagerWrapper.runningTask?.let { TaskInfo(it.topActivity, it.activityType) }
83         }
84 
85     val topActivityBlocked =
86         combine(
87             _topActivity,
88             gestureRepository.gestureBlockedMatchers,
89             _localGestureBlockedMatchers.asStateFlow(),
90         ) { runningTask, global, local ->
91             runningTask != null && (global + local).any { it.matches(runningTask) }
92         }
93 
94     /** Adds an [TaskMatcher] to decide whether gestures should be blocked. */
95     fun addGestureBlockedMatcher(matcher: TaskMatcher, gestureScope: Scope) {
96         scope.launch {
97             when (gestureScope) {
98                 Scope.Local -> {
99                     _localGestureBlockedMatchers.emit(
100                         _localGestureBlockedMatchers.value.toMutableSet().apply { add(matcher) }
101                     )
102                 }
103                 Scope.Global -> {
104                     gestureRepository.addGestureBlockedMatcher(matcher)
105                 }
106             }
107         }
108     }
109 
110     /** Removes a gesture from deciding whether gestures should be blocked */
111     fun removeGestureBlockedMatcher(matcher: TaskMatcher, gestureScope: Scope) {
112         scope.launch {
113             when (gestureScope) {
114                 Scope.Local -> {
115                     _localGestureBlockedMatchers.emit(
116                         _localGestureBlockedMatchers.value.toMutableSet().apply { remove(matcher) }
117                     )
118                 }
119                 Scope.Global -> {
120                     gestureRepository.removeGestureBlockedMatcher(matcher)
121                 }
122             }
123         }
124     }
125 }
126