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:
Nicolo' Mazzucato
2023-06-22 16:24:28 +00:00
parent f328c537db
commit 3214adb648
4 changed files with 258 additions and 0 deletions

View File

@@ -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,

View File

@@ -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
}

View File

@@ -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()
}

View File

@@ -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
}
}