Merge "Move logic from QSCarrierGroup into Controller."
diff --git a/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java b/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java
index 06aba40..d66a53c 100644
--- a/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java
+++ b/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java
@@ -24,6 +24,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.res.Resources;
 import android.net.ConnectivityManager;
 import android.net.wifi.WifiManager;
 import android.os.Handler;
@@ -42,11 +43,14 @@
 import com.android.settingslib.WirelessUtils;
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
+import com.android.systemui.dagger.qualifiers.MainResources;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
 
 import java.util.List;
 import java.util.Objects;
 
+import javax.inject.Inject;
+
 /**
  * Controller that generates text including the carrier names and/or the status of all the SIM
  * interfaces in the device. Through a callback, the updates can be retrieved either as a list or
@@ -598,6 +602,35 @@
         return mContext.getText(carrierHelpTextId);
     }
 
+    public static class Builder {
+        private final Context mContext;
+        private final String mSeparator;
+        private boolean mShowAirplaneMode;
+        private boolean mShowMissingSim;
+
+        @Inject
+        public Builder(Context context, @MainResources Resources resources) {
+            mContext = context;
+            mSeparator = resources.getString(
+                    com.android.internal.R.string.kg_text_message_separator);
+        }
+
+
+        public Builder setShowAirplaneMode(boolean showAirplaneMode) {
+            mShowAirplaneMode = showAirplaneMode;
+            return this;
+        }
+
+        public Builder setShowMissingSim(boolean showMissingSim) {
+            mShowMissingSim = showMissingSim;
+            return this;
+        }
+
+        public CarrierTextController build() {
+            return new CarrierTextController(
+                    mContext, mSeparator, mShowAirplaneMode, mShowMissingSim);
+        }
+    }
     /**
      * Data structure for passing information to CarrierTextController subscribers
      */
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSCarrier.java b/packages/SystemUI/src/com/android/systemui/qs/QSCarrier.java
index 4501b8f..4c7d82e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSCarrier.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSCarrier.java
@@ -73,7 +73,7 @@
         mColorForegroundIntensity = QuickStatusBarHeader.getColorIntensity(colorForeground);
     }
 
-    public void updateState(QSCarrierGroup.CellSignalState state) {
+    public void updateState(QSCarrierGroupController.CellSignalState state) {
         mMobileGroup.setVisibility(state.visible ? View.VISIBLE : View.GONE);
         if (state.visible) {
             mMobileRoaming.setVisibility(state.roaming ? View.VISIBLE : View.GONE);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSCarrierGroup.java b/packages/SystemUI/src/com/android/systemui/qs/QSCarrierGroup.java
index 5742787..346c75d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSCarrierGroup.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSCarrierGroup.java
@@ -16,309 +16,43 @@
 
 package com.android.systemui.qs;
 
-import static com.android.systemui.Dependency.BG_HANDLER;
-import static com.android.systemui.Dependency.MAIN_LOOPER;
-import static com.android.systemui.util.InjectionInflationController.VIEW_CONTEXT;
-
-import android.annotation.MainThread;
 import android.content.Context;
-import android.content.Intent;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.provider.Settings;
-import android.telephony.SubscriptionManager;
 import android.util.AttributeSet;
-import android.util.Log;
 import android.view.View;
 import android.widget.LinearLayout;
 import android.widget.TextView;
 
-import androidx.annotation.VisibleForTesting;
-
-import com.android.keyguard.CarrierTextController;
-import com.android.systemui.Dependency;
 import com.android.systemui.R;
-import com.android.systemui.dagger.qualifiers.BgHandler;
-import com.android.systemui.dagger.qualifiers.MainLooper;
-import com.android.systemui.plugins.ActivityStarter;
-import com.android.systemui.statusbar.policy.NetworkController;
-
-import java.util.function.Consumer;
-
-import javax.inject.Inject;
-import javax.inject.Named;
 
 /**
  * Displays Carrier name and network status in QS
  */
-public class QSCarrierGroup extends LinearLayout implements
-        NetworkController.SignalCallback, View.OnClickListener {
-
-    private static final String TAG = "QSCarrierGroup";
-    /**
-     * Support up to 3 slots which is what's supported by {@link TelephonyManager#getPhoneCount}
-     */
-    private static final int SIM_SLOTS = 3;
-    private final NetworkController mNetworkController;
-    private final Handler mBgHandler;
-    private final H mMainHandler;
-
-    private View[] mCarrierDividers = new View[SIM_SLOTS - 1];
-    private QSCarrier[] mCarrierGroups = new QSCarrier[SIM_SLOTS];
-    private TextView mNoSimTextView;
-    private final CellSignalState[] mInfos = new CellSignalState[SIM_SLOTS];
-    private CarrierTextController mCarrierTextController;
-    private CarrierTextController.CarrierTextCallback mCallback;
-    private ActivityStarter mActivityStarter;
-
-    private boolean mListening;
-
-    @Inject
-    public QSCarrierGroup(@Named(VIEW_CONTEXT) Context context, AttributeSet attrs,
-            NetworkController networkController, ActivityStarter activityStarter,
-            @BgHandler Handler handler,
-            @MainLooper Looper looper) {
-        super(context, attrs);
-        mNetworkController = networkController;
-        mActivityStarter = activityStarter;
-        mBgHandler = handler;
-        mMainHandler = new H(looper, this::handleUpdateCarrierInfo, this::handleUpdateState);
-        mCallback = new Callback(mMainHandler);
-    }
-
-    @VisibleForTesting
-    protected CarrierTextController.CarrierTextCallback getCallback() {
-        return mCallback;
-    }
-
-    @VisibleForTesting
+public class QSCarrierGroup extends LinearLayout {
     public QSCarrierGroup(Context context, AttributeSet attrs) {
-        this(context, attrs,
-                Dependency.get(NetworkController.class),
-                Dependency.get(ActivityStarter.class),
-                Dependency.get(BG_HANDLER),
-                Dependency.get(MAIN_LOOPER));
+        super(context, attrs);
     }
 
-    @Override
-    public void onClick(View v) {
-        if (!v.isVisibleToUser()) return;
-        mActivityStarter.postStartActivityDismissingKeyguard(new Intent(
-                Settings.ACTION_WIRELESS_SETTINGS), 0);
+    TextView getNoSimTextView() {
+        return findViewById(R.id.no_carrier_text);
     }
 
-    @Override
-    protected void onFinishInflate() {
-        super.onFinishInflate();
-
-        mCarrierGroups[0] = findViewById(R.id.carrier1);
-        mCarrierGroups[1] = findViewById(R.id.carrier2);
-        mCarrierGroups[2] = findViewById(R.id.carrier3);
-
-        mCarrierDividers[0] = findViewById(R.id.qs_carrier_divider1);
-        mCarrierDividers[1] = findViewById(R.id.qs_carrier_divider2);
-
-        mNoSimTextView = findViewById(R.id.no_carrier_text);
-
-        for (int i = 0; i < SIM_SLOTS; i++) {
-            mInfos[i] = new CellSignalState();
-            mCarrierGroups[i].setOnClickListener(this);
-        }
-        mNoSimTextView.setOnClickListener(this);
-
-        CharSequence separator = mContext.getString(
-                com.android.internal.R.string.kg_text_message_separator);
-        mCarrierTextController = new CarrierTextController(mContext, separator, false, false);
-        setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
+    QSCarrier getCarrier1View() {
+        return findViewById(R.id.carrier1);
     }
 
-    public void setListening(boolean listening) {
-        if (listening == mListening) {
-            return;
-        }
-        mListening = listening;
-        mBgHandler.post(this::updateListeners);
+    QSCarrier getCarrier2View() {
+        return findViewById(R.id.carrier2);
     }
 
-    @Override
-    @VisibleForTesting
-    public void onDetachedFromWindow() {
-        setListening(false);
-        super.onDetachedFromWindow();
+    QSCarrier getCarrier3View() {
+        return findViewById(R.id.carrier3);
     }
 
-    private void updateListeners() {
-        if (mListening) {
-            if (mNetworkController.hasVoiceCallingFeature()) {
-                mNetworkController.addCallback(this);
-            }
-            mCarrierTextController.setListening(mCallback);
-        } else {
-            mNetworkController.removeCallback(this);
-            mCarrierTextController.setListening(null);
-        }
+    View getCarrierDivider1() {
+        return findViewById(R.id.qs_carrier_divider1);
     }
 
-    @MainThread
-    private void handleUpdateState() {
-        if (!mMainHandler.getLooper().isCurrentThread()) {
-            mMainHandler.obtainMessage(H.MSG_UPDATE_STATE).sendToTarget();
-            return;
-        }
-
-        for (int i = 0; i < SIM_SLOTS; i++) {
-            mCarrierGroups[i].updateState(mInfos[i]);
-        }
-
-        mCarrierDividers[0].setVisibility(
-                mInfos[0].visible && mInfos[1].visible ? View.VISIBLE : View.GONE);
-        // This tackles the case of slots 2 being available as well as at least one other.
-        // In that case we show the second divider. Note that if both dividers are visible, it means
-        // all three slots are in use, and that is correct.
-        mCarrierDividers[1].setVisibility(
-                (mInfos[1].visible && mInfos[2].visible)
-                        || (mInfos[0].visible && mInfos[2].visible) ? View.VISIBLE : View.GONE);
-    }
-
-    @VisibleForTesting
-    protected int getSlotIndex(int subscriptionId) {
-        return SubscriptionManager.getSlotIndex(subscriptionId);
-    }
-
-    @MainThread
-    private void handleUpdateCarrierInfo(CarrierTextController.CarrierTextCallbackInfo info) {
-        if (!mMainHandler.getLooper().isCurrentThread()) {
-            mMainHandler.obtainMessage(H.MSG_UPDATE_CARRIER_INFO, info).sendToTarget();
-            return;
-        }
-
-        mNoSimTextView.setVisibility(View.GONE);
-        if (!info.airplaneMode && info.anySimReady) {
-            boolean[] slotSeen = new boolean[SIM_SLOTS];
-            if (info.listOfCarriers.length == info.subscriptionIds.length) {
-                for (int i = 0; i < SIM_SLOTS && i < info.listOfCarriers.length; i++) {
-                    int slot = getSlotIndex(info.subscriptionIds[i]);
-                    if (slot >= SIM_SLOTS) {
-                        Log.w(TAG, "updateInfoCarrier - slot: " + slot);
-                        continue;
-                    }
-                    if (slot == SubscriptionManager.INVALID_SIM_SLOT_INDEX) {
-                        Log.e(TAG,
-                                "Invalid SIM slot index for subscription: "
-                                        + info.subscriptionIds[i]);
-                        continue;
-                    }
-                    mInfos[slot].visible = true;
-                    slotSeen[slot] = true;
-                    mCarrierGroups[slot].setCarrierText(
-                            info.listOfCarriers[i].toString().trim());
-                    mCarrierGroups[slot].setVisibility(View.VISIBLE);
-                }
-                for (int i = 0; i < SIM_SLOTS; i++) {
-                    if (!slotSeen[i]) {
-                        mInfos[i].visible = false;
-                        mCarrierGroups[i].setVisibility(View.GONE);
-                    }
-                }
-            } else {
-                Log.e(TAG, "Carrier information arrays not of same length");
-            }
-        } else {
-            // No sims or airplane mode (but not WFC). Do not show QSCarrierGroup, instead just show
-            // info.carrierText in a different view.
-            for (int i = 0; i < SIM_SLOTS; i++) {
-                mInfos[i].visible = false;
-                mCarrierGroups[i].setCarrierText("");
-                mCarrierGroups[i].setVisibility(View.GONE);
-            }
-            mNoSimTextView.setText(info.carrierText);
-            mNoSimTextView.setVisibility(View.VISIBLE);
-        }
-        handleUpdateState(); // handleUpdateCarrierInfo is always called from main thread.
-    }
-
-    @Override
-    public void setMobileDataIndicators(NetworkController.IconState statusIcon,
-            NetworkController.IconState qsIcon, int statusType,
-            int qsType, boolean activityIn, boolean activityOut,
-            String typeContentDescription,
-            String description, boolean isWide, int subId, boolean roaming) {
-        int slotIndex = getSlotIndex(subId);
-        if (slotIndex >= SIM_SLOTS) {
-            Log.w(TAG, "setMobileDataIndicators - slot: " + slotIndex);
-            return;
-        }
-        if (slotIndex == SubscriptionManager.INVALID_SIM_SLOT_INDEX) {
-            Log.e(TAG, "Invalid SIM slot index for subscription: " + subId);
-            return;
-        }
-        mInfos[slotIndex].visible = statusIcon.visible;
-        mInfos[slotIndex].mobileSignalIconId = statusIcon.icon;
-        mInfos[slotIndex].contentDescription = statusIcon.contentDescription;
-        mInfos[slotIndex].typeContentDescription = typeContentDescription;
-        mInfos[slotIndex].roaming = roaming;
-        mMainHandler.obtainMessage(H.MSG_UPDATE_STATE).sendToTarget();
-    }
-
-    @Override
-    public void setNoSims(boolean hasNoSims, boolean simDetected) {
-        if (hasNoSims) {
-            for (int i = 0; i < SIM_SLOTS; i++) {
-                mInfos[i].visible = false;
-            }
-        }
-        mMainHandler.obtainMessage(H.MSG_UPDATE_STATE).sendToTarget();
-    }
-
-    static final class CellSignalState {
-        boolean visible;
-        int mobileSignalIconId;
-        String contentDescription;
-        String typeContentDescription;
-        boolean roaming;
-    }
-
-    private static class H extends Handler {
-        private Consumer<CarrierTextController.CarrierTextCallbackInfo> mUpdateCarrierInfo;
-        private Runnable mUpdateState;
-        static final int MSG_UPDATE_CARRIER_INFO = 0;
-        static final int MSG_UPDATE_STATE = 1;
-
-        H(Looper looper,
-                Consumer<CarrierTextController.CarrierTextCallbackInfo> updateCarrierInfo,
-                Runnable updateState) {
-            super(looper);
-            mUpdateCarrierInfo = updateCarrierInfo;
-            mUpdateState = updateState;
-        }
-
-        @Override
-        public void handleMessage(Message msg) {
-            switch (msg.what) {
-                case MSG_UPDATE_CARRIER_INFO:
-                    mUpdateCarrierInfo.accept(
-                            (CarrierTextController.CarrierTextCallbackInfo) msg.obj);
-                    break;
-                case MSG_UPDATE_STATE:
-                    mUpdateState.run();
-                    break;
-                default:
-                    super.handleMessage(msg);
-            }
-        }
-    }
-
-    private static class Callback implements CarrierTextController.CarrierTextCallback {
-        private H mMainHandler;
-
-        Callback(H handler) {
-            mMainHandler = handler;
-        }
-
-        @Override
-        public void updateCarrierInfo(CarrierTextController.CarrierTextCallbackInfo info) {
-            mMainHandler.obtainMessage(H.MSG_UPDATE_CARRIER_INFO, info).sendToTarget();
-        }
+    View getCarrierDivider2() {
+        return findViewById(R.id.qs_carrier_divider2);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSCarrierGroupController.java b/packages/SystemUI/src/com/android/systemui/qs/QSCarrierGroupController.java
new file mode 100644
index 0000000..ac94858
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSCarrierGroupController.java
@@ -0,0 +1,331 @@
+/*
+ * Copyright (C) 2019 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.qs;
+
+import static android.view.View.IMPORTANT_FOR_ACCESSIBILITY_YES;
+
+import android.annotation.MainThread;
+import android.content.Intent;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.provider.Settings;
+import android.telephony.SubscriptionManager;
+import android.util.Log;
+import android.view.View;
+import android.widget.TextView;
+
+import androidx.annotation.VisibleForTesting;
+
+import com.android.keyguard.CarrierTextController;
+import com.android.systemui.dagger.qualifiers.BgHandler;
+import com.android.systemui.dagger.qualifiers.MainLooper;
+import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.statusbar.policy.NetworkController;
+
+import java.util.function.Consumer;
+
+import javax.inject.Inject;
+
+public class QSCarrierGroupController {
+    private static final String TAG = "QSCarrierGroup";
+
+    /**
+     * Support up to 3 slots which is what's supported by {@link TelephonyManager#getPhoneCount}
+     */
+    private static final int SIM_SLOTS = 3;
+
+    private final ActivityStarter mActivityStarter;
+    private final Handler mBgHandler;
+    private final NetworkController mNetworkController;
+    private final CarrierTextController mCarrierTextController;
+    private final TextView mNoSimTextView;
+    private final H mMainHandler;
+    private final Callback mCallback;
+    private boolean mListening;
+    private final CellSignalState[] mInfos =
+            new CellSignalState[SIM_SLOTS];
+    private View[] mCarrierDividers = new View[SIM_SLOTS - 1];
+    private QSCarrier[] mCarrierGroups = new QSCarrier[SIM_SLOTS];
+
+    private final NetworkController.SignalCallback mSignalCallback =
+            new NetworkController.SignalCallback() {
+                @Override
+                public void setMobileDataIndicators(NetworkController.IconState statusIcon,
+                        NetworkController.IconState qsIcon, int statusType, int qsType,
+                        boolean activityIn, boolean activityOut, String typeContentDescription,
+                        String description, boolean isWide, int subId, boolean roaming) {
+                    int slotIndex = getSlotIndex(subId);
+                    if (slotIndex >= SIM_SLOTS) {
+                        Log.w(TAG, "setMobileDataIndicators - slot: " + slotIndex);
+                        return;
+                    }
+                    if (slotIndex == SubscriptionManager.INVALID_SIM_SLOT_INDEX) {
+                        Log.e(TAG, "Invalid SIM slot index for subscription: " + subId);
+                        return;
+                    }
+                    mInfos[slotIndex].visible = statusIcon.visible;
+                    mInfos[slotIndex].mobileSignalIconId = statusIcon.icon;
+                    mInfos[slotIndex].contentDescription = statusIcon.contentDescription;
+                    mInfos[slotIndex].typeContentDescription = typeContentDescription;
+                    mInfos[slotIndex].roaming = roaming;
+                    mMainHandler.obtainMessage(H.MSG_UPDATE_STATE).sendToTarget();
+                }
+
+                @Override
+                public void setNoSims(boolean hasNoSims, boolean simDetected) {
+                    if (hasNoSims) {
+                        for (int i = 0; i < SIM_SLOTS; i++) {
+                            mInfos[i].visible = false;
+                        }
+                    }
+                    mMainHandler.obtainMessage(H.MSG_UPDATE_STATE).sendToTarget();
+                }
+            };
+
+    private static class Callback implements CarrierTextController.CarrierTextCallback {
+        private H mHandler;
+
+        Callback(H handler) {
+            mHandler = handler;
+        }
+
+        @Override
+        public void updateCarrierInfo(CarrierTextController.CarrierTextCallbackInfo info) {
+            mHandler.obtainMessage(H.MSG_UPDATE_CARRIER_INFO, info).sendToTarget();
+        }
+    }
+
+    private QSCarrierGroupController(QSCarrierGroup view, ActivityStarter activityStarter,
+            @BgHandler Handler bgHandler, @MainLooper Looper mainLooper,
+            NetworkController networkController,
+            CarrierTextController.Builder carrierTextControllerBuilder) {
+        mActivityStarter = activityStarter;
+        mBgHandler = bgHandler;
+        mNetworkController = networkController;
+        mCarrierTextController = carrierTextControllerBuilder
+                .setShowAirplaneMode(false)
+                .setShowMissingSim(false)
+                .build();
+
+        View.OnClickListener onClickListener = v -> {
+            if (!v.isVisibleToUser()) {
+                return;
+            }
+            mActivityStarter.postStartActivityDismissingKeyguard(
+                    new Intent(Settings.ACTION_WIRELESS_SETTINGS), 0);
+        };
+        view.setOnClickListener(onClickListener);
+        mNoSimTextView = view.getNoSimTextView();
+        mNoSimTextView.setOnClickListener(onClickListener);
+        mMainHandler = new H(mainLooper, this::handleUpdateCarrierInfo, this::handleUpdateState);
+        mCallback = new Callback(mMainHandler);
+
+
+        mCarrierGroups[0] = view.getCarrier1View();
+        mCarrierGroups[1] = view.getCarrier2View();
+        mCarrierGroups[2] = view.getCarrier3View();
+
+        mCarrierDividers[0] = view.getCarrierDivider1();
+        mCarrierDividers[1] = view.getCarrierDivider2();
+
+        for (int i = 0; i < SIM_SLOTS; i++) {
+            mInfos[i] = new CellSignalState();
+            mCarrierGroups[i].setOnClickListener(onClickListener);
+        }
+        view.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
+
+        view.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
+            @Override
+            public void onViewAttachedToWindow(View v) {
+            }
+
+            @Override
+            public void onViewDetachedFromWindow(View v) {
+                setListening(false);
+            }
+        });
+    }
+
+    @VisibleForTesting
+    protected int getSlotIndex(int subscriptionId) {
+        return SubscriptionManager.getSlotIndex(subscriptionId);
+    }
+
+    public void setListening(boolean listening) {
+        if (listening == mListening) {
+            return;
+        }
+        mListening = listening;
+
+        mBgHandler.post(this::updateListeners);
+    }
+
+    private void updateListeners() {
+        if (mListening) {
+            if (mNetworkController.hasVoiceCallingFeature()) {
+                mNetworkController.addCallback(mSignalCallback);
+            }
+            mCarrierTextController.setListening(mCallback);
+        } else {
+            mNetworkController.removeCallback(mSignalCallback);
+            mCarrierTextController.setListening(null);
+        }
+    }
+
+
+    @MainThread
+    private void handleUpdateState() {
+        if (!mMainHandler.getLooper().isCurrentThread()) {
+            mMainHandler.obtainMessage(H.MSG_UPDATE_STATE).sendToTarget();
+            return;
+        }
+
+        for (int i = 0; i < SIM_SLOTS; i++) {
+            mCarrierGroups[i].updateState(mInfos[i]);
+        }
+
+        mCarrierDividers[0].setVisibility(
+                mInfos[0].visible && mInfos[1].visible ? View.VISIBLE : View.GONE);
+        // This tackles the case of slots 2 being available as well as at least one other.
+        // In that case we show the second divider. Note that if both dividers are visible, it means
+        // all three slots are in use, and that is correct.
+        mCarrierDividers[1].setVisibility(
+                (mInfos[1].visible && mInfos[2].visible)
+                        || (mInfos[0].visible && mInfos[2].visible) ? View.VISIBLE : View.GONE);
+    }
+
+    @MainThread
+    private void handleUpdateCarrierInfo(CarrierTextController.CarrierTextCallbackInfo info) {
+        if (!mMainHandler.getLooper().isCurrentThread()) {
+            mMainHandler.obtainMessage(H.MSG_UPDATE_CARRIER_INFO, info).sendToTarget();
+            return;
+        }
+
+        mNoSimTextView.setVisibility(View.GONE);
+        if (!info.airplaneMode && info.anySimReady) {
+            boolean[] slotSeen = new boolean[SIM_SLOTS];
+            if (info.listOfCarriers.length == info.subscriptionIds.length) {
+                for (int i = 0; i < SIM_SLOTS && i < info.listOfCarriers.length; i++) {
+                    int slot = getSlotIndex(info.subscriptionIds[i]);
+                    if (slot >= SIM_SLOTS) {
+                        Log.w(TAG, "updateInfoCarrier - slot: " + slot);
+                        continue;
+                    }
+                    if (slot == SubscriptionManager.INVALID_SIM_SLOT_INDEX) {
+                        Log.e(TAG,
+                                "Invalid SIM slot index for subscription: "
+                                        + info.subscriptionIds[i]);
+                        continue;
+                    }
+                    mInfos[slot].visible = true;
+                    slotSeen[slot] = true;
+                    mCarrierGroups[slot].setCarrierText(
+                            info.listOfCarriers[i].toString().trim());
+                    mCarrierGroups[slot].setVisibility(View.VISIBLE);
+                }
+                for (int i = 0; i < SIM_SLOTS; i++) {
+                    if (!slotSeen[i]) {
+                        mInfos[i].visible = false;
+                        mCarrierGroups[i].setVisibility(View.GONE);
+                    }
+                }
+            } else {
+                Log.e(TAG, "Carrier information arrays not of same length");
+            }
+        } else {
+            // No sims or airplane mode (but not WFC). Do not show QSCarrierGroup, instead just show
+            // info.carrierText in a different view.
+            for (int i = 0; i < SIM_SLOTS; i++) {
+                mInfos[i].visible = false;
+                mCarrierGroups[i].setCarrierText("");
+                mCarrierGroups[i].setVisibility(View.GONE);
+            }
+            mNoSimTextView.setText(info.carrierText);
+            mNoSimTextView.setVisibility(View.VISIBLE);
+        }
+        handleUpdateState(); // handleUpdateCarrierInfo is always called from main thread.
+    }
+
+    private static class H extends Handler {
+        private Consumer<CarrierTextController.CarrierTextCallbackInfo> mUpdateCarrierInfo;
+        private Runnable mUpdateState;
+        static final int MSG_UPDATE_CARRIER_INFO = 0;
+        static final int MSG_UPDATE_STATE = 1;
+
+        H(Looper looper,
+                Consumer<CarrierTextController.CarrierTextCallbackInfo> updateCarrierInfo,
+                Runnable updateState) {
+            super(looper);
+            mUpdateCarrierInfo = updateCarrierInfo;
+            mUpdateState = updateState;
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_UPDATE_CARRIER_INFO:
+                    mUpdateCarrierInfo.accept(
+                            (CarrierTextController.CarrierTextCallbackInfo) msg.obj);
+                    break;
+                case MSG_UPDATE_STATE:
+                    mUpdateState.run();
+                    break;
+                default:
+                    super.handleMessage(msg);
+            }
+        }
+    }
+
+    static final class CellSignalState {
+        boolean visible;
+        int mobileSignalIconId;
+        String contentDescription;
+        String typeContentDescription;
+        boolean roaming;
+    }
+
+    public static class Builder {
+        private QSCarrierGroup mView;
+        private final ActivityStarter mActivityStarter;
+        private final Handler mHandler;
+        private final Looper mLooper;
+        private final NetworkController mNetworkController;
+        private final CarrierTextController.Builder mCarrierTextControllerBuilder;
+
+        @Inject
+        public Builder(ActivityStarter activityStarter, @BgHandler Handler handler,
+                @MainLooper Looper looper, NetworkController networkController,
+                CarrierTextController.Builder carrierTextControllerBuilder) {
+            mActivityStarter = activityStarter;
+            mHandler = handler;
+            mLooper = looper;
+            mNetworkController = networkController;
+            mCarrierTextControllerBuilder = carrierTextControllerBuilder;
+        }
+
+        Builder setQSCarrierGroup(QSCarrierGroup view) {
+            mView = view;
+            return this;
+        }
+
+        public QSCarrierGroupController build() {
+            return new QSCarrierGroupController(mView, mActivityStarter, mHandler, mLooper,
+                    mNetworkController, mCarrierTextControllerBuilder);
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java
new file mode 100644
index 0000000..fa33284
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2019 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.qs;
+
+import com.android.systemui.R;
+
+import javax.inject.Inject;
+
+public class QSContainerImplController {
+    private final QSContainerImpl mView;
+    private final QuickStatusBarHeaderController mQuickStatusBarHeaderController;
+
+    private QSContainerImplController(QSContainerImpl view,
+            QuickStatusBarHeaderController.Builder quickStatusBarHeaderControllerBuilder) {
+        mView = view;
+        mQuickStatusBarHeaderController = quickStatusBarHeaderControllerBuilder
+                .setQuickStatusBarHeader(mView.findViewById(R.id.header)).build();
+    }
+
+    public void setListening(boolean listening) {
+        mQuickStatusBarHeaderController.setListening(listening);
+    }
+
+    public static class Builder {
+        private final QuickStatusBarHeaderController.Builder mQuickStatusBarHeaderControllerBuilder;
+        private QSContainerImpl mView;
+
+        @Inject
+        public Builder(
+                QuickStatusBarHeaderController.Builder quickStatusBarHeaderControllerBuilder) {
+            mQuickStatusBarHeaderControllerBuilder = quickStatusBarHeaderControllerBuilder;
+        }
+
+        public Builder setQSContainerImpl(QSContainerImpl view) {
+            mView = view;
+            return this;
+        }
+
+        public QSContainerImplController build() {
+            return new QSContainerImplController(mView, mQuickStatusBarHeaderControllerBuilder);
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
index ccc836f..5b09267 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
@@ -18,7 +18,6 @@
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
-import android.content.Context;
 import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.os.Bundle;
@@ -81,6 +80,7 @@
 
     private final RemoteInputQuickSettingsDisabler mRemoteInputQuickSettingsDisabler;
     private final InjectionInflationController mInjectionInflater;
+    private final QSContainerImplController.Builder mQSContainerImplControllerBuilder;
     private final QSTileHost mHost;
     private boolean mShowCollapsedOnKeyguard;
     private boolean mLastKeyguardAndExpanded;
@@ -90,15 +90,16 @@
      * during state transitions which often call into us.
      */
     private int mState;
+    private QSContainerImplController mQSContainerImplController;
 
     @Inject
     public QSFragment(RemoteInputQuickSettingsDisabler remoteInputQsDisabler,
-            InjectionInflationController injectionInflater,
-            Context context,
-            QSTileHost qsTileHost,
-            StatusBarStateController statusBarStateController, CommandQueue commandQueue) {
+            InjectionInflationController injectionInflater, QSTileHost qsTileHost,
+            StatusBarStateController statusBarStateController, CommandQueue commandQueue,
+            QSContainerImplController.Builder qsContainerImplControllerBuilder) {
         mRemoteInputQuickSettingsDisabler = remoteInputQsDisabler;
         mInjectionInflater = injectionInflater;
+        mQSContainerImplControllerBuilder = qsContainerImplControllerBuilder;
         commandQueue.observe(getLifecycle(), this);
         mHost = qsTileHost;
         mStatusBarStateController = statusBarStateController;
@@ -121,6 +122,11 @@
         mFooter = view.findViewById(R.id.qs_footer);
         mContainer = view.findViewById(id.quick_settings_container);
 
+        mQSContainerImplController = mQSContainerImplControllerBuilder
+                .setQSContainerImpl((QSContainerImpl) view)
+                .build();
+
+
         mQSDetail.setQsPanel(mQSPanel, mHeader, (View) mFooter);
         mQSAnimator = new QSAnimator(this,
                 mHeader.findViewById(R.id.quick_qs_panel), mQSPanel);
@@ -340,6 +346,7 @@
     public void setListening(boolean listening) {
         if (DEBUG) Log.d(TAG, "setListening " + listening);
         mListening = listening;
+        mQSContainerImplController.setListening(listening);
         mHeader.setListening(listening);
         mFooter.setListening(listening);
         mQSPanel.setListening(mListening, mQsExpanded);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
index 19af235..02e8f59 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
@@ -549,7 +549,6 @@
         }
         mHeaderQsPanel.setListening(listening);
         mListening = listening;
-        mCarrierGroup.setListening(mListening);
 
         if (listening) {
             mZenController.addCallback(this);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java
new file mode 100644
index 0000000..867677a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2019 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.qs;
+
+import com.android.systemui.R;
+
+import javax.inject.Inject;
+
+public class QuickStatusBarHeaderController {
+    private final QuickStatusBarHeader mView;
+    private final QSCarrierGroupController mQSCarrierGroupController;
+
+    private QuickStatusBarHeaderController(QuickStatusBarHeader view,
+            QSCarrierGroupController.Builder qsCarrierGroupControllerBuilder) {
+        mView = view;
+        mQSCarrierGroupController = qsCarrierGroupControllerBuilder
+                .setQSCarrierGroup(mView.findViewById(R.id.carrier_group))
+                .build();
+    }
+
+    public void setListening(boolean listening) {
+        mQSCarrierGroupController.setListening(listening);
+        // TODO: move mView.setListening logic into here.
+        mView.setListening(listening);
+    }
+
+
+    public static class Builder {
+        private final QSCarrierGroupController.Builder mQSCarrierGroupControllerBuilder;
+        private QuickStatusBarHeader mView;
+
+        @Inject
+        public Builder(QSCarrierGroupController.Builder qsCarrierGroupControllerBuilder) {
+            mQSCarrierGroupControllerBuilder = qsCarrierGroupControllerBuilder;
+        }
+
+        public Builder setQuickStatusBarHeader(QuickStatusBarHeader view) {
+            mView = view;
+            return this;
+        }
+
+        public QuickStatusBarHeaderController build() {
+            return new QuickStatusBarHeaderController(mView, mQSCarrierGroupControllerBuilder);
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java b/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java
index 60d7678..6976649 100644
--- a/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java
+++ b/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java
@@ -27,7 +27,6 @@
 import com.android.keyguard.KeyguardMessageArea;
 import com.android.keyguard.KeyguardSliceView;
 import com.android.systemui.dagger.SystemUIRootComponent;
-import com.android.systemui.qs.QSCarrierGroup;
 import com.android.systemui.qs.QSFooterImpl;
 import com.android.systemui.qs.QSPanel;
 import com.android.systemui.qs.QuickQSPanel;
@@ -135,11 +134,6 @@
         NotificationPanelView createPanelView();
 
         /**
-         * Creates the QSCarrierGroup
-         */
-        QSCarrierGroup createQSCarrierGroup();
-
-        /**
          * Creates the Shelf.
          */
         NotificationShelf creatNotificationShelf();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSCarrierGroupTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSCarrierGroupControllerTest.java
similarity index 61%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/QSCarrierGroupTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/qs/QSCarrierGroupControllerTest.java
index a2a20a95..1bfe1b1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSCarrierGroupTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSCarrierGroupControllerTest.java
@@ -16,7 +16,10 @@
 
 package com.android.systemui.qs;
 
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
@@ -24,55 +27,89 @@
 import android.telephony.SubscriptionManager;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
-import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.TextView;
 
 import androidx.test.filters.SmallTest;
 
 import com.android.keyguard.CarrierTextController;
-import com.android.systemui.Dependency;
-import com.android.systemui.R;
+import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.statusbar.policy.NetworkController;
 import com.android.systemui.utils.leaks.LeakCheckedTest;
+import com.android.systemui.utils.os.FakeHandler;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Mock;
 import org.mockito.Mockito;
-import org.mockito.invocation.InvocationOnMock;
+import org.mockito.MockitoAnnotations;
 import org.mockito.stubbing.Answer;
 
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
 @SmallTest
-public class QSCarrierGroupTest extends LeakCheckedTest {
+public class QSCarrierGroupControllerTest extends LeakCheckedTest {
 
-    private QSCarrierGroup mCarrierGroup;
+    private QSCarrierGroupController mQSCarrierGroupController;
+    private NetworkController.SignalCallback mSignalCallback;
     private CarrierTextController.CarrierTextCallback mCallback;
+    @Mock
+    private QSCarrierGroup mQSCarrierGroup;
+    @Mock
+    private ActivityStarter mActivityStarter;
+    @Mock
+    private NetworkController mNetworkController;
+    @Mock
+    private CarrierTextController.Builder mCarrierTextControllerBuilder;
+    @Mock
+    private CarrierTextController mCarrierTextController;
     private TestableLooper mTestableLooper;
 
     @Before
     public void setup() throws Exception {
+        MockitoAnnotations.initMocks(this);
         injectLeakCheckedDependencies(ALL_SUPPORTED_CLASSES);
         mTestableLooper = TestableLooper.get(this);
-        mDependency.injectTestDependency(
-                Dependency.BG_HANDLER, new Handler(mTestableLooper.getLooper()));
-        mDependency.injectTestDependency(Dependency.MAIN_LOOPER, mTestableLooper.getLooper());
-        mTestableLooper.runWithLooper(
-                () -> mCarrierGroup = (QSCarrierGroup) LayoutInflater.from(mContext).inflate(
-                        R.layout.qs_carrier_group, null));
-        mCallback = mCarrierGroup.getCallback();
+        Handler handler = new FakeHandler(TestableLooper.get(this).getLooper());
+
+        when(mNetworkController.hasVoiceCallingFeature()).thenReturn(true);
+        doAnswer(invocation -> mSignalCallback = invocation.getArgument(0))
+                .when(mNetworkController)
+                .addCallback(any(NetworkController.SignalCallback.class));
+
+        when(mCarrierTextControllerBuilder.setShowAirplaneMode(anyBoolean()))
+                .thenReturn(mCarrierTextControllerBuilder);
+        when(mCarrierTextControllerBuilder.setShowMissingSim(anyBoolean()))
+                .thenReturn(mCarrierTextControllerBuilder);
+        when(mCarrierTextControllerBuilder.build()).thenReturn(mCarrierTextController);
+
+        doAnswer(invocation -> mCallback = invocation.getArgument(0))
+                .when(mCarrierTextController)
+                .setListening(any(CarrierTextController.CarrierTextCallback.class));
+
+        when(mQSCarrierGroup.getNoSimTextView()).thenReturn(new TextView(mContext));
+        when(mQSCarrierGroup.getCarrier1View()).thenReturn(mock(QSCarrier.class));
+        when(mQSCarrierGroup.getCarrier2View()).thenReturn(mock(QSCarrier.class));
+        when(mQSCarrierGroup.getCarrier3View()).thenReturn(mock(QSCarrier.class));
+        when(mQSCarrierGroup.getCarrierDivider1()).thenReturn(new View(mContext));
+        when(mQSCarrierGroup.getCarrierDivider2()).thenReturn(new View(mContext));
+
+        mQSCarrierGroupController = new QSCarrierGroupController.Builder(
+                mActivityStarter, handler, TestableLooper.get(this).getLooper(),
+                mNetworkController, mCarrierTextControllerBuilder)
+                .setQSCarrierGroup(mQSCarrierGroup)
+                .build();
+
+        mQSCarrierGroupController.setListening(true);
     }
 
     @Test // throws no Exception
     public void testUpdateCarrierText_sameLengths() {
-        QSCarrierGroup spiedCarrierGroup = Mockito.spy(mCarrierGroup);
-        when(spiedCarrierGroup.getSlotIndex(anyInt())).thenAnswer(
-                new Answer<Integer>() {
-                    @Override
-                    public Integer answer(InvocationOnMock invocationOnMock) throws Throwable {
-                        return invocationOnMock.getArgument(0);
-                    }
-                });
+        QSCarrierGroupController spiedCarrierGroupController =
+                Mockito.spy(mQSCarrierGroupController);
+        when(spiedCarrierGroupController.getSlotIndex(anyInt())).thenAnswer(
+                (Answer<Integer>) invocationOnMock -> invocationOnMock.getArgument(0));
 
         // listOfCarriers length 1, subscriptionIds length 1, anySims false
         CarrierTextController.CarrierTextCallbackInfo
@@ -115,14 +152,10 @@
 
     @Test // throws no Exception
     public void testUpdateCarrierText_differentLength() {
-        QSCarrierGroup spiedCarrierGroup = Mockito.spy(mCarrierGroup);
-        when(spiedCarrierGroup.getSlotIndex(anyInt())).thenAnswer(
-                new Answer<Integer>() {
-                    @Override
-                    public Integer answer(InvocationOnMock invocationOnMock) throws Throwable {
-                        return invocationOnMock.getArgument(0);
-                    }
-                });
+        QSCarrierGroupController spiedCarrierGroupController =
+                Mockito.spy(mQSCarrierGroupController);
+        when(spiedCarrierGroupController.getSlotIndex(anyInt())).thenAnswer(
+                (Answer<Integer>) invocationOnMock -> invocationOnMock.getArgument(0));
 
         // listOfCarriers length 2, subscriptionIds length 1, anySims false
         CarrierTextController.CarrierTextCallbackInfo
@@ -164,9 +197,11 @@
 
     @Test // throws no Exception
     public void testUpdateCarrierText_invalidSim() {
-        QSCarrierGroup spiedCarrierGroup = Mockito.spy(mCarrierGroup);
-        when(spiedCarrierGroup.getSlotIndex(anyInt())).thenReturn(
+        QSCarrierGroupController spiedCarrierGroupController =
+                Mockito.spy(mQSCarrierGroupController);
+        when(spiedCarrierGroupController.getSlotIndex(anyInt())).thenReturn(
                 SubscriptionManager.INVALID_SIM_SLOT_INDEX);
+
         CarrierTextController.CarrierTextCallbackInfo
                 c4 = new CarrierTextController.CarrierTextCallbackInfo(
                 "",
@@ -179,10 +214,7 @@
 
     @Test // throws no Exception
     public void testSetMobileDataIndicators_invalidSim() {
-        QSCarrierGroup spiedCarrierGroup = Mockito.spy(mCarrierGroup);
-        when(spiedCarrierGroup.getSlotIndex(anyInt())).thenReturn(
-                SubscriptionManager.INVALID_SIM_SLOT_INDEX);
-        spiedCarrierGroup.setMobileDataIndicators(
+        mSignalCallback.setMobileDataIndicators(
                 mock(NetworkController.IconState.class),
                 mock(NetworkController.IconState.class),
                 0, 0, true, true, "", "", true, 0, true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
index 6cebb12..8004562 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
@@ -147,9 +147,9 @@
                 new RemoteInputQuickSettingsDisabler(context, mock(ConfigurationController.class),
                         commandQueue),
                 new InjectionInflationController(SystemUIFactory.getInstance().getRootComponent()),
-                context,
                 mock(QSTileHost.class),
                 mock(StatusBarStateController.class),
-                commandQueue);
+                commandQueue,
+                mock(QSContainerImplController.Builder.class));
     }
 }