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.display.domain.interactor
18 
19 import android.companion.virtual.VirtualDeviceManager
20 import android.companion.virtual.flags.Flags
21 import android.view.Display
22 import com.android.systemui.dagger.SysUISingleton
23 import com.android.systemui.dagger.qualifiers.Background
24 import com.android.systemui.display.data.repository.DeviceStateRepository
25 import com.android.systemui.display.data.repository.DisplayRepository
26 import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor.PendingDisplay
27 import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor.State
28 import com.android.systemui.keyguard.data.repository.KeyguardRepository
29 import javax.inject.Inject
30 import kotlinx.coroutines.CoroutineDispatcher
31 import kotlinx.coroutines.flow.Flow
32 import kotlinx.coroutines.flow.combine
33 import kotlinx.coroutines.flow.distinctUntilChanged
34 import kotlinx.coroutines.flow.filter
35 import kotlinx.coroutines.flow.flowOn
36 import kotlinx.coroutines.flow.map
37 
38 /** Provides information about an external connected display. */
39 interface ConnectedDisplayInteractor {
40     /**
41      * Provides the current external display state.
42      *
43      * The state is:
44      * - [State.CONNECTED] when there is at least one display with [TYPE_EXTERNAL].
45      * - [State.CONNECTED_SECURE] when is at least one display with both [TYPE_EXTERNAL] AND
46      *   [Display.FLAG_SECURE] set
47      */
48     val connectedDisplayState: Flow<State>
49 
50     /**
51      * Indicates that there is a new connected display (either an external display or a virtual
52      * device owned mirror display).
53      */
54     val connectedDisplayAddition: Flow<Unit>
55 
56     /** Pending display that can be enabled to be used by the system. */
57     val pendingDisplay: Flow<PendingDisplay?>
58 
59     /** Pending display that can be enabled to be used by the system. */
60     val concurrentDisplaysInProgress: Flow<Boolean>
61 
62     /** Possible connected display state. */
63     enum class State {
64         DISCONNECTED,
65         CONNECTED,
66         CONNECTED_SECURE,
67     }
68 
69     /** Represents a connected display that has not been enabled yet for the UI layer. */
70     interface PendingDisplay {
71         /** Enables the display, making it available to the system. */
72         suspend fun enable()
73 
74         /**
75          * Ignores the pending display.
76          *
77          * When called, this specific display id doesn't appear as pending anymore until the display
78          * is disconnected and reconnected again.
79          */
80         suspend fun ignore()
81     }
82 }
83 
84 @SysUISingleton
85 class ConnectedDisplayInteractorImpl
86 @Inject
87 constructor(
88     private val virtualDeviceManager: VirtualDeviceManager,
89     keyguardRepository: KeyguardRepository,
90     displayRepository: DisplayRepository,
91     deviceStateRepository: DeviceStateRepository,
92     @Background backgroundCoroutineDispatcher: CoroutineDispatcher,
93 ) : ConnectedDisplayInteractor {
94 
95     override val connectedDisplayState: Flow<State> =
96         displayRepository.displays
displaysnull97             .map { displays ->
98                 val externalDisplays = displays.filter { isExternalDisplay(it) }
99 
100                 val secureExternalDisplays = externalDisplays.filter { isSecureDisplay(it) }
101 
102                 val virtualDeviceMirrorDisplays =
103                     displays.filter { isVirtualDeviceOwnedMirrorDisplay(it) }
104 
105                 if (externalDisplays.isEmpty() && virtualDeviceMirrorDisplays.isEmpty()) {
106                     State.DISCONNECTED
107                 } else if (!secureExternalDisplays.isEmpty()) {
108                     State.CONNECTED_SECURE
109                 } else {
110                     State.CONNECTED
111                 }
112             }
113             .flowOn(backgroundCoroutineDispatcher)
114             .distinctUntilChanged()
115 
116     override val connectedDisplayAddition: Flow<Unit> =
117         displayRepository.displayAdditionEvent
<lambda>null118             .filter {
119                 it != null && (isExternalDisplay(it) || isVirtualDeviceOwnedMirrorDisplay(it))
120             }
121             .flowOn(backgroundCoroutineDispatcher)
<lambda>null122             .map {} // map to Unit
123 
124     // Provides the pending display only if the lockscreen is unlocked
125     override val pendingDisplay: Flow<PendingDisplay?> =
126         displayRepository.pendingDisplay.combine(keyguardRepository.isKeyguardShowing) {
repositoryPendingDisplaynull127             repositoryPendingDisplay,
128             keyguardShowing ->
129             if (repositoryPendingDisplay != null && !keyguardShowing) {
130                 repositoryPendingDisplay.toInteractorPendingDisplay()
131             } else {
132                 null
133             }
134         }
135 
136     override val concurrentDisplaysInProgress: Flow<Boolean> =
137         deviceStateRepository.state
<lambda>null138             .map { it == DeviceStateRepository.DeviceState.CONCURRENT_DISPLAY }
139             .distinctUntilChanged()
140             .flowOn(backgroundCoroutineDispatcher)
141 
toInteractorPendingDisplaynull142     private fun DisplayRepository.PendingDisplay.toInteractorPendingDisplay(): PendingDisplay =
143         object : PendingDisplay {
144             override suspend fun enable() = this@toInteractorPendingDisplay.enable()
145 
146             override suspend fun ignore() = this@toInteractorPendingDisplay.ignore()
147         }
148 
isExternalDisplaynull149     private fun isExternalDisplay(display: Display): Boolean {
150         return display.type == Display.TYPE_EXTERNAL
151     }
152 
isSecureDisplaynull153     private fun isSecureDisplay(display: Display): Boolean {
154         return display.flags and Display.FLAG_SECURE != 0
155     }
156 
isVirtualDeviceOwnedMirrorDisplaynull157     private fun isVirtualDeviceOwnedMirrorDisplay(display: Display): Boolean {
158         return Flags.interactiveScreenMirror() &&
159             virtualDeviceManager.isVirtualDeviceOwnedMirrorDisplay(display.displayId)
160     }
161 }
162