summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Behnam Heydarshahi <bhnm@google.com> 2024-09-09 23:52:50 +0000
committer Behnam Heydarshahi <bhnm@google.com> 2024-09-26 18:07:44 +0000
commit3af495a12662bd32ceab4757b4c6f37da85738a8 (patch)
tree600bf2f8d4c6752eae692e2bd8cf356e18641017
parent6d2364451564e0f3f837b72d7d0ba6c5b9cc7277 (diff)
Deduplicate internet tile cellular icon loading
When loading the mobile internet icon, we updated icon multiple times for the same icon, because the SignalDrawables were Loaded icons and the equality of SignalDrawables was done by comparing their references. This CL compares the drawables by comparing their (signal) level. Which should lead to handleStateChanged being called less frequently when opening the shade, hence fewers frames dropping on that CUJ. The wifi and cellular icons will be loaded in the mapper, rather than the data interactor, which should relieve the Background thread and hence reduce thread contention. Finally, a Handler can be injected into SignalDrawable now, which removes an edge case in our mapper test for cellular drawable. Not creating a handler on the test thread means we no longer need to make that test run with looper. Bug: 361558581 Flag: com.android.systemui.qs_new_tiles Test: manual: run systemui-quicksettings-toggle-jank-suite verify there is no performance regression Test: atest InternetTileMapperTest InternetTileDataInteractorTest Change-Id: I6e4cac667399941029caafe0b8469639c434a8c0
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/graph/SignalDrawable.java19
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapperTest.kt95
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileDataInteractorTest.kt131
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapper.kt53
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileDataInteractor.kt62
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/model/InternetTileModel.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/model/InternetTileIconModel.kt27
7 files changed, 238 insertions, 160 deletions
diff --git a/packages/SettingsLib/src/com/android/settingslib/graph/SignalDrawable.java b/packages/SettingsLib/src/com/android/settingslib/graph/SignalDrawable.java
index ef0f6cbc6ed9..13a06017abbc 100644
--- a/packages/SettingsLib/src/com/android/settingslib/graph/SignalDrawable.java
+++ b/packages/SettingsLib/src/com/android/settingslib/graph/SignalDrawable.java
@@ -42,6 +42,8 @@ import androidx.annotation.Nullable;
import com.android.settingslib.R;
import com.android.settingslib.Utils;
+import java.util.Objects;
+
/**
* Drawable displaying a mobile cell signal indicator.
*/
@@ -90,6 +92,10 @@ public class SignalDrawable extends DrawableWrapper {
private int mCurrentDot;
public SignalDrawable(Context context) {
+ this(context, new Handler());
+ }
+
+ public SignalDrawable(@NonNull Context context, @NonNull Handler handler) {
super(context.getDrawable(ICON_RES));
final String attributionPathString = context.getString(
com.android.internal.R.string.config_signalAttributionPath);
@@ -106,7 +112,7 @@ public class SignalDrawable extends DrawableWrapper {
mIntrinsicSize = context.getResources().getDimensionPixelSize(R.dimen.signal_icon_size);
mTransparentPaint.setColor(context.getColor(android.R.color.transparent));
mTransparentPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
- mHandler = new Handler();
+ mHandler = handler;
setDarkIntensity(0);
}
@@ -304,6 +310,17 @@ public class SignalDrawable extends DrawableWrapper {
| level;
}
+ @Override
+ public boolean equals(@Nullable Object other) {
+ return other instanceof SignalDrawable
+ && ((SignalDrawable) other).getLevel() == this.getLevel();
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(getLevel());
+ }
+
/** Returns the state representing empty mobile signal with the given number of levels. */
public static int getEmptyState(int numLevels) {
return getState(0, numLevels, true);
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
index 620e90dcaa62..d32ba47204c0 100644
--- 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
@@ -17,13 +17,17 @@
package com.android.systemui.qs.tiles.impl.internet.domain
import android.graphics.drawable.TestStubDrawable
+import android.os.fakeExecutorHandler
import android.widget.Switch
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.settingslib.graph.SignalDrawable
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.kosmos.Kosmos
import com.android.systemui.qs.tiles.impl.custom.QSTileStateSubject
import com.android.systemui.qs.tiles.impl.internet.domain.model.InternetTileModel
@@ -31,6 +35,9 @@ 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 com.android.systemui.statusbar.pipeline.mobile.domain.model.SignalIconModel
+import com.android.systemui.statusbar.pipeline.satellite.ui.model.SatelliteIconModel
+import com.android.systemui.statusbar.pipeline.shared.ui.model.InternetTileIconModel
import org.junit.Test
import org.junit.runner.RunWith
@@ -39,25 +46,93 @@ import org.junit.runner.RunWith
class InternetTileMapperTest : SysuiTestCase() {
private val kosmos = Kosmos()
private val internetTileConfig = kosmos.qsInternetTileConfig
+ private val handler = kosmos.fakeExecutorHandler
private val mapper by lazy {
InternetTileMapper(
context.orCreateTestableResources
.apply {
addOverride(R.drawable.ic_qs_no_internet_unavailable, TestStubDrawable())
+ addOverride(R.drawable.ic_satellite_connected_2, TestStubDrawable())
addOverride(wifiRes, TestStubDrawable())
}
.resources,
context.theme,
- context
+ context,
+ handler,
)
}
@Test
- fun withActiveModel_mappedStateMatchesDataModel() {
+ fun withActiveCellularModel_mappedStateMatchesDataModel() {
val inputModel =
InternetTileModel.Active(
secondaryLabel = Text.Resource(R.string.quick_settings_networks_available),
- iconId = wifiRes,
+ icon = InternetTileIconModel.Cellular(3),
+ stateDescription = null,
+ contentDescription =
+ ContentDescription.Resource(R.string.quick_settings_internet_label),
+ )
+
+ val outputState = mapper.map(internetTileConfig, inputModel)
+
+ val signalDrawable = SignalDrawable(context, handler)
+ signalDrawable.setLevel(3)
+ val expectedState =
+ createInternetTileState(
+ QSTileState.ActivationState.ACTIVE,
+ context.getString(R.string.quick_settings_networks_available),
+ Icon.Loaded(signalDrawable, null),
+ null,
+ context.getString(R.string.quick_settings_internet_label),
+ )
+ QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+ }
+
+ @Test
+ fun withActiveSatelliteModel_mappedStateMatchesDataModel() {
+ val inputIcon =
+ SignalIconModel.Satellite(
+ 3,
+ Icon.Resource(
+ res = R.drawable.ic_satellite_connected_2,
+ contentDescription =
+ ContentDescription.Resource(
+ R.string.accessibility_status_bar_satellite_good_connection
+ ),
+ ),
+ )
+ val inputModel =
+ InternetTileModel.Active(
+ secondaryLabel = Text.Resource(R.string.quick_settings_networks_available),
+ icon = InternetTileIconModel.Satellite(inputIcon.icon),
+ stateDescription = null,
+ contentDescription =
+ ContentDescription.Resource(
+ R.string.accessibility_status_bar_satellite_good_connection
+ ),
+ )
+
+ val outputState = mapper.map(internetTileConfig, inputModel)
+
+ val expectedSatIcon = SatelliteIconModel.fromSignalStrength(3)
+
+ val expectedState =
+ createInternetTileState(
+ QSTileState.ActivationState.ACTIVE,
+ inputModel.secondaryLabel.loadText(context).toString(),
+ Icon.Loaded(context.getDrawable(expectedSatIcon!!.res)!!, null),
+ expectedSatIcon.res,
+ expectedSatIcon.contentDescription.loadContentDescription(context).toString(),
+ )
+ QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+ }
+
+ @Test
+ fun withActiveWifiModel_mappedStateMatchesDataModel() {
+ val inputModel =
+ InternetTileModel.Active(
+ secondaryLabel = Text.Resource(R.string.quick_settings_networks_available),
+ icon = InternetTileIconModel.ResourceId(wifiRes),
stateDescription = null,
contentDescription =
ContentDescription.Resource(R.string.quick_settings_internet_label),
@@ -71,7 +146,7 @@ class InternetTileMapperTest : SysuiTestCase() {
context.getString(R.string.quick_settings_networks_available),
Icon.Loaded(context.getDrawable(wifiRes)!!, contentDescription = null),
wifiRes,
- context.getString(R.string.quick_settings_internet_label)
+ context.getString(R.string.quick_settings_internet_label),
)
QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
}
@@ -81,7 +156,7 @@ class InternetTileMapperTest : SysuiTestCase() {
val inputModel =
InternetTileModel.Inactive(
secondaryLabel = Text.Resource(R.string.quick_settings_networks_unavailable),
- iconId = R.drawable.ic_qs_no_internet_unavailable,
+ icon = InternetTileIconModel.ResourceId(R.drawable.ic_qs_no_internet_unavailable),
stateDescription = null,
contentDescription =
ContentDescription.Resource(R.string.quick_settings_networks_unavailable),
@@ -95,10 +170,10 @@ class InternetTileMapperTest : SysuiTestCase() {
context.getString(R.string.quick_settings_networks_unavailable),
Icon.Loaded(
context.getDrawable(R.drawable.ic_qs_no_internet_unavailable)!!,
- contentDescription = null
+ contentDescription = null,
),
R.drawable.ic_qs_no_internet_unavailable,
- context.getString(R.string.quick_settings_networks_unavailable)
+ context.getString(R.string.quick_settings_networks_unavailable),
)
QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
}
@@ -107,7 +182,7 @@ class InternetTileMapperTest : SysuiTestCase() {
activationState: QSTileState.ActivationState,
secondaryLabel: String,
icon: Icon,
- iconRes: Int,
+ iconRes: Int? = null,
contentDescription: String,
): QSTileState {
val label = context.getString(R.string.quick_settings_internet_label)
@@ -120,13 +195,13 @@ class InternetTileMapperTest : SysuiTestCase() {
setOf(
QSTileState.UserAction.CLICK,
QSTileState.UserAction.TOGGLE_CLICK,
- QSTileState.UserAction.LONG_CLICK
+ QSTileState.UserAction.LONG_CLICK,
),
contentDescription,
null,
QSTileState.SideViewIcon.Chevron,
QSTileState.EnabledState.ENABLED,
- Switch::class.qualifiedName
+ Switch::class.qualifiedName,
)
}
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
index 5a4506086058..5259aa84b193 100644
--- 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
@@ -18,14 +18,12 @@ 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
@@ -49,6 +47,7 @@ import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIc
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.shared.ui.model.InternetTileIconModel
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
@@ -60,9 +59,7 @@ import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.flowOf
-import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
-import org.junit.Assume.assumeFalse
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -144,7 +141,6 @@ class InternetTileDataInteractorTest : SysuiTestCase() {
underTest =
InternetTileDataInteractor(
context,
- testScope.coroutineContext,
testScope.backgroundScope,
airplaneModeRepository,
connectivityRepository,
@@ -164,9 +160,11 @@ class InternetTileDataInteractorTest : SysuiTestCase() {
connectivityRepository.defaultConnections.value = DefaultConnectionModel()
+ val expectedIcon =
+ InternetTileIconModel.ResourceId(R.drawable.ic_qs_no_internet_unavailable)
assertThat(latest?.secondaryLabel)
.isEqualTo(Text.Resource(R.string.quick_settings_networks_unavailable))
- assertThat(latest?.iconId).isEqualTo(R.drawable.ic_qs_no_internet_unavailable)
+ assertThat(latest?.icon).isEqualTo(expectedIcon)
}
@Test
@@ -183,11 +181,8 @@ class InternetTileDataInteractorTest : SysuiTestCase() {
underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))
)
- val networkModel =
- WifiNetworkModel.Active.of(
- level = 4,
- ssid = "test ssid",
- )
+ val networkModel = WifiNetworkModel.Active.of(level = 4, ssid = "test ssid")
+
val wifiIcon =
WifiIcon.fromModel(model = networkModel, context = context, showHotspotInfo = true)
as WifiIcon.Visible
@@ -198,12 +193,9 @@ class InternetTileDataInteractorTest : SysuiTestCase() {
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).isEqualTo(WifiIcons.WIFI_NO_INTERNET_ICONS[4])
+ val expectedIcon = InternetTileIconModel.ResourceId(WifiIcons.WIFI_NO_INTERNET_ICONS[4])
+ assertThat(latest?.icon).isEqualTo(expectedIcon)
assertThat(latest?.contentDescription.loadContentDescription(context))
.isEqualTo("$internet,test ssid")
val expectedSd = wifiIcon.contentDescription
@@ -229,8 +221,7 @@ class InternetTileDataInteractorTest : SysuiTestCase() {
wifiRepository.setIsWifiDefault(true)
wifiRepository.setWifiNetwork(networkModel)
- val expectedIcon =
- Icon.Loaded(context.getDrawable(WifiIcons.WIFI_NO_INTERNET_ICONS[4])!!, null)
+ val expectedIcon = InternetTileIconModel.ResourceId(WifiIcons.WIFI_NO_INTERNET_ICONS[4])
assertThat(latest?.icon).isEqualTo(expectedIcon)
assertThat(latest?.stateDescription.loadContentDescription(context))
.doesNotContain(
@@ -249,9 +240,8 @@ class InternetTileDataInteractorTest : SysuiTestCase() {
setWifiNetworkWithHotspot(WifiNetworkModel.HotspotDeviceType.TABLET)
val expectedIcon =
- Icon.Loaded(
- context.getDrawable(com.android.settingslib.R.drawable.ic_hotspot_tablet)!!,
- null
+ InternetTileIconModel.ResourceId(
+ com.android.settingslib.R.drawable.ic_hotspot_tablet
)
assertThat(latest?.icon).isEqualTo(expectedIcon)
assertThat(latest?.stateDescription.loadContentDescription(context))
@@ -271,9 +261,8 @@ class InternetTileDataInteractorTest : SysuiTestCase() {
setWifiNetworkWithHotspot(WifiNetworkModel.HotspotDeviceType.LAPTOP)
val expectedIcon =
- Icon.Loaded(
- context.getDrawable(com.android.settingslib.R.drawable.ic_hotspot_laptop)!!,
- null
+ InternetTileIconModel.ResourceId(
+ com.android.settingslib.R.drawable.ic_hotspot_laptop
)
assertThat(latest?.icon).isEqualTo(expectedIcon)
assertThat(latest?.stateDescription.loadContentDescription(context))
@@ -293,10 +282,10 @@ class InternetTileDataInteractorTest : SysuiTestCase() {
setWifiNetworkWithHotspot(WifiNetworkModel.HotspotDeviceType.WATCH)
val expectedIcon =
- Icon.Loaded(
- context.getDrawable(com.android.settingslib.R.drawable.ic_hotspot_watch)!!,
- null
+ InternetTileIconModel.ResourceId(
+ com.android.settingslib.R.drawable.ic_hotspot_watch
)
+
assertThat(latest?.icon).isEqualTo(expectedIcon)
assertThat(latest?.stateDescription.loadContentDescription(context))
.isEqualTo(
@@ -315,10 +304,7 @@ class InternetTileDataInteractorTest : SysuiTestCase() {
setWifiNetworkWithHotspot(WifiNetworkModel.HotspotDeviceType.AUTO)
val expectedIcon =
- Icon.Loaded(
- context.getDrawable(com.android.settingslib.R.drawable.ic_hotspot_auto)!!,
- null
- )
+ InternetTileIconModel.ResourceId(com.android.settingslib.R.drawable.ic_hotspot_auto)
assertThat(latest?.icon).isEqualTo(expectedIcon)
assertThat(latest?.stateDescription.loadContentDescription(context))
.isEqualTo(
@@ -336,9 +322,8 @@ class InternetTileDataInteractorTest : SysuiTestCase() {
setWifiNetworkWithHotspot(WifiNetworkModel.HotspotDeviceType.PHONE)
val expectedIcon =
- Icon.Loaded(
- context.getDrawable(com.android.settingslib.R.drawable.ic_hotspot_phone)!!,
- null
+ InternetTileIconModel.ResourceId(
+ com.android.settingslib.R.drawable.ic_hotspot_phone
)
assertThat(latest?.icon).isEqualTo(expectedIcon)
assertThat(latest?.stateDescription.loadContentDescription(context))
@@ -358,9 +343,8 @@ class InternetTileDataInteractorTest : SysuiTestCase() {
setWifiNetworkWithHotspot(WifiNetworkModel.HotspotDeviceType.UNKNOWN)
val expectedIcon =
- Icon.Loaded(
- context.getDrawable(com.android.settingslib.R.drawable.ic_hotspot_phone)!!,
- null
+ InternetTileIconModel.ResourceId(
+ com.android.settingslib.R.drawable.ic_hotspot_phone
)
assertThat(latest?.icon).isEqualTo(expectedIcon)
assertThat(latest?.stateDescription.loadContentDescription(context))
@@ -380,10 +364,10 @@ class InternetTileDataInteractorTest : SysuiTestCase() {
setWifiNetworkWithHotspot(WifiNetworkModel.HotspotDeviceType.INVALID)
val expectedIcon =
- Icon.Loaded(
- context.getDrawable(com.android.settingslib.R.drawable.ic_hotspot_phone)!!,
- null
+ InternetTileIconModel.ResourceId(
+ com.android.settingslib.R.drawable.ic_hotspot_phone
)
+
assertThat(latest?.icon).isEqualTo(expectedIcon)
assertThat(latest?.stateDescription.loadContentDescription(context))
.isEqualTo(
@@ -426,8 +410,9 @@ class InternetTileDataInteractorTest : SysuiTestCase() {
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)
+ val expectedIcon =
+ InternetTileIconModel.ResourceId(R.drawable.ic_qs_no_internet_available)
+ assertThat(latest?.icon).isEqualTo(expectedIcon)
assertThat(latest?.stateDescription).isNull()
val expectedCd =
"$internet,${context.getString(R.string.quick_settings_networks_available)}"
@@ -435,54 +420,19 @@ class InternetTileDataInteractorTest : SysuiTestCase() {
.isEqualTo(expectedCd)
}
- /**
- * We expect a RuntimeException because [underTest] instantiates a SignalDrawable on the
- * provided context, and so the SignalDrawable constructor attempts to instantiate a Handler()
- * on the mentioned context. Since that context does not have a looper assigned to it, the
- * handler instantiation will throw a RuntimeException.
- *
- * TODO(b/338068066): Robolectric behavior differs in that it does not throw the exception So
- * either we should make Robolectric behave similar to the device test, or change this test to
- * look for a different signal than the exception, when run by Robolectric. For now we just
- * assume the test is not Robolectric.
- */
- @Test(expected = java.lang.RuntimeException::class)
- fun mobileDefault_usesNetworkNameAndIcon_throwsRunTimeException() =
- testScope.runTest {
- assumeFalse(isRobolectricTest())
-
- 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")
- }
-
- runCurrent()
- }
-
- /**
- * See [mobileDefault_usesNetworkNameAndIcon_throwsRunTimeException] for description of the
- * problem this test solves. The solution here is to assign a looper to the context via
- * RunWithLooper. In the production code, the solution is to use a Main CoroutineContext for
- * creating the SignalDrawable.
- */
- @TestableLooper.RunWithLooper
@Test
- fun mobileDefault_run_withLooper_usesNetworkNameAndIcon() =
+ fun mobileDefault_usesNetworkNameAndIcon() =
testScope.runTest {
val latest by
collectLastValue(
underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))
)
+ val iconLevel = 3
connectivityRepository.setMobileConnected()
mobileConnectionsRepository.mobileIsDefault.value = true
mobileConnectionRepository.apply {
- setAllLevels(3)
+ setAllLevels(iconLevel)
setAllRoaming(false)
networkName.value = NetworkNameModel.Default("test network")
}
@@ -491,8 +441,9 @@ class InternetTileDataInteractorTest : SysuiTestCase() {
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()
+ val expectedIcon = InternetTileIconModel.Cellular(iconLevel)
+
+ assertThat(latest?.icon).isEqualTo(expectedIcon)
assertThat(latest?.stateDescription.loadContentDescription(context))
.isEqualTo(latest?.secondaryTitle.toString())
assertThat(latest?.contentDescription.loadContentDescription(context))
@@ -513,8 +464,8 @@ class InternetTileDataInteractorTest : SysuiTestCase() {
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()
+ val expectedIcon = InternetTileIconModel.ResourceId(R.drawable.stat_sys_ethernet_fully)
+ assertThat(latest?.icon).isEqualTo(expectedIcon)
assertThat(latest?.stateDescription).isNull()
assertThat(latest?.contentDescription.loadContentDescription(context))
.isEqualTo(latest?.secondaryLabel.loadText(context))
@@ -534,8 +485,8 @@ class InternetTileDataInteractorTest : SysuiTestCase() {
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()
+ val expectedIcon = InternetTileIconModel.ResourceId(R.drawable.stat_sys_ethernet)
+ assertThat(latest?.icon).isEqualTo(expectedIcon)
assertThat(latest?.stateDescription).isNull()
assertThat(latest?.contentDescription.loadContentDescription(context))
.isEqualTo(latest?.secondaryLabel.loadText(context))
@@ -543,11 +494,7 @@ class InternetTileDataInteractorTest : SysuiTestCase() {
private fun setWifiNetworkWithHotspot(hotspot: WifiNetworkModel.HotspotDeviceType) {
val networkModel =
- WifiNetworkModel.Active.of(
- level = 4,
- ssid = "test ssid",
- hotspotDeviceType = hotspot,
- )
+ WifiNetworkModel.Active.of(level = 4, ssid = "test ssid", hotspotDeviceType = hotspot)
connectivityRepository.setWifiConnected()
wifiRepository.setIsWifiDefault(true)
@@ -560,7 +507,7 @@ class InternetTileDataInteractorTest : SysuiTestCase() {
val NOT_CONNECTED_NETWORKS_UNAVAILABLE =
InternetTileModel.Inactive(
secondaryLabel = Text.Resource(R.string.quick_settings_networks_unavailable),
- iconId = R.drawable.ic_qs_no_internet_unavailable,
+ icon = InternetTileIconModel.ResourceId(R.drawable.ic_qs_no_internet_unavailable),
stateDescription = null,
contentDescription =
ContentDescription.Resource(R.string.quick_settings_networks_unavailable),
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
index 8965ef2bc493..bb0b9b7084fa 100644
--- 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
@@ -18,7 +18,9 @@ package com.android.systemui.qs.tiles.impl.internet.domain
import android.content.Context
import android.content.res.Resources
+import android.os.Handler
import android.widget.Switch
+import com.android.settingslib.graph.SignalDrawable
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
@@ -28,6 +30,7 @@ import com.android.systemui.qs.tiles.impl.internet.domain.model.InternetTileMode
import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
import com.android.systemui.qs.tiles.viewmodel.QSTileState
import com.android.systemui.res.R
+import com.android.systemui.statusbar.pipeline.shared.ui.model.InternetTileIconModel
import javax.inject.Inject
/** Maps [InternetTileModel] to [QSTileState]. */
@@ -37,6 +40,7 @@ constructor(
@Main private val resources: Resources,
private val theme: Resources.Theme,
private val context: Context,
+ @Main private val handler: Handler,
) : QSTileDataToStateMapper<InternetTileModel> {
override fun map(config: QSTileConfig, data: InternetTileModel): QSTileState =
@@ -44,25 +48,42 @@ constructor(
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
- }
+ secondaryLabel =
+ if (data.secondaryLabel != null) {
+ data.secondaryLabel.loadText(context)
+ } else {
+ data.secondaryTitle
+ }
stateDescription = data.stateDescription.loadContentDescription(context)
contentDescription = data.contentDescription.loadContentDescription(context)
- iconRes = data.iconId
- 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 }
+ when (val dataIcon = data.icon) {
+ is InternetTileIconModel.ResourceId -> {
+ iconRes = dataIcon.resId
+ icon = {
+ Icon.Loaded(
+ resources.getDrawable(dataIcon.resId, theme),
+ contentDescription = null,
+ )
+ }
+ }
+
+ is InternetTileIconModel.Cellular -> {
+ val signalDrawable = SignalDrawable(context, handler)
+ signalDrawable.setLevel(dataIcon.level)
+ icon = { Icon.Loaded(signalDrawable, contentDescription = null) }
+ }
+
+ is InternetTileIconModel.Satellite -> {
+ iconRes = dataIcon.resourceIcon.res // level is inferred from res
+ icon = {
+ Icon.Loaded(
+ resources.getDrawable(dataIcon.resourceIcon.res, theme),
+ contentDescription = null,
+ )
+ }
+ }
}
sideViewIcon = QSTileState.SideViewIcon.Chevron
@@ -75,7 +96,7 @@ constructor(
setOf(
QSTileState.UserAction.CLICK,
QSTileState.UserAction.TOGGLE_CLICK,
- QSTileState.UserAction.LONG_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
index 204ead3fe29c..6fe3979fa446 100644
--- 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
@@ -20,13 +20,10 @@ 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.dagger.qualifiers.Main
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
@@ -36,12 +33,12 @@ import com.android.systemui.statusbar.pipeline.ethernet.domain.EthernetInteracto
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.shared.ui.model.InternetTileIconModel
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 com.android.systemui.utils.coroutines.flow.mapLatestConflated
import javax.inject.Inject
-import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
@@ -51,7 +48,6 @@ import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.stateIn
-import kotlinx.coroutines.withContext
@OptIn(ExperimentalCoroutinesApi::class)
/** Observes internet state changes providing the [InternetTileModel]. */
@@ -59,7 +55,6 @@ class InternetTileDataInteractor
@Inject
constructor(
private val context: Context,
- @Main private val mainCoroutineContext: CoroutineContext,
@Application private val scope: CoroutineScope,
airplaneModeRepository: AirplaneModeRepository,
private val connectivityRepository: ConnectivityRepository,
@@ -79,8 +74,7 @@ constructor(
flowOf(
InternetTileModel.Active(
secondaryTitle = secondary,
- iconId = wifiIcon.icon.res,
- icon = Icon.Loaded(context.getDrawable(wifiIcon.icon.res)!!, null),
+ icon = InternetTileIconModel.ResourceId(wifiIcon.icon.res),
stateDescription = wifiIcon.contentDescription,
contentDescription = ContentDescription.Loaded("$internetLabel,$secondary"),
)
@@ -116,11 +110,10 @@ constructor(
if (it == null) {
notConnectedFlow
} else {
- combine(
- it.networkName,
- it.signalLevelIcon,
- mobileDataContentName,
- ) { networkNameModel, signalIcon, dataContentDescription ->
+ combine(it.networkName, it.signalLevelIcon, mobileDataContentName) {
+ networkNameModel,
+ signalIcon,
+ dataContentDescription ->
Triple(networkNameModel, signalIcon, dataContentDescription)
}
.mapLatestConflated { (networkNameModel, signalIcon, dataContentDescription) ->
@@ -129,17 +122,12 @@ constructor(
val secondary =
mobileDataContentConcat(
networkNameModel.name,
- dataContentDescription
+ dataContentDescription,
)
- val drawable =
- withContext(mainCoroutineContext) { SignalDrawable(context) }
- drawable.setLevel(signalIcon.level)
- val loadedIcon = Icon.Loaded(drawable, null)
-
InternetTileModel.Active(
secondaryTitle = secondary,
- icon = loadedIcon,
+ icon = InternetTileIconModel.Cellular(signalIcon.level),
stateDescription =
ContentDescription.Loaded(secondary.toString()),
contentDescription = ContentDescription.Loaded(internetLabel),
@@ -150,9 +138,10 @@ constructor(
signalIcon.icon.contentDescription.loadContentDescription(
context
)
+
InternetTileModel.Active(
secondaryTitle = secondary,
- iconId = signalIcon.icon.res,
+ icon = InternetTileIconModel.Satellite(signalIcon.icon),
stateDescription = ContentDescription.Loaded(secondary),
contentDescription = ContentDescription.Loaded(internetLabel),
)
@@ -164,7 +153,7 @@ constructor(
private fun mobileDataContentConcat(
networkName: String?,
- dataContentDescription: CharSequence?
+ dataContentDescription: CharSequence?,
): CharSequence {
if (dataContentDescription == null) {
return networkName ?: ""
@@ -177,9 +166,9 @@ constructor(
context.getString(
R.string.mobile_carrier_text_format,
networkName,
- dataContentDescription
+ dataContentDescription,
),
- 0
+ 0,
)
}
@@ -199,7 +188,7 @@ constructor(
flowOf(
InternetTileModel.Active(
secondaryLabel = secondary?.toText(),
- iconId = it.res,
+ icon = InternetTileIconModel.ResourceId(it.res),
stateDescription = null,
contentDescription = secondary,
)
@@ -208,16 +197,18 @@ constructor(
}
private val notConnectedFlow: StateFlow<InternetTileModel> =
- combine(
- wifiInteractor.areNetworksAvailable,
- airplaneModeRepository.isAirplaneMode,
- ) { networksAvailable, isAirplaneMode ->
+ 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,
+ icon =
+ InternetTileIconModel.ResourceId(
+ R.drawable.ic_qs_no_internet_unavailable
+ ),
stateDescription = null,
contentDescription = ContentDescription.Loaded(secondary),
)
@@ -227,10 +218,13 @@ constructor(
context.getString(R.string.quick_settings_networks_available)
InternetTileModel.Inactive(
secondaryTitle = secondary,
- iconId = R.drawable.ic_qs_no_internet_available,
+ icon =
+ InternetTileIconModel.ResourceId(
+ R.drawable.ic_qs_no_internet_available
+ ),
stateDescription = null,
contentDescription =
- ContentDescription.Loaded("$internetLabel,$secondary")
+ ContentDescription.Loaded("$internetLabel,$secondary"),
)
}
else -> {
@@ -248,7 +242,7 @@ constructor(
*/
override fun tileData(
user: UserHandle,
- triggers: Flow<DataUpdateTrigger>
+ triggers: Flow<DataUpdateTrigger>,
): Flow<InternetTileModel> =
connectivityRepository.defaultConnections.flatMapLatest {
when {
@@ -265,7 +259,7 @@ constructor(
val NOT_CONNECTED_NETWORKS_UNAVAILABLE =
InternetTileModel.Inactive(
secondaryLabel = Text.Resource(R.string.quick_settings_networks_unavailable),
- iconId = R.drawable.ic_qs_no_internet_unavailable,
+ icon = InternetTileIconModel.ResourceId(R.drawable.ic_qs_no_internet_unavailable),
stateDescription = null,
contentDescription =
ContentDescription.Resource(R.string.quick_settings_networks_unavailable),
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
index ece904611782..15b4e472eec7 100644
--- 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
@@ -17,23 +17,21 @@
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
+import com.android.systemui.statusbar.pipeline.shared.ui.model.InternetTileIconModel
/** 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 icon: InternetTileIconModel
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 icon: InternetTileIconModel = InternetTileIconModel.Cellular(1),
override val stateDescription: ContentDescription? = null,
override val contentDescription: ContentDescription? = null,
) : InternetTileModel
@@ -41,8 +39,7 @@ sealed interface 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 icon: InternetTileIconModel = InternetTileIconModel.Cellular(1),
override val stateDescription: ContentDescription? = null,
override val contentDescription: ContentDescription? = null,
) : InternetTileModel
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/model/InternetTileIconModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/model/InternetTileIconModel.kt
new file mode 100644
index 000000000000..f8958e0d002f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/model/InternetTileIconModel.kt
@@ -0,0 +1,27 @@
+/*
+ * 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.statusbar.pipeline.shared.ui.model
+
+import com.android.systemui.common.shared.model.Icon
+
+sealed interface InternetTileIconModel {
+ data class ResourceId(val resId: Int) : InternetTileIconModel
+
+ data class Cellular(val level: Int) : InternetTileIconModel
+
+ data class Satellite(val resourceIcon: Icon.Resource) : InternetTileIconModel
+}