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

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

Create Configuration overrides for MCC/MNC values

SystemUI currently only shows the RAT type indicator for the active
network, which may change. At the same time, the Resources system does
not track the active mobile network, which means that
`context.getResources().getConfiguration()` may return a `Configuration`
object with an incorrect MCC/MNC for the current active subscription.

This CL creates a `MobileContextProvider` class, which will lazily
generate `Context`s with a correctly overridden MCC/MNC, so that
resource overlays properly load individual carrier iconography when the
active data subscription changes.

We also will load each individual mobile icon with the proper context,
in case there is ever a need to show the RAT type indicator for multiple
subscriptions at once.

Lastly, this CL adds a new "mccmnc" arg string to demo mode so that we
can easily validate RAT type indicator overlays.

Test: atest MobileContextProviderTest
Test: use the new `mccmnc` string in sysui demo mode to manually verify
Fixes: 240555502
Change-Id: If1f7d31a46f19f335b9545ca33638786a423ba0d
parent 3315391b
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -78,6 +78,9 @@ public class MobileStatusTracker {
     * Config the MobileStatusTracker to start or stop monitoring platform signals.
     */
    public void setListening(boolean listening) {
        if (mListening == listening) {
            return;
        }
        mListening = listening;
        if (listening) {
            mPhone.registerTelephonyCallback(mReceiverHandler::post, mTelephonyCallback);
+10 −0
Original line number Diff line number Diff line
@@ -38,6 +38,7 @@ import com.android.settingslib.graph.SignalDrawable;
import com.android.systemui.DualToneHandler;
import com.android.systemui.R;
import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
import com.android.systemui.statusbar.phone.StatusBarIconController;
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState;

import java.util.ArrayList;
@@ -64,6 +65,15 @@ public class StatusBarMobileView extends FrameLayout implements DarkReceiver,

    /**
     * Designated constructor
     *
     * This view is special, in that it is the only view in SystemUI that allows for a configuration
     * override on a MCC/MNC-basis. This means that for every mobile view inflated, we have to
     * construct a context with that override, since the resource system doesn't have a way to
     * handle this for us.
     *
     * @param context A context with resources configured by MCC/MNC
     * @param slot The string key defining which slot this icon refers to. Always "mobile" for the
     *             mobile icon
     */
    public static StatusBarMobileView fromContext(
            Context context,
+137 −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.systemui.statusbar.connectivity.ui

import android.content.Context
import android.content.res.Configuration
import android.os.Bundle
import android.telephony.SubscriptionInfo
import android.view.ContextThemeWrapper
import com.android.systemui.Dumpable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.demomode.DemoMode
import com.android.systemui.demomode.DemoMode.COMMAND_NETWORK
import com.android.systemui.demomode.DemoModeController
import com.android.systemui.dump.DumpManager
import com.android.systemui.statusbar.connectivity.NetworkController
import com.android.systemui.statusbar.connectivity.SignalCallback
import java.io.PrintWriter
import javax.inject.Inject

/**
 * Every subscriptionId can have its own CarrierConfig associated with it, so we have to create our
 * 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)
 */
@SysUISingleton
class MobileContextProvider
@Inject
constructor(
    networkController: NetworkController,
    dumpManager: DumpManager,
    private val demoModeController: DemoModeController,
) : Dumpable, DemoMode {
    private val subscriptions = mutableMapOf<Int, SubscriptionInfo>()
    private val signalCallback =
        object : SignalCallback {
            override fun setSubs(subs: List<SubscriptionInfo>) {
                subscriptions.clear()
                subs.forEach { info -> subscriptions[info.subscriptionId] = info }
            }
        }

    // These should always be null when not in demo mode
    private var demoMcc: Int? = null
    private var demoMnc: Int? = null

    init {
        networkController.addCallback(signalCallback)
        dumpManager.registerDumpable(this)
        demoModeController.addCallback(this)
    }

    /**
     * @return a context with the MCC/MNC [Configuration] values corresponding to this
     * subscriptionId
     */
    fun getMobileContextForSub(subId: Int, context: Context): Context {
        if (demoModeController.isInDemoMode) {
            return createMobileContextForDemoMode(context)
        }

        // Fail back to the given context if no sub exists
        val info = subscriptions[subId] ?: return context

        return createCarrierConfigContext(context, info.mcc, info.mnc)
    }

    /** For Demo mode (for now), just apply the same MCC/MNC override for all subIds */
    private fun createMobileContextForDemoMode(context: Context): Context {
        return createCarrierConfigContext(context, demoMcc ?: 0, demoMnc ?: 0)
    }

    override fun dump(pw: PrintWriter, args: Array<out String>) {
        pw.println(
            "Subscriptions below will be inflated with a configuration context with " +
                "MCC/MNC overrides"
        )
        subscriptions.forEach { (subId, info) ->
            pw.println("  Subscription with subId($subId) with MCC/MNC(${info.mcc}/${info.mnc})")
        }
        pw.println("  MCC override: ${demoMcc ?: "(none)"}")
        pw.println("  MNC override: ${demoMnc ?: "(none)"}")
    }

    override fun demoCommands(): List<String> {
        return listOf(COMMAND_NETWORK)
    }

    override fun onDemoModeFinished() {
        demoMcc = null
        demoMnc = null
    }

    override fun dispatchDemoCommand(command: String, args: Bundle) {
        val mccmnc = args.getString("mccmnc") ?: return
        // Only length 5/6 strings are valid
        if (!(mccmnc.length == 5 || mccmnc.length == 6)) {
            return
        }

        // MCC is always the first 3 digits, and mnc is the last 2 or 3
        demoMcc = mccmnc.subSequence(0, 3).toString().toInt()
        demoMnc = mccmnc.subSequence(3, mccmnc.length).toString().toInt()
    }

    companion object {
        /**
         * Creates a context based on this [SubscriptionInfo]'s MCC/MNC values, allowing the overlay
         * system to properly load different carrier's iconography
         */
        private fun createCarrierConfigContext(context: Context, mcc: Int, mnc: Int): Context {
            // Copy the existing configuration
            val c = Configuration(context.resources.configuration)
            c.mcc = mcc
            c.mnc = mnc

            return ContextThemeWrapper(context, context.theme).also { ctx ->
                ctx.applyOverrideConfiguration(c)
            }
        }
    }
}
+22 −14
Original line number Diff line number Diff line
@@ -16,6 +16,8 @@

package com.android.systemui.statusbar.phone;

import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.graphics.drawable.Icon;
import android.os.Bundle;
@@ -29,13 +31,13 @@ import android.widget.LinearLayout;
import com.android.internal.statusbar.StatusBarIcon;
import com.android.systemui.R;
import com.android.systemui.demomode.DemoMode;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.plugins.DarkIconDispatcher;
import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
import com.android.systemui.statusbar.StatusBarIconView;
import com.android.systemui.statusbar.StatusBarMobileView;
import com.android.systemui.statusbar.StatusBarWifiView;
import com.android.systemui.statusbar.StatusIconDisplayable;
import com.android.systemui.statusbar.connectivity.ui.MobileContextProvider;
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState;
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState;

@@ -49,7 +51,6 @@ public class DemoStatusIcons extends StatusIconContainer implements DemoMode, Da
    private final LinearLayout mStatusIcons;
    private final ArrayList<StatusBarMobileView> mMobileViews = new ArrayList<>();
    private final int mIconSize;
    private final FeatureFlags mFeatureFlags;

    private StatusBarWifiView mWifiView;
    private boolean mDemoMode;
@@ -57,14 +58,12 @@ public class DemoStatusIcons extends StatusIconContainer implements DemoMode, Da

    public DemoStatusIcons(
            LinearLayout statusIcons,
            int iconSize,
            FeatureFlags featureFlags
            int iconSize
    ) {
        super(statusIcons.getContext());
        mStatusIcons = statusIcons;
        mIconSize = iconSize;
        mColor = DarkIconDispatcher.DEFAULT_ICON_TINT;
        mFeatureFlags = featureFlags;

        if (statusIcons instanceof StatusIconContainer) {
            setShouldRestrictIcons(((StatusIconContainer) statusIcons).isRestrictingIcons());
@@ -253,9 +252,13 @@ public class DemoStatusIcons extends StatusIconContainer implements DemoMode, Da
        }
    }

    public void addMobileView(MobileIconState state) {
    /**
     * Add a new mobile icon view
     */
    public void addMobileView(MobileIconState state, Context mobileContext) {
        Log.d(TAG, "addMobileView: ");
        StatusBarMobileView view = StatusBarMobileView.fromContext(mContext, state.slot);
        StatusBarMobileView view = StatusBarMobileView
                .fromContext(mobileContext, state.slot);

        view.applyMobileState(state);
        view.setStaticDrawableColor(mColor);
@@ -265,19 +268,24 @@ public class DemoStatusIcons extends StatusIconContainer implements DemoMode, Da
        addView(view, getChildCount(), createLayoutParams());
    }

    public void updateMobileState(MobileIconState state) {
        Log.d(TAG, "updateMobileState: ");
        // If the view for this subId exists already, use it
    /**
     * Apply an update to a mobile icon view for the given {@link MobileIconState}. For
     * compatibility with {@link MobileContextProvider}, we have to recreate the view every time we
     * update it, since the context (and thus the {@link Configuration}) may have changed
     */
    public void updateMobileState(MobileIconState state, Context mobileContext) {
        Log.d(TAG, "updateMobileState: " + state);

        // The mobile config provided by MobileContextProvider could have changed; always recreate
        for (int i = 0; i < mMobileViews.size(); i++) {
            StatusBarMobileView view = mMobileViews.get(i);
            if (view.getState().subId == state.subId) {
                view.applyMobileState(state);
                return;
                removeView(view);
            }
        }

        // Else we have to add it
        addMobileView(state);
        // Add the replacement or new icon
        addMobileView(state, mobileContext);
    }

    public void onRemoveIcon(StatusIconDisplayable view) {
+56 −30
Original line number Diff line number Diff line
@@ -36,7 +36,6 @@ import com.android.internal.statusbar.StatusBarIcon;
import com.android.systemui.R;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.demomode.DemoModeCommandReceiver;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.plugins.DarkIconDispatcher;
import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
import com.android.systemui.statusbar.BaseStatusBarWifiView;
@@ -44,6 +43,7 @@ import com.android.systemui.statusbar.StatusBarIconView;
import com.android.systemui.statusbar.StatusBarMobileView;
import com.android.systemui.statusbar.StatusBarWifiView;
import com.android.systemui.statusbar.StatusIconDisplayable;
import com.android.systemui.statusbar.connectivity.ui.MobileContextProvider;
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.CallIndicatorIconState;
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState;
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState;
@@ -70,6 +70,7 @@ public interface StatusBarIconController {
    void addIconGroup(IconManager iconManager);
    /** */
    void removeIconGroup(IconManager iconManager);

    /** Refresh the state of an IconManager by recreating the views */
    void refreshIconGroup(IconManager iconManager);
    /** */
@@ -82,21 +83,25 @@ public interface StatusBarIconController {
    void setSignalIcon(String slot, WifiIconState state);
    /** */
    void setMobileIcons(String slot, List<MobileIconState> states);

    /**
     * Display the no calling & SMS icons.
     */
    void setCallStrengthIcons(String slot, List<CallIndicatorIconState> states);

    /**
     * Display the no calling & SMS icons.
     */
    void setNoCallingIcons(String slot, List<CallIndicatorIconState> states);

    public void setIconVisibility(String slot, boolean b);

    /**
     * Sets the live region mode for the icon
     * @see android.view.View#setAccessibilityLiveRegion(int)
     *
     * @param slot                    Icon slot to set region for
     * @param accessibilityLiveRegion live region mode for the icon
     * @see android.view.View#setAccessibilityLiveRegion(int)
     */
    void setIconAccessibilityLiveRegion(String slot, int accessibilityLiveRegion);

@@ -134,11 +139,14 @@ public interface StatusBarIconController {

        public DarkIconManager(
                LinearLayout linearLayout,
                FeatureFlags featureFlags,
                StatusBarPipelineFlags statusBarPipelineFlags,
                Provider<WifiViewModel> wifiViewModelProvider,
                MobileContextProvider mobileContextProvider,
                DarkIconDispatcher darkIconDispatcher) {
            super(linearLayout, featureFlags, statusBarPipelineFlags, wifiViewModelProvider);
            super(linearLayout,
                    statusBarPipelineFlags,
                    wifiViewModelProvider,
                    mobileContextProvider);
            mIconHPadding = mContext.getResources().getDimensionPixelSize(
                    R.dimen.status_bar_icon_padding);
            mDarkIconDispatcher = darkIconDispatcher;
@@ -195,41 +203,49 @@ public interface StatusBarIconController {

        @SysUISingleton
        public static class Factory {
            private final FeatureFlags mFeatureFlags;
            private final StatusBarPipelineFlags mStatusBarPipelineFlags;
            private final Provider<WifiViewModel> mWifiViewModelProvider;
            private final MobileContextProvider mMobileContextProvider;
            private final DarkIconDispatcher mDarkIconDispatcher;

            @Inject
            public Factory(
                    FeatureFlags featureFlags,
                    StatusBarPipelineFlags statusBarPipelineFlags,
                    Provider<WifiViewModel> wifiViewModelProvider,
                    MobileContextProvider mobileContextProvider,
                    DarkIconDispatcher darkIconDispatcher) {
                mFeatureFlags = featureFlags;
                mStatusBarPipelineFlags = statusBarPipelineFlags;
                mWifiViewModelProvider = wifiViewModelProvider;
                mMobileContextProvider = mobileContextProvider;
                mDarkIconDispatcher = darkIconDispatcher;
            }

            public DarkIconManager create(LinearLayout group) {
                return new DarkIconManager(
                        group, mFeatureFlags, mStatusBarPipelineFlags, mWifiViewModelProvider,
                        group,
                        mStatusBarPipelineFlags,
                        mWifiViewModelProvider,
                        mMobileContextProvider,
                        mDarkIconDispatcher);
            }
        }
    }

    /** */
    /**
     *
     */
    class TintedIconManager extends IconManager {
        private int mColor;

        public TintedIconManager(
                ViewGroup group,
                FeatureFlags featureFlags,
                StatusBarPipelineFlags statusBarPipelineFlags,
                Provider<WifiViewModel> wifiViewModelProvider) {
            super(group, featureFlags, statusBarPipelineFlags, wifiViewModelProvider);
                Provider<WifiViewModel> wifiViewModelProvider,
                MobileContextProvider mobileContextProvider) {
            super(group,
                    statusBarPipelineFlags,
                    wifiViewModelProvider,
                    mobileContextProvider);
        }

        @Override
@@ -261,23 +277,26 @@ public interface StatusBarIconController {

        @SysUISingleton
        public static class Factory {
            private final FeatureFlags mFeatureFlags;
            private final StatusBarPipelineFlags mStatusBarPipelineFlags;
            private final Provider<WifiViewModel> mWifiViewModelProvider;
            private final MobileContextProvider mMobileContextProvider;

            @Inject
            public Factory(
                    FeatureFlags featureFlags,
                    StatusBarPipelineFlags statusBarPipelineFlags,
                    Provider<WifiViewModel> wifiViewModelProvider) {
                mFeatureFlags = featureFlags;
                    Provider<WifiViewModel> wifiViewModelProvider,
                    MobileContextProvider mobileContextProvider) {
                mStatusBarPipelineFlags = statusBarPipelineFlags;
                mWifiViewModelProvider = wifiViewModelProvider;
                mMobileContextProvider = mobileContextProvider;
            }

            public TintedIconManager create(ViewGroup group) {
                return new TintedIconManager(
                        group, mFeatureFlags, mStatusBarPipelineFlags, mWifiViewModelProvider);
                        group,
                        mStatusBarPipelineFlags,
                        mWifiViewModelProvider,
                        mMobileContextProvider);
            }
        }
    }
@@ -287,9 +306,9 @@ public interface StatusBarIconController {
     */
    class IconManager implements DemoModeCommandReceiver {
        protected final ViewGroup mGroup;
        private final FeatureFlags mFeatureFlags;
        private final StatusBarPipelineFlags mStatusBarPipelineFlags;
        private final Provider<WifiViewModel> mWifiViewModelProvider;
        private final MobileContextProvider mMobileContextProvider;
        protected final Context mContext;
        protected final int mIconSize;
        // Whether or not these icons show up in dumpsys
@@ -305,13 +324,13 @@ public interface StatusBarIconController {

        public IconManager(
                ViewGroup group,
                FeatureFlags featureFlags,
                StatusBarPipelineFlags statusBarPipelineFlags,
                Provider<WifiViewModel> wifiViewModelProvider) {
                Provider<WifiViewModel> wifiViewModelProvider,
                MobileContextProvider mobileContextProvider) {
            mGroup = group;
            mFeatureFlags = featureFlags;
            mStatusBarPipelineFlags = statusBarPipelineFlags;
            mWifiViewModelProvider = wifiViewModelProvider;
            mMobileContextProvider = mobileContextProvider;
            mContext = group.getContext();
            mIconSize = mContext.getResources().getDimensionPixelSize(
                    com.android.internal.R.dimen.status_bar_icon_size);
@@ -403,12 +422,15 @@ public interface StatusBarIconController {

        @VisibleForTesting
        protected StatusBarMobileView addMobileIcon(int index, String slot, MobileIconState state) {
            StatusBarMobileView view = onCreateStatusBarMobileView(slot);
            // Use the `subId` field as a key to query for the correct context
            StatusBarMobileView view = onCreateStatusBarMobileView(state.subId, slot);
            view.applyMobileState(state);
            mGroup.addView(view, index, onCreateLayoutParams());

            if (mIsInDemoMode) {
                mDemoStatusIcons.addMobileView(state);
                Context mobileContext = mMobileContextProvider
                        .getMobileContextForSub(state.subId, mContext);
                mDemoStatusIcons.addMobileView(state, mobileContext);
            }
            return view;
        }
@@ -427,8 +449,10 @@ public interface StatusBarIconController {
                    mContext, slot, mWifiViewModelProvider.get());
        }

        private StatusBarMobileView onCreateStatusBarMobileView(String slot) {
            StatusBarMobileView view = StatusBarMobileView.fromContext(mContext, slot);
        private StatusBarMobileView onCreateStatusBarMobileView(int subId, String slot) {
            Context mobileContext = mMobileContextProvider.getMobileContextForSub(subId, mContext);
            StatusBarMobileView view = StatusBarMobileView
                    .fromContext(mobileContext, slot);
            return view;
        }

@@ -516,7 +540,9 @@ public interface StatusBarIconController {
            }

            if (mIsInDemoMode) {
                mDemoStatusIcons.updateMobileState(state);
                Context mobileContext = mMobileContextProvider
                        .getMobileContextForSub(state.subId, mContext);
                mDemoStatusIcons.updateMobileState(state, mobileContext);
            }
        }

@@ -553,7 +579,7 @@ public interface StatusBarIconController {
        }

        protected DemoStatusIcons createDemoStatusIcons() {
            return new DemoStatusIcons((LinearLayout) mGroup, mIconSize, mFeatureFlags);
            return new DemoStatusIcons((LinearLayout) mGroup, mIconSize);
        }
    }
}
Loading