Merge "[SB Refactor] Create an airplane mode repo/interactor/view model and use it to determine when to show the wifi<->airplane spacer." into tm-qpr-dev am: fd6c30921f

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/20208616

Change-Id: I8ea5adc1be285d00e89149459d4c3e41e92f7983
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
Caitlin Shkuratov
2022-10-24 15:04:19 +00:00
committed by Automerger Merge Worker
17 changed files with 682 additions and 0 deletions

View File

@@ -0,0 +1,93 @@
/*
* Copyright (C) 2022 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.statusbar.pipeline.airplane.data.repository
import android.os.Handler
import android.os.UserHandle
import android.provider.Settings.Global
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.qs.SettingObserver
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logInputChange
import com.android.systemui.util.settings.GlobalSettings
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.stateIn
/**
* Provides data related to airplane mode.
*
* IMPORTANT: This is currently *not* used to render any airplane mode information anywhere. It is
* only used to help [com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel]
* determine what parts of the wifi icon view should be shown.
*
* TODO(b/238425913): Consider migrating the status bar airplane mode icon to use this repo.
*/
interface AirplaneModeRepository {
/** Observable for whether the device is currently in airplane mode. */
val isAirplaneMode: StateFlow<Boolean>
}
@SysUISingleton
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
@OptIn(ExperimentalCoroutinesApi::class)
class AirplaneModeRepositoryImpl
@Inject
constructor(
@Background private val bgHandler: Handler,
private val globalSettings: GlobalSettings,
logger: ConnectivityPipelineLogger,
@Application scope: CoroutineScope,
) : AirplaneModeRepository {
// TODO(b/254848912): Replace this with a generic SettingObserver coroutine once we have it.
override val isAirplaneMode: StateFlow<Boolean> =
conflatedCallbackFlow {
val observer =
object :
SettingObserver(
globalSettings,
bgHandler,
Global.AIRPLANE_MODE_ON,
UserHandle.USER_ALL
) {
override fun handleValueChanged(value: Int, observedChange: Boolean) {
trySend(value == 1)
}
}
observer.isListening = true
trySend(observer.value == 1)
awaitClose { observer.isListening = false }
}
.distinctUntilChanged()
.logInputChange(logger, "isAirplaneMode")
.stateIn(
scope,
started = SharingStarted.WhileSubscribed(),
// When the observer starts listening, the flow will emit the current value so the
// initialValue here is irrelevant.
initialValue = false,
)
}

View File

@@ -0,0 +1,46 @@
/*
* Copyright (C) 2022 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.statusbar.pipeline.airplane.domain.interactor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.statusbar.pipeline.airplane.data.repository.AirplaneModeRepository
import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlot
import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
/**
* The business logic layer for airplane mode.
*
* IMPORTANT: This is currently *not* used to render any airplane mode information anywhere. See
* [AirplaneModeRepository] for more details.
*/
@SysUISingleton
class AirplaneModeInteractor
@Inject
constructor(
airplaneModeRepository: AirplaneModeRepository,
connectivityRepository: ConnectivityRepository,
) {
/** True if the device is currently in airplane mode. */
val isAirplaneMode: Flow<Boolean> = airplaneModeRepository.isAirplaneMode
/** True if we're configured to force-hide the airplane mode icon and false otherwise. */
val isForceHidden: Flow<Boolean> =
connectivityRepository.forceHiddenSlots.map { it.contains(ConnectivitySlot.AIRPLANE) }
}

View File

@@ -0,0 +1,57 @@
/*
* Copyright (C) 2022 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.statusbar.pipeline.airplane.ui.viewmodel
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logOutputChange
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.stateIn
/**
* Models the UI state for the status bar airplane mode icon.
*
* IMPORTANT: This is currently *not* used to render any airplane mode information anywhere. See
* [com.android.systemui.statusbar.pipeline.airplane.data.repository.AirplaneModeRepository] for
* more details.
*/
@SysUISingleton
class AirplaneModeViewModel
@Inject
constructor(
interactor: AirplaneModeInteractor,
logger: ConnectivityPipelineLogger,
@Application private val scope: CoroutineScope,
) {
/** True if the airplane mode icon is currently visible in the status bar. */
val isAirplaneModeIconVisible: StateFlow<Boolean> =
combine(interactor.isAirplaneMode, interactor.isForceHidden) {
isAirplaneMode,
isAirplaneIconForceHidden ->
isAirplaneMode && !isAirplaneIconForceHidden
}
.distinctUntilChanged()
.logOutputChange(logger, "isAirplaneModeIconVisible")
.stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = false)
}

View File

@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.pipeline.dagger
import com.android.systemui.statusbar.pipeline.airplane.data.repository.AirplaneModeRepository
import com.android.systemui.statusbar.pipeline.airplane.data.repository.AirplaneModeRepositoryImpl
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileSubscriptionRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileSubscriptionRepositoryImpl
import com.android.systemui.statusbar.pipeline.mobile.data.repository.UserSetupRepository
@@ -29,6 +31,9 @@ import dagger.Module
@Module
abstract class StatusBarPipelineModule {
@Binds
abstract fun airplaneModeRepository(impl: AirplaneModeRepositoryImpl): AirplaneModeRepository
@Binds
abstract fun connectivityRepository(impl: ConnectivityRepositoryImpl): ConnectivityRepository

View File

@@ -91,6 +91,7 @@ object WifiViewBinder {
val activityInView = view.requireViewById<ImageView>(R.id.wifi_in)
val activityOutView = view.requireViewById<ImageView>(R.id.wifi_out)
val activityContainerView = view.requireViewById<View>(R.id.inout_container)
val airplaneSpacer = view.requireViewById<View>(R.id.wifi_airplane_spacer)
view.isVisible = true
iconView.isVisible = true
@@ -142,6 +143,12 @@ object WifiViewBinder {
activityContainerView.isVisible = visible
}
}
launch {
viewModel.isAirplaneSpacerVisible.distinctUntilChanged().collect { visible ->
airplaneSpacer.isVisible = visible
}
}
}
}

View File

@@ -32,6 +32,7 @@ class HomeWifiViewModel(
isActivityInViewVisible: Flow<Boolean>,
isActivityOutViewVisible: Flow<Boolean>,
isActivityContainerVisible: Flow<Boolean>,
isAirplaneSpacerVisible: Flow<Boolean>,
) :
LocationBasedWifiViewModel(
statusBarPipelineFlags,
@@ -40,4 +41,5 @@ class HomeWifiViewModel(
isActivityInViewVisible,
isActivityOutViewVisible,
isActivityContainerVisible,
isAirplaneSpacerVisible,
)

View File

@@ -29,6 +29,7 @@ class KeyguardWifiViewModel(
isActivityInViewVisible: Flow<Boolean>,
isActivityOutViewVisible: Flow<Boolean>,
isActivityContainerVisible: Flow<Boolean>,
isAirplaneSpacerVisible: Flow<Boolean>,
) :
LocationBasedWifiViewModel(
statusBarPipelineFlags,
@@ -37,4 +38,5 @@ class KeyguardWifiViewModel(
isActivityInViewVisible,
isActivityOutViewVisible,
isActivityContainerVisible,
isAirplaneSpacerVisible,
)

View File

@@ -44,6 +44,9 @@ abstract class LocationBasedWifiViewModel(
/** True if the activity container view should be visible. */
val isActivityContainerVisible: Flow<Boolean>,
/** True if the airplane spacer view should be visible. */
val isAirplaneSpacerVisible: Flow<Boolean>,
) {
/** The color that should be used to tint the icon. */
val tint: Flow<Int> =

View File

@@ -29,6 +29,7 @@ class QsWifiViewModel(
isActivityInViewVisible: Flow<Boolean>,
isActivityOutViewVisible: Flow<Boolean>,
isActivityContainerVisible: Flow<Boolean>,
isAirplaneSpacerVisible: Flow<Boolean>,
) :
LocationBasedWifiViewModel(
statusBarPipelineFlags,
@@ -37,4 +38,5 @@ class QsWifiViewModel(
isActivityInViewVisible,
isActivityOutViewVisible,
isActivityContainerVisible,
isAirplaneSpacerVisible,
)

View File

@@ -31,6 +31,7 @@ import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_FULL_ICONS
import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_NO_INTERNET_ICONS
import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_NO_NETWORK
import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
import com.android.systemui.statusbar.pipeline.airplane.ui.viewmodel.AirplaneModeViewModel
import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logOutputChange
@@ -66,6 +67,7 @@ import kotlinx.coroutines.flow.stateIn
class WifiViewModel
@Inject
constructor(
airplaneModeViewModel: AirplaneModeViewModel,
connectivityConstants: ConnectivityConstants,
private val context: Context,
logger: ConnectivityPipelineLogger,
@@ -177,6 +179,12 @@ constructor(
}
.stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = false)
// TODO(b/238425913): It isn't ideal for the wifi icon to need to know about whether the
// airplane icon is visible. Instead, we should have a parent StatusBarSystemIconsViewModel
// that appropriately knows about both icons and sets the padding appropriately.
private val isAirplaneSpacerVisible: Flow<Boolean> =
airplaneModeViewModel.isAirplaneModeIconVisible
/** A view model for the status bar on the home screen. */
val home: HomeWifiViewModel =
HomeWifiViewModel(
@@ -185,6 +193,7 @@ constructor(
isActivityInViewVisible,
isActivityOutViewVisible,
isActivityContainerVisible,
isAirplaneSpacerVisible,
)
/** A view model for the status bar on keyguard. */
@@ -195,6 +204,7 @@ constructor(
isActivityInViewVisible,
isActivityOutViewVisible,
isActivityContainerVisible,
isAirplaneSpacerVisible,
)
/** A view model for the status bar in quick settings. */
@@ -205,6 +215,7 @@ constructor(
isActivityInViewVisible,
isActivityOutViewVisible,
isActivityContainerVisible,
isAirplaneSpacerVisible,
)
companion object {

View File

@@ -0,0 +1,116 @@
/*
* Copyright (C) 2022 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.statusbar.pipeline.airplane.data.repository
import android.os.Handler
import android.os.Looper
import android.os.UserHandle
import android.provider.Settings.Global
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
import com.android.systemui.util.settings.FakeSettings
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.yield
import org.junit.After
import org.junit.Before
import org.junit.Test
import org.mockito.Mock
import org.mockito.MockitoAnnotations
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
class AirplaneModeRepositoryImplTest : SysuiTestCase() {
private lateinit var underTest: AirplaneModeRepositoryImpl
@Mock private lateinit var logger: ConnectivityPipelineLogger
private lateinit var bgHandler: Handler
private lateinit var scope: CoroutineScope
private lateinit var settings: FakeSettings
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
bgHandler = Handler(Looper.getMainLooper())
scope = CoroutineScope(IMMEDIATE)
settings = FakeSettings()
settings.userId = UserHandle.USER_ALL
underTest =
AirplaneModeRepositoryImpl(
bgHandler,
settings,
logger,
scope,
)
}
@After
fun tearDown() {
scope.cancel()
}
@Test
fun isAirplaneMode_initiallyGetsSettingsValue() =
runBlocking(IMMEDIATE) {
settings.putInt(Global.AIRPLANE_MODE_ON, 1)
underTest =
AirplaneModeRepositoryImpl(
bgHandler,
settings,
logger,
scope,
)
val job = underTest.isAirplaneMode.launchIn(this)
assertThat(underTest.isAirplaneMode.value).isTrue()
job.cancel()
}
@Test
fun isAirplaneMode_settingUpdated_valueUpdated() =
runBlocking(IMMEDIATE) {
val job = underTest.isAirplaneMode.launchIn(this)
settings.putInt(Global.AIRPLANE_MODE_ON, 0)
yield()
assertThat(underTest.isAirplaneMode.value).isFalse()
settings.putInt(Global.AIRPLANE_MODE_ON, 1)
yield()
assertThat(underTest.isAirplaneMode.value).isTrue()
settings.putInt(Global.AIRPLANE_MODE_ON, 0)
yield()
assertThat(underTest.isAirplaneMode.value).isFalse()
job.cancel()
}
}
private val IMMEDIATE = Dispatchers.Main.immediate

View File

@@ -0,0 +1,29 @@
/*
* Copyright (C) 2022 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.statusbar.pipeline.airplane.data.repository
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
class FakeAirplaneModeRepository : AirplaneModeRepository {
private val _isAirplaneMode = MutableStateFlow(false)
override val isAirplaneMode: StateFlow<Boolean> = _isAirplaneMode
fun setIsAirplaneMode(isAirplaneMode: Boolean) {
_isAirplaneMode.value = isAirplaneMode
}
}

View File

@@ -0,0 +1,99 @@
/*
* Copyright (C) 2022 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.statusbar.pipeline.airplane.domain.interactor
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlot
import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.yield
import org.junit.Before
import org.junit.Test
@SmallTest
@OptIn(ExperimentalCoroutinesApi::class)
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
class AirplaneModeInteractorTest : SysuiTestCase() {
private lateinit var underTest: AirplaneModeInteractor
private lateinit var airplaneModeRepository: FakeAirplaneModeRepository
private lateinit var connectivityRepository: FakeConnectivityRepository
@Before
fun setUp() {
airplaneModeRepository = FakeAirplaneModeRepository()
connectivityRepository = FakeConnectivityRepository()
underTest = AirplaneModeInteractor(airplaneModeRepository, connectivityRepository)
}
@Test
fun isAirplaneMode_matchesRepo() =
runBlocking(IMMEDIATE) {
var latest: Boolean? = null
val job = underTest.isAirplaneMode.onEach { latest = it }.launchIn(this)
airplaneModeRepository.setIsAirplaneMode(true)
yield()
assertThat(latest).isTrue()
airplaneModeRepository.setIsAirplaneMode(false)
yield()
assertThat(latest).isFalse()
airplaneModeRepository.setIsAirplaneMode(true)
yield()
assertThat(latest).isTrue()
job.cancel()
}
@Test
fun isForceHidden_repoHasWifiHidden_outputsTrue() =
runBlocking(IMMEDIATE) {
connectivityRepository.setForceHiddenIcons(setOf(ConnectivitySlot.AIRPLANE))
var latest: Boolean? = null
val job = underTest.isForceHidden.onEach { latest = it }.launchIn(this)
assertThat(latest).isTrue()
job.cancel()
}
@Test
fun isForceHidden_repoDoesNotHaveWifiHidden_outputsFalse() =
runBlocking(IMMEDIATE) {
connectivityRepository.setForceHiddenIcons(setOf())
var latest: Boolean? = null
val job = underTest.isForceHidden.onEach { latest = it }.launchIn(this)
assertThat(latest).isFalse()
job.cancel()
}
}
private val IMMEDIATE = Dispatchers.Main.immediate

View File

@@ -0,0 +1,110 @@
/*
* Copyright (C) 2022 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.statusbar.pipeline.airplane.ui.viewmodel
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlot
import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.runBlocking
import org.junit.Before
import org.junit.Test
import org.mockito.Mock
import org.mockito.MockitoAnnotations
@SmallTest
@OptIn(ExperimentalCoroutinesApi::class)
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
class AirplaneModeViewModelTest : SysuiTestCase() {
private lateinit var underTest: AirplaneModeViewModel
@Mock private lateinit var logger: ConnectivityPipelineLogger
private lateinit var airplaneModeRepository: FakeAirplaneModeRepository
private lateinit var connectivityRepository: FakeConnectivityRepository
private lateinit var interactor: AirplaneModeInteractor
private lateinit var scope: CoroutineScope
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
airplaneModeRepository = FakeAirplaneModeRepository()
connectivityRepository = FakeConnectivityRepository()
interactor = AirplaneModeInteractor(airplaneModeRepository, connectivityRepository)
scope = CoroutineScope(IMMEDIATE)
underTest =
AirplaneModeViewModel(
interactor,
logger,
scope,
)
}
@Test
fun isAirplaneModeIconVisible_notAirplaneMode_outputsFalse() =
runBlocking(IMMEDIATE) {
connectivityRepository.setForceHiddenIcons(setOf())
airplaneModeRepository.setIsAirplaneMode(false)
var latest: Boolean? = null
val job = underTest.isAirplaneModeIconVisible.onEach { latest = it }.launchIn(this)
assertThat(latest).isFalse()
job.cancel()
}
@Test
fun isAirplaneModeIconVisible_forceHidden_outputsFalse() =
runBlocking(IMMEDIATE) {
connectivityRepository.setForceHiddenIcons(setOf(ConnectivitySlot.AIRPLANE))
airplaneModeRepository.setIsAirplaneMode(true)
var latest: Boolean? = null
val job = underTest.isAirplaneModeIconVisible.onEach { latest = it }.launchIn(this)
assertThat(latest).isFalse()
job.cancel()
}
@Test
fun isAirplaneModeIconVisible_isAirplaneModeAndNotForceHidden_outputsTrue() =
runBlocking(IMMEDIATE) {
connectivityRepository.setForceHiddenIcons(setOf())
airplaneModeRepository.setIsAirplaneMode(true)
var latest: Boolean? = null
val job = underTest.isAirplaneModeIconVisible.onEach { latest = it }.launchIn(this)
assertThat(latest).isTrue()
job.cancel()
}
}
private val IMMEDIATE = Dispatchers.Main.immediate

View File

@@ -30,6 +30,9 @@ import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN
import com.android.systemui.statusbar.StatusBarIconView.STATE_ICON
import com.android.systemui.statusbar.phone.StatusBarLocation
import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
import com.android.systemui.statusbar.pipeline.airplane.ui.viewmodel.AirplaneModeViewModel
import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
@@ -63,11 +66,13 @@ class ModernStatusBarWifiViewTest : SysuiTestCase() {
private lateinit var connectivityConstants: ConnectivityConstants
@Mock
private lateinit var wifiConstants: WifiConstants
private lateinit var airplaneModeRepository: FakeAirplaneModeRepository
private lateinit var connectivityRepository: FakeConnectivityRepository
private lateinit var wifiRepository: FakeWifiRepository
private lateinit var interactor: WifiInteractor
private lateinit var viewModel: WifiViewModel
private lateinit var scope: CoroutineScope
private lateinit var airplaneModeViewModel: AirplaneModeViewModel
@JvmField @Rule
val instantTaskExecutor = InstantTaskExecutorRule()
@@ -77,12 +82,22 @@ class ModernStatusBarWifiViewTest : SysuiTestCase() {
MockitoAnnotations.initMocks(this)
testableLooper = TestableLooper.get(this)
airplaneModeRepository = FakeAirplaneModeRepository()
connectivityRepository = FakeConnectivityRepository()
wifiRepository = FakeWifiRepository()
wifiRepository.setIsWifiEnabled(true)
interactor = WifiInteractor(connectivityRepository, wifiRepository)
scope = CoroutineScope(Dispatchers.Unconfined)
airplaneModeViewModel = AirplaneModeViewModel(
AirplaneModeInteractor(
airplaneModeRepository,
connectivityRepository,
),
logger,
scope,
)
viewModel = WifiViewModel(
airplaneModeViewModel,
connectivityConstants,
context,
logger,

View File

@@ -27,6 +27,9 @@ import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_FULL_ICONS
import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_NO_INTERNET_ICONS
import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_NO_NETWORK
import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
import com.android.systemui.statusbar.pipeline.airplane.ui.viewmodel.AirplaneModeViewModel
import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlot
@@ -64,19 +67,31 @@ internal class WifiViewModelIconParameterizedTest(private val testCase: TestCase
@Mock private lateinit var logger: ConnectivityPipelineLogger
@Mock private lateinit var connectivityConstants: ConnectivityConstants
@Mock private lateinit var wifiConstants: WifiConstants
private lateinit var airplaneModeRepository: FakeAirplaneModeRepository
private lateinit var connectivityRepository: FakeConnectivityRepository
private lateinit var wifiRepository: FakeWifiRepository
private lateinit var interactor: WifiInteractor
private lateinit var airplaneModeViewModel: AirplaneModeViewModel
private lateinit var scope: CoroutineScope
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
airplaneModeRepository = FakeAirplaneModeRepository()
connectivityRepository = FakeConnectivityRepository()
wifiRepository = FakeWifiRepository()
wifiRepository.setIsWifiEnabled(true)
interactor = WifiInteractor(connectivityRepository, wifiRepository)
scope = CoroutineScope(IMMEDIATE)
airplaneModeViewModel =
AirplaneModeViewModel(
AirplaneModeInteractor(
airplaneModeRepository,
connectivityRepository,
),
logger,
scope,
)
}
@After
@@ -102,6 +117,7 @@ internal class WifiViewModelIconParameterizedTest(private val testCase: TestCase
.thenReturn(testCase.hasDataCapabilities)
underTest =
WifiViewModel(
airplaneModeViewModel,
connectivityConstants,
context,
logger,

View File

@@ -20,8 +20,12 @@ import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
import com.android.systemui.statusbar.pipeline.airplane.ui.viewmodel.AirplaneModeViewModel
import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlot
import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository
@@ -55,19 +59,31 @@ class WifiViewModelTest : SysuiTestCase() {
@Mock private lateinit var logger: ConnectivityPipelineLogger
@Mock private lateinit var connectivityConstants: ConnectivityConstants
@Mock private lateinit var wifiConstants: WifiConstants
private lateinit var airplaneModeRepository: FakeAirplaneModeRepository
private lateinit var connectivityRepository: FakeConnectivityRepository
private lateinit var wifiRepository: FakeWifiRepository
private lateinit var interactor: WifiInteractor
private lateinit var airplaneModeViewModel: AirplaneModeViewModel
private lateinit var scope: CoroutineScope
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
airplaneModeRepository = FakeAirplaneModeRepository()
connectivityRepository = FakeConnectivityRepository()
wifiRepository = FakeWifiRepository()
wifiRepository.setIsWifiEnabled(true)
interactor = WifiInteractor(connectivityRepository, wifiRepository)
scope = CoroutineScope(IMMEDIATE)
airplaneModeViewModel = AirplaneModeViewModel(
AirplaneModeInteractor(
airplaneModeRepository,
connectivityRepository,
),
logger,
scope,
)
createAndSetViewModel()
}
@@ -462,11 +478,64 @@ class WifiViewModelTest : SysuiTestCase() {
job.cancel()
}
@Test
fun airplaneSpacer_notAirplaneMode_outputsFalse() = runBlocking(IMMEDIATE) {
var latest: Boolean? = null
val job = underTest
.qs
.isAirplaneSpacerVisible
.onEach { latest = it }
.launchIn(this)
airplaneModeRepository.setIsAirplaneMode(false)
yield()
assertThat(latest).isFalse()
job.cancel()
}
@Test
fun airplaneSpacer_airplaneForceHidden_outputsFalse() = runBlocking(IMMEDIATE) {
var latest: Boolean? = null
val job = underTest
.qs
.isAirplaneSpacerVisible
.onEach { latest = it }
.launchIn(this)
airplaneModeRepository.setIsAirplaneMode(true)
connectivityRepository.setForceHiddenIcons(setOf(ConnectivitySlot.AIRPLANE))
yield()
assertThat(latest).isFalse()
job.cancel()
}
@Test
fun airplaneSpacer_airplaneIconVisible_outputsTrue() = runBlocking(IMMEDIATE) {
var latest: Boolean? = null
val job = underTest
.qs
.isAirplaneSpacerVisible
.onEach { latest = it }
.launchIn(this)
airplaneModeRepository.setIsAirplaneMode(true)
yield()
assertThat(latest).isTrue()
job.cancel()
}
private fun createAndSetViewModel() {
// [WifiViewModel] creates its flows as soon as it's instantiated, and some of those flow
// creations rely on certain config values that we mock out in individual tests. This method
// allows tests to create the view model only after those configs are correctly set up.
underTest = WifiViewModel(
airplaneModeViewModel,
connectivityConstants,
context,
logger,