Loading packages/SettingsLib/res/drawable/ic_5g_plus_mobiledata_default.xml 0 → 100644 +33 −0 Original line number Diff line number Diff line <!-- 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> packages/SettingsLib/res/values/carrierid_icon_overrides.xml 0 → 100644 +32 −0 Original line number Diff line number Diff line <?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 packages/SettingsLib/src/com/android/settingslib/mobile/MobileIconCarrierIdOverrides.kt 0 → 100644 +142 −0 Original line number Diff line number Diff line /* * 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) } } packages/SettingsLib/tests/robotests/src/com/android/settingslib/mobile/MobileIconCarrierIdOverridesTest.java 0 → 100644 +140 −0 Original line number Diff line number Diff line /* * 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; } } } packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileSignalController.java +18 −10 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading @@ -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; Loading Loading @@ -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, Loading @@ -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; Loading @@ -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(); } Loading Loading @@ -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, Loading Loading @@ -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); } } Loading Loading @@ -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 { Loading Loading
packages/SettingsLib/res/drawable/ic_5g_plus_mobiledata_default.xml 0 → 100644 +33 −0 Original line number Diff line number Diff line <!-- 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>
packages/SettingsLib/res/values/carrierid_icon_overrides.xml 0 → 100644 +32 −0 Original line number Diff line number Diff line <?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
packages/SettingsLib/src/com/android/settingslib/mobile/MobileIconCarrierIdOverrides.kt 0 → 100644 +142 −0 Original line number Diff line number Diff line /* * 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) } }
packages/SettingsLib/tests/robotests/src/com/android/settingslib/mobile/MobileIconCarrierIdOverridesTest.java 0 → 100644 +140 −0 Original line number Diff line number Diff line /* * 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; } } }
packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileSignalController.java +18 −10 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading @@ -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; Loading Loading @@ -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, Loading @@ -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; Loading @@ -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(); } Loading Loading @@ -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, Loading Loading @@ -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); } } Loading Loading @@ -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 { Loading