Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit bb7f9638 authored by Evan Laird's avatar Evan Laird
Browse files

Enable network type icon overlays by carrier Id

This CL builds out a new configuration resource
`carrierid_icon_overrides`, and a format that allows for overriding a
known network type with an arbitrary icon.

We store a mapping of all known carrierId overrides that exist, and
check for an override for the `carrierId & networkType` combination at
runtime.

This CL also caches the calculation in SystemUI since it represents a
new expensive calculation potential at the time of display for the icon.

Lastly, add demo mode command parsing for carrierId

Test: atest MobileStateTest; atest NetworkControllerDataTest
Test: manual using `-e carrierid <id>` network subcommand
Bug: 241996752
Change-Id: Id0e8acf179db5c3a70f4caf31e58f8d8040690a0
parent 172b95c2
Loading
Loading
Loading
Loading
+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>
+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
+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)
    }
}
+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;
        }
    }
}
+18 −10
Original line number Diff line number Diff line
@@ -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 {
Loading