Introduce ConnectedDisplayInteractor
This class provides the status of external connected devices though a Flow. Used in child cls to add a status bar icon when an external display is connected. Bug: 286186256 Test: ConnectedDisplayInteractorTest Change-Id: Ib31a9bcbd2062488eed4b4933b814929704a4ff2
This commit is contained in:
@@ -47,6 +47,7 @@ import com.android.systemui.controls.dagger.ControlsModule;
|
||||
import com.android.systemui.dagger.qualifiers.Main;
|
||||
import com.android.systemui.dagger.qualifiers.SystemUser;
|
||||
import com.android.systemui.demomode.dagger.DemoModeModule;
|
||||
import com.android.systemui.display.DisplayModule;
|
||||
import com.android.systemui.doze.dagger.DozeComponent;
|
||||
import com.android.systemui.dreams.dagger.DreamModule;
|
||||
import com.android.systemui.dump.DumpManager;
|
||||
@@ -163,6 +164,7 @@ import javax.inject.Named;
|
||||
ClipboardOverlayModule.class,
|
||||
ClockRegistryModule.class,
|
||||
CommonRepositoryModule.class,
|
||||
DisplayModule.class,
|
||||
ConnectivityModule.class,
|
||||
CoroutinesModule.class,
|
||||
DreamModule.class,
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright (C) 2023 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.systemui.display
|
||||
|
||||
import com.android.systemui.display.data.repository.DisplayRepository
|
||||
import com.android.systemui.display.data.repository.DisplayRepositoryImpl
|
||||
import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor
|
||||
import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractorImpl
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
|
||||
/** Module binding display related classes. */
|
||||
@Module
|
||||
interface DisplayModule {
|
||||
@Binds
|
||||
fun bindConnectedDisplayInteractor(
|
||||
provider: ConnectedDisplayInteractorImpl
|
||||
): ConnectedDisplayInteractor
|
||||
|
||||
@Binds fun bindsDisplayRepository(displayRepository: DisplayRepositoryImpl): DisplayRepository
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright (C) 2023 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.systemui.display.domain.interactor
|
||||
|
||||
import android.view.Display
|
||||
import com.android.systemui.dagger.SysUISingleton
|
||||
import com.android.systemui.display.data.repository.DisplayRepository
|
||||
import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor.State
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
/** Provides information about an external connected display. */
|
||||
interface ConnectedDisplayInteractor {
|
||||
/**
|
||||
* Provides the current external display state.
|
||||
*
|
||||
* The state is:
|
||||
* - [State.CONNECTED] when there is at least one display with [TYPE_EXTERNAL].
|
||||
* - [State.CONNECTED_SECURE] when is at least one display with both [TYPE_EXTERNAL] AND
|
||||
* [Display.FLAG_SECURE] set
|
||||
*/
|
||||
val connectedDisplayState: Flow<State>
|
||||
|
||||
/** Possible connected display state. */
|
||||
enum class State {
|
||||
DISCONNECTED,
|
||||
CONNECTED,
|
||||
CONNECTED_SECURE,
|
||||
}
|
||||
}
|
||||
|
||||
@SysUISingleton
|
||||
class ConnectedDisplayInteractorImpl
|
||||
@Inject
|
||||
constructor(
|
||||
displayRepository: DisplayRepository,
|
||||
) : ConnectedDisplayInteractor {
|
||||
|
||||
override val connectedDisplayState: Flow<State> =
|
||||
displayRepository.displays
|
||||
.map { displays ->
|
||||
val externalDisplays =
|
||||
displays.filter { display -> display.type == Display.TYPE_EXTERNAL }
|
||||
|
||||
val secureExternalDisplays =
|
||||
externalDisplays.filter { it.flags and Display.FLAG_SECURE != 0 }
|
||||
|
||||
if (externalDisplays.isEmpty()) {
|
||||
State.DISCONNECTED
|
||||
} else if (!secureExternalDisplays.isEmpty()) {
|
||||
State.CONNECTED_SECURE
|
||||
} else {
|
||||
State.CONNECTED
|
||||
}
|
||||
}
|
||||
.distinctUntilChanged()
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
/*
|
||||
* Copyright (C) 2023 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.systemui.display.domain.interactor
|
||||
|
||||
import android.testing.AndroidTestingRunner
|
||||
import android.testing.TestableLooper
|
||||
import android.view.Display
|
||||
import android.view.Display.TYPE_EXTERNAL
|
||||
import android.view.Display.TYPE_INTERNAL
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.coroutines.FlowValue
|
||||
import com.android.systemui.coroutines.collectLastValue
|
||||
import com.android.systemui.display.data.repository.DisplayRepository
|
||||
import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor.State
|
||||
import com.android.systemui.util.mockito.mock
|
||||
import com.android.systemui.util.mockito.whenever
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.test.TestScope
|
||||
import kotlinx.coroutines.test.UnconfinedTestDispatcher
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@RunWith(AndroidTestingRunner::class)
|
||||
@TestableLooper.RunWithLooper
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
@SmallTest
|
||||
class ConnectedDisplayInteractorTest : SysuiTestCase() {
|
||||
|
||||
private val fakeDisplayRepository = FakeDisplayRepository()
|
||||
private val connectedDisplayStateProvider: ConnectedDisplayInteractor =
|
||||
ConnectedDisplayInteractorImpl(fakeDisplayRepository)
|
||||
private val testScope = TestScope(UnconfinedTestDispatcher())
|
||||
|
||||
@Test
|
||||
fun displayState_nullDisplays_disconnected() =
|
||||
testScope.runTest {
|
||||
val value by lastValue()
|
||||
|
||||
fakeDisplayRepository.emit(emptySet())
|
||||
|
||||
assertThat(value).isEqualTo(State.DISCONNECTED)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun displayState_emptyDisplays_disconnected() =
|
||||
testScope.runTest {
|
||||
val value by lastValue()
|
||||
|
||||
fakeDisplayRepository.emit(emptySet())
|
||||
|
||||
assertThat(value).isEqualTo(State.DISCONNECTED)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun displayState_internalDisplay_disconnected() =
|
||||
testScope.runTest {
|
||||
val value by lastValue()
|
||||
|
||||
fakeDisplayRepository.emit(setOf(display(type = TYPE_INTERNAL)))
|
||||
|
||||
assertThat(value).isEqualTo(State.DISCONNECTED)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun displayState_externalDisplay_connected() =
|
||||
testScope.runTest {
|
||||
val value by lastValue()
|
||||
|
||||
fakeDisplayRepository.emit(setOf(display(type = TYPE_EXTERNAL)))
|
||||
|
||||
assertThat(value).isEqualTo(State.CONNECTED)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun displayState_multipleExternalDisplays_connected() =
|
||||
testScope.runTest {
|
||||
val value by lastValue()
|
||||
|
||||
fakeDisplayRepository.emit(
|
||||
setOf(display(type = TYPE_EXTERNAL), display(type = TYPE_EXTERNAL))
|
||||
)
|
||||
|
||||
assertThat(value).isEqualTo(State.CONNECTED)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun displayState_externalSecure_connectedSecure() =
|
||||
testScope.runTest {
|
||||
val value by lastValue()
|
||||
|
||||
fakeDisplayRepository.emit(
|
||||
setOf(display(type = TYPE_EXTERNAL, flags = Display.FLAG_SECURE))
|
||||
)
|
||||
|
||||
assertThat(value).isEqualTo(State.CONNECTED_SECURE)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun displayState_multipleExternal_onlyOneSecure_connectedSecure() =
|
||||
testScope.runTest {
|
||||
val value by lastValue()
|
||||
|
||||
fakeDisplayRepository.emit(
|
||||
setOf(
|
||||
display(type = TYPE_EXTERNAL, flags = Display.FLAG_SECURE),
|
||||
display(type = TYPE_EXTERNAL, flags = 0)
|
||||
)
|
||||
)
|
||||
|
||||
assertThat(value).isEqualTo(State.CONNECTED_SECURE)
|
||||
}
|
||||
|
||||
private fun TestScope.lastValue(): FlowValue<State?> =
|
||||
collectLastValue(connectedDisplayStateProvider.connectedDisplayState)
|
||||
|
||||
private fun display(type: Int, flags: Int = 0): Display {
|
||||
return mock<Display>().also { mockDisplay ->
|
||||
whenever(mockDisplay.type).thenReturn(type)
|
||||
whenever(mockDisplay.flags).thenReturn(flags)
|
||||
}
|
||||
}
|
||||
|
||||
private class FakeDisplayRepository : DisplayRepository {
|
||||
private val flow = MutableSharedFlow<Set<Display>>()
|
||||
suspend fun emit(value: Set<Display>) = flow.emit(value)
|
||||
override val displays: Flow<Set<Display>>
|
||||
get() = flow
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user