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
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/AirplaneModeRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/AirplaneModeRepository.kt
new file mode 100644
index 0000000..7aa5ee1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/AirplaneModeRepository.kt
@@ -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,
+ )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/airplane/domain/interactor/AirplaneModeInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/airplane/domain/interactor/AirplaneModeInteractor.kt
new file mode 100644
index 0000000..3e9b2c2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/airplane/domain/interactor/AirplaneModeInteractor.kt
@@ -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) }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/airplane/ui/viewmodel/AirplaneModeViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/airplane/ui/viewmodel/AirplaneModeViewModel.kt
new file mode 100644
index 0000000..fe30c01
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/airplane/ui/viewmodel/AirplaneModeViewModel.kt
@@ -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)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
index 06d5542..2aaa085 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
@@ -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
@@ -30,6 +32,9 @@
@Module
abstract class StatusBarPipelineModule {
@Binds
+ abstract fun airplaneModeRepository(impl: AirplaneModeRepositoryImpl): AirplaneModeRepository
+
+ @Binds
abstract fun connectivityRepository(impl: ConnectivityRepositoryImpl): ConnectivityRepository
@Binds
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt
index 273be63..25537b9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt
@@ -91,6 +91,7 @@
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 @@
activityContainerView.isVisible = visible
}
}
+
+ launch {
+ viewModel.isAirplaneSpacerVisible.distinctUntilChanged().collect { visible ->
+ airplaneSpacer.isVisible = visible
+ }
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/HomeWifiViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/HomeWifiViewModel.kt
index 40f948f..95ab251 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/HomeWifiViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/HomeWifiViewModel.kt
@@ -32,6 +32,7 @@
isActivityInViewVisible: Flow<Boolean>,
isActivityOutViewVisible: Flow<Boolean>,
isActivityContainerVisible: Flow<Boolean>,
+ isAirplaneSpacerVisible: Flow<Boolean>,
) :
LocationBasedWifiViewModel(
statusBarPipelineFlags,
@@ -40,4 +41,5 @@
isActivityInViewVisible,
isActivityOutViewVisible,
isActivityContainerVisible,
+ isAirplaneSpacerVisible,
)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/KeyguardWifiViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/KeyguardWifiViewModel.kt
index 9642ac4..86535d6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/KeyguardWifiViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/KeyguardWifiViewModel.kt
@@ -29,6 +29,7 @@
isActivityInViewVisible: Flow<Boolean>,
isActivityOutViewVisible: Flow<Boolean>,
isActivityContainerVisible: Flow<Boolean>,
+ isAirplaneSpacerVisible: Flow<Boolean>,
) :
LocationBasedWifiViewModel(
statusBarPipelineFlags,
@@ -37,4 +38,5 @@
isActivityInViewVisible,
isActivityOutViewVisible,
isActivityContainerVisible,
+ isAirplaneSpacerVisible,
)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/LocationBasedWifiViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/LocationBasedWifiViewModel.kt
index cc6a375..7cbdf5d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/LocationBasedWifiViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/LocationBasedWifiViewModel.kt
@@ -44,6 +44,9 @@
/** 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> =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/QsWifiViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/QsWifiViewModel.kt
index 0ddf90e..fd54c5f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/QsWifiViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/QsWifiViewModel.kt
@@ -29,6 +29,7 @@
isActivityInViewVisible: Flow<Boolean>,
isActivityOutViewVisible: Flow<Boolean>,
isActivityContainerVisible: Flow<Boolean>,
+ isAirplaneSpacerVisible: Flow<Boolean>,
) :
LocationBasedWifiViewModel(
statusBarPipelineFlags,
@@ -37,4 +38,5 @@
isActivityInViewVisible,
isActivityOutViewVisible,
isActivityContainerVisible,
+ isAirplaneSpacerVisible,
)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt
index 160c577..89b96b7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt
@@ -31,6 +31,7 @@
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 @@
class WifiViewModel
@Inject
constructor(
+ airplaneModeViewModel: AirplaneModeViewModel,
connectivityConstants: ConnectivityConstants,
private val context: Context,
logger: ConnectivityPipelineLogger,
@@ -177,6 +179,12 @@
}
.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 @@
isActivityInViewVisible,
isActivityOutViewVisible,
isActivityContainerVisible,
+ isAirplaneSpacerVisible,
)
/** A view model for the status bar on keyguard. */
@@ -195,6 +204,7 @@
isActivityInViewVisible,
isActivityOutViewVisible,
isActivityContainerVisible,
+ isAirplaneSpacerVisible,
)
/** A view model for the status bar in quick settings. */
@@ -205,6 +215,7 @@
isActivityInViewVisible,
isActivityOutViewVisible,
isActivityContainerVisible,
+ isAirplaneSpacerVisible,
)
companion object {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/AirplaneModeRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/AirplaneModeRepositoryImplTest.kt
new file mode 100644
index 0000000..b7a6c01
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/AirplaneModeRepositoryImplTest.kt
@@ -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
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/FakeAirplaneModeRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/FakeAirplaneModeRepository.kt
new file mode 100644
index 0000000..63bbdfc
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/FakeAirplaneModeRepository.kt
@@ -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
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/domain/interactor/AirplaneModeInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/domain/interactor/AirplaneModeInteractorTest.kt
new file mode 100644
index 0000000..33a80e1
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/domain/interactor/AirplaneModeInteractorTest.kt
@@ -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
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/ui/viewmodel/AirplaneModeViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/ui/viewmodel/AirplaneModeViewModelTest.kt
new file mode 100644
index 0000000..76016a1
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/ui/viewmodel/AirplaneModeViewModelTest.kt
@@ -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
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt
index 4efb135..c584109 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt
@@ -30,6 +30,9 @@
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 @@
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 @@
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,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt
index 7686071..a1afcd7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt
@@ -27,6 +27,9 @@
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 @@
@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 @@
.thenReturn(testCase.hasDataCapabilities)
underTest =
WifiViewModel(
+ airplaneModeViewModel,
connectivityConstants,
context,
logger,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
index 79633d4..7d2c560 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
@@ -20,8 +20,12 @@
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 @@
@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 @@
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,