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