1 /* 2 * Copyright (C) 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.settingslib.mobile 18 19 import android.annotation.DrawableRes 20 import android.content.res.Resources 21 import android.content.res.TypedArray 22 import android.util.Log 23 import androidx.annotation.VisibleForTesting 24 import com.android.settingslib.R 25 import com.android.settingslib.SignalIcon.MobileIconGroup 26 27 /** 28 * This class defines a network type (3G, 4G, etc.) override mechanism on a per-carrierId basis. 29 * 30 * Traditionally, carrier-customized network type iconography was achieved using the `MCC/MNC` 31 * resource qualifiers, and swapping out the drawable resource by name. It would look like this: 32 * 33 * res/ 34 * drawable/ 35 * 3g_mobiledata_icon.xml 36 * drawable-MCC-MNC/ 37 * 3g_mobiledata_icon.xml 38 * 39 * This would mean that, provided a context created with this MCC/MNC configuration set, loading 40 * the network type icon through [MobileIconGroup] would provide a carrier-defined network type 41 * icon rather than the AOSP-defined default. 42 * 43 * The MCC/MNC mechanism no longer can fully define carrier-specific network type icons, because 44 * there is no longer a 1:1 mapping between MCC/MNC and carrier. With the advent of MVNOs, multiple 45 * carriers can have the same MCC/MNC value, but wish to differentiate based on their carrier ID. 46 * CarrierId is a newer concept than MCC/MNC, and provides more granularity when it comes to 47 * determining the carrier (e.g. MVNOs can share MCC/MNC values with the network owner), therefore 48 * it can fit all of the same use cases currently handled by `MCC/MNC`, without the need to apply a 49 * configuration context in order to get the proper UI for a given SIM icon. 50 * 51 * NOTE: CarrierId icon overrides will always take precedence over those defined using `MCC/MNC` 52 * resource qualifiers. 53 * 54 * [MAPPING] encodes the relationship between CarrierId and the corresponding override array 55 * that exists in the config.xml. An alternative approach could be to generate the resource name 56 * by string concatenation at run-time: 57 * 58 * val resName = "carrierId_$carrierId_iconOverrides" 59 * val override = resources.getResourceIdentifier(resName) 60 * 61 * However, that's going to be far less efficient until MAPPING grows to a sufficient size. For now, 62 * given a relatively small number of entries, we should just maintain the mapping here. 63 */ 64 interface MobileIconCarrierIdOverrides { 65 @DrawableRes getOverrideFornull66 fun getOverrideFor(carrierId: Int, networkType: String, resources: Resources): Int 67 fun carrierIdEntryExists(carrierId: Int): Boolean 68 } 69 70 class MobileIconCarrierIdOverridesImpl : MobileIconCarrierIdOverrides { 71 @DrawableRes 72 override fun getOverrideFor(carrierId: Int, networkType: String, resources: Resources): Int { 73 val resId = MAPPING[carrierId] ?: return 0 74 val ta = resources.obtainTypedArray(resId) 75 val map = parseNetworkIconOverrideTypedArray(ta) 76 ta.recycle() 77 return map[networkType] ?: 0 78 } 79 80 override fun carrierIdEntryExists(carrierId: Int) = 81 overrideExists(carrierId, MAPPING) 82 83 companion object { 84 private const val TAG = "MobileIconOverrides" 85 /** 86 * This map maintains the lookup from the canonical carrier ID (see below link) to the 87 * corresponding overlay resource. New overrides should add an entry below in order to 88 * change the network type icon resources based on carrier ID 89 * 90 * Refer to the link below for the canonical mapping maintained in AOSP: 91 * https://android.googlesource.com/platform/packages/providers/TelephonyProvider/+/master/assets/latest_carrier_id/carrier_list.textpb 92 */ 93 private val MAPPING = mapOf( 94 // 2032 == Xfinity Mobile 95 2032 to R.array.carrierId_2032_iconOverrides, 96 ) 97 98 /** 99 * Parse `carrierId_XXXX_iconOverrides` for a particular network type. The resource file 100 * "carrierid_icon_overrides.xml" defines a TypedArray format for overriding specific 101 * network type icons (a.k.a. RAT icons) for a particular carrier ID. The format is defined 102 * as an array of (network type name, drawable) pairs: 103 * <array name="carrierId_XXXX_iconOverrides> 104 * <item>NET_TYPE_1</item> 105 * <item>@drawable/net_type_1_override</item> 106 * <item>NET_TYPE_2</item> 107 * <item>@drawable/net_type_2_override</item> 108 * </array> 109 * 110 * @param ta the [TypedArray] defined in carrierid_icon_overrides.xml 111 * @return the overridden drawable resource ID if it exists, or 0 if it does not 112 */ 113 @VisibleForTesting 114 @JvmStatic 115 fun parseNetworkIconOverrideTypedArray(ta: TypedArray): Map<String, Int> { 116 if (ta.length() % 2 != 0) { 117 Log.w(TAG, 118 "override must contain an even number of (key, value) entries. skipping") 119 120 return mapOf() 121 } 122 123 val result = mutableMapOf<String, Int>() 124 // The array is defined as Pair(String, resourceId), so walk by 2 125 for (i in 0 until ta.length() step 2) { 126 val key = ta.getString(i) 127 val override = ta.getResourceId(i + 1, 0) 128 if (key == null || override == 0) { 129 Log.w(TAG, "Invalid override found. Skipping") 130 continue 131 } 132 result[key] = override 133 } 134 135 return result 136 } 137 138 @JvmStatic 139 private fun overrideExists(carrierId: Int, mapping: Map<Int, Int>): Boolean = 140 mapping.containsKey(carrierId) 141 } 142 } 143