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