Move NetworkController broadcasts/listeners to BG

Also do some refactoring to avoid having to sets of callback interfaces
with 75% of the same data.

Bug: 19520495
Change-Id: Ife1c71174c0d6a21f924f7de3cb2f97a04c3d5a1
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
index 1721335..dcf0438 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
@@ -30,9 +30,11 @@
 import com.android.systemui.qs.QSTileView;
 import com.android.systemui.qs.SignalTileView;
 import com.android.systemui.statusbar.policy.NetworkController;
+import com.android.systemui.statusbar.policy.NetworkController.IconState;
 import com.android.systemui.statusbar.policy.NetworkController.MobileDataController;
 import com.android.systemui.statusbar.policy.NetworkController.MobileDataController.DataUsageInfo;
-import com.android.systemui.statusbar.policy.NetworkController.NetworkSignalChangedCallback;
+import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
+import com.android.systemui.statusbar.policy.SignalCallbackAdapter;
 
 /** Quick settings tile: Cellular **/
 public class CellularTile extends QSTile<QSTile.SignalState> {
@@ -63,9 +65,9 @@
     @Override
     public void setListening(boolean listening) {
         if (listening) {
-            mController.addNetworkSignalChangedCallback(mCallback);
+            mController.addSignalCallback(mSignalCallback);
         } else {
-            mController.removeNetworkSignalChangedCallback(mCallback);
+            mController.removeSignalCallback(mSignalCallback);
         }
     }
 
@@ -138,7 +140,6 @@
     private static final class CallbackInfo {
         boolean enabled;
         boolean wifiEnabled;
-        boolean wifiConnected;
         boolean airplaneModeEnabled;
         int mobileSignalIconId;
         String signalContentDescription;
@@ -151,40 +152,39 @@
         boolean isDataTypeIconWide;
     }
 
-    private final NetworkSignalChangedCallback mCallback = new NetworkSignalChangedCallback() {
+    private final SignalCallback mSignalCallback = new SignalCallbackAdapter() {
         private final CallbackInfo mInfo = new CallbackInfo();
-
         @Override
-        public void onWifiSignalChanged(boolean enabled, boolean connected, int wifiSignalIconId,
-                boolean activityIn, boolean activityOut,
-                String wifiSignalContentDescriptionId, String description) {
+        public void setWifiIndicators(boolean enabled, IconState statusIcon, IconState qsIcon,
+                boolean activityIn, boolean activityOut, String description) {
             mInfo.wifiEnabled = enabled;
-            mInfo.wifiConnected = connected;
             refreshState(mInfo);
         }
 
         @Override
-        public void onMobileDataSignalChanged(boolean enabled,
-                int mobileSignalIconId,
-                String mobileSignalContentDescriptionId, int dataTypeIconId,
-                boolean activityIn, boolean activityOut,
-                String dataTypeContentDescriptionId, String description,
-                boolean isDataTypeIconWide) {
-            mInfo.enabled = enabled;
-            mInfo.mobileSignalIconId = mobileSignalIconId;
-            mInfo.signalContentDescription = mobileSignalContentDescriptionId;
-            mInfo.dataTypeIconId = dataTypeIconId;
-            mInfo.dataContentDescription = dataTypeContentDescriptionId;
+        public void setMobileDataIndicators(IconState statusIcon, IconState qsIcon,
+                int darkStatusIcon, int statusType, int qsType, boolean activityIn,
+                boolean activityOut, String typeContentDescription, String description,
+                boolean isWide, int subId) {
+            if (qsIcon == null) {
+                // Not data sim, don't display.
+                return;
+            }
+            mInfo.enabled = qsIcon.visible;
+            mInfo.mobileSignalIconId = qsIcon.icon;
+            mInfo.signalContentDescription = qsIcon.contentDescription;
+            mInfo.dataTypeIconId = qsType;
+            mInfo.dataContentDescription = typeContentDescription;
             mInfo.activityIn = activityIn;
             mInfo.activityOut = activityOut;
             mInfo.enabledDesc = description;
-            mInfo.isDataTypeIconWide = isDataTypeIconWide;
+            mInfo.isDataTypeIconWide = qsType != 0 && isWide;
             refreshState(mInfo);
         }
 
         @Override
-        public void onNoSimVisibleChanged(boolean visible) {
-            mInfo.noSim = visible;
+        public void setNoSims(boolean show) {
+            mInfo.noSim = show;
             if (mInfo.noSim) {
                 // Make sure signal gets cleared out when no sims.
                 mInfo.mobileSignalIconId = 0;
@@ -199,12 +199,13 @@
         }
 
         @Override
-        public void onAirplaneModeChanged(boolean enabled) {
-            mInfo.airplaneModeEnabled = enabled;
+        public void setIsAirplaneMode(IconState icon) {
+            mInfo.airplaneModeEnabled = icon.visible;
             refreshState(mInfo);
         }
 
-        public void onMobileDataEnabled(boolean enabled) {
+        @Override
+        public void setMobileDataEnabled(boolean enabled) {
             mDetailAdapter.setMobileDataEnabled(enabled);
         }
     };
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
index c3f9e33..9504ea3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
@@ -16,8 +16,6 @@
 
 package com.android.systemui.qs.tiles;
 
-import java.util.List;
-
 import android.content.Context;
 import android.content.Intent;
 import android.content.res.Resources;
@@ -36,7 +34,11 @@
 import com.android.systemui.qs.SignalTileView;
 import com.android.systemui.statusbar.policy.NetworkController;
 import com.android.systemui.statusbar.policy.NetworkController.AccessPointController;
-import com.android.systemui.statusbar.policy.NetworkController.NetworkSignalChangedCallback;
+import com.android.systemui.statusbar.policy.NetworkController.IconState;
+import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
+import com.android.systemui.statusbar.policy.SignalCallbackAdapter;
+
+import java.util.List;
 
 /** Quick settings tile: Wifi **/
 public class WifiTile extends QSTile<QSTile.SignalState> {
@@ -67,9 +69,9 @@
     @Override
     public void setListening(boolean listening) {
         if (listening) {
-            mController.addNetworkSignalChangedCallback(mCallback);
+            mController.addSignalCallback(mSignalCallback);
         } else {
-            mController.removeNetworkSignalChangedCallback(mCallback);
+            mController.removeSignalCallback(mSignalCallback);
         }
     }
 
@@ -211,46 +213,21 @@
         }
     }
 
-    private final NetworkSignalChangedCallback mCallback = new NetworkSignalChangedCallback() {
+    private final SignalCallback mSignalCallback = new SignalCallbackAdapter() {
         @Override
-        public void onWifiSignalChanged(boolean enabled, boolean connected, int wifiSignalIconId,
-                boolean activityIn, boolean activityOut,
-                String wifiSignalContentDescriptionId, String description) {
+        public void setWifiIndicators(boolean enabled, IconState statusIcon, IconState qsIcon,
+                boolean activityIn, boolean activityOut, String description) {
             if (DEBUG) Log.d(TAG, "onWifiSignalChanged enabled=" + enabled);
             final CallbackInfo info = new CallbackInfo();
             info.enabled = enabled;
-            info.connected = connected;
-            info.wifiSignalIconId = wifiSignalIconId;
+            info.connected = qsIcon.visible;
+            info.wifiSignalIconId = qsIcon.icon;
             info.enabledDesc = description;
             info.activityIn = activityIn;
             info.activityOut = activityOut;
-            info.wifiSignalContentDescription = wifiSignalContentDescriptionId;
+            info.wifiSignalContentDescription = qsIcon.contentDescription;
             refreshState(info);
         }
-
-        @Override
-        public void onMobileDataSignalChanged(boolean enabled,
-                int mobileSignalIconId,
-                String mobileSignalContentDescriptionId, int dataTypeIconId,
-                boolean activityIn, boolean activityOut,
-                String dataTypeContentDescriptionId, String description,
-                boolean isDataTypeIconWide) {
-            // noop
-        }
-
-        public void onNoSimVisibleChanged(boolean noSims) {
-            // noop
-        }
-
-        @Override
-        public void onAirplaneModeChanged(boolean enabled) {
-            // noop
-        }
-
-        @Override
-        public void onMobileDataEnabled(boolean enabled) {
-            // noop
-        }
     };
 
     private final class WifiDetailAdapter implements DetailAdapter,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java
index f6629dd..14e491b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java
@@ -33,6 +33,7 @@
 import android.widget.LinearLayout;
 
 import com.android.systemui.R;
+import com.android.systemui.statusbar.policy.NetworkController.IconState;
 import com.android.systemui.statusbar.policy.NetworkControllerImpl;
 import com.android.systemui.statusbar.policy.SecurityController;
 
@@ -42,7 +43,7 @@
 // Intimately tied to the design of res/layout/signal_cluster_view.xml
 public class SignalClusterView
         extends LinearLayout
-        implements NetworkControllerImpl.SignalCluster,
+        implements NetworkControllerImpl.SignalCallback,
         SecurityController.SecurityControllerCallback {
 
     static final String TAG = "SignalClusterView";
@@ -59,7 +60,7 @@
     private int mWifiStrengthId = 0;
     private boolean mIsAirplaneMode = false;
     private int mAirplaneIconId = 0;
-    private int mAirplaneContentDescription;
+    private String mAirplaneContentDescription;
     private String mWifiDescription;
     private String mEthernetDescription;
     private ArrayList<PhoneState> mPhoneStates = new ArrayList<PhoneState>();
@@ -166,35 +167,36 @@
     }
 
     @Override
-    public void setWifiIndicators(boolean visible, int strengthIcon, String contentDescription) {
-        mWifiVisible = visible;
-        mWifiStrengthId = strengthIcon;
-        mWifiDescription = contentDescription;
+    public void setWifiIndicators(boolean enabled, IconState statusIcon, IconState qsIcon,
+            boolean activityIn, boolean activityOut, String description) {
+        mWifiVisible = statusIcon.visible;
+        mWifiStrengthId = statusIcon.icon;
+        mWifiDescription = statusIcon.contentDescription;
 
         apply();
     }
 
     @Override
-    public void setMobileDataIndicators(boolean visible, int strengthIcon, int darkStrengthIcon,
-            int typeIcon, String contentDescription, String typeContentDescription,
-            boolean isTypeIconWide, int subId) {
+    public void setMobileDataIndicators(IconState statusIcon, IconState qsIcon, int darkStatusIcon,
+            int statusType, int qsType, boolean activityIn, boolean activityOut,
+            String typeContentDescription, String description, boolean isWide, int subId) {
         PhoneState state = getOrInflateState(subId);
-        state.mMobileVisible = visible;
-        state.mMobileStrengthId = strengthIcon;
-        state.mMobileDarkStrengthId = darkStrengthIcon;
-        state.mMobileTypeId = typeIcon;
-        state.mMobileDescription = contentDescription;
+        state.mMobileVisible = statusIcon.visible;
+        state.mMobileStrengthId = statusIcon.icon;
+        state.mMobileDarkStrengthId = darkStatusIcon;
+        state.mMobileTypeId = statusType;
+        state.mMobileDescription = statusIcon.contentDescription;
         state.mMobileTypeDescription = typeContentDescription;
-        state.mIsMobileTypeIconWide = isTypeIconWide;
+        state.mIsMobileTypeIconWide = statusType != 0 && isWide;
 
         apply();
     }
 
     @Override
-    public void setEthernetIndicators(boolean visible, int icon, String contentDescription) {
-        mEthernetVisible = visible;
-        mEthernetIconId = icon;
-        mEthernetDescription = contentDescription;
+    public void setEthernetIndicators(IconState state) {
+        mEthernetVisible = state.visible;
+        mEthernetIconId = state.icon;
+        mEthernetDescription = state.contentDescription;
 
         apply();
     }
@@ -239,15 +241,20 @@
     }
 
     @Override
-    public void setIsAirplaneMode(boolean is, int airplaneIconId, int contentDescription) {
-        mIsAirplaneMode = is;
-        mAirplaneIconId = airplaneIconId;
-        mAirplaneContentDescription = contentDescription;
+    public void setIsAirplaneMode(IconState icon) {
+        mIsAirplaneMode = icon.visible;
+        mAirplaneIconId = icon.icon;
+        mAirplaneContentDescription = icon.contentDescription;
 
         apply();
     }
 
     @Override
+    public void setMobileDataEnabled(boolean enabled) {
+        // Don't care.
+    }
+
+    @Override
     public boolean dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event) {
         // Standard group layout onPopulateAccessibilityEvent() implementations
         // ignore content description, so populate manually
@@ -343,8 +350,7 @@
 
         if (mIsAirplaneMode) {
             mAirplane.setImageResource(mAirplaneIconId);
-            mAirplane.setContentDescription(mAirplaneContentDescription != 0 ?
-                    mContext.getString(mAirplaneContentDescription) : null);
+            mAirplane.setContentDescription(mAirplaneContentDescription);
             mAirplane.setVisibility(View.VISIBLE);
         } else {
             mAirplane.setVisibility(View.GONE);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
index 887b8f4..2a9df19 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -779,9 +779,9 @@
                 (SignalClusterView) mKeyguardStatusBar.findViewById(R.id.signal_cluster);
         final SignalClusterView signalClusterQs =
                 (SignalClusterView) mHeader.findViewById(R.id.signal_cluster);
-        mNetworkController.addSignalCluster(signalCluster);
-        mNetworkController.addSignalCluster(signalClusterKeyguard);
-        mNetworkController.addSignalCluster(signalClusterQs);
+        mNetworkController.addSignalCallback(signalCluster);
+        mNetworkController.addSignalCallback(signalClusterKeyguard);
+        mNetworkController.addSignalCallback(signalClusterQs);
         signalCluster.setSecurityController(mSecurityController);
         signalCluster.setNetworkController(mNetworkController);
         signalClusterKeyguard.setSecurityController(mSecurityController);
@@ -3087,6 +3087,16 @@
         }
         mContext.unregisterReceiver(mBroadcastReceiver);
         mAssistManager.destroy();
+
+        final SignalClusterView signalCluster =
+                (SignalClusterView) mStatusBarView.findViewById(R.id.signal_cluster);
+        final SignalClusterView signalClusterKeyguard =
+                (SignalClusterView) mKeyguardStatusBar.findViewById(R.id.signal_cluster);
+        final SignalClusterView signalClusterQs =
+                (SignalClusterView) mHeader.findViewById(R.id.signal_cluster);
+        mNetworkController.addSignalCallback(signalCluster);
+        mNetworkController.addSignalCallback(signalClusterKeyguard);
+        mNetworkController.addSignalCallback(signalClusterQs);
     }
 
     private boolean mDemoModeAllowed;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CallbackHandler.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CallbackHandler.java
new file mode 100644
index 0000000..7f52191
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CallbackHandler.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2015 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.policy;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.telephony.SubscriptionInfo;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.statusbar.policy.NetworkController.IconState;
+import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
+import com.android.systemui.statusbar.policy.NetworkControllerImpl.EmergencyListener;
+
+import java.util.ArrayList;
+import java.util.List;
+
+
+/**
+ * Implements network listeners and forwards the calls along onto other listeners but on
+ * the current or specified Looper.
+ */
+public class CallbackHandler extends Handler implements EmergencyListener, SignalCallback {
+    private static final int MSG_EMERGENCE_CHANGED           = 0;
+    private static final int MSG_SUBS_CHANGED                = 1;
+    private static final int MSG_NO_SIM_VISIBLE_CHANGED      = 2;
+    private static final int MSG_ETHERNET_CHANGED            = 3;
+    private static final int MSG_AIRPLANE_MODE_CHANGED       = 4;
+    private static final int MSG_MOBILE_DATA_ENABLED_CHANGED = 5;
+    private static final int MSG_ADD_REMOVE_EMERGENCY        = 6;
+    private static final int MSG_ADD_REMOVE_SIGNAL           = 7;
+
+    // All the callbacks.
+    private final ArrayList<EmergencyListener> mEmergencyListeners = new ArrayList<>();
+    private final ArrayList<SignalCallback> mSignalCallbacks = new ArrayList<>();
+
+    public CallbackHandler() {
+        super();
+    }
+
+    @VisibleForTesting
+    CallbackHandler(Looper looper) {
+        super(looper);
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public void handleMessage(Message msg) {
+        switch (msg.what) {
+            case MSG_EMERGENCE_CHANGED:
+                for (EmergencyListener listener : mEmergencyListeners) {
+                    listener.setEmergencyCallsOnly(msg.arg1 != 0);
+                }
+                break;
+            case MSG_SUBS_CHANGED:
+                for (SignalCallback signalCluster : mSignalCallbacks) {
+                    signalCluster.setSubs((List<SubscriptionInfo>) msg.obj);
+                }
+                break;
+            case MSG_NO_SIM_VISIBLE_CHANGED:
+                for (SignalCallback signalCluster : mSignalCallbacks) {
+                    signalCluster.setNoSims(msg.arg1 != 0);
+                }
+                break;
+            case MSG_ETHERNET_CHANGED:
+                for (SignalCallback signalCluster : mSignalCallbacks) {
+                    signalCluster.setEthernetIndicators((IconState) msg.obj);
+                }
+                break;
+            case MSG_AIRPLANE_MODE_CHANGED:
+                for (SignalCallback signalCluster : mSignalCallbacks) {
+                    signalCluster.setIsAirplaneMode((IconState) msg.obj);
+                }
+                break;
+            case MSG_MOBILE_DATA_ENABLED_CHANGED:
+                for (SignalCallback signalCluster : mSignalCallbacks) {
+                    signalCluster.setMobileDataEnabled(msg.arg1 != 0);
+                }
+                break;
+            case MSG_ADD_REMOVE_EMERGENCY:
+                if (msg.arg1 != 0) {
+                    mEmergencyListeners.add((EmergencyListener) msg.obj);
+                } else {
+                    mEmergencyListeners.remove((EmergencyListener) msg.obj);
+                }
+                break;
+            case MSG_ADD_REMOVE_SIGNAL:
+                if (msg.arg1 != 0) {
+                    mSignalCallbacks.add((SignalCallback) msg.obj);
+                } else {
+                    mSignalCallbacks.remove((SignalCallback) msg.obj);
+                }
+                break;
+        }
+    }
+
+    @Override
+    public void setWifiIndicators(final boolean enabled, final IconState statusIcon,
+            final IconState qsIcon, final boolean activityIn, final boolean activityOut,
+            final String description) {
+        post(new Runnable() {
+            @Override
+            public void run() {
+                for (SignalCallback callback : mSignalCallbacks) {
+                    callback.setWifiIndicators(enabled, statusIcon, qsIcon, activityIn, activityOut,
+                            description);
+                }
+            }
+        });
+    }
+
+    @Override
+    public void setMobileDataIndicators(final IconState statusIcon, final IconState qsIcon,
+            final int darkStatusIcon, final int statusType, final int qsType,
+            final boolean activityIn, final boolean activityOut,
+            final String typeContentDescription, final String description, final boolean isWide,
+            final int subId) {
+        post(new Runnable() {
+            @Override
+            public void run() {
+                for (SignalCallback signalCluster : mSignalCallbacks) {
+                    signalCluster.setMobileDataIndicators(statusIcon, qsIcon, darkStatusIcon,
+                            statusType, qsType, activityIn, activityOut, typeContentDescription,
+                            description, isWide, subId);
+                }
+            }
+        });
+    }
+
+    @Override
+    public void setSubs(List<SubscriptionInfo> subs) {
+        obtainMessage(MSG_SUBS_CHANGED, subs).sendToTarget();
+    }
+
+    @Override
+    public void setNoSims(boolean show) {
+        obtainMessage(MSG_NO_SIM_VISIBLE_CHANGED, show ? 1 : 0, 0).sendToTarget();
+    }
+
+    @Override
+    public void setMobileDataEnabled(boolean enabled) {
+        obtainMessage(MSG_MOBILE_DATA_ENABLED_CHANGED, enabled ? 1 : 0, 0).sendToTarget();
+    }
+
+    @Override
+    public void setEmergencyCallsOnly(boolean emergencyOnly) {
+        obtainMessage(MSG_EMERGENCE_CHANGED, emergencyOnly ? 1 : 0, 0).sendToTarget();
+    }
+
+    @Override
+    public void setEthernetIndicators(IconState icon) {
+        obtainMessage(MSG_ETHERNET_CHANGED, icon).sendToTarget();;
+    }
+
+    @Override
+    public void setIsAirplaneMode(IconState icon) {
+        obtainMessage(MSG_AIRPLANE_MODE_CHANGED, icon).sendToTarget();;
+    }
+
+    public void setListening(EmergencyListener listener, boolean listening) {
+        obtainMessage(MSG_ADD_REMOVE_EMERGENCY, listening ? 1 : 0, 0, listener).sendToTarget();
+    }
+
+    public void setListening(SignalCallback listener, boolean listening) {
+        obtainMessage(MSG_ADD_REMOVE_SIGNAL, listening ? 1 : 0, 0, listener).sendToTarget();
+    }
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/EthernetSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/EthernetSignalController.java
index 9c044c4..a2cd50a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/EthernetSignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/EthernetSignalController.java
@@ -18,22 +18,16 @@
 import android.content.Context;
 import android.net.NetworkCapabilities;
 
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.systemui.statusbar.policy.NetworkController.NetworkSignalChangedCallback;
-import com.android.systemui.statusbar.policy.NetworkControllerImpl.SignalCluster;
-
-import java.util.List;
-import java.util.Objects;
+import com.android.systemui.statusbar.policy.NetworkController.IconState;
 
 
 public class EthernetSignalController extends
         SignalController<SignalController.State, SignalController.IconGroup> {
 
     public EthernetSignalController(Context context,
-            List<NetworkSignalChangedCallback> signalCallbacks,
-            List<SignalCluster> signalClusters, NetworkControllerImpl networkController) {
+            CallbackHandler callbackHandler, NetworkControllerImpl networkController) {
         super("EthernetSignalController", context, NetworkCapabilities.TRANSPORT_ETHERNET,
-                signalCallbacks, signalClusters, networkController);
+                callbackHandler, networkController);
         mCurrentState.iconGroup = mLastState.iconGroup = new IconGroup(
                 "Ethernet Icons",
                 EthernetIcons.ETHERNET_ICONS,
@@ -49,11 +43,8 @@
         String contentDescription = getStringIfExists(getContentDescription());
 
         // TODO: wire up data transfer using WifiSignalPoller.
-        int signalClustersLength = mSignalClusters.size();
-        for (int i = 0; i < signalClustersLength; i++) {
-            mSignalClusters.get(i).setEthernetIndicators(ethernetVisible, getCurrentIconId(),
-                    contentDescription);
-        }
+        mCallbackHandler.setEthernetIndicators(new IconState(ethernetVisible, getCurrentIconId(),
+                contentDescription));
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
index 22bf47c..5515873 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
@@ -18,6 +18,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.net.NetworkCapabilities;
+import android.os.Looper;
 import android.telephony.PhoneStateListener;
 import android.telephony.ServiceState;
 import android.telephony.SignalStrength;
@@ -31,12 +32,10 @@
 import com.android.internal.telephony.TelephonyIntents;
 import com.android.internal.telephony.cdma.EriInfo;
 import com.android.systemui.R;
-import com.android.systemui.statusbar.policy.NetworkController.NetworkSignalChangedCallback;
+import com.android.systemui.statusbar.policy.NetworkController.IconState;
 import com.android.systemui.statusbar.policy.NetworkControllerImpl.Config;
-import com.android.systemui.statusbar.policy.NetworkControllerImpl.SignalCluster;
 
 import java.io.PrintWriter;
-import java.util.List;
 import java.util.Objects;
 
 
@@ -66,17 +65,17 @@
     // TODO: Reduce number of vars passed in, if we have the NetworkController, probably don't
     // need listener lists anymore.
     public MobileSignalController(Context context, Config config, boolean hasMobileData,
-            TelephonyManager phone, List<NetworkSignalChangedCallback> signalCallbacks,
-            List<SignalCluster> signalClusters, NetworkControllerImpl networkController,
-            SubscriptionInfo info) {
+            TelephonyManager phone, CallbackHandler callbackHandler,
+            NetworkControllerImpl networkController, SubscriptionInfo info, Looper receiverLooper) {
         super("MobileSignalController(" + info.getSubscriptionId() + ")", context,
-                NetworkCapabilities.TRANSPORT_CELLULAR, signalCallbacks, signalClusters,
+                NetworkCapabilities.TRANSPORT_CELLULAR, callbackHandler,
                 networkController);
         mNetworkToIconLookup = new SparseArray<>();
         mConfig = config;
         mPhone = phone;
         mSubscriptionInfo = info;
-        mPhoneStateListener = new MobilePhoneStateListener(info.getSubscriptionId());
+        mPhoneStateListener = new MobilePhoneStateListener(info.getSubscriptionId(),
+                receiverLooper);
         mNetworkNameSeparator = getStringIfExists(R.string.status_bar_network_name_separator);
         mNetworkNameDefault = getStringIfExists(
                 com.android.internal.R.string.lockscreen_carrier_default);
@@ -199,41 +198,29 @@
         boolean showDataIcon = mCurrentState.dataConnected && mCurrentState.inetForNetwork != 0
                 || mCurrentState.iconGroup == TelephonyIcons.ROAMING;
 
+        IconState statusIcon = new IconState(mCurrentState.enabled && !mCurrentState.airplaneMode,
+                getCurrentIconId(), contentDescription);
+
+        int qsTypeIcon = 0;
+        IconState qsIcon = null;
+        String description = null;
         // Only send data sim callbacks to QS.
         if (mCurrentState.dataSim) {
-            int qsTypeIcon = showDataIcon ? icons.mQsDataType[mCurrentState.inetForNetwork] : 0;
-            int length = mSignalsChangedCallbacks.size();
-            for (int i = 0; i < length; i++) {
-                mSignalsChangedCallbacks.get(i).onMobileDataSignalChanged(mCurrentState.enabled
-                        && !mCurrentState.isEmergency,
-                        getQsCurrentIconId(), contentDescription,
-                        qsTypeIcon,
-                        mCurrentState.dataConnected
-                            && !mCurrentState.carrierNetworkChangeMode
-                            && mCurrentState.activityIn,
-                        mCurrentState.dataConnected
-                            && !mCurrentState.carrierNetworkChangeMode
-                            && mCurrentState.activityOut,
-                        dataContentDescription,
-                        mCurrentState.isEmergency ? null : mCurrentState.networkName,
-                        // Only wide if actually showing something.
-                        icons.mIsWide && qsTypeIcon != 0);
-            }
+            qsTypeIcon = showDataIcon ? icons.mQsDataType[mCurrentState.inetForNetwork] : 0;
+            qsIcon = new IconState(mCurrentState.enabled
+                    && !mCurrentState.isEmergency, getQsCurrentIconId(), contentDescription);
+            description = mCurrentState.isEmergency ? null : mCurrentState.networkName;
         }
+        boolean activityIn = mCurrentState.dataConnected
+                        && !mCurrentState.carrierNetworkChangeMode
+                        && mCurrentState.activityIn;
+        boolean activityOut = mCurrentState.dataConnected
+                        && !mCurrentState.carrierNetworkChangeMode
+                        && mCurrentState.activityOut;
         int typeIcon = showDataIcon ? icons.mDataType : 0;
-        int signalClustersLength = mSignalClusters.size();
-        for (int i = 0; i < signalClustersLength; i++) {
-            mSignalClusters.get(i).setMobileDataIndicators(
-                    mCurrentState.enabled && !mCurrentState.airplaneMode,
-                    getCurrentIconId(),
-                    getCurrentDarkIconId(),
-                    typeIcon,
-                    contentDescription,
-                    dataContentDescription,
-                    // Only wide if actually showing something.
-                    icons.mIsWide && typeIcon != 0,
-                    mSubscriptionInfo.getSubscriptionId());
-        }
+        mCallbackHandler.setMobileDataIndicators(statusIcon, qsIcon, getCurrentDarkIconId(),
+                typeIcon, qsTypeIcon, activityIn, activityOut, dataContentDescription, description,
+                icons.mIsWide, mSubscriptionInfo.getSubscriptionId());
     }
 
     private int getCurrentDarkIconId() {
@@ -425,8 +412,8 @@
     }
 
     class MobilePhoneStateListener extends PhoneStateListener {
-        public MobilePhoneStateListener(int subId) {
-            super(subId);
+        public MobilePhoneStateListener(int subId, Looper looper) {
+            super(subId, looper);
         }
 
         @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
index 9212837..070ca63 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
@@ -16,7 +16,9 @@
 
 package com.android.systemui.statusbar.policy;
 
+import android.content.Context;
 import android.content.Intent;
+import android.telephony.SubscriptionInfo;
 
 import com.android.settingslib.wifi.AccessPoint;
 
@@ -25,25 +27,45 @@
 public interface NetworkController {
 
     boolean hasMobileDataFeature();
-    void addNetworkSignalChangedCallback(NetworkSignalChangedCallback cb);
-    void removeNetworkSignalChangedCallback(NetworkSignalChangedCallback cb);
+    void addSignalCallback(SignalCallback cb);
+    void removeSignalCallback(SignalCallback cb);
     void setWifiEnabled(boolean enabled);
     void onUserSwitched(int newUserId);
     AccessPointController getAccessPointController();
     MobileDataController getMobileDataController();
 
-    public interface NetworkSignalChangedCallback {
-        void onWifiSignalChanged(boolean enabled, boolean connected, int wifiSignalIconId,
-                boolean activityIn, boolean activityOut,
-                String wifiSignalContentDescriptionId, String description);
-        void onMobileDataSignalChanged(boolean enabled, int mobileSignalIconId,
-                String mobileSignalContentDescriptionId, int dataTypeIconId,
-                boolean activityIn, boolean activityOut,
-                String dataTypeContentDescriptionId, String description,
-                boolean isDataTypeIconWide);
-        void onNoSimVisibleChanged(boolean visible);
-        void onAirplaneModeChanged(boolean enabled);
-        void onMobileDataEnabled(boolean enabled);
+    public interface SignalCallback {
+        void setWifiIndicators(boolean enabled, IconState statusIcon, IconState qsIcon,
+                boolean activityIn, boolean activityOut, String description);
+
+        void setMobileDataIndicators(IconState statusIcon, IconState qsIcon, int darkStatusIcon,
+                int statusType, int qsType, boolean activityIn, boolean activityOut,
+                String typeContentDescription, String description, boolean isWide, int subId);
+        void setSubs(List<SubscriptionInfo> subs);
+        void setNoSims(boolean show);
+
+        void setEthernetIndicators(IconState icon);
+
+        void setIsAirplaneMode(IconState icon);
+
+        void setMobileDataEnabled(boolean enabled);
+    }
+
+    public static class IconState {
+        public final boolean visible;
+        public final int icon;
+        public final String contentDescription;
+
+        public IconState(boolean visible, int icon, String contentDescription) {
+            this.visible = visible;
+            this.icon = icon;
+            this.contentDescription = contentDescription;
+        }
+
+        public IconState(boolean visible, int icon, int contentDescription,
+                Context context) {
+            this(visible, icon, context.getString(contentDescription));
+        }
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
index 95bac58..484f66a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
@@ -28,6 +28,7 @@
 import android.net.wifi.WifiManager;
 import android.os.AsyncTask;
 import android.os.Bundle;
+import android.os.Handler;
 import android.os.Looper;
 import android.provider.Settings;
 import android.telephony.SubscriptionInfo;
@@ -62,7 +63,7 @@
     static final String TAG = "NetworkController";
     static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
     // additional diagnostics, but not logspew
-    static final boolean CHATTY =  Log.isLoggable(TAG + ".Chat", Log.DEBUG);
+    static final boolean CHATTY =  Log.isLoggable(TAG + "Chat", Log.DEBUG);
 
     private final Context mContext;
     private final TelephonyManager mPhone;
@@ -102,16 +103,17 @@
     // This list holds our ordering.
     private List<SubscriptionInfo> mCurrentSubscriptions = new ArrayList<>();
 
-    // All the callbacks.
-    private ArrayList<EmergencyListener> mEmergencyListeners = new ArrayList<>();
-    private ArrayList<SignalCluster> mSignalClusters = new ArrayList<>();
-    private ArrayList<NetworkSignalChangedCallback> mSignalsChangedCallbacks = new ArrayList<>();
     @VisibleForTesting
     boolean mListening;
 
     // The current user ID.
     private int mCurrentUserId;
 
+    // Handler that all broadcasts are received on.
+    private final Handler mReceiverHandler;
+    // Handler that all callbacks are made on.
+    private final CallbackHandler mCallbackHandler;
+
     /**
      * Construct this controller object and register for updates.
      */
@@ -119,20 +121,24 @@
         this(context, (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE),
                 (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE),
                 (WifiManager) context.getSystemService(Context.WIFI_SERVICE),
-                SubscriptionManager.from(context), Config.readConfig(context),
+                SubscriptionManager.from(context), Config.readConfig(context), bgLooper,
+                new CallbackHandler(),
                 new AccessPointControllerImpl(context, bgLooper),
                 new MobileDataControllerImpl(context));
-        registerListeners();
+        mReceiverHandler.post(mRegisterListeners);
     }
 
     @VisibleForTesting
     NetworkControllerImpl(Context context, ConnectivityManager connectivityManager,
             TelephonyManager telephonyManager, WifiManager wifiManager,
-            SubscriptionManager subManager, Config config,
+            SubscriptionManager subManager, Config config, Looper bgLooper,
+            CallbackHandler callbackHandler,
             AccessPointControllerImpl accessPointController,
             MobileDataControllerImpl mobileDataController) {
         mContext = context;
         mConfig = config;
+        mReceiverHandler = new Handler(bgLooper);
+        mCallbackHandler = callbackHandler;
 
         mSubscriptionManager = subManager;
         mConnectivityManager = connectivityManager;
@@ -140,7 +146,7 @@
                 mConnectivityManager.isNetworkSupported(ConnectivityManager.TYPE_MOBILE);
 
         // telephony
-        mPhone = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
+        mPhone = telephonyManager;
 
         // wifi
         mWifiManager = wifiManager;
@@ -153,14 +159,13 @@
         mMobileDataController.setCallback(new MobileDataControllerImpl.Callback() {
             @Override
             public void onMobileDataEnabled(boolean enabled) {
-                notifyMobileDataEnabled(enabled);
+                mCallbackHandler.setMobileDataEnabled(enabled);
             }
         });
         mWifiSignalController = new WifiSignalController(mContext, mHasMobileDataFeature,
-                mSignalsChangedCallbacks, mSignalClusters, this);
+                mCallbackHandler, this);
 
-        mEthernetSignalController = new EthernetSignalController(mContext, mSignalsChangedCallbacks,
-                mSignalClusters, this);
+        mEthernetSignalController = new EthernetSignalController(mContext, mCallbackHandler, this);
 
         // AIRPLANE_MODE_CHANGED is sent at boot; we've probably already missed it
         updateAirplaneMode(true /* force callback */);
@@ -185,7 +190,7 @@
         filter.addAction(ConnectivityManager.INET_CONDITION_ACTION);
         filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
         filter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED);
-        mContext.registerReceiver(this, filter);
+        mContext.registerReceiver(this, filter, null, mReceiverHandler);
         mListening = true;
 
         updateMobileControllers();
@@ -215,15 +220,8 @@
     }
 
     public void addEmergencyListener(EmergencyListener listener) {
-        mEmergencyListeners.add(listener);
-        listener.setEmergencyCallsOnly(isEmergencyOnly());
-    }
-
-    private void notifyMobileDataEnabled(boolean enabled) {
-        final int length = mSignalsChangedCallbacks.size();
-        for (int i = 0; i < length; i++) {
-            mSignalsChangedCallbacks.get(i).onMobileDataEnabled(enabled);
-        }
+        mCallbackHandler.setListening(listener, true);
+        mCallbackHandler.setEmergencyCallsOnly(isEmergencyOnly());
     }
 
     public boolean hasMobileDataFeature() {
@@ -275,19 +273,15 @@
      * so we should recheck and send out the state to listeners.
      */
     void recalculateEmergency() {
-        final boolean emergencyOnly = isEmergencyOnly();
-        final int length = mEmergencyListeners.size();
-        for (int i = 0; i < length; i++) {
-            mEmergencyListeners.get(i).setEmergencyCallsOnly(emergencyOnly);
-        }
+        mCallbackHandler.setEmergencyCallsOnly(isEmergencyOnly());
     }
 
-    public void addSignalCluster(SignalCluster cluster) {
-        mSignalClusters.add(cluster);
-        cluster.setSubs(mCurrentSubscriptions);
-        cluster.setIsAirplaneMode(mAirplaneMode, TelephonyIcons.FLIGHT_MODE_ICON,
-                R.string.accessibility_airplane_mode);
-        cluster.setNoSims(mHasNoSims);
+    public void addSignalCallback(SignalCallback cb) {
+        mCallbackHandler.setListening(cb, true);
+        mCallbackHandler.setSubs(mCurrentSubscriptions);
+        mCallbackHandler.setIsAirplaneMode(new IconState(mAirplaneMode,
+                TelephonyIcons.FLIGHT_MODE_ICON, R.string.accessibility_airplane_mode, mContext));
+        mCallbackHandler.setNoSims(mHasNoSims);
         mWifiSignalController.notifyListeners();
         mEthernetSignalController.notifyListeners();
         for (MobileSignalController mobileSignalController : mMobileSignalControllers.values()) {
@@ -295,19 +289,9 @@
         }
     }
 
-    public void addNetworkSignalChangedCallback(NetworkSignalChangedCallback cb) {
-        mSignalsChangedCallbacks.add(cb);
-        cb.onAirplaneModeChanged(mAirplaneMode);
-        cb.onNoSimVisibleChanged(mHasNoSims);
-        mWifiSignalController.notifyListeners();
-        mEthernetSignalController.notifyListeners();
-        for (MobileSignalController mobileSignalController : mMobileSignalControllers.values()) {
-            mobileSignalController.notifyListeners();
-        }
-    }
-
-    public void removeNetworkSignalChangedCallback(NetworkSignalChangedCallback cb) {
-        mSignalsChangedCallbacks.remove(cb);
+    @Override
+    public void removeSignalCallback(SignalCallback cb) {
+        mCallbackHandler.setListening(cb, false);
     }
 
     @Override
@@ -426,10 +410,7 @@
                         : lhs.getSimSlotIndex() - rhs.getSimSlotIndex();
             }
         });
-        final int length = mSignalClusters.size();
-        for (int i = 0; i < length; i++) {
-            mSignalClusters.get(i).setSubs(subscriptions);
-        }
+        mCallbackHandler.setSubs(subscriptions);
         mCurrentSubscriptions = subscriptions;
 
         HashMap<Integer, MobileSignalController> cachedControllers =
@@ -443,8 +424,8 @@
                 mMobileSignalControllers.put(subId, cachedControllers.remove(subId));
             } else {
                 MobileSignalController controller = new MobileSignalController(mContext, mConfig,
-                        mHasMobileDataFeature, mPhone, mSignalsChangedCallbacks, mSignalClusters,
-                        this, subscriptions.get(i));
+                        mHasMobileDataFeature, mPhone, mCallbackHandler,
+                        this, subscriptions.get(i), mReceiverHandler.getLooper());
                 mMobileSignalControllers.put(subId, controller);
                 if (subscriptions.get(i).getSimSlotIndex() == 0) {
                     mDefaultSignalController = controller;
@@ -520,17 +501,9 @@
      * notifyAllListeners.
      */
     private void notifyListeners() {
-        int length = mSignalClusters.size();
-        for (int i = 0; i < length; i++) {
-            mSignalClusters.get(i).setIsAirplaneMode(mAirplaneMode, TelephonyIcons.FLIGHT_MODE_ICON,
-                    R.string.accessibility_airplane_mode);
-            mSignalClusters.get(i).setNoSims(mHasNoSims);
-        }
-        int signalsChangedLength = mSignalsChangedCallbacks.size();
-        for (int i = 0; i < signalsChangedLength; i++) {
-            mSignalsChangedCallbacks.get(i).onAirplaneModeChanged(mAirplaneMode);
-            mSignalsChangedCallbacks.get(i).onNoSimVisibleChanged(mHasNoSims);
-        }
+        mCallbackHandler.setIsAirplaneMode(new IconState(mAirplaneMode,
+                TelephonyIcons.FLIGHT_MODE_ICON, R.string.accessibility_airplane_mode, mContext));
+        mCallbackHandler.setNoSims(mHasNoSims);
     }
 
     /**
@@ -629,17 +602,15 @@
                 controller.resetLastState();
             }
             mWifiSignalController.resetLastState();
-            registerListeners();
+            mReceiverHandler.post(mRegisterListeners);
             notifyAllListeners();
         } else if (mDemoMode && command.equals(COMMAND_NETWORK)) {
             String airplane = args.getString("airplane");
             if (airplane != null) {
                 boolean show = airplane.equals("show");
-                int length = mSignalClusters.size();
-                for (int i = 0; i < length; i++) {
-                    mSignalClusters.get(i).setIsAirplaneMode(show, TelephonyIcons.FLIGHT_MODE_ICON,
-                            R.string.accessibility_airplane_mode);
-                }
+                mCallbackHandler.setIsAirplaneMode(new IconState(show,
+                        TelephonyIcons.FLIGHT_MODE_ICON, R.string.accessibility_airplane_mode,
+                        mContext));
             }
             String fully = args.getString("fully");
             if (fully != null) {
@@ -672,18 +643,12 @@
                         subs.add(addSignalController(i, i));
                     }
                 }
-                final int n = mSignalClusters.size();
-                for (int i = 0; i < n; i++) {
-                    mSignalClusters.get(i).setSubs(subs);
-                }
+                mCallbackHandler.setSubs(subs);
             }
             String nosim = args.getString("nosim");
             if (nosim != null) {
                 boolean show = nosim.equals("show");
-                final int n = mSignalClusters.size();
-                for (int i = 0; i < n; i++) {
-                    mSignalClusters.get(i).setNoSims(show);
-                }
+                mCallbackHandler.setNoSims(show);
             }
             String mobile = args.getString("mobile");
             if (mobile != null) {
@@ -699,10 +664,7 @@
                     subs.add(addSignalController(nextSlot, nextSlot));
                 }
                 if (!subs.isEmpty()) {
-                    final int n = mSignalClusters.size();
-                    for (int i = 0; i < n; i++) {
-                        mSignalClusters.get(i).setSubs(subs);
-                    }
+                    mCallbackHandler.setSubs(subs);
                 }
                 // Hack to index linearly for easy use.
                 MobileSignalController controller = mMobileSignalControllers
@@ -744,8 +706,8 @@
         SubscriptionInfo info = new SubscriptionInfo(id, "", simSlotIndex, "", "", 0, 0, "", 0,
                 null, 0, 0, "");
         mMobileSignalControllers.put(id, new MobileSignalController(mContext,
-                mConfig, mHasMobileDataFeature, mPhone, mSignalsChangedCallbacks,
-                mSignalClusters, this, info));
+                mConfig, mHasMobileDataFeature, mPhone, mCallbackHandler, this, info,
+                mReceiverHandler.getLooper()));
         return info;
     }
 
@@ -757,28 +719,21 @@
         };
     };
 
-    public interface SignalCluster {
-        void setWifiIndicators(boolean visible, int strengthIcon, String contentDescription);
-
-        void setMobileDataIndicators(boolean visible, int strengthIcon, int darkStrengthIcon,
-                int typeIcon, String contentDescription, String typeContentDescription,
-                boolean isTypeIconWide, int subId);
-        void setSubs(List<SubscriptionInfo> subs);
-        void setNoSims(boolean show);
-
-        void setEthernetIndicators(boolean visible, int icon, String contentDescription);
-
-        void setIsAirplaneMode(boolean is, int airplaneIcon, int contentDescription);
-    }
+    /**
+     * Used to register listeners from the BG Looper, this way the PhoneStateListeners that
+     * get created will also run on the BG Looper.
+     */
+    private final Runnable mRegisterListeners = new Runnable() {
+        @Override
+        public void run() {
+            registerListeners();
+        }
+    };
 
     public interface EmergencyListener {
         void setEmergencyCallsOnly(boolean emergencyOnly);
     }
 
-    public interface CarrierLabelListener {
-        void setCarrierLabel(String label);
-    }
-
     @VisibleForTesting
     static class Config {
         boolean showAtLeast3G = false;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SignalCallbackAdapter.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SignalCallbackAdapter.java
new file mode 100644
index 0000000..83a7d3d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SignalCallbackAdapter.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2015 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.policy;
+
+import android.telephony.SubscriptionInfo;
+
+import com.android.systemui.statusbar.policy.NetworkController.IconState;
+import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
+
+import java.util.List;
+
+
+/**
+ * Provides empty implementations of SignalCallback for those that only want some of
+ * the callbacks.
+ */
+public class SignalCallbackAdapter implements SignalCallback {
+
+    @Override
+    public void setWifiIndicators(boolean enabled, IconState statusIcon, IconState qsIcon,
+            boolean activityIn, boolean activityOut, String description) {
+    }
+
+    @Override
+    public void setMobileDataIndicators(IconState statusIcon, IconState qsIcon,
+            int darkStatusIcon, int statusType, int qsType, boolean activityIn,
+            boolean activityOut, String typeContentDescription, String description,
+            boolean isWide, int subId) {
+    }
+
+    @Override
+    public void setSubs(List<SubscriptionInfo> subs) {
+    }
+
+    @Override
+    public void setNoSims(boolean show) {
+    }
+
+    @Override
+    public void setEthernetIndicators(IconState icon) {
+    }
+
+    @Override
+    public void setIsAirplaneMode(IconState icon) {
+    }
+
+    @Override
+    public void setMobileDataEnabled(boolean enabled) {
+    }
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SignalController.java
index f3322a1..97b530c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SignalController.java
@@ -21,11 +21,7 @@
 import android.text.format.DateFormat;
 import android.util.Log;
 
-import com.android.systemui.statusbar.policy.NetworkController.NetworkSignalChangedCallback;
-import com.android.systemui.statusbar.policy.NetworkControllerImpl.SignalCluster;
-
 import java.io.PrintWriter;
-import java.util.List;
 
 
 /**
@@ -49,24 +45,22 @@
     // The owner of the SignalController (i.e. NetworkController will maintain the following
     // lists and call notifyListeners whenever the list has changed to ensure everyone
     // is aware of current state.
-    protected final List<NetworkSignalChangedCallback> mSignalsChangedCallbacks;
-    protected final List<SignalCluster> mSignalClusters;
     protected final NetworkControllerImpl mNetworkController;
 
+    protected final CallbackHandler mCallbackHandler;
+
     // Save the previous HISTORY_SIZE states for logging.
     private final State[] mHistory;
     // Where to copy the next state into.
     private int mHistoryIndex;
 
-    public SignalController(String tag, Context context, int type,
-            List<NetworkSignalChangedCallback> signalCallbacks,
-            List<SignalCluster> signalClusters, NetworkControllerImpl networkController) {
+    public SignalController(String tag, Context context, int type, CallbackHandler callbackHandler,
+            NetworkControllerImpl networkController) {
         mTag = TAG + "." + tag;
         mNetworkController = networkController;
         mTransportType = type;
         mContext = context;
-        mSignalsChangedCallbacks = signalCallbacks;
-        mSignalClusters = signalClusters;
+        mCallbackHandler = callbackHandler;
         mCurrentState = cleanState();
         mLastState = cleanState();
         if (RECORD_HISTORY) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java
index a97ca50..9b1e72a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java
@@ -29,8 +29,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.AsyncChannel;
-import com.android.systemui.statusbar.policy.NetworkController.NetworkSignalChangedCallback;
-import com.android.systemui.statusbar.policy.NetworkControllerImpl.SignalCluster;
+import com.android.systemui.statusbar.policy.NetworkController.IconState;
 
 import java.util.List;
 import java.util.Objects;
@@ -43,10 +42,9 @@
     private final boolean mHasMobileData;
 
     public WifiSignalController(Context context, boolean hasMobileData,
-            List<NetworkSignalChangedCallback> signalCallbacks,
-            List<SignalCluster> signalClusters, NetworkControllerImpl networkController) {
+            CallbackHandler callbackHandler, NetworkControllerImpl networkController) {
         super("WifiSignalController", context, NetworkCapabilities.TRANSPORT_WIFI,
-                signalCallbacks, signalClusters, networkController);
+                callbackHandler, networkController);
         mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
         mHasMobileData = hasMobileData;
         Handler handler = new WifiHandler();
@@ -82,19 +80,13 @@
         String wifiDesc = wifiVisible ? mCurrentState.ssid : null;
         boolean ssidPresent = wifiVisible && mCurrentState.ssid != null;
         String contentDescription = getStringIfExists(getContentDescription());
-        int length = mSignalsChangedCallbacks.size();
-        for (int i = 0; i < length; i++) {
-            mSignalsChangedCallbacks.get(i).onWifiSignalChanged(mCurrentState.enabled,
-                    mCurrentState.connected, getQsCurrentIconId(),
-                    ssidPresent && mCurrentState.activityIn,
-                    ssidPresent && mCurrentState.activityOut, contentDescription, wifiDesc);
-        }
 
-        int signalClustersLength = mSignalClusters.size();
-        for (int i = 0; i < signalClustersLength; i++) {
-            mSignalClusters.get(i).setWifiIndicators(wifiVisible, getCurrentIconId(),
-                    contentDescription);
-        }
+        IconState statusIcon = new IconState(wifiVisible, getCurrentIconId(), contentDescription);
+        IconState qsIcon = new IconState(mCurrentState.connected, getQsCurrentIconId(),
+                contentDescription);
+        mCallbackHandler.setWifiIndicators(mCurrentState.enabled, statusIcon, qsIcon,
+                ssidPresent && mCurrentState.activityIn, ssidPresent && mCurrentState.activityOut,
+                wifiDesc);
     }
 
     /**
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CallbackHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CallbackHandlerTest.java
new file mode 100644
index 0000000..1cc6fa8
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CallbackHandlerTest.java
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) 2015 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.policy;
+
+import android.os.HandlerThread;
+import android.telephony.SubscriptionInfo;
+import android.test.AndroidTestCase;
+
+import com.android.systemui.R;
+import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
+import com.android.systemui.statusbar.policy.NetworkControllerImpl.EmergencyListener;
+import com.android.systemui.statusbar.policy.NetworkControllerImpl.IconState;
+
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class CallbackHandlerTest extends AndroidTestCase {
+
+    private CallbackHandler mHandler;
+    private HandlerThread mHandlerThread;
+
+    @Mock
+    private EmergencyListener mEmengencyListener;
+    @Mock
+    private SignalCallback mSignalCallback;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        mHandlerThread = new HandlerThread("TestThread");
+        mHandlerThread.start();
+        mHandler = new CallbackHandler(mHandlerThread.getLooper());
+
+        MockitoAnnotations.initMocks(this);
+        mHandler.setListening(mEmengencyListener, true);
+        mHandler.setListening(mSignalCallback, true);
+    }
+
+    public void testEmergencyListener() {
+        mHandler.setEmergencyCallsOnly(true);
+        waitForCallbacks();
+
+        ArgumentCaptor<Boolean> captor = ArgumentCaptor.forClass(Boolean.class);
+        Mockito.verify(mEmengencyListener).setEmergencyCallsOnly(captor.capture());
+        assertTrue(captor.getValue());
+    }
+
+    public void testSignalCallback_setWifiIndicators() {
+        boolean enabled = true;
+        IconState status = new IconState(true, 0, "");
+        IconState qs = new IconState(true, 1, "");
+        boolean in = true;
+        boolean out = true;
+        String description = "Test";
+        mHandler.setWifiIndicators(enabled, status, qs, in, out, description);
+        waitForCallbacks();
+
+        ArgumentCaptor<Boolean> enableArg = ArgumentCaptor.forClass(Boolean.class);
+        ArgumentCaptor<IconState> statusArg = ArgumentCaptor.forClass(IconState.class);
+        ArgumentCaptor<IconState> qsArg = ArgumentCaptor.forClass(IconState.class);
+        ArgumentCaptor<Boolean> inArg = ArgumentCaptor.forClass(Boolean.class);
+        ArgumentCaptor<Boolean> outArg = ArgumentCaptor.forClass(Boolean.class);
+        ArgumentCaptor<String> descArg = ArgumentCaptor.forClass(String.class);
+        Mockito.verify(mSignalCallback).setWifiIndicators(enableArg.capture(),
+                statusArg.capture(), qsArg.capture(), inArg.capture(), outArg.capture(),
+                descArg.capture());
+        assertEquals(enabled, (boolean) enableArg.getValue());
+        assertEquals(status, statusArg.getValue());
+        assertEquals(qs, qsArg.getValue());
+        assertEquals(in, (boolean) inArg.getValue());
+        assertEquals(out, (boolean) outArg.getValue());
+        assertEquals(description, descArg.getValue());
+    }
+
+    public void testSignalCallback_setMobileDataIndicators() {
+        IconState status = new IconState(true, 0, "");
+        IconState qs = new IconState(true, 1, "");
+        int dark = 2;
+        boolean in = true;
+        boolean out = true;
+        String typeDescription = "Test 1";
+        String description = "Test 2";
+        int type = R.drawable.stat_sys_data_fully_connected_1x;
+        int qsType = R.drawable.ic_qs_signal_1x;
+        boolean wide = true;
+        int subId = 5;
+        mHandler.setMobileDataIndicators(status, qs, dark, type, qsType, in, out, typeDescription,
+                description, wide, subId);
+        waitForCallbacks();
+
+        ArgumentCaptor<IconState> statusArg = ArgumentCaptor.forClass(IconState.class);
+        ArgumentCaptor<IconState> qsArg = ArgumentCaptor.forClass(IconState.class);
+        ArgumentCaptor<Integer> darkStrengthArg = ArgumentCaptor.forClass(Integer.class);
+        ArgumentCaptor<Integer> typeIconArg = ArgumentCaptor.forClass(Integer.class);
+        ArgumentCaptor<Integer> qsTypeIconArg = ArgumentCaptor.forClass(Integer.class);
+        ArgumentCaptor<Boolean> inArg = ArgumentCaptor.forClass(Boolean.class);
+        ArgumentCaptor<Boolean> outArg = ArgumentCaptor.forClass(Boolean.class);
+        ArgumentCaptor<String> typeContentArg = ArgumentCaptor.forClass(String.class);
+        ArgumentCaptor<String> descArg = ArgumentCaptor.forClass(String.class);
+        ArgumentCaptor<Boolean> wideArg = ArgumentCaptor.forClass(Boolean.class);
+        ArgumentCaptor<Integer> subIdArg = ArgumentCaptor.forClass(Integer.class);
+        Mockito.verify(mSignalCallback).setMobileDataIndicators(statusArg.capture(), qsArg.capture(),
+                darkStrengthArg.capture(), typeIconArg.capture(), qsTypeIconArg.capture(),
+                inArg.capture(), outArg.capture(), typeContentArg.capture(), descArg.capture(),
+                wideArg.capture(), subIdArg.capture());
+        assertEquals(status, statusArg.getValue());
+        assertEquals(qs, qsArg.getValue());
+        assertEquals(dark, (int) darkStrengthArg.getValue());
+        assertEquals(type, (int) typeIconArg.getValue());
+        assertEquals(qsType, (int) qsTypeIconArg.getValue());
+        assertEquals(in, (boolean) inArg.getValue());
+        assertEquals(out, (boolean) outArg.getValue());
+        assertEquals(typeDescription, typeContentArg.getValue());
+        assertEquals(description, descArg.getValue());
+        assertEquals(wide, (boolean) wideArg.getValue());
+        assertEquals(subId, (int) subIdArg.getValue());
+    }
+
+    @SuppressWarnings("unchecked")
+    public void testSignalCallback_setSubs() {
+        List<SubscriptionInfo> subs = new ArrayList<>();
+        mHandler.setSubs(subs);
+        waitForCallbacks();
+
+        ArgumentCaptor<ArrayList> subsArg = ArgumentCaptor.forClass(ArrayList.class);
+        Mockito.verify(mSignalCallback).setSubs(subsArg.capture());
+        assertTrue(subs == subsArg.getValue());
+    }
+
+    public void testSignalCallback_setNoSims() {
+        boolean noSims = true;
+        mHandler.setNoSims(noSims);
+        waitForCallbacks();
+
+        ArgumentCaptor<Boolean> noSimsArg = ArgumentCaptor.forClass(Boolean.class);
+        Mockito.verify(mSignalCallback).setNoSims(noSimsArg.capture());
+        assertEquals(noSims, (boolean) noSimsArg.getValue());
+    }
+
+    public void testSignalCallback_setEthernetIndicators() {
+        IconState state = new IconState(true, R.drawable.stat_sys_ethernet, "Test Description");
+        mHandler.setEthernetIndicators(state);
+        waitForCallbacks();
+
+        ArgumentCaptor<IconState> iconArg = ArgumentCaptor.forClass(IconState.class);
+        Mockito.verify(mSignalCallback).setEthernetIndicators(iconArg.capture());
+        assertEquals(state, iconArg.getValue());
+    }
+
+    public void testSignalCallback_setIsAirplaneMode() {
+        IconState state = new IconState(true, R.drawable.stat_sys_airplane_mode, "Test Description");
+        mHandler.setIsAirplaneMode(state);
+        waitForCallbacks();
+
+        ArgumentCaptor<IconState> iconArg = ArgumentCaptor.forClass(IconState.class);
+        Mockito.verify(mSignalCallback).setIsAirplaneMode(iconArg.capture());
+        assertEquals(state, iconArg.getValue());
+    }
+
+    private void waitForCallbacks() {
+        mHandlerThread.quitSafely();
+        try {
+            mHandlerThread.join();
+        } catch (InterruptedException e) {
+        }
+    }
+
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
index 29f461e..3d5f045 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
@@ -23,6 +23,7 @@
 import android.net.ConnectivityManager;
 import android.net.NetworkCapabilities;
 import android.net.wifi.WifiManager;
+import android.os.Looper;
 import android.telephony.PhoneStateListener;
 import android.telephony.ServiceState;
 import android.telephony.SignalStrength;
@@ -31,12 +32,10 @@
 import android.telephony.TelephonyManager;
 import android.util.Log;
 
-import com.android.internal.telephony.IccCardConstants;
 import com.android.internal.telephony.cdma.EriInfo;
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.statusbar.policy.NetworkController.NetworkSignalChangedCallback;
 import com.android.systemui.statusbar.policy.NetworkControllerImpl.Config;
-import com.android.systemui.statusbar.policy.NetworkControllerImpl.SignalCluster;
+import com.android.systemui.statusbar.policy.NetworkControllerImpl.IconState;
 
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mockito;
@@ -59,8 +58,6 @@
     protected NetworkControllerImpl mNetworkController;
     protected MobileSignalController mMobileSignalController;
     protected PhoneStateListener mPhoneStateListener;
-    protected SignalCluster mSignalCluster;
-    protected NetworkSignalChangedCallback mNetworkSignalChangedCallback;
     private SignalStrength mSignalStrength;
     private ServiceState mServiceState;
     protected ConnectivityManager mMockCm;
@@ -68,6 +65,7 @@
     protected SubscriptionManager mMockSm;
     protected TelephonyManager mMockTm;
     protected Config mConfig;
+    protected CallbackHandler mCallbackHandler;
 
     protected int mSubId;
 
@@ -91,33 +89,36 @@
 
         mConfig = new Config();
         mConfig.hspaDataDistinguishable = true;
+        mCallbackHandler = mock(CallbackHandler.class);
         mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm, mMockSm,
-                mConfig, mock(AccessPointControllerImpl.class),
-                mock(MobileDataControllerImpl.class));
+                mConfig, Looper.getMainLooper(), mCallbackHandler,
+                mock(AccessPointControllerImpl.class), mock(MobileDataControllerImpl.class));
         setupNetworkController();
     }
 
     protected void setupNetworkController() {
         // For now just pretend to be the data sim, so we can test that too.
-        mSubId = SubscriptionManager.getDefaultDataSubId();
+        mSubId = SubscriptionManager.DEFAULT_SUBSCRIPTION_ID;
         SubscriptionInfo subscription = mock(SubscriptionInfo.class);
         List<SubscriptionInfo> subs = new ArrayList<SubscriptionInfo>();
         when(subscription.getSubscriptionId()).thenReturn(mSubId);
         subs.add(subscription);
         mNetworkController.setCurrentSubscriptions(subs);
         mMobileSignalController = mNetworkController.mMobileSignalControllers.get(mSubId);
+        mMobileSignalController.getState().dataSim = true;
         mPhoneStateListener = mMobileSignalController.mPhoneStateListener;
-        mSignalCluster = mock(SignalCluster.class);
-        mNetworkSignalChangedCallback = mock(NetworkSignalChangedCallback.class);
-        mNetworkController.addSignalCluster(mSignalCluster);
-        mNetworkController.addNetworkSignalChangedCallback(mNetworkSignalChangedCallback);
+
+        // Trigger blank callbacks to always get the current state (some tests don't trigger
+        // changes from default state).
+        mNetworkController.addSignalCallback(null);
     }
 
     protected NetworkControllerImpl setUpNoMobileData() {
       when(mMockCm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE)).thenReturn(false);
       NetworkControllerImpl networkControllerNoMobile
               = new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm, mMockSm,
-                        mConfig, mock(AccessPointControllerImpl.class),
+                        mConfig, Looper.getMainLooper(), mCallbackHandler,
+                        mock(AccessPointControllerImpl.class),
                         mock(MobileDataControllerImpl.class));
 
       setupNetworkController();
@@ -242,34 +243,30 @@
     protected void verifyHasNoSims(boolean hasNoSimsVisible) {
         ArgumentCaptor<Boolean> hasNoSimsArg = ArgumentCaptor.forClass(Boolean.class);
 
-        Mockito.verify(mSignalCluster, Mockito.atLeastOnce()).setNoSims(hasNoSimsArg.capture());
-        assertEquals("No sims in status bar", hasNoSimsVisible, (boolean) hasNoSimsArg.getValue());
-
-        Mockito.verify(mNetworkSignalChangedCallback, Mockito.atLeastOnce())
-                .onNoSimVisibleChanged(hasNoSimsArg.capture());
-        assertEquals("No sims in quick settings", hasNoSimsVisible,
-                (boolean) hasNoSimsArg.getValue());
+        Mockito.verify(mCallbackHandler, Mockito.atLeastOnce()).setNoSims(hasNoSimsArg.capture());
+        assertEquals("No sims", hasNoSimsVisible, (boolean) hasNoSimsArg.getValue());
     }
 
     protected void verifyLastQsMobileDataIndicators(boolean visible, int icon, int typeIcon,
             boolean dataIn, boolean dataOut) {
-        ArgumentCaptor<Integer> iconArg = ArgumentCaptor.forClass(Integer.class);
+        ArgumentCaptor<IconState> iconArg = ArgumentCaptor.forClass(IconState.class);
         ArgumentCaptor<Integer> typeIconArg = ArgumentCaptor.forClass(Integer.class);
-        ArgumentCaptor<Boolean> visibleArg = ArgumentCaptor.forClass(Boolean.class);
         ArgumentCaptor<Boolean> dataInArg = ArgumentCaptor.forClass(Boolean.class);
         ArgumentCaptor<Boolean> dataOutArg = ArgumentCaptor.forClass(Boolean.class);
 
-        Mockito.verify(mNetworkSignalChangedCallback, Mockito.atLeastOnce())
-                .onMobileDataSignalChanged(visibleArg.capture(), iconArg.capture(),
-                        ArgumentCaptor.forClass(String.class).capture(),
-                        typeIconArg.capture(),
-                        dataInArg.capture(),
-                        dataOutArg.capture(),
-                        ArgumentCaptor.forClass(String.class).capture(),
-                        ArgumentCaptor.forClass(String.class).capture(),
-                        ArgumentCaptor.forClass(Boolean.class).capture());
-        assertEquals("Visibility in, quick settings", visible, (boolean) visibleArg.getValue());
-        assertEquals("Signal icon in, quick settings", icon, (int) iconArg.getValue());
+        Mockito.verify(mCallbackHandler, Mockito.atLeastOnce()).setMobileDataIndicators(
+                    ArgumentCaptor.forClass(IconState.class).capture(),
+                    iconArg.capture(),
+                    ArgumentCaptor.forClass(Integer.class).capture(),
+                    ArgumentCaptor.forClass(Integer.class).capture(),
+                    typeIconArg.capture(), dataInArg.capture(), dataOutArg.capture(),
+                    ArgumentCaptor.forClass(String.class).capture(),
+                    ArgumentCaptor.forClass(String.class).capture(),
+                    ArgumentCaptor.forClass(Boolean.class).capture(),
+                    ArgumentCaptor.forClass(Integer.class).capture());
+        IconState iconState = iconArg.getValue();
+        assertEquals("Visibility in, quick settings", visible, iconState.visible);
+        assertEquals("Signal icon in, quick settings", icon, iconState.icon);
         assertEquals("Data icon in, quick settings", typeIcon, (int) typeIconArg.getValue());
         assertEquals("Data direction in, in quick settings", dataIn,
                 (boolean) dataInArg.getValue());
@@ -283,29 +280,32 @@
 
     protected void verifyLastMobileDataIndicators(boolean visible, int strengthIcon,
             int darkStrengthIcon, int typeIcon) {
-        ArgumentCaptor<Integer> strengthIconArg = ArgumentCaptor.forClass(Integer.class);
+        ArgumentCaptor<IconState> iconArg = ArgumentCaptor.forClass(IconState.class);
         ArgumentCaptor<Integer> darkStrengthIconArg = ArgumentCaptor.forClass(Integer.class);
         ArgumentCaptor<Integer> typeIconArg = ArgumentCaptor.forClass(Integer.class);
-        ArgumentCaptor<Boolean> visibleArg = ArgumentCaptor.forClass(Boolean.class);
 
         // TODO: Verify all fields.
-        Mockito.verify(mSignalCluster, Mockito.atLeastOnce()).setMobileDataIndicators(
-                visibleArg.capture(), strengthIconArg.capture(), darkStrengthIconArg.capture(),
-                typeIconArg.capture(),
+        Mockito.verify(mCallbackHandler, Mockito.atLeastOnce()).setMobileDataIndicators(
+                iconArg.capture(),
+                ArgumentCaptor.forClass(IconState.class).capture(),
+                darkStrengthIconArg.capture(), typeIconArg.capture(),
+                ArgumentCaptor.forClass(Integer.class).capture(),
+                ArgumentCaptor.forClass(Boolean.class).capture(),
+                ArgumentCaptor.forClass(Boolean.class).capture(),
                 ArgumentCaptor.forClass(String.class).capture(),
                 ArgumentCaptor.forClass(String.class).capture(),
                 ArgumentCaptor.forClass(Boolean.class).capture(),
                 ArgumentCaptor.forClass(Integer.class).capture());
+        IconState iconState = iconArg.getValue();
 
-        assertEquals("Signal strength icon in status bar", strengthIcon,
-                (int) strengthIconArg.getValue());
+        assertEquals("Signal strength icon in status bar", strengthIcon, iconState.icon);
         assertEquals("Signal strength icon (dark mode) in status bar", darkStrengthIcon,
                 (int) darkStrengthIconArg.getValue());
         assertEquals("Data icon in status bar", typeIcon, (int) typeIconArg.getValue());
-        assertEquals("Visibility in status bar", visible, (boolean) visibleArg.getValue());
+        assertEquals("Visibility in status bar", visible, iconState.visible);
     }
 
    protected void assertNetworkNameEquals(String expected) {
-       assertEquals("Network name", expected, mNetworkController.getMobileDataNetworkName());
+       assertEquals("Network name", expected, mMobileSignalController.getState().networkName);
    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java
index 3f9312d..015127d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java
@@ -2,6 +2,7 @@
 
 import org.mockito.Mockito;
 
+import android.os.Looper;
 import android.telephony.TelephonyManager;
 
 public class NetworkControllerDataTest extends NetworkControllerBaseTest {
@@ -74,7 +75,8 @@
         // Switch to showing 4g icon and re-initialize the NetworkController.
         mConfig.show4gForLte = true;
         mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm, mMockSm,
-                mConfig, Mockito.mock(AccessPointControllerImpl.class),
+                mConfig, Looper.getMainLooper(), mCallbackHandler,
+                Mockito.mock(AccessPointControllerImpl.class),
                 Mockito.mock(MobileDataControllerImpl.class));
         setupNetworkController();
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerEthernetTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerEthernetTest.java
index 82ced9f..f1d2acb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerEthernetTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerEthernetTest.java
@@ -2,6 +2,8 @@
 
 import android.net.NetworkCapabilities;
 
+import com.android.systemui.statusbar.policy.NetworkControllerImpl.IconState;
+
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mockito;
 
@@ -28,13 +30,12 @@
     }
 
     protected void verifyLastEthernetIcon(boolean visible, int icon) {
-        ArgumentCaptor<Boolean> visibleArg = ArgumentCaptor.forClass(Boolean.class);
-        ArgumentCaptor<Integer> iconArg = ArgumentCaptor.forClass(Integer.class);
+        ArgumentCaptor<IconState> iconArg = ArgumentCaptor.forClass(IconState.class);
 
-        Mockito.verify(mSignalCluster, Mockito.atLeastOnce()).setEthernetIndicators(
-                visibleArg.capture(), iconArg.capture(),
-                ArgumentCaptor.forClass(String.class).capture());
-        assertEquals("Ethernet visible, in status bar", visible, (boolean) visibleArg.getValue());
-        assertEquals("Ethernet icon, in status bar", icon, (int) iconArg.getValue());
+        Mockito.verify(mCallbackHandler, Mockito.atLeastOnce()).setEthernetIndicators(
+                iconArg.capture());
+        IconState iconState = iconArg.getValue();
+        assertEquals("Ethernet visible, in status bar", visible, iconState.visible);
+        assertEquals("Ethernet icon, in status bar", icon, iconState.icon);
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java
index 389ad6f..a85bca2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java
@@ -19,12 +19,12 @@
 
 import android.content.Intent;
 import android.net.ConnectivityManager;
+import android.os.Looper;
 import android.telephony.ServiceState;
 import android.telephony.SignalStrength;
 import android.telephony.SubscriptionInfo;
 import android.telephony.TelephonyManager;
 
-import com.android.internal.telephony.IccCardConstants;
 import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.telephony.TelephonyIntents;
 import com.android.systemui.R;
@@ -41,8 +41,8 @@
         Mockito.when(mMockCm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE)).thenReturn(false);
         // Create a new NetworkController as this is currently handled in constructor.
         mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm, mMockSm,
-                mConfig, mock(AccessPointControllerImpl.class),
-                mock(MobileDataControllerImpl.class));
+                mConfig, Looper.getMainLooper(), mCallbackHandler,
+                mock(AccessPointControllerImpl.class), mock(MobileDataControllerImpl.class));
         setupNetworkController();
 
         verifyLastMobileDataIndicators(false, 0, 0);
@@ -61,8 +61,8 @@
         Mockito.when(mMockCm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE)).thenReturn(false);
         // Create a new NetworkController as this is currently handled in constructor.
         mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm, mMockSm,
-                mConfig, mock(AccessPointControllerImpl.class),
-                mock(MobileDataControllerImpl.class));
+                mConfig, Looper.getMainLooper(), mCallbackHandler,
+                mock(AccessPointControllerImpl.class), mock(MobileDataControllerImpl.class));
         setupNetworkController();
 
         // No Subscriptions.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java
index 2e0e9a3..d0c95b9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java
@@ -6,6 +6,8 @@
 import android.net.wifi.WifiInfo;
 import android.net.wifi.WifiManager;
 
+import com.android.systemui.statusbar.policy.NetworkControllerImpl.IconState;
+
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mockito;
 
@@ -133,46 +135,39 @@
         ArgumentCaptor<Boolean> inArg = ArgumentCaptor.forClass(Boolean.class);
         ArgumentCaptor<Boolean> outArg = ArgumentCaptor.forClass(Boolean.class);
 
-        Mockito.verify(mNetworkSignalChangedCallback, Mockito.atLeastOnce()).onWifiSignalChanged(
-                ArgumentCaptor.forClass(Boolean.class).capture(),
-                ArgumentCaptor.forClass(Boolean.class).capture(),
-                ArgumentCaptor.forClass(Integer.class).capture(),
-                inArg.capture(), outArg.capture(),
-                ArgumentCaptor.forClass(String.class).capture(),
-                ArgumentCaptor.forClass(String.class).capture());
+        Mockito.verify(mCallbackHandler, Mockito.atLeastOnce()).setWifiIndicators(
+                Mockito.anyBoolean(), Mockito.any(IconState.class), Mockito.any(IconState.class),
+                inArg.capture(), outArg.capture(), Mockito.anyString());
         assertEquals("WiFi data in, in quick settings", in, (boolean) inArg.getValue());
         assertEquals("WiFi data out, in quick settings", out, (boolean) outArg.getValue());
     }
 
     protected void verifyLastQsWifiIcon(boolean enabled, boolean connected, int icon,
             String description) {
+        ArgumentCaptor<IconState> iconArg = ArgumentCaptor.forClass(IconState.class);
         ArgumentCaptor<Boolean> enabledArg = ArgumentCaptor.forClass(Boolean.class);
-        ArgumentCaptor<Boolean> connectedArg = ArgumentCaptor.forClass(Boolean.class);
-        ArgumentCaptor<Integer> iconArg = ArgumentCaptor.forClass(Integer.class);
         ArgumentCaptor<String> descArg = ArgumentCaptor.forClass(String.class);
 
-        Mockito.verify(mNetworkSignalChangedCallback, Mockito.atLeastOnce()).onWifiSignalChanged(
-                enabledArg.capture(), connectedArg.capture(), iconArg.capture(),
-                ArgumentCaptor.forClass(Boolean.class).capture(),
-                ArgumentCaptor.forClass(Boolean.class).capture(),
-                ArgumentCaptor.forClass(String.class).capture(),
+        Mockito.verify(mCallbackHandler, Mockito.atLeastOnce()).setWifiIndicators(
+                enabledArg.capture(), Mockito.any(IconState.class),
+                iconArg.capture(), Mockito.anyBoolean(),
+                Mockito.anyBoolean(),
                 descArg.capture());
+        IconState iconState = iconArg.getValue();
         assertEquals("WiFi enabled, in quick settings", enabled, (boolean) enabledArg.getValue());
-        assertEquals("WiFi connected, in quick settings", connected,
-                (boolean) connectedArg.getValue());
-        assertEquals("WiFi signal, in quick settings", icon, (int) iconArg.getValue());
-        assertEquals("WiFI desc (ssid), in quick settings", description,
-                (String) descArg.getValue());
+        assertEquals("WiFi connected, in quick settings", connected, iconState.visible);
+        assertEquals("WiFi signal, in quick settings", icon, iconState.icon);
+        assertEquals("WiFI desc (ssid), in quick settings", description, descArg.getValue());
     }
 
     protected void verifyLastWifiIcon(boolean visible, int icon) {
-        ArgumentCaptor<Boolean> visibleArg = ArgumentCaptor.forClass(Boolean.class);
-        ArgumentCaptor<Integer> iconArg = ArgumentCaptor.forClass(Integer.class);
+        ArgumentCaptor<IconState> iconArg = ArgumentCaptor.forClass(IconState.class);
 
-        Mockito.verify(mSignalCluster, Mockito.atLeastOnce()).setWifiIndicators(
-                visibleArg.capture(), iconArg.capture(),
-                ArgumentCaptor.forClass(String.class).capture());
-        assertEquals("WiFi visible, in status bar", visible, (boolean) visibleArg.getValue());
-        assertEquals("WiFi signal, in status bar", icon, (int) iconArg.getValue());
+        Mockito.verify(mCallbackHandler, Mockito.atLeastOnce()).setWifiIndicators(
+                Mockito.anyBoolean(), iconArg.capture(), Mockito.any(IconState.class),
+                Mockito.anyBoolean(), Mockito.anyBoolean(), Mockito.anyString());
+        IconState iconState = iconArg.getValue();
+        assertEquals("WiFi visible, in status bar", visible, iconState.visible);
+        assertEquals("WiFi signal, in status bar", icon, iconState.icon);
     }
 }