diff options
9 files changed, 1313 insertions, 0 deletions
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapperTest.kt new file mode 100644 index 000000000000..39755bf8d764 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapperTest.kt @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2024 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.qs.tiles.impl.internet.domain + +import android.graphics.drawable.TestStubDrawable +import android.widget.Switch +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.common.shared.model.ContentDescription +import com.android.systemui.common.shared.model.Icon +import com.android.systemui.common.shared.model.Text +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.qs.tiles.impl.custom.QSTileStateSubject +import com.android.systemui.qs.tiles.impl.internet.domain.model.InternetTileModel +import com.android.systemui.qs.tiles.impl.internet.qsInternetTileConfig +import com.android.systemui.qs.tiles.viewmodel.QSTileState +import com.android.systemui.res.R +import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_FULL_ICONS +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class InternetTileMapperTest : SysuiTestCase() { + private val kosmos = Kosmos() + private val internetTileConfig = kosmos.qsInternetTileConfig + private val mapper by lazy { + InternetTileMapper( + context.orCreateTestableResources + .apply { + addOverride(R.drawable.ic_qs_no_internet_unavailable, TestStubDrawable()) + addOverride(wifiRes, TestStubDrawable()) + } + .resources, + context.theme, + context + ) + } + + @Test + fun withActiveModel_mappedStateMatchesDataModel() { + val inputModel = + InternetTileModel.Active( + secondaryLabel = Text.Resource(R.string.quick_settings_networks_available), + iconId = wifiRes, + stateDescription = null, + contentDescription = + ContentDescription.Resource(R.string.quick_settings_internet_label), + ) + + val outputState = mapper.map(internetTileConfig, inputModel) + + val expectedState = + createInternetTileState( + QSTileState.ActivationState.ACTIVE, + context.getString(R.string.quick_settings_networks_available), + Icon.Loaded(context.getDrawable(wifiRes)!!, contentDescription = null), + context.getString(R.string.quick_settings_internet_label) + ) + QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState) + } + + @Test + fun withInactiveModel_mappedStateMatchesDataModel() { + val inputModel = + InternetTileModel.Inactive( + secondaryLabel = Text.Resource(R.string.quick_settings_networks_unavailable), + iconId = R.drawable.ic_qs_no_internet_unavailable, + stateDescription = null, + contentDescription = + ContentDescription.Resource(R.string.quick_settings_networks_unavailable), + ) + + val outputState = mapper.map(internetTileConfig, inputModel) + + val expectedState = + createInternetTileState( + QSTileState.ActivationState.INACTIVE, + context.getString(R.string.quick_settings_networks_unavailable), + Icon.Loaded( + context.getDrawable(R.drawable.ic_qs_no_internet_unavailable)!!, + contentDescription = null + ), + context.getString(R.string.quick_settings_networks_unavailable) + ) + QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState) + } + + private fun createInternetTileState( + activationState: QSTileState.ActivationState, + secondaryLabel: String, + icon: Icon, + contentDescription: String, + ): QSTileState { + val label = context.getString(R.string.quick_settings_internet_label) + return QSTileState( + { icon }, + label, + activationState, + secondaryLabel, + setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK), + contentDescription, + null, + QSTileState.SideViewIcon.Chevron, + QSTileState.EnabledState.ENABLED, + Switch::class.qualifiedName + ) + } + + private companion object { + val wifiRes = WIFI_FULL_ICONS[4] + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileDataInteractorTest.kt new file mode 100644 index 000000000000..37ef6ad17a64 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileDataInteractorTest.kt @@ -0,0 +1,548 @@ +/* + * Copyright (C) 2024 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.qs.tiles.impl.internet.domain.interactor + +import android.graphics.drawable.TestStubDrawable +import android.os.UserHandle +import android.testing.TestableLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.settingslib.AccessibilityContentDescriptions +import com.android.systemui.SysuiTestCase +import com.android.systemui.common.shared.model.ContentDescription +import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription +import com.android.systemui.common.shared.model.Icon +import com.android.systemui.common.shared.model.Text +import com.android.systemui.common.shared.model.Text.Companion.loadText +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.flags.FakeFeatureFlagsClassic +import com.android.systemui.flags.Flags +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.testScope +import com.android.systemui.log.table.TableLogBuffer +import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger +import com.android.systemui.qs.tiles.impl.internet.domain.model.InternetTileModel +import com.android.systemui.res.R +import com.android.systemui.statusbar.connectivity.WifiIcons +import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository +import com.android.systemui.statusbar.pipeline.ethernet.domain.EthernetInteractor +import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState +import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel +import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionRepository +import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository +import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor +import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractorImpl +import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy +import com.android.systemui.statusbar.pipeline.shared.data.model.DefaultConnectionModel +import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository +import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository +import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractorImpl +import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel +import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiScanEntry +import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon +import com.android.systemui.statusbar.policy.data.repository.FakeUserSetupRepository +import com.android.systemui.util.CarrierConfigTracker +import com.android.systemui.util.mockito.mock +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@TestableLooper.RunWithLooper +@SmallTest +@RunWith(AndroidJUnit4::class) +class InternetTileDataInteractorTest : SysuiTestCase() { + private val testUser = UserHandle.of(1) + private val kosmos = Kosmos() + private val testScope = kosmos.testScope + + private lateinit var underTest: InternetTileDataInteractor + private lateinit var mobileIconsInteractor: MobileIconsInteractor + + private val airplaneModeRepository = FakeAirplaneModeRepository() + private val connectivityRepository = FakeConnectivityRepository() + private val ethernetInteractor = EthernetInteractor(connectivityRepository) + private val wifiRepository = FakeWifiRepository() + private val userSetupRepo = FakeUserSetupRepository() + private val wifiInteractor = + WifiInteractorImpl(connectivityRepository, wifiRepository, testScope.backgroundScope) + + private val tableLogBuffer: TableLogBuffer = mock() + private val carrierConfigTracker: CarrierConfigTracker = mock() + + private val mobileConnectionsRepository = + FakeMobileConnectionsRepository(FakeMobileMappingsProxy(), tableLogBuffer) + private val mobileConnectionRepository = + FakeMobileConnectionRepository(SUB_1_ID, tableLogBuffer) + + private val flags = + FakeFeatureFlagsClassic().also { + it.set(Flags.FILTER_PROVISIONING_NETWORK_SUBSCRIPTIONS, true) + } + + private val internet = context.getString(R.string.quick_settings_internet_label) + + @Before + fun setUp() { + mobileConnectionRepository.apply { + setNetworkTypeKey(mobileConnectionsRepository.GSM_KEY) + isInService.value = true + dataConnectionState.value = DataConnectionState.Connected + dataEnabled.value = true + } + + mobileConnectionsRepository.apply { + activeMobileDataRepository.value = mobileConnectionRepository + activeMobileDataSubscriptionId.value = SUB_1_ID + setMobileConnectionRepositoryMap(mapOf(SUB_1_ID to mobileConnectionRepository)) + } + + mobileIconsInteractor = + MobileIconsInteractorImpl( + mobileConnectionsRepository, + carrierConfigTracker, + tableLogBuffer, + connectivityRepository, + userSetupRepo, + testScope.backgroundScope, + context, + flags, + ) + + context.orCreateTestableResources.apply { + addOverride(com.android.internal.R.drawable.ic_signal_cellular, TestStubDrawable()) + addOverride( + com.android.settingslib.R.drawable.ic_no_internet_wifi_signal_0, + TestStubDrawable() + ) + addOverride( + com.android.settingslib.R.drawable.ic_no_internet_wifi_signal_1, + TestStubDrawable() + ) + addOverride( + com.android.settingslib.R.drawable.ic_no_internet_wifi_signal_2, + TestStubDrawable() + ) + addOverride( + com.android.settingslib.R.drawable.ic_no_internet_wifi_signal_3, + TestStubDrawable() + ) + addOverride( + com.android.settingslib.R.drawable.ic_no_internet_wifi_signal_4, + TestStubDrawable() + ) + addOverride(com.android.settingslib.R.drawable.ic_hotspot_phone, TestStubDrawable()) + addOverride(com.android.settingslib.R.drawable.ic_hotspot_laptop, TestStubDrawable()) + addOverride(com.android.settingslib.R.drawable.ic_hotspot_tablet, TestStubDrawable()) + addOverride(com.android.settingslib.R.drawable.ic_hotspot_watch, TestStubDrawable()) + addOverride(com.android.settingslib.R.drawable.ic_hotspot_auto, TestStubDrawable()) + } + + underTest = + InternetTileDataInteractor( + context, + testScope.backgroundScope, + airplaneModeRepository, + connectivityRepository, + ethernetInteractor, + mobileIconsInteractor, + wifiInteractor, + ) + } + + @Test + fun noDefault_noNetworksAvailable() = + testScope.runTest { + val latest by + collectLastValue( + underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest)) + ) + + connectivityRepository.defaultConnections.value = DefaultConnectionModel() + + assertThat(latest?.secondaryLabel) + .isEqualTo(Text.Resource(R.string.quick_settings_networks_unavailable)) + assertThat(latest?.iconId).isEqualTo(R.drawable.ic_qs_no_internet_unavailable) + } + + @Test + fun noDefault_networksAvailable() = + testScope.runTest { + // TODO(b/328419203): support [WifiInteractor.areNetworksAvailable] + } + + @Test + fun wifiDefaultAndActive() = + testScope.runTest { + val latest by + collectLastValue( + underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest)) + ) + + val networkModel = + WifiNetworkModel.Active( + networkId = 1, + level = 4, + ssid = "test ssid", + ) + val wifiIcon = + WifiIcon.fromModel(model = networkModel, context = context, showHotspotInfo = true) + as WifiIcon.Visible + + connectivityRepository.setWifiConnected() + wifiRepository.setIsWifiDefault(true) + wifiRepository.setWifiNetwork(networkModel) + + assertThat(latest?.secondaryTitle).isEqualTo("test ssid") + assertThat(latest?.secondaryLabel).isNull() + val expectedIcon = + Icon.Loaded(context.getDrawable(WifiIcons.WIFI_NO_INTERNET_ICONS[4])!!, null) + + val actualIcon = latest?.icon + assertThat(actualIcon).isEqualTo(expectedIcon) + assertThat(latest?.iconId).isNull() + assertThat(latest?.contentDescription.loadContentDescription(context)) + .isEqualTo("$internet,test ssid") + val expectedSd = wifiIcon.contentDescription + assertThat(latest?.stateDescription).isEqualTo(expectedSd) + } + + @Test + fun wifiDefaultAndActive_hotspotNone() = + testScope.runTest { + val latest by + collectLastValue( + underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest)) + ) + + val networkModel = + WifiNetworkModel.Active( + networkId = 1, + level = 4, + ssid = "test ssid", + hotspotDeviceType = WifiNetworkModel.HotspotDeviceType.NONE, + ) + + connectivityRepository.setWifiConnected() + wifiRepository.setIsWifiDefault(true) + wifiRepository.setWifiNetwork(networkModel) + + val expectedIcon = + Icon.Loaded(context.getDrawable(WifiIcons.WIFI_NO_INTERNET_ICONS[4])!!, null) + assertThat(latest?.icon).isEqualTo(expectedIcon) + assertThat(latest?.stateDescription.loadContentDescription(context)) + .doesNotContain( + context.getString(AccessibilityContentDescriptions.WIFI_OTHER_DEVICE_CONNECTION) + ) + } + + @Test + fun wifiDefaultAndActive_hotspotTablet() = + testScope.runTest { + val latest by + collectLastValue( + underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest)) + ) + + setWifiNetworkWithHotspot(WifiNetworkModel.HotspotDeviceType.TABLET) + + val expectedIcon = + Icon.Loaded( + context.getDrawable(com.android.settingslib.R.drawable.ic_hotspot_tablet)!!, + null + ) + assertThat(latest?.icon).isEqualTo(expectedIcon) + assertThat(latest?.stateDescription.loadContentDescription(context)) + .isEqualTo( + context.getString(AccessibilityContentDescriptions.WIFI_OTHER_DEVICE_CONNECTION) + ) + } + + @Test + fun wifiDefaultAndActive_hotspotLaptop() = + testScope.runTest { + val latest by + collectLastValue( + underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest)) + ) + + setWifiNetworkWithHotspot(WifiNetworkModel.HotspotDeviceType.LAPTOP) + + val expectedIcon = + Icon.Loaded( + context.getDrawable(com.android.settingslib.R.drawable.ic_hotspot_laptop)!!, + null + ) + assertThat(latest?.icon).isEqualTo(expectedIcon) + assertThat(latest?.stateDescription.loadContentDescription(context)) + .isEqualTo( + context.getString(AccessibilityContentDescriptions.WIFI_OTHER_DEVICE_CONNECTION) + ) + } + + @Test + fun wifiDefaultAndActive_hotspotWatch() = + testScope.runTest { + val latest by + collectLastValue( + underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest)) + ) + + setWifiNetworkWithHotspot(WifiNetworkModel.HotspotDeviceType.WATCH) + + val expectedIcon = + Icon.Loaded( + context.getDrawable(com.android.settingslib.R.drawable.ic_hotspot_watch)!!, + null + ) + assertThat(latest?.icon).isEqualTo(expectedIcon) + assertThat(latest?.stateDescription.loadContentDescription(context)) + .isEqualTo( + context.getString(AccessibilityContentDescriptions.WIFI_OTHER_DEVICE_CONNECTION) + ) + } + + @Test + fun wifiDefaultAndActive_hotspotAuto() = + testScope.runTest { + val latest by + collectLastValue( + underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest)) + ) + + setWifiNetworkWithHotspot(WifiNetworkModel.HotspotDeviceType.AUTO) + + val expectedIcon = + Icon.Loaded( + context.getDrawable(com.android.settingslib.R.drawable.ic_hotspot_auto)!!, + null + ) + assertThat(latest?.icon).isEqualTo(expectedIcon) + assertThat(latest?.stateDescription.loadContentDescription(context)) + .isEqualTo( + context.getString(AccessibilityContentDescriptions.WIFI_OTHER_DEVICE_CONNECTION) + ) + } + + @Test + fun wifiDefaultAndActive_hotspotPhone() = + testScope.runTest { + val latest by + collectLastValue( + underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest)) + ) + setWifiNetworkWithHotspot(WifiNetworkModel.HotspotDeviceType.PHONE) + + val expectedIcon = + Icon.Loaded( + context.getDrawable(com.android.settingslib.R.drawable.ic_hotspot_phone)!!, + null + ) + assertThat(latest?.icon).isEqualTo(expectedIcon) + assertThat(latest?.stateDescription.loadContentDescription(context)) + .isEqualTo( + context.getString(AccessibilityContentDescriptions.WIFI_OTHER_DEVICE_CONNECTION) + ) + } + + @Test + fun wifiDefaultAndActive_hotspotUnknown() = + testScope.runTest { + val latest by + collectLastValue( + underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest)) + ) + + setWifiNetworkWithHotspot(WifiNetworkModel.HotspotDeviceType.UNKNOWN) + + val expectedIcon = + Icon.Loaded( + context.getDrawable(com.android.settingslib.R.drawable.ic_hotspot_phone)!!, + null + ) + assertThat(latest?.icon).isEqualTo(expectedIcon) + assertThat(latest?.stateDescription.loadContentDescription(context)) + .isEqualTo( + context.getString(AccessibilityContentDescriptions.WIFI_OTHER_DEVICE_CONNECTION) + ) + } + + @Test + fun wifiDefaultAndActive_hotspotInvalid() = + testScope.runTest { + val latest by + collectLastValue( + underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest)) + ) + + setWifiNetworkWithHotspot(WifiNetworkModel.HotspotDeviceType.INVALID) + + val expectedIcon = + Icon.Loaded( + context.getDrawable(com.android.settingslib.R.drawable.ic_hotspot_phone)!!, + null + ) + assertThat(latest?.icon).isEqualTo(expectedIcon) + assertThat(latest?.stateDescription.loadContentDescription(context)) + .isEqualTo( + context.getString(AccessibilityContentDescriptions.WIFI_OTHER_DEVICE_CONNECTION) + ) + } + + @Test + fun wifiDefaultAndNotActive_noNetworksAvailable() = + testScope.runTest { + val latest by + collectLastValue( + underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest)) + ) + val networkModel = WifiNetworkModel.Inactive + + connectivityRepository.setWifiConnected(validated = false) + wifiRepository.setIsWifiDefault(true) + wifiRepository.setWifiNetwork(networkModel) + wifiRepository.wifiScanResults.value = emptyList() + + assertThat(latest).isEqualTo(NOT_CONNECTED_NETWORKS_UNAVAILABLE) + } + + @Test + fun wifiDefaultAndNotActive_networksAvailable() = + testScope.runTest { + val latest by + collectLastValue( + underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest)) + ) + + val networkModel = WifiNetworkModel.Inactive + + connectivityRepository.setWifiConnected(validated = false) + wifiRepository.setIsWifiDefault(true) + wifiRepository.setWifiNetwork(networkModel) + wifiRepository.wifiScanResults.value = listOf(WifiScanEntry("test 1")) + + assertThat(latest?.secondaryLabel).isNull() + assertThat(latest?.secondaryTitle) + .isEqualTo(context.getString(R.string.quick_settings_networks_available)) + assertThat(latest?.icon).isNull() + assertThat(latest?.iconId).isEqualTo(R.drawable.ic_qs_no_internet_available) + assertThat(latest?.stateDescription).isNull() + val expectedCd = + "$internet,${context.getString(R.string.quick_settings_networks_available)}" + assertThat(latest?.contentDescription.loadContentDescription(context)) + .isEqualTo(expectedCd) + } + + @Test + fun mobileDefault_usesNetworkNameAndIcon() = + testScope.runTest { + val latest by + collectLastValue( + underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest)) + ) + + connectivityRepository.setMobileConnected() + mobileConnectionsRepository.mobileIsDefault.value = true + mobileConnectionRepository.apply { + setAllLevels(3) + setAllRoaming(false) + networkName.value = NetworkNameModel.Default("test network") + } + + assertThat(latest).isNotNull() + assertThat(latest?.secondaryTitle).isNotNull() + assertThat(latest?.secondaryTitle.toString()).contains("test network") + assertThat(latest?.secondaryLabel).isNull() + assertThat(latest?.icon).isInstanceOf(Icon.Loaded::class.java) + assertThat(latest?.iconId).isNull() + assertThat(latest?.stateDescription.loadContentDescription(context)) + .isEqualTo(latest?.secondaryTitle.toString()) + assertThat(latest?.contentDescription.loadContentDescription(context)) + .isEqualTo(internet) + } + + @Test + fun ethernetDefault_validated_matchesInteractor() = + testScope.runTest { + val latest by + collectLastValue( + underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest)) + ) + val ethernetIcon by collectLastValue(ethernetInteractor.icon) + + connectivityRepository.setEthernetConnected(default = true, validated = true) + + assertThat(latest?.secondaryLabel.loadText(context)) + .isEqualTo(ethernetIcon!!.contentDescription.loadContentDescription(context)) + assertThat(latest?.secondaryTitle).isNull() + assertThat(latest?.iconId).isEqualTo(R.drawable.stat_sys_ethernet_fully) + assertThat(latest?.icon).isNull() + assertThat(latest?.stateDescription).isNull() + assertThat(latest?.contentDescription.loadContentDescription(context)) + .isEqualTo(latest?.secondaryLabel.loadText(context)) + } + + @Test + fun ethernetDefault_notValidated_matchesInteractor() = + testScope.runTest { + val latest by + collectLastValue( + underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest)) + ) + val ethernetIcon by collectLastValue(ethernetInteractor.icon) + + connectivityRepository.setEthernetConnected(default = true, validated = false) + + assertThat(latest?.secondaryLabel.loadText(context)) + .isEqualTo(ethernetIcon!!.contentDescription.loadContentDescription(context)) + assertThat(latest?.secondaryTitle).isNull() + assertThat(latest?.iconId).isEqualTo(R.drawable.stat_sys_ethernet) + assertThat(latest?.icon).isNull() + assertThat(latest?.stateDescription).isNull() + assertThat(latest?.contentDescription.loadContentDescription(context)) + .isEqualTo(latest?.secondaryLabel.loadText(context)) + } + + private fun setWifiNetworkWithHotspot(hotspot: WifiNetworkModel.HotspotDeviceType) { + val networkModel = + WifiNetworkModel.Active( + networkId = 1, + level = 4, + ssid = "test ssid", + hotspotDeviceType = hotspot, + ) + + connectivityRepository.setWifiConnected() + wifiRepository.setIsWifiDefault(true) + wifiRepository.setWifiNetwork(networkModel) + } + + private companion object { + const val SUB_1_ID = 1 + + val NOT_CONNECTED_NETWORKS_UNAVAILABLE = + InternetTileModel.Inactive( + secondaryLabel = Text.Resource(R.string.quick_settings_networks_unavailable), + iconId = R.drawable.ic_qs_no_internet_unavailable, + stateDescription = null, + contentDescription = + ContentDescription.Resource(R.string.quick_settings_networks_unavailable), + ) + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileUserActionInteractorTest.kt new file mode 100644 index 000000000000..e1f3d97eb35c --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileUserActionInteractorTest.kt @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2024 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.qs.tiles.impl.internet.domain.interactor + +import android.platform.test.annotations.EnabledOnRavenwood +import android.provider.Settings +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.testScope +import com.android.systemui.qs.tiles.base.actions.FakeQSTileIntentUserInputHandler +import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandlerSubject +import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx +import com.android.systemui.qs.tiles.dialog.InternetDialogManager +import com.android.systemui.qs.tiles.impl.internet.domain.model.InternetTileModel +import com.android.systemui.statusbar.connectivity.AccessPointController +import com.android.systemui.util.mockito.mock +import com.android.systemui.util.mockito.nullable +import com.google.common.truth.Truth +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.anyBoolean +import org.mockito.ArgumentMatchers.eq +import org.mockito.Mock +import org.mockito.Mockito.verify + +@SmallTest +@EnabledOnRavenwood +@RunWith(AndroidJUnit4::class) +class InternetTileUserActionInteractorTest : SysuiTestCase() { + private val kosmos = Kosmos() + private val inputHandler = FakeQSTileIntentUserInputHandler() + + private lateinit var underTest: InternetTileUserActionInteractor + + @Mock private lateinit var internetDialogManager: InternetDialogManager + @Mock private lateinit var controller: AccessPointController + + @Before + fun setup() { + internetDialogManager = mock<InternetDialogManager>() + controller = mock<AccessPointController>() + + underTest = + InternetTileUserActionInteractor( + kosmos.testScope.coroutineContext, + internetDialogManager, + controller, + inputHandler, + ) + } + + @Test + fun handleClickWhenActive() = + kosmos.testScope.runTest { + val input = InternetTileModel.Active() + + underTest.handleInput(QSTileInputTestKtx.click(input)) + + verify(internetDialogManager).create(eq(true), anyBoolean(), anyBoolean(), nullable()) + } + + @Test + fun handleClickWhenInactive() = + kosmos.testScope.runTest { + val input = InternetTileModel.Inactive() + + underTest.handleInput(QSTileInputTestKtx.click(input)) + + verify(internetDialogManager).create(eq(true), anyBoolean(), anyBoolean(), nullable()) + } + + @Test + fun handleLongClickWhenActive() = + kosmos.testScope.runTest { + val input = InternetTileModel.Active() + + underTest.handleInput(QSTileInputTestKtx.longClick(input)) + + QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOneIntentInput { + Truth.assertThat(it.intent.action).isEqualTo(Settings.ACTION_WIFI_SETTINGS) + } + } + + @Test + fun handleLongClickWhenInactive() = + kosmos.testScope.runTest { + val input = InternetTileModel.Inactive() + + underTest.handleInput(QSTileInputTestKtx.longClick(input)) + + QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOneIntentInput { + Truth.assertThat(it.intent.action).isEqualTo(Settings.ACTION_WIFI_SETTINGS) + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapper.kt new file mode 100644 index 000000000000..caae4d26634d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapper.kt @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2024 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.qs.tiles.impl.internet.domain + +import android.content.Context +import android.content.res.Resources +import android.widget.Switch +import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription +import com.android.systemui.common.shared.model.Icon +import com.android.systemui.common.shared.model.Text.Companion.loadText +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper +import com.android.systemui.qs.tiles.impl.internet.domain.model.InternetTileModel +import com.android.systemui.qs.tiles.viewmodel.QSTileConfig +import com.android.systemui.qs.tiles.viewmodel.QSTileState +import com.android.systemui.res.R +import javax.inject.Inject + +/** Maps [InternetTileModel] to [QSTileState]. */ +class InternetTileMapper +@Inject +constructor( + @Main private val resources: Resources, + private val theme: Resources.Theme, + private val context: Context, +) : QSTileDataToStateMapper<InternetTileModel> { + + override fun map(config: QSTileConfig, data: InternetTileModel): QSTileState = + QSTileState.build(resources, theme, config.uiConfig) { + label = resources.getString(R.string.quick_settings_internet_label) + expandedAccessibilityClass = Switch::class + + if (data.secondaryLabel != null) { + secondaryLabel = data.secondaryLabel.loadText(context) + } else { + secondaryLabel = data.secondaryTitle + } + + stateDescription = data.stateDescription.loadContentDescription(context) + contentDescription = data.contentDescription.loadContentDescription(context) + + if (data.icon != null) { + this.icon = { data.icon } + } else if (data.iconId != null) { + val loadedIcon = + Icon.Loaded( + resources.getDrawable(data.iconId!!, theme), + contentDescription = null + ) + this.icon = { loadedIcon } + } + + sideViewIcon = QSTileState.SideViewIcon.Chevron + + activationState = + if (data is InternetTileModel.Active) QSTileState.ActivationState.ACTIVE + else QSTileState.ActivationState.INACTIVE + + supportedActions = + setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileDataInteractor.kt new file mode 100644 index 000000000000..fdc596b3030c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileDataInteractor.kt @@ -0,0 +1,275 @@ +/* + * Copyright (C) 2024 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.qs.tiles.impl.internet.domain.interactor + +import android.annotation.StringRes +import android.content.Context +import android.os.UserHandle +import android.text.Html +import com.android.settingslib.graph.SignalDrawable +import com.android.systemui.common.shared.model.ContentDescription +import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription +import com.android.systemui.common.shared.model.Icon +import com.android.systemui.common.shared.model.Text +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger +import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor +import com.android.systemui.qs.tiles.impl.internet.domain.model.InternetTileModel +import com.android.systemui.res.R +import com.android.systemui.statusbar.pipeline.airplane.data.repository.AirplaneModeRepository +import com.android.systemui.statusbar.pipeline.ethernet.domain.EthernetInteractor +import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor +import com.android.systemui.statusbar.pipeline.mobile.domain.model.SignalIconModel +import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository +import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractor +import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel +import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon +import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.stateIn + +@OptIn(ExperimentalCoroutinesApi::class) +/** Observes internet state changes providing the [InternetTileModel]. */ +class InternetTileDataInteractor +@Inject +constructor( + private val context: Context, + @Application private val scope: CoroutineScope, + airplaneModeRepository: AirplaneModeRepository, + private val connectivityRepository: ConnectivityRepository, + ethernetInteractor: EthernetInteractor, + mobileIconsInteractor: MobileIconsInteractor, + wifiInteractor: WifiInteractor, +) : QSTileDataInteractor<InternetTileModel> { + private val internetLabel: String = context.getString(R.string.quick_settings_internet_label) + + // Three symmetrical Flows that can be switched upon based on the value of + // [DefaultConnectionModel] + private val wifiIconFlow: Flow<InternetTileModel> = + wifiInteractor.wifiNetwork.flatMapLatest { + val wifiIcon = WifiIcon.fromModel(it, context, showHotspotInfo = true) + if (it is WifiNetworkModel.Active && wifiIcon is WifiIcon.Visible) { + val secondary = removeDoubleQuotes(it.ssid) + flowOf( + InternetTileModel.Active( + secondaryTitle = secondary, + icon = Icon.Loaded(context.getDrawable(wifiIcon.icon.res)!!, null), + stateDescription = wifiIcon.contentDescription, + contentDescription = ContentDescription.Loaded("$internetLabel,$secondary"), + ) + ) + } else { + notConnectedFlow + } + } + + private val mobileDataContentName: Flow<CharSequence?> = + mobileIconsInteractor.activeDataIconInteractor.flatMapLatest { + if (it == null) { + flowOf(null) + } else { + combine(it.isRoaming, it.networkTypeIconGroup) { isRoaming, networkTypeIconGroup -> + val cd = loadString(networkTypeIconGroup.contentDescription) + if (isRoaming) { + val roaming = context.getString(R.string.data_connection_roaming) + if (cd != null) { + context.getString(R.string.mobile_data_text_format, roaming, cd) + } else { + roaming + } + } else { + cd + } + } + } + } + + private val mobileIconFlow: Flow<InternetTileModel> = + mobileIconsInteractor.activeDataIconInteractor.flatMapLatest { + if (it == null) { + notConnectedFlow + } else { + combine( + it.networkName, + it.signalLevelIcon, + mobileDataContentName, + ) { networkNameModel, signalIcon, dataContentDescription -> + when (signalIcon) { + is SignalIconModel.Cellular -> { + val secondary = + mobileDataContentConcat( + networkNameModel.name, + dataContentDescription + ) + + val stateLevel = signalIcon.level + val drawable = SignalDrawable(context) + drawable.setLevel(stateLevel) + val loadedIcon = Icon.Loaded(drawable, null) + + InternetTileModel.Active( + secondaryTitle = secondary, + icon = loadedIcon, + stateDescription = ContentDescription.Loaded(secondary.toString()), + contentDescription = ContentDescription.Loaded(internetLabel), + ) + } + is SignalIconModel.Satellite -> { + val secondary = + signalIcon.icon.contentDescription.loadContentDescription(context) + InternetTileModel.Active( + secondaryTitle = secondary, + iconId = signalIcon.icon.res, + stateDescription = ContentDescription.Loaded(secondary), + contentDescription = ContentDescription.Loaded(internetLabel), + ) + } + } + } + } + } + + private fun mobileDataContentConcat( + networkName: String?, + dataContentDescription: CharSequence? + ): CharSequence { + if (dataContentDescription == null) { + return networkName ?: "" + } + if (networkName == null) { + return Html.fromHtml(dataContentDescription.toString(), 0) + } + + return Html.fromHtml( + context.getString( + R.string.mobile_carrier_text_format, + networkName, + dataContentDescription + ), + 0 + ) + } + + private fun loadString(@StringRes resId: Int): CharSequence? = + if (resId != 0) { + context.getString(resId) + } else { + null + } + + private val ethernetIconFlow: Flow<InternetTileModel> = + ethernetInteractor.icon.flatMapLatest { + if (it == null) { + notConnectedFlow + } else { + val secondary = it.contentDescription + flowOf( + InternetTileModel.Active( + secondaryLabel = secondary?.toText(), + iconId = it.res, + stateDescription = null, + contentDescription = secondary, + ) + ) + } + } + + private val notConnectedFlow: StateFlow<InternetTileModel> = + combine( + wifiInteractor.areNetworksAvailable, + airplaneModeRepository.isAirplaneMode, + ) { networksAvailable, isAirplaneMode -> + when { + isAirplaneMode -> { + val secondary = context.getString(R.string.status_bar_airplane) + InternetTileModel.Inactive( + secondaryTitle = secondary, + iconId = R.drawable.ic_qs_no_internet_unavailable, + stateDescription = null, + contentDescription = ContentDescription.Loaded(secondary), + ) + } + networksAvailable -> { + val secondary = + context.getString(R.string.quick_settings_networks_available) + InternetTileModel.Inactive( + secondaryTitle = secondary, + iconId = R.drawable.ic_qs_no_internet_available, + stateDescription = null, + contentDescription = + ContentDescription.Loaded("$internetLabel,$secondary") + ) + } + else -> { + NOT_CONNECTED_NETWORKS_UNAVAILABLE + } + } + } + .stateIn(scope, SharingStarted.WhileSubscribed(), NOT_CONNECTED_NETWORKS_UNAVAILABLE) + + /** + * Consumable flow describing the correct state for the InternetTile. + * + * Strict ordering of which repo is sending its data to the internet tile. Swaps between each of + * the interim providers (wifi, mobile, ethernet, or not-connected). + */ + override fun tileData( + user: UserHandle, + triggers: Flow<DataUpdateTrigger> + ): Flow<InternetTileModel> = + connectivityRepository.defaultConnections.flatMapLatest { + when { + it.ethernet.isDefault -> ethernetIconFlow + it.mobile.isDefault || it.carrierMerged.isDefault -> mobileIconFlow + it.wifi.isDefault -> wifiIconFlow + else -> notConnectedFlow + } + } + + override fun availability(user: UserHandle): Flow<Boolean> = flowOf(true) + + private companion object { + val NOT_CONNECTED_NETWORKS_UNAVAILABLE = + InternetTileModel.Inactive( + secondaryLabel = Text.Resource(R.string.quick_settings_networks_unavailable), + iconId = R.drawable.ic_qs_no_internet_unavailable, + stateDescription = null, + contentDescription = + ContentDescription.Resource(R.string.quick_settings_networks_unavailable), + ) + + fun removeDoubleQuotes(string: String?): String? { + if (string == null) return null + return if (string.firstOrNull() == '"' && string.lastOrNull() == '"') { + string.substring(1, string.length - 1) + } else string + } + + fun ContentDescription.toText(): Text = + when (this) { + is ContentDescription.Loaded -> Text.Loaded(this.description) + is ContentDescription.Resource -> Text.Resource(this.res) + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileUserActionInteractor.kt new file mode 100644 index 000000000000..2620cd555d8e --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileUserActionInteractor.kt @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2024 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.qs.tiles.impl.internet.domain.interactor + +import android.content.Intent +import android.provider.Settings +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandler +import com.android.systemui.qs.tiles.base.interactor.QSTileInput +import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor +import com.android.systemui.qs.tiles.dialog.InternetDialogManager +import com.android.systemui.qs.tiles.impl.internet.domain.model.InternetTileModel +import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction +import com.android.systemui.statusbar.connectivity.AccessPointController +import javax.inject.Inject +import kotlin.coroutines.CoroutineContext +import kotlinx.coroutines.withContext + +/** Handles internet tile clicks. */ +class InternetTileUserActionInteractor +@Inject +constructor( + @Main private val mainContext: CoroutineContext, + private val internetDialogManager: InternetDialogManager, + private val accessPointController: AccessPointController, + private val qsTileIntentUserActionHandler: QSTileIntentUserInputHandler, +) : QSTileUserActionInteractor<InternetTileModel> { + + override suspend fun handleInput(input: QSTileInput<InternetTileModel>): Unit = + with(input) { + when (action) { + is QSTileUserAction.Click -> { + withContext(mainContext) { + internetDialogManager.create( + aboveStatusBar = true, + accessPointController.canConfigMobileData(), + accessPointController.canConfigWifi(), + action.view, + ) + } + } + is QSTileUserAction.LongClick -> { + qsTileIntentUserActionHandler.handle( + action.view, + Intent(Settings.ACTION_WIFI_SETTINGS) + ) + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/model/InternetTileModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/model/InternetTileModel.kt new file mode 100644 index 000000000000..ece904611782 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/model/InternetTileModel.kt @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2024 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.qs.tiles.impl.internet.domain.model + +import com.android.systemui.common.shared.model.ContentDescription +import com.android.systemui.common.shared.model.Icon +import com.android.systemui.common.shared.model.Text + +/** Model describing the state that the QS Internet tile should be in. */ +sealed interface InternetTileModel { + val secondaryTitle: CharSequence? + val secondaryLabel: Text? + val iconId: Int? + val icon: Icon? + val stateDescription: ContentDescription? + val contentDescription: ContentDescription? + + data class Active( + override val secondaryTitle: CharSequence? = null, + override val secondaryLabel: Text? = null, + override val iconId: Int? = null, + override val icon: Icon? = null, + override val stateDescription: ContentDescription? = null, + override val contentDescription: ContentDescription? = null, + ) : InternetTileModel + + data class Inactive( + override val secondaryTitle: CharSequence? = null, + override val secondaryLabel: Text? = null, + override val iconId: Int? = null, + override val icon: Icon? = null, + override val stateDescription: ContentDescription? = null, + override val contentDescription: ContentDescription? = null, + ) : InternetTileModel +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ConnectivityModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ConnectivityModule.kt index 642eaccc3c99..c4d9cbfcd55c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ConnectivityModule.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ConnectivityModule.kt @@ -35,6 +35,10 @@ import com.android.systemui.qs.tiles.impl.airplane.domain.AirplaneModeMapper import com.android.systemui.qs.tiles.impl.airplane.domain.interactor.AirplaneModeTileDataInteractor import com.android.systemui.qs.tiles.impl.airplane.domain.interactor.AirplaneModeTileUserActionInteractor import com.android.systemui.qs.tiles.impl.airplane.domain.model.AirplaneModeTileModel +import com.android.systemui.qs.tiles.impl.internet.domain.InternetTileMapper +import com.android.systemui.qs.tiles.impl.internet.domain.interactor.InternetTileDataInteractor +import com.android.systemui.qs.tiles.impl.internet.domain.interactor.InternetTileUserActionInteractor +import com.android.systemui.qs.tiles.impl.internet.domain.model.InternetTileModel import com.android.systemui.qs.tiles.impl.saver.domain.DataSaverTileMapper import com.android.systemui.qs.tiles.impl.saver.domain.interactor.DataSaverTileDataInteractor import com.android.systemui.qs.tiles.impl.saver.domain.interactor.DataSaverTileUserActionInteractor @@ -90,6 +94,7 @@ interface ConnectivityModule { const val AIRPLANE_MODE_TILE_SPEC = "airplane" const val DATA_SAVER_TILE_SPEC = "saver" + const val INTERNET_TILE_SPEC = "internet" /** Inject InternetTile or InternetTileNewImpl into tileMap in QSModule */ @Provides @@ -168,5 +173,36 @@ interface ConnectivityModule { stateInteractor, mapper, ) + + @Provides + @IntoMap + @StringKey(INTERNET_TILE_SPEC) + fun provideInternetTileConfig(uiEventLogger: QsEventLogger): QSTileConfig = + QSTileConfig( + tileSpec = TileSpec.create(INTERNET_TILE_SPEC), + uiConfig = + QSTileUIConfig.Resource( + iconRes = R.drawable.ic_qs_no_internet_available, + labelRes = R.string.quick_settings_internet_label, + ), + instanceId = uiEventLogger.getNewInstanceId(), + ) + + /** Inject InternetTile into tileViewModelMap in QSModule */ + @Provides + @IntoMap + @StringKey(INTERNET_TILE_SPEC) + fun provideInternetTileViewModel( + factory: QSTileViewModelFactory.Static<InternetTileModel>, + mapper: InternetTileMapper, + stateInteractor: InternetTileDataInteractor, + userActionInteractor: InternetTileUserActionInteractor + ): QSTileViewModel = + factory.create( + TileSpec.create(INTERNET_TILE_SPEC), + userActionInteractor, + stateInteractor, + mapper, + ) } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/internet/InternetTileKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/internet/InternetTileKosmos.kt new file mode 100644 index 000000000000..6772ba317da4 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/internet/InternetTileKosmos.kt @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2024 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.qs.tiles.impl.internet + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.qs.qsEventLogger +import com.android.systemui.statusbar.connectivity.ConnectivityModule + +val Kosmos.qsInternetTileConfig by + Kosmos.Fixture { ConnectivityModule.provideInternetTileConfig(qsEventLogger) } |