diff options
18 files changed, 865 insertions, 153 deletions
diff --git a/packages/SettingsLib/res/drawable/ic_5g_plus_mobiledata_default.xml b/packages/SettingsLib/res/drawable/ic_5g_plus_mobiledata_default.xml new file mode 100644 index 000000000000..46abff81d38f --- /dev/null +++ b/packages/SettingsLib/res/drawable/ic_5g_plus_mobiledata_default.xml @@ -0,0 +1,33 @@ +<!-- + 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. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:viewportWidth="22" + android:viewportHeight="17" + android:width="22dp" + android:height="17dp"> + <group> + <group> + <path android:fillColor="#FF000000" + android:pathData="M1.03 8.47l0.43-4.96h4.33v1.17H2.48L2.25 7.39C2.66 7.1 3.1 6.96 3.57 6.96c0.77 0 1.38 0.3 1.83 0.9 s0.66 1.41 0.66 2.43c0 1.03-0.24 1.84-0.72 2.43S4.2 13.6 3.36 13.6c-0.75 0-1.36-0.24-1.83-0.73s-0.74-1.16-0.81-2.02h1.13 c0.07 0.57 0.23 1 0.49 1.29s0.59 0.43 1.01 0.43c0.47 0 0.84-0.2 1.1-0.61c0.26-0.41 0.4-0.96 0.4-1.65 c0-0.65-0.14-1.18-0.43-1.59S3.76 8.09 3.28 8.09c-0.4 0-0.72 0.1-0.96 0.31L1.99 8.73L1.03 8.47z"/> + </group> + <group> + <path android:fillColor="#FF000000" + android:pathData="M 18.93,5.74 L 18.93,3.39 L 17.63,3.39 L 17.63,5.74 L 15.28,5.74 L 15.28,7.04 L 17.63,7.04 L 17.63,9.39 L 18.93,9.39 L 18.93,7.04 L 21.28,7.04 L 21.28,5.74 z"/> + </group> + <path android:fillColor="#FF000000" + android:pathData="M13.78 12.24l-0.22 0.27c-0.63 0.73-1.55 1.1-2.76 1.1c-1.08 0-1.92-0.36-2.53-1.07s-0.93-1.72-0.94-3.02V7.56 c0-1.39 0.28-2.44 0.84-3.13s1.39-1.04 2.51-1.04c0.95 0 1.69 0.26 2.23 0.79s0.83 1.28 0.89 2.26h-1.25 c-0.05-0.62-0.22-1.1-0.52-1.45s-0.74-0.52-1.34-0.52c-0.72 0-1.24 0.23-1.57 0.7S8.6 6.37 8.59 7.4v2.03c0 1 0.19 1.77 0.57 2.31 c0.38 0.54 0.93 0.8 1.65 0.8c0.67 0 1.19-0.16 1.54-0.49l0.18-0.17V9.59h-1.82V8.52h3.07V12.24z"/> + </group> +</vector> diff --git a/packages/SettingsLib/res/values/carrierid_icon_overrides.xml b/packages/SettingsLib/res/values/carrierid_icon_overrides.xml new file mode 100644 index 000000000000..d2ae52d8347a --- /dev/null +++ b/packages/SettingsLib/res/values/carrierid_icon_overrides.xml @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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. + --> +<!-- + ~ This resource file exists to enumerate all network type icon overrides on a + ~ per-carrierId basis +--> +<resources> + <!-- + Network type (RAT) icon overrides can be configured here on a per-carrierId basis. + 1. Add a new TypedArray here, using the naming scheme below + 2. The entries are (NetworkType, drawable ID) pairs + 3. Add this array's ID to the MAPPING field of MobileIconCarrierIdOverrides.kt + --> + <array name="carrierId_2032_iconOverrides"> + <item>5G_PLUS</item> + <item>@drawable/ic_5g_plus_mobiledata_default</item> + </array> +</resources>
\ No newline at end of file diff --git a/packages/SettingsLib/src/com/android/settingslib/mobile/MobileIconCarrierIdOverrides.kt b/packages/SettingsLib/src/com/android/settingslib/mobile/MobileIconCarrierIdOverrides.kt new file mode 100644 index 000000000000..a0395b559291 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/mobile/MobileIconCarrierIdOverrides.kt @@ -0,0 +1,142 @@ +/* + * 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.settingslib.mobile + +import android.annotation.DrawableRes +import android.content.res.Resources +import android.content.res.TypedArray +import android.util.Log +import androidx.annotation.VisibleForTesting +import com.android.settingslib.R +import com.android.settingslib.SignalIcon.MobileIconGroup + +/** + * This class defines a network type (3G, 4G, etc.) override mechanism on a per-carrierId basis. + * + * Traditionally, carrier-customized network type iconography was achieved using the `MCC/MNC` + * resource qualifiers, and swapping out the drawable resource by name. It would look like this: + * + * res/ + * drawable/ + * 3g_mobiledata_icon.xml + * drawable-MCC-MNC/ + * 3g_mobiledata_icon.xml + * + * This would mean that, provided a context created with this MCC/MNC configuration set, loading + * the network type icon through [MobileIconGroup] would provide a carrier-defined network type + * icon rather than the AOSP-defined default. + * + * The MCC/MNC mechanism no longer can fully define carrier-specific network type icons, because + * there is no longer a 1:1 mapping between MCC/MNC and carrier. With the advent of MVNOs, multiple + * carriers can have the same MCC/MNC value, but wish to differentiate based on their carrier ID. + * CarrierId is a newer concept than MCC/MNC, and provides more granularity when it comes to + * determining the carrier (e.g. MVNOs can share MCC/MNC values with the network owner), therefore + * it can fit all of the same use cases currently handled by `MCC/MNC`, without the need to apply a + * configuration context in order to get the proper UI for a given SIM icon. + * + * NOTE: CarrierId icon overrides will always take precedence over those defined using `MCC/MNC` + * resource qualifiers. + * + * [MAPPING] encodes the relationship between CarrierId and the corresponding override array + * that exists in the config.xml. An alternative approach could be to generate the resource name + * by string concatenation at run-time: + * + * val resName = "carrierId_$carrierId_iconOverrides" + * val override = resources.getResourceIdentifier(resName) + * + * However, that's going to be far less efficient until MAPPING grows to a sufficient size. For now, + * given a relatively small number of entries, we should just maintain the mapping here. + */ +interface MobileIconCarrierIdOverrides { + @DrawableRes + fun getOverrideFor(carrierId: Int, networkType: String, resources: Resources): Int + fun carrierIdEntryExists(carrierId: Int): Boolean +} + +class MobileIconCarrierIdOverridesImpl : MobileIconCarrierIdOverrides { + @DrawableRes + override fun getOverrideFor(carrierId: Int, networkType: String, resources: Resources): Int { + val resId = MAPPING[carrierId] ?: return 0 + val ta = resources.obtainTypedArray(resId) + val map = parseNetworkIconOverrideTypedArray(ta) + ta.recycle() + return map[networkType] ?: 0 + } + + override fun carrierIdEntryExists(carrierId: Int) = + overrideExists(carrierId, MAPPING) + + companion object { + private const val TAG = "MobileIconOverrides" + /** + * This map maintains the lookup from the canonical carrier ID (see below link) to the + * corresponding overlay resource. New overrides should add an entry below in order to + * change the network type icon resources based on carrier ID + * + * Refer to the link below for the canonical mapping maintained in AOSP: + * https://android.googlesource.com/platform/packages/providers/TelephonyProvider/+/master/assets/latest_carrier_id/carrier_list.textpb + */ + private val MAPPING = mapOf( + // 2032 == Xfinity Mobile + 2032 to R.array.carrierId_2032_iconOverrides, + ) + + /** + * Parse `carrierId_XXXX_iconOverrides` for a particular network type. The resource file + * "carrierid_icon_overrides.xml" defines a TypedArray format for overriding specific + * network type icons (a.k.a. RAT icons) for a particular carrier ID. The format is defined + * as an array of (network type name, drawable) pairs: + * <array name="carrierId_XXXX_iconOverrides> + * <item>NET_TYPE_1</item> + * <item>@drawable/net_type_1_override</item> + * <item>NET_TYPE_2</item> + * <item>@drawable/net_type_2_override</item> + * </array> + * + * @param ta the [TypedArray] defined in carrierid_icon_overrides.xml + * @return the overridden drawable resource ID if it exists, or 0 if it does not + */ + @VisibleForTesting + @JvmStatic + fun parseNetworkIconOverrideTypedArray(ta: TypedArray): Map<String, Int> { + if (ta.length() % 2 != 0) { + Log.w(TAG, + "override must contain an even number of (key, value) entries. skipping") + + return mapOf() + } + + val result = mutableMapOf<String, Int>() + // The array is defined as Pair(String, resourceId), so walk by 2 + for (i in 0 until ta.length() step 2) { + val key = ta.getString(i) + val override = ta.getResourceId(i + 1, 0) + if (key == null || override == 0) { + Log.w(TAG, "Invalid override found. Skipping") + continue + } + result[key] = override + } + + return result + } + + @JvmStatic + private fun overrideExists(carrierId: Int, mapping: Map<Int, Int>): Boolean = + mapping.containsKey(carrierId) + } +} diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/mobile/MobileIconCarrierIdOverridesTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/mobile/MobileIconCarrierIdOverridesTest.java new file mode 100644 index 000000000000..740261d3bac2 --- /dev/null +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/mobile/MobileIconCarrierIdOverridesTest.java @@ -0,0 +1,140 @@ +/* + * 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.settingslib.mobile; + +import static com.android.settingslib.mobile.MobileIconCarrierIdOverridesImpl.parseNetworkIconOverrideTypedArray; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import android.content.res.TypedArray; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; + +import java.util.Map; + +@RunWith(RobolectricTestRunner.class) +public final class MobileIconCarrierIdOverridesTest { + private static final String OVERRIDE_ICON_1_NAME = "name_1"; + private static final int OVERRIDE_ICON_1_RES = 1; + + private static final String OVERRIDE_ICON_2_NAME = "name_2"; + private static final int OVERRIDE_ICON_2_RES = 2; + + NetworkOverrideTypedArrayMock mResourceMock; + + @Before + public void setUp() { + mResourceMock = new NetworkOverrideTypedArrayMock( + new String[] { OVERRIDE_ICON_1_NAME, OVERRIDE_ICON_2_NAME }, + new int[] { OVERRIDE_ICON_1_RES, OVERRIDE_ICON_2_RES } + ); + } + + @Test + public void testParse_singleOverride() { + mResourceMock.setOverrides( + new String[] { OVERRIDE_ICON_1_NAME }, + new int[] { OVERRIDE_ICON_1_RES } + ); + + Map<String, Integer> parsed = parseNetworkIconOverrideTypedArray(mResourceMock.getMock()); + + assertThat(parsed.get(OVERRIDE_ICON_1_NAME)).isEqualTo(OVERRIDE_ICON_1_RES); + } + + @Test + public void testParse_multipleOverrides() { + mResourceMock.setOverrides( + new String[] { OVERRIDE_ICON_1_NAME, OVERRIDE_ICON_2_NAME }, + new int[] { OVERRIDE_ICON_1_RES, OVERRIDE_ICON_2_RES } + ); + + Map<String, Integer> parsed = parseNetworkIconOverrideTypedArray(mResourceMock.getMock()); + + assertThat(parsed.get(OVERRIDE_ICON_2_NAME)).isEqualTo(OVERRIDE_ICON_2_RES); + assertThat(parsed.get(OVERRIDE_ICON_1_NAME)).isEqualTo(OVERRIDE_ICON_1_RES); + } + + @Test + public void testParse_nonexistentKey_isNull() { + mResourceMock.setOverrides( + new String[] { OVERRIDE_ICON_1_NAME }, + new int[] { OVERRIDE_ICON_1_RES } + ); + + Map<String, Integer> parsed = parseNetworkIconOverrideTypedArray(mResourceMock.getMock()); + + assertThat(parsed.get(OVERRIDE_ICON_2_NAME)).isNull(); + } + + static class NetworkOverrideTypedArrayMock { + private Object[] mInterleaved; + + private final TypedArray mMockTypedArray = mock(TypedArray.class); + + NetworkOverrideTypedArrayMock( + String[] networkTypes, + int[] iconOverrides) { + + mInterleaved = interleaveTypes(networkTypes, iconOverrides); + + doAnswer(invocation -> { + return mInterleaved[(int) invocation.getArgument(0)]; + }).when(mMockTypedArray).getString(/* index */ anyInt()); + + doAnswer(invocation -> { + return mInterleaved[(int) invocation.getArgument(0)]; + }).when(mMockTypedArray).getResourceId(/* index */ anyInt(), /* default */ anyInt()); + + when(mMockTypedArray.length()).thenAnswer(invocation -> { + return mInterleaved.length; + }); + } + + TypedArray getMock() { + return mMockTypedArray; + } + + void setOverrides(String[] types, int[] resIds) { + mInterleaved = interleaveTypes(types, resIds); + } + + private Object[] interleaveTypes(String[] strs, int[] ints) { + assertThat(strs.length).isEqualTo(ints.length); + + Object[] ret = new Object[strs.length * 2]; + + // Keep track of where we are in the interleaved array, but iterate the overrides + int interleavedIndex = 0; + for (int i = 0; i < strs.length; i++) { + ret[interleavedIndex] = strs[i]; + interleavedIndex += 1; + ret[interleavedIndex] = ints[i]; + interleavedIndex += 1; + } + return ret; + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileSignalController.java index ec221b7eccc0..c523d22456f6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileSignalController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileSignalController.java @@ -15,9 +15,7 @@ */ package com.android.systemui.statusbar.connectivity; -import static com.android.settingslib.mobile.MobileMappings.getDefaultIcons; -import static com.android.settingslib.mobile.MobileMappings.getIconKey; -import static com.android.settingslib.mobile.MobileMappings.mapIconSets; +import static android.telephony.TelephonyManager.UNKNOWN_CARRIER_ID; import android.content.Context; import android.content.Intent; @@ -46,6 +44,7 @@ import com.android.settingslib.mobile.MobileStatusTracker.SubscriptionDefaults; import com.android.settingslib.mobile.TelephonyIcons; import com.android.settingslib.net.SignalStrengthUtil; import com.android.systemui.R; +import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy; import com.android.systemui.util.CarrierConfigTracker; import java.io.PrintWriter; @@ -63,6 +62,7 @@ public class MobileSignalController extends SignalController<MobileState, Mobile private final TelephonyManager mPhone; private final CarrierConfigTracker mCarrierConfigTracker; private final SubscriptionDefaults mDefaults; + private final MobileMappingsProxy mMobileMappingsProxy; private final String mNetworkNameDefault; private final String mNetworkNameSeparator; private final ContentObserver mObserver; @@ -121,6 +121,7 @@ public class MobileSignalController extends SignalController<MobileState, Mobile TelephonyManager phone, CallbackHandler callbackHandler, NetworkControllerImpl networkController, + MobileMappingsProxy mobileMappingsProxy, SubscriptionInfo info, SubscriptionDefaults defaults, Looper receiverLooper, @@ -135,13 +136,14 @@ public class MobileSignalController extends SignalController<MobileState, Mobile mPhone = phone; mDefaults = defaults; mSubscriptionInfo = info; + mMobileMappingsProxy = mobileMappingsProxy; mNetworkNameSeparator = getTextIfExists( R.string.status_bar_network_name_separator).toString(); mNetworkNameDefault = getTextIfExists( com.android.internal.R.string.lockscreen_carrier_default).toString(); - mNetworkToIconLookup = mapIconSets(mConfig); - mDefaultIcons = getDefaultIcons(mConfig); + mNetworkToIconLookup = mMobileMappingsProxy.mapIconSets(mConfig); + mDefaultIcons = mMobileMappingsProxy.getDefaultIcons(mConfig); String networkName = info.getCarrierName() != null ? info.getCarrierName().toString() : mNetworkNameDefault; @@ -161,8 +163,8 @@ public class MobileSignalController extends SignalController<MobileState, Mobile void setConfiguration(Config config) { mConfig = config; updateInflateSignalStrength(); - mNetworkToIconLookup = mapIconSets(mConfig); - mDefaultIcons = getDefaultIcons(mConfig); + mNetworkToIconLookup = mMobileMappingsProxy.mapIconSets(mConfig); + mDefaultIcons = mMobileMappingsProxy.getDefaultIcons(mConfig); updateTelephony(); } @@ -271,8 +273,9 @@ public class MobileSignalController extends SignalController<MobileState, Mobile dataContentDescription = mContext.getString(R.string.data_connection_no_internet); } - final QsInfo qsInfo = getQsInfo(contentDescription, icons.dataType); - final SbInfo sbInfo = getSbInfo(contentDescription, icons.dataType); + int iconId = mCurrentState.getNetworkTypeIcon(mContext); + final QsInfo qsInfo = getQsInfo(contentDescription, iconId); + final SbInfo sbInfo = getSbInfo(contentDescription, iconId); MobileDataIndicators mobileDataIndicators = new MobileDataIndicators( sbInfo.icon, @@ -373,6 +376,10 @@ public class MobileSignalController extends SignalController<MobileState, Mobile } else if (action.equals(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED)) { updateDataSim(); notifyListenersIfNecessary(); + } else if (action.equals(TelephonyManager.ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED)) { + int carrierId = intent.getIntExtra( + TelephonyManager.EXTRA_CARRIER_ID, UNKNOWN_CARRIER_ID); + mCurrentState.setCarrierId(carrierId); } } @@ -477,7 +484,8 @@ public class MobileSignalController extends SignalController<MobileState, Mobile mCurrentState.level = getSignalLevel(mCurrentState.signalStrength); } - String iconKey = getIconKey(mCurrentState.telephonyDisplayInfo); + mCurrentState.setCarrierId(mPhone.getSimCarrierId()); + String iconKey = mMobileMappingsProxy.getIconKey(mCurrentState.telephonyDisplayInfo); if (mNetworkToIconLookup.get(iconKey) != null) { mCurrentState.iconGroup = mNetworkToIconLookup.get(iconKey); } else { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileSignalControllerFactory.kt b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileSignalControllerFactory.kt index 793817948803..a323454a7ed8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileSignalControllerFactory.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileSignalControllerFactory.kt @@ -22,6 +22,7 @@ import android.telephony.TelephonyManager import com.android.settingslib.mobile.MobileMappings import com.android.settingslib.mobile.MobileStatusTracker import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy import com.android.systemui.util.CarrierConfigTracker import javax.inject.Inject @@ -33,6 +34,7 @@ internal class MobileSignalControllerFactory @Inject constructor( val context: Context, val callbackHandler: CallbackHandler, val carrierConfigTracker: CarrierConfigTracker, + val mobileMappings: MobileMappingsProxy, ) { fun createMobileSignalController( config: MobileMappings.Config, @@ -56,6 +58,7 @@ internal class MobileSignalControllerFactory @Inject constructor( phone, callbackHandler, networkController, + mobileMappings, subscriptionInfo, subscriptionDefaults, receiverLooper, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileState.kt b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileState.kt index f20d20631c95..1fb6a982fdf3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileState.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileState.kt @@ -16,10 +16,14 @@ package com.android.systemui.statusbar.connectivity +import android.annotation.DrawableRes +import android.content.Context import android.telephony.ServiceState import android.telephony.SignalStrength import android.telephony.TelephonyDisplayInfo import android.telephony.TelephonyManager +import com.android.internal.annotations.VisibleForTesting +import com.android.settingslib.SignalIcon.MobileIconGroup import com.android.settingslib.Utils import com.android.settingslib.mobile.MobileStatusTracker.MobileStatus import com.android.settingslib.mobile.TelephonyIcons @@ -41,7 +45,7 @@ internal class MobileState( @JvmField var roaming: Boolean = false, @JvmField var dataState: Int = TelephonyManager.DATA_DISCONNECTED, // Tracks the on/off state of the defaultDataSubscription - @JvmField var defaultDataOff: Boolean = false + @JvmField var defaultDataOff: Boolean = false, ) : ConnectivityState() { @JvmField var telephonyDisplayInfo = TelephonyDisplayInfo(TelephonyManager.NETWORK_TYPE_UNKNOWN, @@ -49,6 +53,11 @@ internal class MobileState( @JvmField var serviceState: ServiceState? = null @JvmField var signalStrength: SignalStrength? = null + var carrierId = TelephonyManager.UNKNOWN_CARRIER_ID + + @VisibleForTesting + var networkTypeResIdCache: NetworkTypeResIdCache = NetworkTypeResIdCache() + /** @return true if this state is disabled or not default data */ val isDataDisabledOrNotDefault: Boolean get() = (iconGroup === TelephonyIcons.DATA_DISABLED || @@ -125,6 +134,21 @@ internal class MobileState( return serviceState != null && serviceState!!.roaming } + /** + * + * Load the (potentially customized) icon resource id for the current network type. Note that + * this operation caches the result. Note that reading the [MobileIconGroup.dataType] field + * directly will not yield correct results in cases where the carrierId has an associated + * override. This is the preferred method for getting the network type indicator. + * + * @return a drawable res id appropriate for the current (carrierId, networkType) pair + */ + @DrawableRes + fun getNetworkTypeIcon(context: Context): Int { + val icon = (iconGroup as MobileIconGroup) + return networkTypeResIdCache.get(icon, carrierId, context) + } + fun setFromMobileStatus(mobileStatus: MobileStatus) { activityIn = mobileStatus.activityIn activityOut = mobileStatus.activityOut @@ -140,6 +164,7 @@ internal class MobileState( super.toString(builder) builder.append(',') builder.append("dataSim=$dataSim,") + builder.append("carrierId=$carrierId") builder.append("networkName=$networkName,") builder.append("networkNameData=$networkNameData,") builder.append("dataConnected=$dataConnected,") @@ -157,6 +182,8 @@ internal class MobileState( builder.append("voiceServiceState=${getVoiceServiceState()},") builder.append("isInService=${isInService()},") + builder.append("networkTypeIconCache=$networkTypeResIdCache") + builder.append("serviceState=${serviceState?.minLog() ?: "(null)"},") builder.append("signalStrength=${signalStrength?.minLog() ?: "(null)"},") builder.append("displayInfo=$telephonyDisplayInfo") @@ -164,6 +191,7 @@ internal class MobileState( override fun tableColumns(): List<String> { val columns = listOf("dataSim", + "carrierId", "networkName", "networkNameData", "dataConnected", @@ -178,6 +206,7 @@ internal class MobileState( "showQuickSettingsRatIcon", "voiceServiceState", "isInService", + "networkTypeIconCache", "serviceState", "signalStrength", "displayInfo") @@ -187,6 +216,7 @@ internal class MobileState( override fun tableData(): List<String> { val columns = listOf(dataSim, + carrierId, networkName, networkNameData, dataConnected, @@ -201,6 +231,7 @@ internal class MobileState( showQuickSettingsRatIcon(), getVoiceServiceState(), isInService(), + networkTypeResIdCache, serviceState?.minLog() ?: "(null)", signalStrength?.minLog() ?: "(null)", telephonyDisplayInfo).map { it.toString() } @@ -217,6 +248,7 @@ internal class MobileState( if (networkName != other.networkName) return false if (networkNameData != other.networkNameData) return false + if (carrierId != other.carrierId) return false if (dataSim != other.dataSim) return false if (dataConnected != other.dataConnected) return false if (isEmergency != other.isEmergency) return false @@ -238,6 +270,7 @@ internal class MobileState( var result = super.hashCode() result = 31 * result + (networkName?.hashCode() ?: 0) result = 31 * result + (networkNameData?.hashCode() ?: 0) + result = 31 * result + (carrierId.hashCode()) result = 31 * result + dataSim.hashCode() result = 31 * result + dataConnected.hashCode() result = 31 * result + isEmergency.hashCode() diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java index 450b757295bc..73d6483e65fb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java @@ -22,6 +22,7 @@ import static android.net.wifi.WifiManager.TrafficStateCallback.DATA_ACTIVITY_IN import static android.net.wifi.WifiManager.TrafficStateCallback.DATA_ACTIVITY_INOUT; import static android.net.wifi.WifiManager.TrafficStateCallback.DATA_ACTIVITY_NONE; import static android.net.wifi.WifiManager.TrafficStateCallback.DATA_ACTIVITY_OUT; +import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID; import android.annotation.Nullable; import android.content.BroadcastReceiver; @@ -138,7 +139,7 @@ public class NetworkControllerImpl extends BroadcastReceiver private final MobileSignalControllerFactory mMobileFactory; private TelephonyCallback.ActiveDataSubscriptionIdListener mPhoneStateListener; - private int mActiveMobileDataSubscription = SubscriptionManager.INVALID_SUBSCRIPTION_ID; + private int mActiveMobileDataSubscription = INVALID_SUBSCRIPTION_ID; // Subcontrollers. @VisibleForTesting @@ -502,6 +503,7 @@ public class NetworkControllerImpl extends BroadcastReceiver filter.addAction(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED); filter.addAction(TelephonyManager.ACTION_DEFAULT_VOICE_SUBSCRIPTION_CHANGED); filter.addAction(TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED); + filter.addAction(TelephonyManager.ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED); filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION); mBroadcastDispatcher.registerReceiverWithHandler(this, filter, mReceiverHandler); mListening = true; @@ -792,6 +794,20 @@ public class NetworkControllerImpl extends BroadcastReceiver mConfig = Config.readConfig(mContext); mReceiverHandler.post(this::handleConfigurationChanged); break; + + case TelephonyManager.ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED: { + // Notify the relevant MobileSignalController of the change + int subId = intent.getIntExtra( + TelephonyManager.EXTRA_SUBSCRIPTION_ID, + INVALID_SUBSCRIPTION_ID + ); + if (SubscriptionManager.isValidSubscriptionId(subId)) { + if (mMobileSignalControllers.indexOfKey(subId) >= 0) { + mMobileSignalControllers.get(subId).handleBroadcast(intent); + } + } + } + break; case Intent.ACTION_SIM_STATE_CHANGED: // Avoid rebroadcast because SysUI is direct boot aware. if (intent.getBooleanExtra(Intent.EXTRA_REBROADCAST_ON_UNLOCK, false)) { @@ -819,7 +835,7 @@ public class NetworkControllerImpl extends BroadcastReceiver break; default: int subId = intent.getIntExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, - SubscriptionManager.INVALID_SUBSCRIPTION_ID); + INVALID_SUBSCRIPTION_ID); if (SubscriptionManager.isValidSubscriptionId(subId)) { if (mMobileSignalControllers.indexOfKey(subId) >= 0) { mMobileSignalControllers.get(subId).handleBroadcast(intent); @@ -1335,6 +1351,9 @@ public class NetworkControllerImpl extends BroadcastReceiver String slotString = args.getString("slot"); int slot = TextUtils.isEmpty(slotString) ? 0 : Integer.parseInt(slotString); slot = MathUtils.constrain(slot, 0, 8); + String carrierIdString = args.getString("carrierid"); + int carrierId = TextUtils.isEmpty(carrierIdString) ? 0 + : Integer.parseInt(carrierIdString); // Ensure we have enough sim slots List<SubscriptionInfo> subs = new ArrayList<>(); while (mMobileSignalControllers.size() <= slot) { @@ -1346,6 +1365,9 @@ public class NetworkControllerImpl extends BroadcastReceiver } // Hack to index linearly for easy use. MobileSignalController controller = mMobileSignalControllers.valueAt(slot); + if (carrierId != 0) { + controller.getState().setCarrierId(carrierId); + } controller.getState().dataSim = datatype != null; controller.getState().isDefault = datatype != null; controller.getState().dataConnected = datatype != null; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkTypeResIdCache.kt b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkTypeResIdCache.kt new file mode 100644 index 000000000000..9be7ee99cf02 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkTypeResIdCache.kt @@ -0,0 +1,78 @@ +/* + * 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.connectivity + +import android.annotation.DrawableRes +import android.content.Context +import com.android.settingslib.SignalIcon.MobileIconGroup +import com.android.settingslib.mobile.MobileIconCarrierIdOverrides +import com.android.settingslib.mobile.MobileIconCarrierIdOverridesImpl + +/** + * Cache for network type resource IDs. + * + * The default framework behavior is to have a statically defined icon per network type. See + * [MobileIconGroup] for the standard mapping. + * + * For the case of carrierId-defined overrides, we want to check [MobileIconCarrierIdOverrides] for + * an existing icon override, and cache the result of the operation + */ +class NetworkTypeResIdCache( + private val overrides: MobileIconCarrierIdOverrides = MobileIconCarrierIdOverridesImpl() +) { + @DrawableRes private var cachedResId: Int = 0 + private var lastCarrierId: Int? = null + private var lastIconGroup: MobileIconGroup? = null + private var isOverridden: Boolean = false + + @DrawableRes + fun get(iconGroup: MobileIconGroup, carrierId: Int, context: Context): Int { + if (lastCarrierId != carrierId || lastIconGroup != iconGroup) { + lastCarrierId = carrierId + lastIconGroup = iconGroup + + val maybeOverride = calculateOverriddenIcon(iconGroup, carrierId, context) + if (maybeOverride > 0) { + cachedResId = maybeOverride + isOverridden = true + } else { + cachedResId = iconGroup.dataType + isOverridden = false + } + } + + return cachedResId + } + + override fun toString(): String { + return "networkTypeResIdCache={id=$cachedResId, isOverridden=$isOverridden}" + } + + @DrawableRes + private fun calculateOverriddenIcon( + iconGroup: MobileIconGroup, + carrierId: Int, + context: Context, + ): Int { + val name = iconGroup.name + if (!overrides.carrierIdEntryExists(carrierId)) { + return 0 + } + + return overrides.getOverrideFor(carrierId, name, context.resources) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ui/MobileContextProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ui/MobileContextProvider.kt index a02dd3490341..42b874fd7156 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ui/MobileContextProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ui/MobileContextProvider.kt @@ -37,6 +37,13 @@ import javax.inject.Inject * own [Configuration] and track resources based on the full set of available mcc-mnc combinations. * * (for future reference: b/240555502 is the initiating bug for this) + * + * NOTE: MCC/MNC qualifiers are not sufficient to fully describe a network type icon qualified by + * network type + carrier ID. This class exists to keep the legacy behavior of using the MCC/MNC + * resource qualifiers working, but if a carrier-specific icon is requested, then the override + * provided by [MobileIconCarrierIdOverrides] will take precedence. + * + * TODO(b/258503704): consider removing this class in favor of the `carrierId` overrides */ @SysUISingleton class MobileContextProvider diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/util/MobileMappings.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/util/MobileMappings.kt index 60bd0383f8c7..501467f13007 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/util/MobileMappings.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/util/MobileMappings.kt @@ -32,6 +32,7 @@ import javax.inject.Inject interface MobileMappingsProxy { fun mapIconSets(config: Config): Map<String, MobileIconGroup> fun getDefaultIcons(config: Config): MobileIconGroup + fun getIconKey(displayInfo: TelephonyDisplayInfo): String fun toIconKey(@NetworkType networkType: Int): String fun toIconKeyOverride(@NetworkType networkType: Int): String } @@ -44,6 +45,9 @@ class MobileMappingsProxyImpl @Inject constructor() : MobileMappingsProxy { override fun getDefaultIcons(config: Config): MobileIconGroup = MobileMappings.getDefaultIcons(config) + override fun getIconKey(displayInfo: TelephonyDisplayInfo): String = + MobileMappings.getIconKey(displayInfo) + override fun toIconKey(@NetworkType networkType: Int): String = MobileMappings.toIconKey(networkType) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/MobileIconCarrierIdOverridesFake.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/MobileIconCarrierIdOverridesFake.kt new file mode 100644 index 000000000000..62b4e7b79f5e --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/MobileIconCarrierIdOverridesFake.kt @@ -0,0 +1,48 @@ +/* + * 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.connectivity + +import android.content.res.Resources +import com.android.settingslib.mobile.MobileIconCarrierIdOverrides + +typealias CarrierId = Int + +typealias NetworkType = String + +typealias ResId = Int + +class MobileIconCarrierIdOverridesFake : MobileIconCarrierIdOverrides { + /** Backing for [carrierIdEntryExists] */ + var overriddenIds = mutableSetOf<Int>() + + /** Backing for [getOverrideFor]. Map should be Map< CarrierId < NetworkType, ResId>> */ + var overridesByCarrierId = mutableMapOf<CarrierId, Map<NetworkType, ResId>>() + + override fun getOverrideFor( + carrierId: CarrierId, + networkType: NetworkType, + resources: Resources + ): ResId { + if (!overriddenIds.contains(carrierId)) return 0 + + return overridesByCarrierId[carrierId]?.get(networkType) ?: 0 + } + + override fun carrierIdEntryExists(carrierId: Int): Boolean { + return overriddenIds.contains(carrierId) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/MobileStateTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/MobileStateTest.java deleted file mode 100644 index 7ddfde370afa..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/MobileStateTest.java +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Copyright (C) 2021 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.connectivity; - -import static junit.framework.Assert.assertFalse; -import static junit.framework.Assert.assertTrue; - -import android.test.suitebuilder.annotation.SmallTest; -import android.testing.AndroidTestingRunner; - -import com.android.settingslib.mobile.TelephonyIcons; -import com.android.systemui.SysuiTestCase; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -@SmallTest -@RunWith(AndroidTestingRunner.class) -public class MobileStateTest extends SysuiTestCase { - - private final MobileState mState = new MobileState(); - - @Before - public void setUp() { - } - - @Test - public void testIsDataDisabledOrNotDefault_dataDisabled() { - mState.iconGroup = TelephonyIcons.DATA_DISABLED; - mState.userSetup = true; - - assertTrue(mState.isDataDisabledOrNotDefault()); - } - - @Test - public void testIsDataDisabledOrNotDefault_notDefaultData() { - mState.iconGroup = TelephonyIcons.NOT_DEFAULT_DATA; - mState.userSetup = true; - - assertTrue(mState.isDataDisabledOrNotDefault()); - } - - @Test - public void testIsDataDisabledOrNotDefault_notDisabled() { - mState.iconGroup = TelephonyIcons.G; - mState.userSetup = true; - - assertFalse(mState.isDataDisabledOrNotDefault()); - } - - @Test - public void testHasActivityIn_noData_noActivity() { - mState.dataConnected = false; - mState.carrierNetworkChangeMode = false; - mState.activityIn = false; - - assertFalse(mState.hasActivityIn()); - } - - @Test - public void testHasActivityIn_noData_activityIn() { - mState.dataConnected = false; - mState.carrierNetworkChangeMode = false; - mState.activityIn = true; - - assertFalse(mState.hasActivityIn()); - } - - @Test - public void testHasActivityIn_dataConnected_activityIn() { - mState.dataConnected = true; - mState.carrierNetworkChangeMode = false; - mState.activityIn = true; - - assertTrue(mState.hasActivityIn()); - } - - @Test - public void testHasActivityIn_carrierNetworkChange() { - mState.dataConnected = true; - mState.carrierNetworkChangeMode = true; - mState.activityIn = true; - - assertFalse(mState.hasActivityIn()); - } - - @Test - public void testHasActivityOut_noData_noActivity() { - mState.dataConnected = false; - mState.carrierNetworkChangeMode = false; - mState.activityOut = false; - - assertFalse(mState.hasActivityOut()); - } - - @Test - public void testHasActivityOut_noData_activityOut() { - mState.dataConnected = false; - mState.carrierNetworkChangeMode = false; - mState.activityOut = true; - - assertFalse(mState.hasActivityOut()); - } - - @Test - public void testHasActivityOut_dataConnected_activityOut() { - mState.dataConnected = true; - mState.carrierNetworkChangeMode = false; - mState.activityOut = true; - - assertTrue(mState.hasActivityOut()); - } - - @Test - public void testHasActivityOut_carrierNetworkChange() { - mState.dataConnected = true; - mState.carrierNetworkChangeMode = true; - mState.activityOut = true; - - assertFalse(mState.hasActivityOut()); - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/MobileStateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/MobileStateTest.kt new file mode 100644 index 000000000000..a226ded06111 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/MobileStateTest.kt @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2021 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.connectivity + +import android.test.suitebuilder.annotation.SmallTest +import android.testing.AndroidTestingRunner +import com.android.settingslib.mobile.TelephonyIcons +import com.android.systemui.SysuiTestCase +import junit.framework.Assert.assertFalse +import junit.framework.Assert.assertTrue +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidTestingRunner::class) +class MobileStateTest : SysuiTestCase() { + + private val state = MobileState() + @Before fun setUp() {} + + @Test + fun testIsDataDisabledOrNotDefault_dataDisabled() { + state.iconGroup = TelephonyIcons.DATA_DISABLED + state.userSetup = true + assertTrue(state.isDataDisabledOrNotDefault) + } + + @Test + fun testIsDataDisabledOrNotDefault_notDefaultData() { + state.iconGroup = TelephonyIcons.NOT_DEFAULT_DATA + state.userSetup = true + assertTrue(state.isDataDisabledOrNotDefault) + } + + @Test + fun testIsDataDisabledOrNotDefault_notDisabled() { + state.iconGroup = TelephonyIcons.G + state.userSetup = true + assertFalse(state.isDataDisabledOrNotDefault) + } + + @Test + fun testHasActivityIn_noData_noActivity() { + state.dataConnected = false + state.carrierNetworkChangeMode = false + state.activityIn = false + assertFalse(state.hasActivityIn()) + } + + @Test + fun testHasActivityIn_noData_activityIn() { + state.dataConnected = false + state.carrierNetworkChangeMode = false + state.activityIn = true + assertFalse(state.hasActivityIn()) + } + + @Test + fun testHasActivityIn_dataConnected_activityIn() { + state.dataConnected = true + state.carrierNetworkChangeMode = false + state.activityIn = true + assertTrue(state.hasActivityIn()) + } + + @Test + fun testHasActivityIn_carrierNetworkChange() { + state.dataConnected = true + state.carrierNetworkChangeMode = true + state.activityIn = true + assertFalse(state.hasActivityIn()) + } + + @Test + fun testHasActivityOut_noData_noActivity() { + state.dataConnected = false + state.carrierNetworkChangeMode = false + state.activityOut = false + assertFalse(state.hasActivityOut()) + } + + @Test + fun testHasActivityOut_noData_activityOut() { + state.dataConnected = false + state.carrierNetworkChangeMode = false + state.activityOut = true + assertFalse(state.hasActivityOut()) + } + + @Test + fun testHasActivityOut_dataConnected_activityOut() { + state.dataConnected = true + state.carrierNetworkChangeMode = false + state.activityOut = true + assertTrue(state.hasActivityOut()) + } + + @Test + fun testHasActivityOut_carrierNetworkChange() { + state.dataConnected = true + state.carrierNetworkChangeMode = true + state.activityOut = true + assertFalse(state.hasActivityOut()) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java index 9c65fac1af45..9c870b5aa363 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java @@ -71,6 +71,7 @@ import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.demomode.DemoModeController; import com.android.systemui.dump.DumpManager; import com.android.systemui.plugins.log.LogBuffer; +import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy; import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener; import com.android.systemui.telephony.TelephonyListenerManager; @@ -125,6 +126,8 @@ public class NetworkControllerBaseTest extends SysuiTestCase { protected CarrierConfigTracker mCarrierConfigTracker; protected FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock()); protected Handler mMainHandler; + // Use a real mobile mappings object since lots of tests rely on it + protected FakeMobileMappingsProxy mMobileMappingsProxy = new FakeMobileMappingsProxy(); protected WifiStatusTrackerFactory mWifiStatusTrackerFactory; protected MobileSignalControllerFactory mMobileFactory; @@ -219,10 +222,13 @@ public class NetworkControllerBaseTest extends SysuiTestCase { mWifiStatusTrackerFactory = new WifiStatusTrackerFactory( mContext, mMockWm, mMockNsm, mMockCm, mMainHandler); + // Most of these tests rely on the actual MobileMappings behavior + mMobileMappingsProxy.setUseRealImpl(true); mMobileFactory = new MobileSignalControllerFactory( mContext, mCallbackHandler, - mCarrierConfigTracker + mCarrierConfigTracker, + mMobileMappingsProxy ); mNetworkController = new NetworkControllerImpl(mContext, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java index 4bed4a19b3d9..1d112262765e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java @@ -18,12 +18,21 @@ package com.android.systemui.statusbar.connectivity; import static android.telephony.AccessNetworkConstants.TRANSPORT_TYPE_WWAN; import static android.telephony.NetworkRegistrationInfo.DOMAIN_PS; +import static android.telephony.TelephonyManager.ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED; +import static android.telephony.TelephonyManager.EXTRA_CARRIER_ID; +import static android.telephony.TelephonyManager.EXTRA_SUBSCRIPTION_ID; +import static com.android.settingslib.mobile.TelephonyIcons.NR_5G_PLUS; + +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import android.content.Intent; import android.net.NetworkCapabilities; import android.os.Handler; import android.os.Looper; @@ -35,6 +44,7 @@ import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; +import com.android.settingslib.SignalIcon.MobileIconGroup; import com.android.settingslib.mobile.TelephonyIcons; import com.android.settingslib.net.DataUsageController; import com.android.systemui.dump.DumpManager; @@ -45,6 +55,8 @@ import com.android.systemui.util.CarrierConfigTracker; import org.junit.Test; import org.junit.runner.RunWith; +import java.util.HashMap; + @SmallTest @RunWith(AndroidTestingRunner.class) @RunWithLooper @@ -329,6 +341,57 @@ public class NetworkControllerDataTest extends NetworkControllerBaseTest { assertFalse(mNetworkController.isMobileDataNetworkInService()); } + @Test + public void mobileSignalController_getsCarrierId() { + when(mMockTm.getSimCarrierId()).thenReturn(1); + setupDefaultSignal(); + + assertEquals(1, mMobileSignalController.getState().getCarrierId()); + } + + @Test + public void mobileSignalController_updatesCarrierId_onChange() { + when(mMockTm.getSimCarrierId()).thenReturn(1); + setupDefaultSignal(); + + // Updates are sent down through this broadcast, we can send the intent directly + Intent intent = new Intent(ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED); + intent.putExtra(EXTRA_SUBSCRIPTION_ID, mSubId); + intent.putExtra(EXTRA_CARRIER_ID, 2); + + mMobileSignalController.handleBroadcast(intent); + + assertEquals(2, mMobileSignalController.getState().getCarrierId()); + } + + @Test + public void networkTypeIcon_hasCarrierIdOverride() { + int fakeCarrier = 1; + int fakeIconOverride = 12345; + int testDataNetType = 100; + String testDataString = "100"; + HashMap<String, MobileIconGroup> testMap = new HashMap<>(); + testMap.put(testDataString, NR_5G_PLUS); + + // Pretend that there is an override for this icon, and this carrier ID + NetworkTypeResIdCache mockCache = mock(NetworkTypeResIdCache.class); + when(mockCache.get(eq(NR_5G_PLUS), eq(fakeCarrier), any())).thenReturn(fakeIconOverride); + + // Turn off the default mobile mapping, so we can override + mMobileMappingsProxy.setUseRealImpl(false); + mMobileMappingsProxy.setIconMap(testMap); + // Use the mocked cache + mMobileSignalController.mCurrentState.setNetworkTypeResIdCache(mockCache); + // Rebuild the network map + mMobileSignalController.setConfiguration(mConfig); + when(mMockTm.getSimCarrierId()).thenReturn(fakeCarrier); + + setupDefaultSignal(); + updateDataConnectionState(TelephonyManager.DATA_CONNECTED, testDataNetType); + + verifyDataIndicators(fakeIconOverride); + } + private void testDataActivity(int direction, boolean in, boolean out) { updateDataActivity(direction); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkTypeResIdCacheTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkTypeResIdCacheTest.kt new file mode 100644 index 000000000000..9e73487972e8 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkTypeResIdCacheTest.kt @@ -0,0 +1,83 @@ +/* + * 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.connectivity + +import android.test.suitebuilder.annotation.SmallTest +import android.testing.AndroidTestingRunner +import com.android.settingslib.SignalIcon.MobileIconGroup +import com.android.systemui.SysuiTestCase +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidTestingRunner::class) +class NetworkTypeResIdCacheTest : SysuiTestCase() { + private lateinit var cache: NetworkTypeResIdCache + private var overrides = MobileIconCarrierIdOverridesFake() + + @Before + fun setUp() { + cache = NetworkTypeResIdCache(overrides) + } + + @Test + fun carrier1_noOverride_usesDefault() { + assertThat(cache.get(group1, CARRIER_1, context)).isEqualTo(iconDefault1) + } + + @Test + fun carrier1_overridden_usesOverride() { + overrides.overriddenIds.add(CARRIER_1) + overrides.overridesByCarrierId[CARRIER_1] = mapOf(NET_TYPE_1 to iconOverride1) + + assertThat(cache.get(group1, CARRIER_1, context)).isEqualTo(iconOverride1) + } + + @Test + fun carrier1_override_carrier2UsesDefault() { + overrides.overriddenIds.add(CARRIER_1) + overrides.overridesByCarrierId[CARRIER_1] = mapOf(NET_TYPE_1 to iconOverride1) + + assertThat(cache.get(group1, CARRIER_2, context)).isEqualTo(iconDefault1) + } + + @Test + fun carrier1_overrideType1_type2UsesDefault() { + overrides.overriddenIds.add(CARRIER_1) + overrides.overridesByCarrierId[CARRIER_1] = mapOf(NET_TYPE_1 to iconOverride1) + + assertThat(cache.get(group2, CARRIER_1, context)).isEqualTo(iconDefault2) + } + + companion object { + // Simplified icon overrides here + const val CARRIER_1 = 1 + const val CARRIER_2 = 2 + + const val NET_TYPE_1 = "one" + const val iconDefault1 = 123 + const val iconOverride1 = 321 + val group1 = MobileIconGroup(NET_TYPE_1, /* dataContentDesc */ 0, iconDefault1) + + const val NET_TYPE_2 = "two" + const val iconDefault2 = 234 + + val group2 = MobileIconGroup(NET_TYPE_2, /* dataContentDesc*/ 0, iconDefault2) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/util/FakeMobileMappingsProxy.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/util/FakeMobileMappingsProxy.kt index 6d8d902615de..a052008d4832 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/util/FakeMobileMappingsProxy.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/util/FakeMobileMappingsProxy.kt @@ -16,31 +16,59 @@ package com.android.systemui.statusbar.pipeline.mobile.util +import android.telephony.TelephonyDisplayInfo import com.android.settingslib.SignalIcon.MobileIconGroup import com.android.settingslib.mobile.MobileMappings.Config import com.android.settingslib.mobile.TelephonyIcons class FakeMobileMappingsProxy : MobileMappingsProxy { + // The old [NetworkControllerDataTest] infra requires us to be able to use the real + // impl sometimes + var useRealImpl = false + + private var realImpl = MobileMappingsProxyImpl() private var iconMap = mapOf<String, MobileIconGroup>() private var defaultIcons = TelephonyIcons.THREE_G fun setIconMap(map: Map<String, MobileIconGroup>) { iconMap = map } - override fun mapIconSets(config: Config): Map<String, MobileIconGroup> = iconMap + override fun mapIconSets(config: Config): Map<String, MobileIconGroup> { + if (useRealImpl) { + return realImpl.mapIconSets(config) + } + return iconMap + } fun getIconMap() = iconMap fun setDefaultIcons(group: MobileIconGroup) { defaultIcons = group } - override fun getDefaultIcons(config: Config): MobileIconGroup = defaultIcons + override fun getDefaultIcons(config: Config): MobileIconGroup { + if (useRealImpl) { + return realImpl.getDefaultIcons(config) + } + return defaultIcons + } + + /** This is only used in the old pipeline, use the real impl always */ + override fun getIconKey(displayInfo: TelephonyDisplayInfo): String { + return realImpl.getIconKey(displayInfo) + } + fun getDefaultIcons(): MobileIconGroup = defaultIcons override fun toIconKey(networkType: Int): String { + if (useRealImpl) { + return realImpl.toIconKeyOverride(networkType) + } return networkType.toString() } override fun toIconKeyOverride(networkType: Int): String { + if (useRealImpl) { + return realImpl.toIconKeyOverride(networkType) + } return toIconKey(networkType) + "_override" } } |