[SB Refactor] Create an airplane mode repo/interactor/view model and use
it to determine when to show the wifi<->airplane spacer. Bug: 238425913 Test: manual: Verify appropriate amount of space appears between wifi icon and airplane mode icon Test: statusbar.pipeline tests Change-Id: Ie8fa9cca65919fd34eb02516449f14b38d465a4a
This commit is contained in:
@@ -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,
|
||||
)
|
||||
}
|
||||
@@ -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) }
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
@@ -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> =
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user