Remove SignalClusterView in favor of IconManager

Refactors SignalClusterView into StatusBarSignalPolicy so that it can be
used in a StatusBarIconContainer and solve all of our status bar
problems.

- Remove all uses of SignalClusterView and references to
  R.id.signal_cluster. The class still exists it's just unused now

- Add StatusBarIconHolder, which can point to StatusBarIcon,
  WifiIconState, or PhoneIconState

- StatusBarIconList.Slot. Allows for easier indexing of icons now that
  there can be multiple icons per slot

- Add StatusBarWifiView to be inflated inside of the
  StatusBarIconController when needed

- StatusBarMobile view. similar to above

- Upgrade StatusBarIconControllerImpl and StatusBarIconList to
  understand the holders and added 2 new methods to specifically handle
  wifi / phone state changes

- Create IStatusBarIconView (todo: rename). Abstracts the properties we
  want away from StatusBarIconView so that we can use an arbitrary view
  type

- NeutralGoodDrawable. Draws a light and a dark icon when needed.

- Fixes a few demo mode bugs: multi sim was broken and also turning off
  demo mode was a little broken

TODO: More tests are needed for StatusBarSignalPolicy and maybe the
IconManagers

Test: runtest systemui; visual
Bug: 63772836
Bug: 73778753
Bug: 74985733
Fixes: 74427768
Fixes: 74338687
Fixes: 74388467

Change-Id: I5621b3013cdc9638b61552bd4d7211f211eddf1b
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 66d25df..2a6b331 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -30,33 +30,32 @@
         <item><xliff:g id="id">@string/status_bar_rotate</xliff:g></item>
         <item><xliff:g id="id">@string/status_bar_headset</xliff:g></item>
         <item><xliff:g id="id">@string/status_bar_data_saver</xliff:g></item>
-        <item><xliff:g id="id">@string/status_bar_managed_profile</xliff:g></item>
         <item><xliff:g id="id">@string/status_bar_ime</xliff:g></item>
         <item><xliff:g id="id">@string/status_bar_sync_failing</xliff:g></item>
         <item><xliff:g id="id">@string/status_bar_sync_active</xliff:g></item>
-        <item><xliff:g id="id">@string/status_bar_cast</xliff:g></item>
-        <item><xliff:g id="id">@string/status_bar_hotspot</xliff:g></item>
-        <item><xliff:g id="id">@string/status_bar_location</xliff:g></item>
-        <item><xliff:g id="id">@string/status_bar_bluetooth</xliff:g></item>
         <item><xliff:g id="id">@string/status_bar_nfc</xliff:g></item>
         <item><xliff:g id="id">@string/status_bar_tty</xliff:g></item>
         <item><xliff:g id="id">@string/status_bar_speakerphone</xliff:g></item>
         <item><xliff:g id="id">@string/status_bar_zen</xliff:g></item>
         <item><xliff:g id="id">@string/status_bar_mute</xliff:g></item>
         <item><xliff:g id="id">@string/status_bar_volume</xliff:g></item>
-        <item><xliff:g id="id">@string/status_bar_vpn</xliff:g></item>
-        <item><xliff:g id="id">@string/status_bar_ethernet</xliff:g></item>
-        <item><xliff:g id="id">@string/status_bar_wifi</xliff:g></item>
-        <item><xliff:g id="id">@string/status_bar_mobile</xliff:g></item>
-        <item><xliff:g id="id">@string/status_bar_airplane</xliff:g></item>
         <item><xliff:g id="id">@string/status_bar_cdma_eri</xliff:g></item>
         <item><xliff:g id="id">@string/status_bar_data_connection</xliff:g></item>
         <item><xliff:g id="id">@string/status_bar_phone_evdo_signal</xliff:g></item>
         <item><xliff:g id="id">@string/status_bar_phone_signal</xliff:g></item>
-        <item><xliff:g id="id">@string/status_bar_battery</xliff:g></item>
-        <item><xliff:g id="id">@string/status_bar_alarm_clock</xliff:g></item>
         <item><xliff:g id="id">@string/status_bar_secure</xliff:g></item>
-        <item><xliff:g id="id">@string/status_bar_clock</xliff:g></item>
+        <item><xliff:g id="id">@string/status_bar_alarm_clock</xliff:g></item>
+        <item><xliff:g id="id">@string/status_bar_bluetooth</xliff:g></item>
+        <item><xliff:g id="id">@string/status_bar_managed_profile</xliff:g></item>
+        <item><xliff:g id="id">@string/status_bar_cast</xliff:g></item>
+        <item><xliff:g id="id">@string/status_bar_vpn</xliff:g></item>
+        <item><xliff:g id="id">@string/status_bar_location</xliff:g></item>
+        <item><xliff:g id="id">@string/status_bar_hotspot</xliff:g></item>
+        <item><xliff:g id="id">@string/status_bar_ethernet</xliff:g></item>
+        <item><xliff:g id="id">@string/status_bar_wifi</xliff:g></item>
+        <item><xliff:g id="id">@string/status_bar_mobile</xliff:g></item>
+        <item><xliff:g id="id">@string/status_bar_airplane</xliff:g></item>
+        <item><xliff:g id="id">@string/status_bar_battery</xliff:g></item>
     </string-array>
 
     <string translatable="false" name="status_bar_rotate">rotate</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index cb8d629..cbd7b4d 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2732,6 +2732,10 @@
   <java-symbol type="string" name="status_bar_alarm_clock" />
   <java-symbol type="string" name="status_bar_secure" />
   <java-symbol type="string" name="status_bar_clock" />
+  <java-symbol type="string" name="status_bar_airplane" />
+  <java-symbol type="string" name="status_bar_mobile" />
+  <java-symbol type="string" name="status_bar_ethernet" />
+  <java-symbol type="string" name="status_bar_vpn" />
 
   <!-- Locale picker -->
   <java-symbol type="id" name="locale_search_menu" />
diff --git a/packages/SystemUI/res/layout/quick_qs_status_icons.xml b/packages/SystemUI/res/layout/quick_qs_status_icons.xml
index cd3271f..c03f25c 100644
--- a/packages/SystemUI/res/layout/quick_qs_status_icons.xml
+++ b/packages/SystemUI/res/layout/quick_qs_status_icons.xml
@@ -21,7 +21,7 @@
     android:layout_marginTop="8dp"
     android:layout_marginBottom="14dp"
     android:layout_below="@id/quick_status_bar_system_icons"
-    >
+    android:paddingEnd="@dimen/signal_cluster_battery_padding" >
 
     <com.android.systemui.statusbar.phone.StatusIconContainer
         android:id="@+id/statusIcons"
@@ -29,9 +29,4 @@
         android:layout_height="match_parent"
         android:layout_weight="1" />
 
-    <include layout="@layout/signal_cluster_view"
-        android:layout_width="wrap_content"
-        android:layout_height="match_parent"
-        android:layout_marginStart="@dimen/signal_cluster_margin_start" />
-
 </LinearLayout>
diff --git a/packages/SystemUI/res/layout/status_bar_mobile_signal_group.xml b/packages/SystemUI/res/layout/status_bar_mobile_signal_group.xml
new file mode 100644
index 0000000..d607c8c
--- /dev/null
+++ b/packages/SystemUI/res/layout/status_bar_mobile_signal_group.xml
@@ -0,0 +1,81 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 2018, 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.
+*/
+-->
+<com.android.systemui.statusbar.StatusBarMobileView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:systemui="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/mobile_combo"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:layout_gravity="center_vertical"
+    android:orientation="horizontal">
+    <FrameLayout
+        android:id="@+id/inout_container"
+        android:layout_height="17dp"
+        android:layout_width="wrap_content"
+        android:layout_gravity="center_vertical">
+        <ImageView
+            android:id="@+id/mobile_in"
+            android:layout_height="wrap_content"
+            android:layout_width="wrap_content"
+            android:src="@drawable/ic_activity_down"
+            android:visibility="gone"
+            android:paddingEnd="2dp"
+        />
+        <ImageView
+            android:id="@+id/mobile_out"
+            android:layout_height="wrap_content"
+            android:layout_width="wrap_content"
+            android:src="@drawable/ic_activity_up"
+            android:paddingEnd="2dp"
+            android:visibility="gone"
+        />
+    </FrameLayout>
+    <ImageView
+        android:id="@+id/mobile_type"
+        android:layout_height="wrap_content"
+        android:layout_width="wrap_content"
+        android:layout_gravity="center_vertical"
+        android:paddingEnd="1dp"
+        android:visibility="gone" />
+    <Space
+        android:id="@+id/mobile_roaming_space"
+        android:layout_height="match_parent"
+        android:layout_width="@dimen/roaming_icon_start_padding"
+        android:visibility="gone"
+    />
+    <FrameLayout
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_vertical">
+        <com.android.systemui.statusbar.AnimatedImageView
+            android:id="@+id/mobile_signal"
+            android:layout_height="wrap_content"
+            android:layout_width="wrap_content"
+            systemui:hasOverlappingRendering="false"
+        />
+        <ImageView
+            android:id="@+id/mobile_roaming"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:src="@drawable/stat_sys_roaming"
+            android:contentDescription="@string/data_connection_roaming"
+            android:visibility="gone" />
+    </FrameLayout>
+</com.android.systemui.statusbar.StatusBarMobileView>
+
diff --git a/packages/SystemUI/res/layout/status_bar_wifi_group.xml b/packages/SystemUI/res/layout/status_bar_wifi_group.xml
new file mode 100644
index 0000000..08cef55
--- /dev/null
+++ b/packages/SystemUI/res/layout/status_bar_wifi_group.xml
@@ -0,0 +1,82 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 2018, 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.
+*/
+-->
+<com.android.systemui.statusbar.StatusBarWifiView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:systemui="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/wifi_combo"
+    android:layout_width="wrap_content"
+    android:layout_height="match_parent"
+    android:paddingStart="4dp"
+    android:gravity="center_vertical"
+    android:orientation="horizontal" >
+
+    <FrameLayout
+            android:id="@+id/inout_container"
+            android:layout_height="17dp"
+            android:layout_width="wrap_content"
+            android:gravity="center_vertical" >
+        <ImageView
+            android:id="@+id/wifi_in"
+            android:layout_height="wrap_content"
+            android:layout_width="wrap_content"
+            android:src="@drawable/ic_activity_down"
+            android:visibility="gone"
+            android:paddingEnd="2dp"
+        />
+        <ImageView
+            android:id="@+id/wifi_out"
+            android:layout_height="wrap_content"
+            android:layout_width="wrap_content"
+            android:src="@drawable/ic_activity_up"
+            android:paddingEnd="2dp"
+            android:visibility="gone"
+        />
+    </FrameLayout>
+    <FrameLayout
+        android:id="@+id/wifi_combo"
+        android:layout_height="wrap_content"
+        android:layout_width="wrap_content"
+        android:gravity="center_vertical" >
+        <com.android.systemui.statusbar.AlphaOptimizedImageView
+            android:theme="?attr/lightIconTheme"
+            android:id="@+id/wifi_signal"
+            android:layout_height="wrap_content"
+            android:layout_width="wrap_content" />
+    </FrameLayout>
+
+    <View
+        android:id="@+id/wifi_signal_spacer"
+        android:layout_width="@dimen/status_bar_wifi_signal_spacer_width"
+        android:layout_height="4dp"
+        android:visibility="gone" />
+
+    <!-- Looks like CarStatusBar uses this... -->
+    <ViewStub
+        android:id="@+id/connected_device_signals_stub"
+        android:layout="@layout/connected_device_signal"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content" />
+
+    <View
+        android:id="@+id/wifi_airplane_spacer"
+        android:layout_width="@dimen/status_bar_airplane_spacer_width"
+        android:layout_height="4dp"
+        android:visibility="gone"
+    />
+</com.android.systemui.statusbar.StatusBarWifiView>
diff --git a/packages/SystemUI/res/layout/system_icons.xml b/packages/SystemUI/res/layout/system_icons.xml
index 1fafb2f..258b82a 100644
--- a/packages/SystemUI/res/layout/system_icons.xml
+++ b/packages/SystemUI/res/layout/system_icons.xml
@@ -24,14 +24,10 @@
         android:layout_width="0dp"
         android:layout_weight="1"
         android:layout_height="match_parent"
+        android:paddingEnd="4dp"
         android:gravity="center_vertical"
         android:orientation="horizontal"/>
 
-    <include layout="@layout/signal_cluster_view"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_marginStart="@dimen/signal_cluster_margin_start"/>
-
     <com.android.systemui.BatteryMeterView android:id="@+id/battery"
         android:layout_height="match_parent"
         android:layout_width="wrap_content"
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
index 0ac8b9c..df65d1f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
@@ -47,7 +47,6 @@
 import com.android.systemui.Dependency;
 import com.android.systemui.Prefs;
 import com.android.systemui.R;
-import com.android.systemui.R.id;
 import com.android.systemui.SysUiServiceProvider;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.qs.QSDetail.Callback;
@@ -161,7 +160,6 @@
 
         // Set light text on the header icons because they will always be on a black background
         applyDarkness(R.id.clock, tintArea, 0, DarkIconDispatcher.DEFAULT_ICON_TINT);
-        applyDarkness(id.signal_cluster, tintArea, intensity, colorForeground);
 
         // Set the correct tint for the status icons so they contrast
         mIconManager.setTint(fillColor);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/car/CarStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/car/CarStatusBarHeader.java
index ec18376..31c455d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/car/CarStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/car/CarStatusBarHeader.java
@@ -44,7 +44,6 @@
         float intensity = colorForeground == Color.WHITE ? 0f : 1f;
         Rect tintArea = new Rect(0, 0, 0, 0);
 
-        applyDarkness(R.id.signal_cluster, tintArea, intensity, colorForeground);
         applyDarkness(R.id.battery, tintArea, intensity, colorForeground);
         applyDarkness(R.id.clock, tintArea, intensity, colorForeground);
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
index 943a176..6f664d7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
@@ -102,7 +102,9 @@
             mTokenMap.remove(service.getToken());
             mTiles.remove(tile.getComponent());
             final String slot = tile.getComponent().getClassName();
-            mMainHandler.post(() -> mHost.getIconController().removeIcon(slot));
+            // TileServices doesn't know how to add more than 1 icon per slot, so remove all
+            mMainHandler.post(() -> mHost.getIconController()
+                    .removeAllIconsForSlot(slot));
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NeutralGoodDrawable.java b/packages/SystemUI/src/com/android/systemui/statusbar/NeutralGoodDrawable.java
new file mode 100644
index 0000000..cdb0514
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NeutralGoodDrawable.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2017 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;
+
+import android.content.Context;
+import android.content.res.Resources.Theme;
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.LayerDrawable;
+import android.view.ContextThemeWrapper;
+import android.view.Gravity;
+import com.android.settingslib.Utils;
+import com.android.systemui.R;
+
+/**
+ * NeutralGoodDrawable implements a drawable that will load 2 underlying drawable resources, one
+ * with each the DualToneDarkTheme and DualToneLightTheme, choosing which one based on what
+ * DarkIconDispatcher tells us about darkness
+ */
+public class NeutralGoodDrawable extends LayerDrawable {
+
+    public static NeutralGoodDrawable create(Context context, int resId) {
+        int dualToneLightTheme = Utils.getThemeAttr(context, R.attr.lightIconTheme);
+        int dualToneDarkTheme = Utils.getThemeAttr(context, R.attr.darkIconTheme);
+        ContextThemeWrapper light = new ContextThemeWrapper(context, dualToneLightTheme);
+        ContextThemeWrapper dark = new ContextThemeWrapper(context, dualToneDarkTheme);
+
+        return create(light, dark, resId);
+    }
+
+    /**
+     * For the on-the-go young entrepreneurial who wants to cache contexts
+     * @param light - a context using the R.attr.lightIconTheme
+     * @param dark - a context using the R.attr.darkIconTheme
+     * @param resId - the resId for our drawable
+     */
+    public static NeutralGoodDrawable create(Context light, Context dark, int resId) {
+        return new NeutralGoodDrawable(
+                new Drawable[] {
+                        light.getDrawable(resId).mutate(),
+                        dark.getDrawable(resId).mutate() });
+    }
+
+    protected NeutralGoodDrawable(Drawable []drawables) {
+        super(drawables);
+
+        for (int i = 0; i < drawables.length; i++) {
+            setLayerGravity(i, Gravity.CENTER);
+        }
+
+        mutate();
+        setDarkIntensity(0);
+    }
+
+    public void setDarkIntensity(float intensity) {
+
+        getDrawable(0).setAlpha((int) ((1 - intensity) * 255f));
+        getDrawable(1).setAlpha((int) (intensity * 255f));
+
+        invalidateSelf();
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconContainer.java
new file mode 100644
index 0000000..56f78f4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconContainer.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2017 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;
+
+import com.android.internal.statusbar.StatusBarIcon;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Holds an array of {@link com.android.internal.statusbar.StatusBarIcon}s and draws them
+ * in a linear layout
+ */
+public class StatusBarIconContainer {
+    private final List<StatusBarIcon> mIcons = new ArrayList<>();
+
+    public StatusBarIconContainer(List<StatusBarIcon> icons) {
+        mIcons.addAll(icons);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
index 603902a..bd6bd12 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.statusbar;
 
+import static com.android.systemui.statusbar.policy.DarkIconDispatcher.getTint;
+
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ObjectAnimator;
@@ -23,6 +25,7 @@
 import android.app.Notification;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
+import android.content.res.ColorStateList;
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.graphics.Canvas;
@@ -56,7 +59,7 @@
 import java.text.NumberFormat;
 import java.util.Arrays;
 
-public class StatusBarIconView extends AnimatedImageView {
+public class StatusBarIconView extends AnimatedImageView implements StatusIconDisplayable {
     public static final int NO_COLOR = 0;
 
     /**
@@ -867,6 +870,21 @@
         mOnDismissListener = onDismissListener;
     }
 
+    @Override
+    public void onDarkChanged(Rect area, float darkIntensity, int tint) {
+        setImageTintList(ColorStateList.valueOf(getTint(area, this, tint)));
+    }
+
+    @Override
+    public boolean isIconVisible() {
+        return mIcon != null && mIcon.visible;
+    }
+
+    @Override
+    public boolean isIconBlocked() {
+        return mBlocked;
+    }
+
     public interface OnVisibilityChangedListener {
         void onVisibilityChanged(int newVisibility);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java
new file mode 100644
index 0000000..b7620f3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java
@@ -0,0 +1,207 @@
+/*
+ * Copyright (C) 2018 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;
+
+import static com.android.systemui.statusbar.policy.DarkIconDispatcher.getTint;
+
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.graphics.Color;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.ImageView;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.keyguard.AlphaOptimizedLinearLayout;
+import com.android.settingslib.graph.SignalDrawable;
+import com.android.systemui.R;
+import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState;
+import com.android.systemui.statusbar.policy.DarkIconDispatcher.DarkReceiver;
+
+public class StatusBarMobileView extends AlphaOptimizedLinearLayout implements DarkReceiver,
+        StatusIconDisplayable {
+    private static final String TAG = "StatusBarMobileView";
+
+    private String mSlot;
+    private MobileIconState mState;
+    private SignalDrawable mMobileDrawable;
+    private View mInoutContainer;
+    private ImageView mIn;
+    private ImageView mOut;
+    private ImageView mMobile, mMobileType, mMobileRoaming;
+    private View mMobileRoamingSpace;
+
+    public static StatusBarMobileView fromContext(Context context) {
+        LayoutInflater inflater = LayoutInflater.from(context);
+
+        return (StatusBarMobileView)
+                inflater.inflate(R.layout.status_bar_mobile_signal_group, null);
+    }
+
+    public StatusBarMobileView(Context context) {
+        super(context);
+    }
+
+    public StatusBarMobileView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public StatusBarMobileView(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    public StatusBarMobileView(Context context, AttributeSet attrs, int defStyleAttr,
+            int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+
+        init();
+    }
+
+    private void init() {
+        mMobile = findViewById(R.id.mobile_signal);
+        mMobileType = findViewById(R.id.mobile_type);
+        mMobileRoaming = findViewById(R.id.mobile_roaming);
+        mMobileRoamingSpace = findViewById(R.id.mobile_roaming_space);
+        mIn = findViewById(R.id.mobile_in);
+        mOut = findViewById(R.id.mobile_out);
+        mInoutContainer = findViewById(R.id.inout_container);
+
+        mMobileDrawable = new SignalDrawable(getContext());
+        mMobile.setImageDrawable(mMobileDrawable);
+    }
+
+    public void applyMobileState(MobileIconState state) {
+        if (state == null) {
+            setVisibility(View.GONE);
+            mState = null;
+            return;
+        }
+
+        if (mState == null) {
+            mState = state;
+            initViewState();
+            return;
+        }
+
+        if (!mState.equals(state)) {
+            updateState(state);
+        }
+    }
+
+    private void initViewState() {
+        setContentDescription(mState.contentDescription);
+        if (!mState.visible) {
+            setVisibility(View.GONE);
+        } else {
+            setVisibility(View.VISIBLE);
+        }
+        mMobileDrawable.setLevel(mState.strengthId);
+        if (mState.typeId > 0) {
+            mMobileType.setContentDescription(mState.typeContentDescription);
+            mMobileType.setImageResource(mState.typeId);
+            mMobileType.setVisibility(View.VISIBLE);
+        } else {
+            mMobileType.setVisibility(View.GONE);
+        }
+
+        mMobileRoaming.setVisibility(mState.roaming ? View.VISIBLE : View.GONE);
+        mMobileRoamingSpace.setVisibility(mState.roaming ? View.VISIBLE : View.GONE);
+        mIn.setVisibility(mState.activityIn ? View.VISIBLE : View.GONE);
+        mOut.setVisibility(mState.activityIn ? View.VISIBLE : View.GONE);
+        mInoutContainer.setVisibility((mState.activityIn || mState.activityOut)
+                ? View.VISIBLE : View.GONE);
+    }
+
+    private void updateState(MobileIconState state) {
+        setContentDescription(state.contentDescription);
+        if (mState.visible != state.visible) {
+            setVisibility(state.visible ? View.VISIBLE : View.GONE);
+        }
+        if (mState.strengthId != state.strengthId) {
+            mMobileDrawable.setLevel(state.strengthId);
+        }
+        if (mState.typeId != state.typeId && state.typeId != 0) {
+            mMobileType.setContentDescription(state.typeContentDescription);
+            mMobileType.setImageResource(state.typeId);
+            mMobileType.setVisibility(View.VISIBLE);
+        } else {
+            mMobileType.setVisibility(View.GONE);
+        }
+
+        mMobileRoaming.setVisibility(state.roaming ? View.VISIBLE : View.GONE);
+        mMobileRoamingSpace.setVisibility(state.roaming ? View.VISIBLE : View.GONE);
+        mIn.setVisibility(state.activityIn ? View.VISIBLE : View.GONE);
+        mOut.setVisibility(state.activityIn ? View.VISIBLE : View.GONE);
+        mInoutContainer.setVisibility((state.activityIn || state.activityOut)
+                ? View.VISIBLE : View.GONE);
+
+        mState = state;
+    }
+
+    @Override
+    public void onDarkChanged(Rect area, float darkIntensity, int tint) {
+        mMobileDrawable.setDarkIntensity(darkIntensity);
+        ColorStateList color = ColorStateList.valueOf(getTint(area, this, tint));
+        mIn.setImageTintList(color);
+        mOut.setImageTintList(color);
+        mMobileType.setImageTintList(color);
+        mMobileRoaming.setImageTintList(color);
+    }
+
+    @Override
+    public String getSlot() {
+        return mSlot;
+    }
+
+    public void setSlot(String slot) {
+        mSlot = slot;
+    }
+
+    @Override
+    public void setStaticDrawableColor(int color) {
+        ColorStateList list = ColorStateList.valueOf(color);
+        float intensity = color == Color.WHITE ? 0 : 1;
+        mMobileDrawable.setDarkIntensity(intensity);
+
+        mIn.setImageTintList(list);
+        mOut.setImageTintList(list);
+        mMobileType.setImageTintList(list);
+        mMobileRoaming.setImageTintList(list);
+    }
+
+    @Override
+    public boolean isIconVisible() {
+        return mState.visible;
+    }
+
+    @VisibleForTesting
+    public MobileIconState getState() {
+        return mState;
+    }
+
+    @Override
+    public String toString() {
+        return "StatusBarMobileView(slot=" + mSlot + " state=" + mState + ")";
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarWifiView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarWifiView.java
new file mode 100644
index 0000000..afd373e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarWifiView.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) 2018 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;
+
+import static com.android.systemui.statusbar.policy.DarkIconDispatcher.getTint;
+
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.view.ContextThemeWrapper;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.ImageView;
+
+import com.android.keyguard.AlphaOptimizedLinearLayout;
+import com.android.settingslib.Utils;
+import com.android.systemui.R;
+import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState;
+import com.android.systemui.statusbar.policy.DarkIconDispatcher.DarkReceiver;
+
+/**
+ * Start small: StatusBarWifiView will be able to layout from a WifiIconState
+ */
+public class StatusBarWifiView extends AlphaOptimizedLinearLayout implements DarkReceiver,
+        StatusIconDisplayable {
+    private static final String TAG = "StatusBarWifiView";
+
+    private ImageView mWifiIcon;
+    private ImageView mIn;
+    private ImageView mOut;
+    private View mInoutContainer;
+    private View mSignalSpacer;
+    private View mAirplaneSpacer;
+    private WifiIconState mState;
+    private String mSlot;
+    private float mDarkIntensity = 0;
+
+    private ContextThemeWrapper mDarkContext;
+    private ContextThemeWrapper mLightContext;
+
+    public static StatusBarWifiView fromContext(Context context) {
+        LayoutInflater inflater = LayoutInflater.from(context);
+        return (StatusBarWifiView) inflater.inflate(R.layout.status_bar_wifi_group, null);
+    }
+
+    public StatusBarWifiView(Context context) {
+        super(context);
+    }
+
+    public StatusBarWifiView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public StatusBarWifiView(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    public StatusBarWifiView(Context context, AttributeSet attrs, int defStyleAttr,
+            int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        init();
+    }
+
+    public void setSlot(String slot) {
+        mSlot = slot;
+    }
+
+    @Override
+    public void setStaticDrawableColor(int color) {
+        ColorStateList list = ColorStateList.valueOf(color);
+        mWifiIcon.setImageTintList(list);
+        mIn.setImageTintList(list);
+        mOut.setImageTintList(list);
+    }
+
+    @Override
+    public String getSlot() {
+        return mSlot;
+    }
+
+    @Override
+    public boolean isIconVisible() {
+        return mState != null && mState.visible;
+    }
+
+    private void init() {
+        int dualToneLightTheme = Utils.getThemeAttr(mContext, R.attr.lightIconTheme);
+        int dualToneDarkTheme = Utils.getThemeAttr(mContext, R.attr.darkIconTheme);
+        mLightContext = new ContextThemeWrapper(mContext, dualToneLightTheme);
+        mDarkContext = new ContextThemeWrapper(mContext, dualToneDarkTheme);
+
+        mWifiIcon = findViewById(R.id.wifi_signal);
+        mIn = findViewById(R.id.wifi_in);
+        mOut = findViewById(R.id.wifi_out);
+        mSignalSpacer = findViewById(R.id.wifi_signal_spacer);
+        mAirplaneSpacer = findViewById(R.id.wifi_airplane_spacer);
+        mInoutContainer = findViewById(R.id.inout_container);
+    }
+
+    public void applyWifiState(WifiIconState state) {
+        if (state == null) {
+            setVisibility(View.GONE);
+            mState = null;
+            return;
+        }
+
+        if (mState == null) {
+            mState = state;
+            initViewState();
+        }
+
+        if (!mState.equals(state)) {
+            updateState(state);
+        }
+    }
+
+    private void updateState(WifiIconState state) {
+        if (mState.resId != state.resId && state.resId >= 0) {
+            NeutralGoodDrawable drawable = NeutralGoodDrawable
+                    .create(mLightContext, mDarkContext, state.resId);
+            drawable.setDarkIntensity(mDarkIntensity);
+            mWifiIcon.setImageDrawable(drawable);
+        }
+
+        mIn.setVisibility(state.activityIn ? View.VISIBLE : View.GONE);
+        mOut.setVisibility(state.activityOut ? View.VISIBLE : View.GONE);
+        mInoutContainer.setVisibility(
+                (state.activityIn || state.activityOut) ? View.VISIBLE : View.GONE);
+        mAirplaneSpacer.setVisibility(state.airplaneSpacerVisible ? View.VISIBLE : View.GONE);
+        mSignalSpacer.setVisibility(state.signalSpacerVisible ? View.VISIBLE : View.GONE);
+        if (mState.visible != state.visible) {
+            setVisibility(state.visible ? View.VISIBLE : View.GONE);
+        }
+
+        mState = state;
+    }
+
+    private void initViewState() {
+        if (mState.resId >= 0) {
+            NeutralGoodDrawable drawable = NeutralGoodDrawable.create(
+                    mLightContext, mDarkContext, mState.resId);
+            drawable.setDarkIntensity(mDarkIntensity);
+            mWifiIcon.setImageDrawable(drawable);
+        }
+
+        mIn.setVisibility(mState.activityIn ? View.VISIBLE : View.GONE);
+        mOut.setVisibility(mState.activityOut ? View.VISIBLE : View.GONE);
+        mInoutContainer.setVisibility(
+                (mState.activityIn || mState.activityOut) ? View.VISIBLE : View.GONE);
+        mAirplaneSpacer.setVisibility(mState.airplaneSpacerVisible ? View.VISIBLE : View.GONE);
+        mSignalSpacer.setVisibility(mState.signalSpacerVisible ? View.VISIBLE : View.GONE);
+        setVisibility(mState.visible ? View.VISIBLE : View.GONE);
+    }
+
+    @Override
+    public void onDarkChanged(Rect area, float darkIntensity, int tint) {
+        mDarkIntensity = darkIntensity;
+        Drawable d = mWifiIcon.getDrawable();
+        if (d instanceof NeutralGoodDrawable) {
+            ((NeutralGoodDrawable)d).setDarkIntensity(darkIntensity);
+        }
+        mIn.setImageTintList(ColorStateList.valueOf(getTint(area, this, tint)));
+        mOut.setImageTintList(ColorStateList.valueOf(getTint(area, this, tint)));
+    }
+
+
+    @Override
+    public String toString() {
+        return "StatusBarWifiView(slot=" + mSlot + " state=" + mState + ")";
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusIconDisplayable.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusIconDisplayable.java
new file mode 100644
index 0000000..ccab0d6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusIconDisplayable.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2017 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;
+
+import com.android.systemui.statusbar.policy.DarkIconDispatcher.DarkReceiver;
+
+public interface StatusIconDisplayable extends DarkReceiver {
+    String getSlot();
+    void setStaticDrawableColor(int color);
+    boolean isIconVisible();
+    default boolean isIconBlocked() {
+        return false;
+    }
+}
+
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
index f42473d..75b31c5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
@@ -17,8 +17,6 @@
 import static android.app.StatusBarManager.DISABLE_NOTIFICATION_ICONS;
 import static android.app.StatusBarManager.DISABLE_SYSTEM_INFO;
 
-import static com.android.systemui.statusbar.phone.StatusBar.reinflateSignalCluster;
-
 import android.annotation.Nullable;
 import android.app.Fragment;
 import android.app.StatusBarManager;
@@ -34,7 +32,6 @@
 import com.android.systemui.R;
 import com.android.systemui.SysUiServiceProvider;
 import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.SignalClusterView;
 import com.android.systemui.statusbar.phone.StatusBarIconController.DarkIconManager;
 import com.android.systemui.statusbar.policy.DarkIconDispatcher;
 import com.android.systemui.statusbar.policy.EncryptionHelper;
@@ -63,7 +60,6 @@
     private int mDisabled1;
     private StatusBar mStatusBarComponent;
     private DarkIconManager mDarkIconManager;
-    private SignalClusterView mSignalClusterView;
     private View mOperatorNameFrame;
 
     private SignalCallback mSignalCallback = new SignalCallback() {
@@ -99,9 +95,6 @@
         Dependency.get(StatusBarIconController.class).addIconGroup(mDarkIconManager);
         mSystemIconArea = mStatusBar.findViewById(R.id.system_icon_area);
         mClockView = mStatusBar.findViewById(R.id.clock);
-        mSignalClusterView = mStatusBar.findViewById(R.id.signal_cluster);
-        Dependency.get(DarkIconDispatcher.class).addDarkReceiver(mSignalClusterView);
-        // Default to showing until we know otherwise.
         showSystemIconArea(false);
         initEmergencyCryptkeeperText();
         initOperatorName();
@@ -128,7 +121,6 @@
     @Override
     public void onDestroyView() {
         super.onDestroyView();
-        Dependency.get(DarkIconDispatcher.class).removeDarkReceiver(mSignalClusterView);
         Dependency.get(StatusBarIconController.class).removeIconGroup(mDarkIconManager);
         if (mNetworkController.hasEmergencyCryptKeeperText()) {
             mNetworkController.removeCallback(mSignalCallback);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java
index 3f9ae80..80c4eb0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java
@@ -69,7 +69,7 @@
         mReceivers.remove(object);
     }
 
-    public void applyDark(ImageView object) {
+    public void applyDark(DarkReceiver object) {
         mReceivers.get(object).onDarkChanged(mTintArea, mDarkIntensity, mIconTint);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java
index edfd02b..48540b1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java
@@ -20,25 +20,33 @@
 import android.graphics.drawable.Icon;
 import android.os.Bundle;
 import android.os.UserHandle;
+import android.util.Log;
 import android.view.Gravity;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.LinearLayout;
 
 import com.android.internal.statusbar.StatusBarIcon;
-import com.android.settingslib.Utils;
 import com.android.systemui.DemoMode;
 import com.android.systemui.R;
+import com.android.systemui.statusbar.StatusIconDisplayable;
 import com.android.systemui.statusbar.StatusBarIconView;
+import com.android.systemui.statusbar.StatusBarMobileView;
+import com.android.systemui.statusbar.StatusBarWifiView;
+import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState;
+import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState;
 import com.android.systemui.statusbar.policy.DarkIconDispatcher;
 import com.android.systemui.statusbar.policy.DarkIconDispatcher.DarkReceiver;
-import com.android.systemui.statusbar.policy.LocationControllerImpl;
-import com.android.systemui.util.leak.LeakDetector;
+import java.util.ArrayList;
 
 public class DemoStatusIcons extends StatusIconContainer implements DemoMode, DarkReceiver {
+    private static final String TAG = "DemoStatusIcons";
+
     private final LinearLayout mStatusIcons;
+    private final ArrayList<StatusBarMobileView> mMobileViews = new ArrayList<>();
     private final int mIconSize;
 
+    private StatusBarWifiView mWifiView;
     private boolean mDemoMode;
     private int mColor;
 
@@ -56,6 +64,7 @@
     }
 
     public void remove() {
+        mMobileViews.clear();
         ((ViewGroup) getParent()).removeView(this);
     }
 
@@ -66,7 +75,7 @@
 
     private void updateColors() {
         for (int i = 0; i < getChildCount(); i++) {
-            StatusBarIconView child = (StatusBarIconView) getChildAt(i);
+            StatusIconDisplayable child = (StatusIconDisplayable) getChildAt(i);
             child.setStaticDrawableColor(mColor);
         }
     }
@@ -145,6 +154,7 @@
         }
     }
 
+    /// Can only be used to update non-signal related slots
     private void updateSlot(String slot, String iconPkg, int iconId) {
         if (!mDemoMode) return;
         if (iconPkg == null) {
@@ -152,7 +162,11 @@
         }
         int removeIndex = -1;
         for (int i = 0; i < getChildCount(); i++) {
-            StatusBarIconView v = (StatusBarIconView) getChildAt(i);
+            View child = getChildAt(i);
+            if (!(child instanceof StatusBarIconView)) {
+                continue;
+            }
+            StatusBarIconView v = (StatusBarIconView) child;
             if (slot.equals(v.getTag())) {
                 if (iconId == 0) {
                     removeIndex = i;
@@ -182,8 +196,101 @@
         addView(v, 0, new LinearLayout.LayoutParams(mIconSize, mIconSize));
     }
 
+    public void addDemoWifiView(WifiIconState state) {
+        Log.d(TAG, "addDemoWifiView: ");
+        StatusBarWifiView view = StatusBarWifiView.fromContext(mContext);
+        view.setSlot(state.slot);
+
+        int viewIndex = getChildCount();
+        // If we have mobile views, put wifi before them
+        for (int i = 0; i < getChildCount(); i++) {
+            View child = getChildAt(i);
+            if (child instanceof StatusBarMobileView) {
+                viewIndex = i;
+                break;
+            }
+        }
+
+        mWifiView = view;
+        mWifiView.applyWifiState(state);
+        mWifiView.setStaticDrawableColor(mColor);
+        addView(view, viewIndex);
+    }
+
+    public void updateWifiState(WifiIconState state) {
+        Log.d(TAG, "updateWifiState: ");
+        if (mWifiView == null) {
+            addDemoWifiView(state);
+        } else {
+            mWifiView.applyWifiState(state);
+        }
+    }
+
+    public void addMobileView(MobileIconState state) {
+        Log.d(TAG, "addMobileView: ");
+        StatusBarMobileView view = StatusBarMobileView.fromContext(mContext);
+
+        view.setSlot(state.slot);
+        view.applyMobileState(state);
+        view.setStaticDrawableColor(mColor);
+
+        // mobile always goes at the end
+        mMobileViews.add(view);
+        addView(view, getChildCount());
+    }
+
+    public void updateMobileState(MobileIconState state) {
+        Log.d(TAG, "updateMobileState: ");
+        // If the view for this subId exists already, use it
+        for (int i = 0; i < mMobileViews.size(); i++) {
+            StatusBarMobileView view = mMobileViews.get(i);
+            if (view.getState().subId == state.subId) {
+                view.applyMobileState(state);
+                return;
+            }
+        }
+
+        // Else we have to add it
+        addMobileView(state);
+    }
+
+    public void onRemoveIcon(StatusIconDisplayable view) {
+        if (view.getSlot().equals("wifi")) {
+            removeView(mWifiView);
+            mWifiView = null;
+        } else {
+            StatusBarMobileView mobileView = matchingMobileView(view);
+            if (mobileView != null) {
+                removeView(mobileView);
+                mMobileViews.remove(mobileView);
+            }
+        }
+    }
+
+    private StatusBarMobileView matchingMobileView(StatusIconDisplayable otherView) {
+        if (!(otherView instanceof StatusBarMobileView)) {
+            return null;
+        }
+
+        StatusBarMobileView v = (StatusBarMobileView) otherView;
+        for (StatusBarMobileView view : mMobileViews) {
+            if (view.getState().subId == v.getState().subId) {
+                return view;
+            }
+        }
+
+        return null;
+    }
+
     @Override
     public void onDarkChanged(Rect area, float darkIntensity, int tint) {
         setColor(DarkIconDispatcher.getTint(area, mStatusIcons, tint));
+
+        if (mWifiView != null) {
+            mWifiView.onDarkChanged(area, darkIntensity, tint);
+        }
+        for (StatusBarMobileView view : mMobileViews) {
+            view.onDarkChanged(area, darkIntensity, tint);
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
index 994c0ab..b817809 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
@@ -207,7 +207,6 @@
     @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         updateLayoutConsideringCutout();
-        setSignalClusterLayoutWidth();
         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
     }
 
@@ -296,17 +295,6 @@
         return true;
     }
 
-    //TODO: Something is setting signal_cluster to MATCH_PARENT. Why?
-    private void setSignalClusterLayoutWidth() {
-        View signalCluster = findViewById(R.id.signal_cluster);
-        if (signalCluster == null) {
-            return;
-        }
-
-        LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) signalCluster.getLayoutParams();
-        lp.width = LinearLayout.LayoutParams.WRAP_CONTENT;
-    }
-
     public void setListening(boolean listening) {
         if (listening == mBatteryListening) {
             return;
@@ -459,7 +447,6 @@
         mIconManager.setTint(iconColor);
         Rect tintArea = new Rect(0, 0, 0, 0);
 
-        applyDarkness(R.id.signal_cluster, tintArea, intensity, iconColor);
         applyDarkness(R.id.battery, tintArea, intensity, iconColor);
         applyDarkness(R.id.clock, tintArea, intensity, iconColor);
         // Reload user avatar
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
index 33c3ee9..3e7b0d9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
@@ -297,7 +297,7 @@
             mIconController.setIcon(mSlotLocation, LOCATION_STATUS_ICON_ID,
                     mContext.getString(R.string.accessibility_location_active));
         } else {
-            mIconController.removeIcon(mSlotLocation);
+            mIconController.removeAllIconsForSlot(mSlotLocation);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarTransitions.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarTransitions.java
index fb1addf..12bdfc6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarTransitions.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarTransitions.java
@@ -32,7 +32,7 @@
     private final PhoneStatusBarView mView;
     private final float mIconAlphaWhenOpaque;
 
-    private View mLeftSide, mStatusIcons, mSignalCluster, mBattery, mClock;
+    private View mLeftSide, mStatusIcons, mBattery, mClock;
     private Animator mCurrentAnimation;
 
     public PhoneStatusBarTransitions(PhoneStatusBarView view) {
@@ -45,7 +45,6 @@
     public void init() {
         mLeftSide = mView.findViewById(R.id.notification_icon_area);
         mStatusIcons = mView.findViewById(R.id.statusIcons);
-        mSignalCluster = mView.findViewById(R.id.signal_cluster);
         mBattery = mView.findViewById(R.id.battery);
         mClock = mView.findViewById(R.id.clock);
         applyModeBackground(-1, getMode(), false /*animate*/);
@@ -90,7 +89,6 @@
             anims.playTogether(
                     animateTransitionTo(mLeftSide, newAlpha),
                     animateTransitionTo(mStatusIcons, newAlpha),
-                    animateTransitionTo(mSignalCluster, newAlpha),
                     animateTransitionTo(mBattery, newAlphaBC),
                     animateTransitionTo(mClock, newAlphaBC)
                     );
@@ -102,7 +100,6 @@
         } else {
             mLeftSide.setAlpha(newAlpha);
             mStatusIcons.setAlpha(newAlpha);
-            mSignalCluster.setAlpha(newAlpha);
             mBattery.setAlpha(newAlphaBC);
             mClock.setAlpha(newAlphaBC);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index beeba83..750d2a5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -348,6 +348,7 @@
     protected boolean mBouncerShowing;
 
     private PhoneStatusBarPolicy mIconPolicy;
+    private StatusBarSignalPolicy mSignalPolicy;
 
     private VolumeComponent mVolumeComponent;
     private BrightnessMirrorController mBrightnessMirrorController;
@@ -747,6 +748,7 @@
 
         // Lastly, call to the icon policy to install/update all the icons.
         mIconPolicy = new PhoneStatusBarPolicy(mContext, mIconController);
+        mSignalPolicy = new StatusBarSignalPolicy(mContext, mIconController);
 
         mUnlockMethodCache = UnlockMethodCache.getInstance(mContext);
         mUnlockMethodCache.addListener(this);
@@ -1131,7 +1133,6 @@
     }
 
     protected void reevaluateStyles() {
-        inflateSignalClusters();
         inflateFooterView();
         updateFooter();
         inflateEmptyShadeView();
@@ -1145,36 +1146,6 @@
         }
     }
 
-    private void inflateSignalClusters() {
-        if (mKeyguardStatusBar != null) reinflateSignalCluster(mKeyguardStatusBar);
-    }
-
-    public static SignalClusterView reinflateSignalCluster(View view) {
-        Context context = view.getContext();
-        SignalClusterView signalCluster = view.findViewById(R.id.signal_cluster);
-        if (signalCluster != null) {
-            ViewParent parent = signalCluster.getParent();
-            if (parent instanceof ViewGroup) {
-                ViewGroup viewParent = (ViewGroup) parent;
-                int index = viewParent.indexOfChild(signalCluster);
-                viewParent.removeView(signalCluster);
-                SignalClusterView newCluster = (SignalClusterView) LayoutInflater.from(context)
-                        .inflate(R.layout.signal_cluster_view, viewParent, false);
-                ViewGroup.MarginLayoutParams layoutParams =
-                        (ViewGroup.MarginLayoutParams) viewParent.getLayoutParams();
-                layoutParams.setMarginsRelative(
-                        context.getResources().getDimensionPixelSize(
-                                R.dimen.signal_cluster_margin_start),
-                        0, 0, 0);
-                newCluster.setLayoutParams(layoutParams);
-                viewParent.addView(newCluster, index);
-                return newCluster;
-            }
-            return signalCluster;
-        }
-        return null;
-    }
-
     private void inflateEmptyShadeView() {
         if (mStackScroller == null) {
             return;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
index 956bebb..94e004b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
@@ -16,12 +16,16 @@
 
 import static android.app.StatusBarManager.DISABLE2_SYSTEM_ICONS;
 import static android.app.StatusBarManager.DISABLE_NONE;
+import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_ICON;
+import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_MOBILE;
+import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_WIFI;
 
 import android.content.Context;
 import android.os.Bundle;
 import android.support.annotation.VisibleForTesting;
 import android.text.TextUtils;
 import android.util.ArraySet;
+import android.util.Log;
 import android.view.Gravity;
 import android.view.View;
 import android.view.ViewGroup;
@@ -33,19 +37,39 @@
 import com.android.systemui.DemoMode;
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
+import com.android.systemui.statusbar.StatusIconDisplayable;
 import com.android.systemui.statusbar.StatusBarIconView;
+import com.android.systemui.statusbar.StatusBarMobileView;
+import com.android.systemui.statusbar.StatusBarWifiView;
+import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState;
+import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState;
 import com.android.systemui.statusbar.policy.DarkIconDispatcher;
+import com.android.systemui.statusbar.policy.DarkIconDispatcher.DarkReceiver;
 import com.android.systemui.util.Utils.DisableStateTracker;
+import java.util.List;
 
 public interface StatusBarIconController {
 
+    /**
+     * When an icon is added with TAG_PRIMARY, it will be treated as the primary icon
+     * in that slot and not added as a sub slot.
+     */
+    public static final int TAG_PRIMARY = 0;
+
     public void addIconGroup(IconManager iconManager);
     public void removeIconGroup(IconManager iconManager);
     public void setExternalIcon(String slot);
     public void setIcon(String slot, int resourceId, CharSequence contentDescription);
     public void setIcon(String slot, StatusBarIcon icon);
-    public void setIconVisibility(String slotTty, boolean b);
-    public void removeIcon(String slot);
+    public void setSignalIcon(String slot, WifiIconState state);
+    public void setMobileIcons(String slot, List<MobileIconState> states);
+    public void setIconVisibility(String slot, boolean b);
+    /**
+     * If you don't know what to pass for `tag`, either remove all icons for slot, or use
+     * TAG_PRIMARY to refer to the first icon at a given slot.
+     */
+    public void removeIcon(String slot, int tag);
+    public void removeAllIconsForSlot(String slot);
 
     public static final String ICON_BLACKLIST = "icon_blacklist";
 
@@ -79,9 +103,9 @@
 
         @Override
         protected void onIconAdded(int index, String slot, boolean blocked,
-                StatusBarIcon icon) {
-            StatusBarIconView v = addIcon(index, slot, blocked, icon);
-            mDarkIconDispatcher.addDarkReceiver(v);
+                StatusBarIconHolder holder) {
+            StatusIconDisplayable view = addHolder(index, slot, blocked, holder);
+            mDarkIconDispatcher.addDarkReceiver((DarkReceiver) view);
         }
 
         @Override
@@ -95,21 +119,21 @@
         @Override
         protected void destroy() {
             for (int i = 0; i < mGroup.getChildCount(); i++) {
-                mDarkIconDispatcher.removeDarkReceiver((ImageView) mGroup.getChildAt(i));
+                mDarkIconDispatcher.removeDarkReceiver((DarkReceiver) mGroup.getChildAt(i));
             }
             mGroup.removeAllViews();
         }
 
         @Override
         protected void onRemoveIcon(int viewIndex) {
-            mDarkIconDispatcher.removeDarkReceiver((ImageView) mGroup.getChildAt(viewIndex));
+            mDarkIconDispatcher.removeDarkReceiver((DarkReceiver) mGroup.getChildAt(viewIndex));
             super.onRemoveIcon(viewIndex);
         }
 
         @Override
         public void onSetIcon(int viewIndex, StatusBarIcon icon) {
             super.onSetIcon(viewIndex, icon);
-            mDarkIconDispatcher.applyDark((ImageView) mGroup.getChildAt(viewIndex));
+            mDarkIconDispatcher.applyDark((DarkReceiver) mGroup.getChildAt(viewIndex));
         }
 
         @Override
@@ -135,17 +159,18 @@
         }
 
         @Override
-        protected void onIconAdded(int index, String slot, boolean blocked, StatusBarIcon icon) {
-            StatusBarIconView v = addIcon(index, slot, blocked, icon);
-            v.setStaticDrawableColor(mColor);
+        protected void onIconAdded(int index, String slot, boolean blocked,
+                StatusBarIconHolder holder) {
+            StatusIconDisplayable view = addHolder(index, slot, blocked, holder);
+            view.setStaticDrawableColor(mColor);
         }
 
         public void setTint(int color) {
             mColor = color;
             for (int i = 0; i < mGroup.getChildCount(); i++) {
                 View child = mGroup.getChildAt(i);
-                if (child instanceof StatusBarIconView) {
-                    StatusBarIconView icon = (StatusBarIconView) child;
+                if (child instanceof StatusIconDisplayable) {
+                    StatusIconDisplayable icon = (StatusIconDisplayable) child;
                     icon.setStaticDrawableColor(mColor);
                 }
             }
@@ -171,6 +196,7 @@
 
         // Enables SystemUI demo mode to take effect in this group
         protected boolean mDemoable = true;
+        private boolean mIsInDemoMode;
         protected DemoStatusIcons mDemoStatusIcons;
 
         public IconManager(ViewGroup group) {
@@ -205,10 +231,27 @@
         }
 
         protected void onIconAdded(int index, String slot, boolean blocked,
-                StatusBarIcon icon) {
-            addIcon(index, slot, blocked, icon);
+                StatusBarIconHolder holder) {
+            addHolder(index, slot, blocked, holder);
         }
 
+        protected StatusIconDisplayable addHolder(int index, String slot, boolean blocked,
+                StatusBarIconHolder holder) {
+            switch (holder.getType()) {
+                case TYPE_ICON:
+                    return addIcon(index, slot, blocked, holder.getIcon());
+
+                case TYPE_WIFI:
+                    return addSignalIcon(index, slot, holder.getWifiState());
+
+                case TYPE_MOBILE:
+                    return addMobileIcon(index, slot, holder.getMobileState());
+            }
+
+            return null;
+        }
+
+        @VisibleForTesting
         protected StatusBarIconView addIcon(int index, String slot, boolean blocked,
                 StatusBarIcon icon) {
             StatusBarIconView view = onCreateStatusBarIconView(slot, blocked);
@@ -218,10 +261,45 @@
         }
 
         @VisibleForTesting
-        protected StatusBarIconView onCreateStatusBarIconView(String slot, boolean blocked) {
+        protected StatusBarWifiView addSignalIcon(int index, String slot, WifiIconState state) {
+            StatusBarWifiView view = onCreateStatusBarWifiView(slot);
+            view.applyWifiState(state);
+            mGroup.addView(view, index, onCreateLayoutParams());
+
+            if (mIsInDemoMode) {
+                mDemoStatusIcons.addDemoWifiView(state);
+            }
+            return view;
+        }
+
+        @VisibleForTesting
+        protected StatusBarMobileView addMobileIcon(int index, String slot, MobileIconState state) {
+            StatusBarMobileView view = onCreateStatusBarMobileView(slot);
+            view.applyMobileState(state);
+            mGroup.addView(view, index, onCreateLayoutParams());
+
+            if (mIsInDemoMode) {
+                mDemoStatusIcons.addMobileView(state);
+            }
+            return view;
+        }
+
+        private StatusBarIconView onCreateStatusBarIconView(String slot, boolean blocked) {
             return new StatusBarIconView(mContext, slot, null, blocked);
         }
 
+        private StatusBarWifiView onCreateStatusBarWifiView(String slot) {
+            StatusBarWifiView view = StatusBarWifiView.fromContext(mContext);
+            view.setSlot(slot);
+            return view;
+        }
+
+        private StatusBarMobileView onCreateStatusBarMobileView(String slot) {
+            StatusBarMobileView view = StatusBarMobileView.fromContext(mContext);
+            view.setSlot(slot);
+            return view;
+        }
+
         protected LinearLayout.LayoutParams onCreateLayoutParams() {
             return new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, mIconSize);
         }
@@ -256,6 +334,9 @@
         }
 
         protected void onRemoveIcon(int viewIndex) {
+            if (mIsInDemoMode) {
+                mDemoStatusIcons.onRemoveIcon((StatusIconDisplayable) mGroup.getChildAt(viewIndex));
+            }
             mGroup.removeViewAt(viewIndex);
         }
 
@@ -264,17 +345,59 @@
             view.set(icon);
         }
 
+        public void onSetIconHolder(int viewIndex, StatusBarIconHolder holder) {
+            switch (holder.getType()) {
+                case TYPE_ICON:
+                    onSetIcon(viewIndex, holder.getIcon());
+                    return;
+                case TYPE_WIFI:
+                    onSetSignalIcon(viewIndex, holder.getWifiState());
+                    return;
+
+                case TYPE_MOBILE:
+                    onSetMobileIcon(viewIndex, holder.getMobileState());
+                default:
+                    break;
+            }
+        }
+
+        public void onSetSignalIcon(int viewIndex, WifiIconState state) {
+            StatusBarWifiView wifiView = (StatusBarWifiView) mGroup.getChildAt(viewIndex);
+            if (wifiView != null) {
+                wifiView.applyWifiState(state);
+            }
+
+            if (mIsInDemoMode) {
+                mDemoStatusIcons.updateWifiState(state);
+            }
+        }
+
+        public void onSetMobileIcon(int viewIndex, MobileIconState state) {
+            StatusBarMobileView view = (StatusBarMobileView) mGroup.getChildAt(viewIndex);
+            if (view != null) {
+                view.applyMobileState(state);
+            }
+
+            if (mIsInDemoMode) {
+                mDemoStatusIcons.updateMobileState(state);
+            }
+        }
+
         @Override
         public void dispatchDemoCommand(String command, Bundle args) {
             if (!mDemoable) {
                 return;
             }
 
-            if (mDemoStatusIcons != null && command.equals(COMMAND_EXIT)) {
-                mDemoStatusIcons.dispatchDemoCommand(command, args);
-                exitDemoMode();
+            if (command.equals(COMMAND_EXIT)) {
+                if (mDemoStatusIcons != null) {
+                    mDemoStatusIcons.dispatchDemoCommand(command, args);
+                    exitDemoMode();
+                }
+                mIsInDemoMode = false;
             } else {
                 if (mDemoStatusIcons == null) {
+                    mIsInDemoMode = true;
                     mDemoStatusIcons = createDemoStatusIcons();
                 }
                 mDemoStatusIcons.dispatchDemoCommand(command, args);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
index 8f5e705..510af03 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
@@ -16,13 +16,14 @@
 
 package com.android.systemui.statusbar.phone;
 
+import android.annotation.NonNull;
 import android.content.Context;
 import android.graphics.drawable.Icon;
 import android.os.Bundle;
 import android.os.UserHandle;
+import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.view.ViewGroup;
-import android.widget.LinearLayout;
 
 import com.android.internal.statusbar.StatusBarIcon;
 import com.android.systemui.Dependency;
@@ -30,7 +31,9 @@
 import com.android.systemui.R;
 import com.android.systemui.SysUiServiceProvider;
 import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.StatusBarIconView;
+import com.android.systemui.statusbar.StatusIconDisplayable;
+import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState;
+import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
 import com.android.systemui.statusbar.policy.IconLogger;
@@ -40,8 +43,9 @@
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.List;
 
-import static com.android.systemui.statusbar.phone.CollapsedStatusBarFragment.STATUS_BAR_ICON_MANAGER_TAG;
+import static com.android.systemui.statusbar.phone.StatusBarIconController.TAG_PRIMARY;
 
 /**
  * Receives the callbacks from CommandQueue related to icons and tracks the state of
@@ -50,20 +54,25 @@
  */
 public class StatusBarIconControllerImpl extends StatusBarIconList implements Tunable,
         ConfigurationListener, Dumpable, CommandQueue.Callbacks, StatusBarIconController {
+
     private static final String TAG = "StatusBarIconController";
 
     private final ArrayList<IconManager> mIconGroups = new ArrayList<>();
     private final ArraySet<String> mIconBlacklist = new ArraySet<>();
     private final IconLogger mIconLogger = Dependency.get(IconLogger.class);
 
+    // Points to light or dark context depending on the... context?
     private Context mContext;
-    private DemoStatusIcons mDemoStatusIcons;
-    private IconManager mStatusBarIconManager;
+    private Context mLightContext;
+    private Context mDarkContext;
+
+    private boolean mIsDark = false;
 
     public StatusBarIconControllerImpl(Context context) {
         super(context.getResources().getStringArray(
                 com.android.internal.R.array.config_statusBarIcons));
         Dependency.get(ConfigurationController.class).addCallback(this);
+
         mContext = context;
 
         loadDimens();
@@ -76,12 +85,16 @@
     @Override
     public void addIconGroup(IconManager group) {
         mIconGroups.add(group);
-        for (int i = 0; i < mIcons.size(); i++) {
-            StatusBarIcon icon = mIcons.get(i);
-            if (icon != null) {
-                String slot = mSlots.get(i);
-                boolean blocked = mIconBlacklist.contains(slot);
-                group.onIconAdded(getViewIndex(getSlotIndex(slot)), slot, blocked, icon);
+        List<Slot> allSlots = getSlots();
+        for (int i = 0; i < allSlots.size(); i++) {
+            Slot slot = allSlots.get(i);
+            List<StatusBarIconHolder> holders = slot.getHolderList();
+            boolean blocked = mIconBlacklist.contains(slot.getName());
+
+            for (StatusBarIconHolder holder : holders) {
+                int tag = holder.getTag();
+                int viewIndex = getViewIndex(getSlotIndex(slot.getName()), holder.getTag());
+                group.onIconAdded(viewIndex, slot.getName(), blocked, holder);
             }
         }
     }
@@ -99,104 +112,209 @@
         }
         mIconBlacklist.clear();
         mIconBlacklist.addAll(StatusBarIconController.getIconBlacklist(newValue));
-        ArrayList<StatusBarIcon> current = new ArrayList<>(mIcons);
-        ArrayList<String> currentSlots = new ArrayList<>(mSlots);
+        ArrayList<Slot> currentSlots = getSlots();
+        ArrayMap<Slot, List<StatusBarIconHolder>> slotsToReAdd = new ArrayMap<>();
+
+        // This is a little hacky... Peel off all of the holders on all of the slots
+        // but keep them around so they can be re-added
+
         // Remove all the icons.
-        for (int i = current.size() - 1; i >= 0; i--) {
-            removeIcon(currentSlots.get(i));
+        for (int i = currentSlots.size() - 1; i >= 0; i--) {
+            Slot s = currentSlots.get(i);
+            slotsToReAdd.put(s, s.getHolderList());
+            removeAllIconsForSlot(s.getName());
         }
+
         // Add them all back
-        for (int i = 0; i < current.size(); i++) {
-            setIcon(currentSlots.get(i), current.get(i));
+        for (int i = 0; i < currentSlots.size(); i++) {
+            Slot item = currentSlots.get(i);
+            List<StatusBarIconHolder> iconsForSlot = slotsToReAdd.get(item);
+            if (iconsForSlot == null) continue;
+            for (StatusBarIconHolder holder : iconsForSlot) {
+                setIcon(getSlotIndex(item.getName()), holder);
+            }
         }
     }
 
     private void loadDimens() {
     }
 
-    private void addSystemIcon(int index, StatusBarIcon icon) {
-        String slot = getSlot(index);
-        int viewIndex = getViewIndex(index);
+    private void addSystemIcon(int index, StatusBarIconHolder holder) {
+        String slot = getSlotName(index);
+        int viewIndex = getViewIndex(index, holder.getTag());
         boolean blocked = mIconBlacklist.contains(slot);
 
-        mIconLogger.onIconVisibility(getSlot(index), icon.visible);
-        mIconGroups.forEach(l -> l.onIconAdded(viewIndex, slot, blocked, icon));
+        mIconLogger.onIconVisibility(getSlotName(index), holder.isVisible());
+        mIconGroups.forEach(l -> l.onIconAdded(viewIndex, slot, blocked, holder));
     }
 
     @Override
     public void setIcon(String slot, int resourceId, CharSequence contentDescription) {
         int index = getSlotIndex(slot);
-        StatusBarIcon icon = getIcon(index);
-        if (icon == null) {
-            icon = new StatusBarIcon(UserHandle.SYSTEM, mContext.getPackageName(),
-                    Icon.createWithResource(mContext, resourceId), 0, 0, contentDescription);
-            setIcon(slot, icon);
+        StatusBarIconHolder holder = getIcon(index, 0);
+        if (holder == null) {
+            StatusBarIcon icon = new StatusBarIcon(UserHandle.SYSTEM, mContext.getPackageName(),
+                    Icon.createWithResource(
+                            mContext, resourceId), 0, 0, contentDescription);
+            holder = StatusBarIconHolder.fromIcon(icon);
+            setIcon(index, holder);
         } else {
-            icon.icon = Icon.createWithResource(mContext, resourceId);
-            icon.contentDescription = contentDescription;
-            handleSet(index, icon);
+            holder.getIcon().icon = Icon.createWithResource(mContext, resourceId);
+            holder.getIcon().contentDescription = contentDescription;
+            handleSet(index, holder);
+        }
+    }
+
+    /**
+     * Signal icons need to be handled differently, because they can be
+     * composite views
+     */
+    @Override
+    public void setSignalIcon(String slot, WifiIconState state) {
+
+        int index = getSlotIndex(slot);
+
+        if (state == null) {
+            removeIcon(index, 0);
+            return;
+        }
+
+        StatusBarIconHolder holder = getIcon(index, 0);
+        if (holder == null) {
+            holder = StatusBarIconHolder.fromWifiIconState(state);
+            setIcon(index, holder);
+        } else {
+            holder.setWifiState(state);
+            handleSet(index, holder);
+        }
+    }
+
+    /**
+     * Accept a list of MobileIconStates, which all live in the same slot(?!), and then are sorted
+     * by subId. Don't worry this definitely makes sense and works.
+     * @param slot da slot
+     * @param iconStates All of the mobile icon states
+     */
+    @Override
+    public void setMobileIcons(String slot, List<MobileIconState> iconStates) {
+        Slot mobileSlot = getSlot(slot);
+        int slotIndex = getSlotIndex(slot);
+
+        for (MobileIconState state : iconStates) {
+            StatusBarIconHolder holder = mobileSlot.getHolderForTag(state.subId);
+            if (holder == null) {
+                holder = StatusBarIconHolder.fromMobileIconState(state);
+                setIcon(slotIndex, holder);
+            } else {
+                holder.setMobileState(state);
+                handleSet(slotIndex, holder);
+            }
         }
     }
 
     @Override
     public void setExternalIcon(String slot) {
-        int viewIndex = getViewIndex(getSlotIndex(slot));
+        int viewIndex = getViewIndex(getSlotIndex(slot), 0);
         int height = mContext.getResources().getDimensionPixelSize(
                 R.dimen.status_bar_icon_drawing_size);
         mIconGroups.forEach(l -> l.onIconExternal(viewIndex, height));
     }
 
+    //TODO: remove this (used in command queue and for 3rd party tiles?)
     @Override
     public void setIcon(String slot, StatusBarIcon icon) {
         setIcon(getSlotIndex(slot), icon);
     }
 
+    /**
+     * For backwards compatibility, in the event that someone gives us a slot and a status bar icon
+     */
+    private void setIcon(int index, StatusBarIcon icon) {
+        if (icon == null) {
+            removeAllIconsForSlot(getSlotName(index));
+            return;
+        }
+
+        StatusBarIconHolder holder = StatusBarIconHolder.fromIcon(icon);
+        setIcon(index, holder);
+    }
+
     @Override
-    public void removeIcon(String slot) {
-        int index = getSlotIndex(slot);
-        removeIcon(index);
+    public void setIcon(int index, @NonNull StatusBarIconHolder holder) {
+        boolean isNew = getIcon(index, holder.getTag()) == null;
+        super.setIcon(index, holder);
+
+        if (isNew) {
+            addSystemIcon(index, holder);
+        } else {
+            handleSet(index, holder);
+        }
     }
 
     public void setIconVisibility(String slot, boolean visibility) {
         int index = getSlotIndex(slot);
-        StatusBarIcon icon = getIcon(index);
-        if (icon == null || icon.visible == visibility) {
+        StatusBarIconHolder holder = getIcon(index, 0);
+        if (holder == null || holder.isVisible() == visibility) {
             return;
         }
-        icon.visible = visibility;
-        handleSet(index, icon);
+
+        holder.setVisible(visibility);
+        handleSet(index, holder);
+    }
+
+    public void removeIcon(String slot) {
+        removeAllIconsForSlot(slot);
     }
 
     @Override
-    public void removeIcon(int index) {
-        if (getIcon(index) == null) {
+    public void removeIcon(String slot, int tag) {
+        removeIcon(getSlotIndex(slot), tag);
+    }
+
+    @Override
+    public void removeAllIconsForSlot(String slotName) {
+        Slot slot = getSlot(slotName);
+        if (!slot.hasIconsInSlot()) {
             return;
         }
-        mIconLogger.onIconHidden(getSlot(index));
-        super.removeIcon(index);
-        int viewIndex = getViewIndex(index);
+
+        mIconLogger.onIconHidden(slotName);
+
+        int slotIndex = getSlotIndex(slotName);
+        List<StatusBarIconHolder> iconsToRemove = slot.getHolderList();
+        for (StatusBarIconHolder holder : iconsToRemove) {
+            int viewIndex = getViewIndex(slotIndex, holder.getTag());
+            slot.removeForTag(holder.getTag());
+            mIconGroups.forEach(l -> l.onRemoveIcon(viewIndex));
+        }
+    }
+
+    @Override
+    public void removeIcon(int index, int tag) {
+        if (getIcon(index, tag) == null) {
+            return;
+        }
+        mIconLogger.onIconHidden(getSlotName(index));
+        super.removeIcon(index, tag);
+        int viewIndex = getViewIndex(index, 0);
         mIconGroups.forEach(l -> l.onRemoveIcon(viewIndex));
     }
 
-    @Override
-    public void setIcon(int index, StatusBarIcon icon) {
-        if (icon == null) {
-            removeIcon(index);
-            return;
-        }
-        boolean isNew = getIcon(index) == null;
-        super.setIcon(index, icon);
-        if (isNew) {
-            addSystemIcon(index, icon);
-        } else {
-            handleSet(index, icon);
-        }
+    private void handleSet(int index, StatusBarIconHolder holder) {
+        int viewIndex = getViewIndex(index, holder.getTag());
+        mIconLogger.onIconVisibility(getSlotName(index), holder.isVisible());
+        mIconGroups.forEach(l -> l.onSetIconHolder(viewIndex, holder));
     }
 
-    private void handleSet(int index, StatusBarIcon icon) {
-        int viewIndex = getViewIndex(index);
-        mIconLogger.onIconVisibility(getSlot(index), icon.visible);
-        mIconGroups.forEach(l -> l.onSetIcon(viewIndex, icon));
+    /**
+     * For mobile essentially (an array of holders in one slot)
+     */
+    private void handleSet(int slotIndex, List<StatusBarIconHolder> holders) {
+        for (StatusBarIconHolder holder : holders) {
+            int viewIndex = getViewIndex(slotIndex, holder.getTag());
+            mIconLogger.onIconVisibility(getSlotName(slotIndex), holder.isVisible());
+            mIconGroups.forEach(l -> l.onSetIconHolder(viewIndex, holder));
+        }
     }
 
     @Override
@@ -208,7 +326,7 @@
                 int N = group.getChildCount();
                 pw.println("  icon views: " + N);
                 for (int i = 0; i < N; i++) {
-                    StatusBarIconView ic = (StatusBarIconView) group.getChildAt(i);
+                    StatusIconDisplayable ic = (StatusIconDisplayable) group.getChildAt(i);
                     pw.println("    [" + i + "] icon=" + ic);
                 }
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java
new file mode 100644
index 0000000..e854dd0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2017 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.phone;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.graphics.drawable.Icon;
+import android.os.UserHandle;
+import com.android.internal.statusbar.StatusBarIcon;
+import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState;
+import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState;
+
+/**
+ * Wraps {@link com.android.internal.statusbar.StatusBarIcon} so we can still have a uniform list
+ */
+public class StatusBarIconHolder {
+    public static final int TYPE_ICON = 0;
+    public static final int TYPE_WIFI = 1;
+    public static final int TYPE_MOBILE = 2;
+
+    private StatusBarIcon mIcon;
+    private WifiIconState mWifiState;
+    private MobileIconState mMobileState;
+    private int mType = TYPE_ICON;
+    private int mTag = 0;
+    private boolean mVisible = true;
+
+    public static StatusBarIconHolder fromIcon(StatusBarIcon icon) {
+        StatusBarIconHolder wrapper = new StatusBarIconHolder();
+        wrapper.mIcon = icon;
+
+        return wrapper;
+    }
+
+    public static StatusBarIconHolder fromResId(Context context, int resId,
+            CharSequence contentDescription) {
+        StatusBarIconHolder holder = new StatusBarIconHolder();
+        holder.mIcon = new StatusBarIcon(UserHandle.SYSTEM, context.getPackageName(),
+                Icon.createWithResource( context, resId), 0, 0, contentDescription);
+        return holder;
+    }
+
+    public static StatusBarIconHolder fromWifiIconState(WifiIconState state) {
+        StatusBarIconHolder holder = new StatusBarIconHolder();
+        holder.mWifiState = state;
+        holder.mType = TYPE_WIFI;
+        return holder;
+    }
+
+    public static StatusBarIconHolder fromMobileIconState(MobileIconState state) {
+        StatusBarIconHolder holder = new StatusBarIconHolder();
+        holder.mMobileState = state;
+        holder.mType = TYPE_MOBILE;
+        holder.mTag = state.subId;
+        return holder;
+    }
+
+    public int getType() {
+        return mType;
+    }
+
+    @Nullable
+    public StatusBarIcon getIcon() {
+        return mIcon;
+    }
+
+    @Nullable
+    public WifiIconState getWifiState() {
+        return mWifiState;
+    }
+
+    public void setWifiState(WifiIconState state) {
+        mWifiState = state;
+    }
+
+    @Nullable
+    public MobileIconState getMobileState() {
+        return mMobileState;
+    }
+
+    public void setMobileState(MobileIconState state) {
+        mMobileState = state;
+    }
+
+    public boolean isVisible() {
+        switch (mType) {
+            case TYPE_ICON:
+                return mIcon.visible;
+            case TYPE_WIFI:
+                return mWifiState.visible;
+            case TYPE_MOBILE:
+                return mMobileState.visible;
+
+            default: return true;
+        }
+    }
+
+    public void setVisible(boolean visible) {
+        switch (mType) {
+            case TYPE_ICON:
+                mIcon.visible = visible;
+                break;
+
+            case TYPE_WIFI:
+                mWifiState.visible = visible;
+                break;
+
+            case TYPE_MOBILE:
+                mMobileState.visible = visible;
+                break;
+        }
+    }
+
+    public int getTag() {
+        return mTag;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconList.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconList.java
index 1aa3a43..c773170 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconList.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconList.java
@@ -16,64 +16,78 @@
 
 package com.android.systemui.statusbar.phone;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.statusbar.StatusBarIcon;
-
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.List;
+
+import static com.android.systemui.statusbar.phone.StatusBarIconController.TAG_PRIMARY;
 
 public class StatusBarIconList {
-    protected ArrayList<String> mSlots = new ArrayList<>();
-    protected ArrayList<StatusBarIcon> mIcons = new ArrayList<>();
+    private ArrayList<Slot> mSlots = new ArrayList<>();
 
     public StatusBarIconList(String[] slots) {
         final int N = slots.length;
         for (int i=0; i < N; i++) {
-            mSlots.add(slots[i]);
-            mIcons.add(null);
+            mSlots.add(new Slot(slots[i], null));
         }
     }
 
     public int getSlotIndex(String slot) {
         final int N = mSlots.size();
         for (int i=0; i<N; i++) {
-            if (slot.equals(mSlots.get(i))) {
+            Slot item = mSlots.get(i);
+            if (item.getName().equals(slot)) {
                 return i;
             }
         }
         // Auto insert new items at the beginning.
-        mSlots.add(0, slot);
-        mIcons.add(0, null);
+        mSlots.add(0, new Slot(slot, null));
         return 0;
     }
 
+    protected ArrayList<Slot> getSlots() {
+        return new ArrayList<>(mSlots);
+    }
+
+    protected Slot getSlot(String name) {
+        return mSlots.get(getSlotIndex(name));
+    }
+
     public int size() {
         return mSlots.size();
     }
 
-    public void setIcon(int index, StatusBarIcon icon) {
-        mIcons.set(index, icon);
+    public void setIcon(int index, @NonNull StatusBarIconHolder holder) {
+        mSlots.get(index).addHolder(holder);
     }
 
-    public void removeIcon(int index) {
-        mIcons.set(index, null);
+    public void removeIcon(int index, int tag) {
+        mSlots.get(index).removeForTag(tag);
     }
 
-    public String getSlot(int index) {
-        return mSlots.get(index);
+    public String getSlotName(int index) {
+        return mSlots.get(index).getName();
     }
 
-    public StatusBarIcon getIcon(int index) {
-        return mIcons.get(index);
+    public StatusBarIconHolder getIcon(int index, int tag) {
+        return mSlots.get(index).getHolderForTag(tag);
     }
 
-    public int getViewIndex(int index) {
+    public int getViewIndex(int slotIndex, int tag) {
         int count = 0;
-        for (int i = 0; i < index; i++) {
-            if (mIcons.get(i) != null) {
-                count++;
+        for (int i = 0; i < slotIndex; i++) {
+            Slot item = mSlots.get(i);
+            if (item.hasIconsInSlot()) {
+                count += item.numberOfIcons();
             }
         }
-        return count;
+
+        Slot viewItem = mSlots.get(slotIndex);
+        return count + viewItem.viewIndexOffsetForTag(tag);
     }
 
     public void dump(PrintWriter pw) {
@@ -81,7 +95,163 @@
         final int N = mSlots.size();
         pw.println("  icon slots: " + N);
         for (int i=0; i<N; i++) {
-            pw.printf("    %2d: (%s) %s\n", i, mSlots.get(i), mIcons.get(i));
+            pw.printf("    %2d:%s\n", i, mSlots.get(i).toString());
+        }
+    }
+
+    public static class Slot {
+        private final String mName;
+        private StatusBarIconHolder mHolder;
+        /**
+         * Only used if multiple icons are added to the same slot.
+         *
+         * If there are mSubSlots, then these are structured like:
+         *      [ First item | (the rest) ]
+         *
+         * The tricky thing to keep in mind here is that the list [mHolder, mSubSlots] is ordered
+         * ascending, but for view logic we should go backwards through the list. I.e., the first
+         * element (mHolder) should be the highest index, because higher priority items go to the
+         * right of lower priority items
+         */
+        private ArrayList<StatusBarIconHolder> mSubSlots;
+
+        public Slot(String name, StatusBarIconHolder iconHolder) {
+            mName = name;
+            mHolder = iconHolder;
+        }
+
+        public String getName() {
+            return mName;
+        }
+
+        @Nullable
+        public StatusBarIconHolder getHolderForTag(int tag) {
+            if (tag == TAG_PRIMARY) {
+                return mHolder;
+            }
+
+            if (mSubSlots != null) {
+                for (StatusBarIconHolder holder : mSubSlots) {
+                    if (holder.getTag() == tag) {
+                        return holder;
+                    }
+                }
+            }
+
+            return null;
+        }
+
+        public void addHolder(StatusBarIconHolder holder) {
+            int tag = holder.getTag();
+            if (tag == TAG_PRIMARY) {
+                mHolder = holder;
+            } else {
+                setSubSlot(holder, tag);
+            }
+        }
+
+        public void removeForTag(int tag) {
+            if (tag == TAG_PRIMARY) {
+                mHolder = null;
+            } else {
+                int index = getIndexForTag(tag);
+                if (index != -1) {
+                    mSubSlots.remove(index);
+                }
+            }
+        }
+
+        @VisibleForTesting
+        public void clear() {
+            mHolder = null;
+            if (mSubSlots != null) {
+                mSubSlots = null;
+            }
+        }
+
+        private void setSubSlot(StatusBarIconHolder holder, int tag) {
+            if (mSubSlots == null) {
+                mSubSlots = new ArrayList<>();
+                mSubSlots.add(holder);
+                return;
+            }
+
+            if (getIndexForTag(tag) != -1) {
+                // Holder exists for tag; no-op
+                return;
+            }
+
+            // These holders get added to the end. Confused yet?
+            mSubSlots.add(holder);
+        }
+
+        private int getIndexForTag(int tag) {
+            for (int i = 0; i < mSubSlots.size(); i++) {
+                StatusBarIconHolder h = mSubSlots.get(i);
+                if (h.getTag() == tag) {
+                    return i;
+                }
+            }
+
+            return -1;
+        }
+
+        public boolean hasIconsInSlot() {
+            if (mHolder != null) return true;
+            if (mSubSlots == null) return false;
+
+            return mSubSlots.size() > 0;
+        }
+
+        public int numberOfIcons() {
+            int num = mHolder == null ? 0 : 1;
+            if (mSubSlots == null) return num;
+
+            return num + mSubSlots.size();
+        }
+
+        /**
+         * View index is backwards from regular index
+         * @param tag the tag of the holder being viewed
+         * @return (1 + mSubSlots.size() - indexOfTag)
+         */
+        public int viewIndexOffsetForTag(int tag) {
+            if (mSubSlots == null) {
+                return 0;
+            }
+
+            int subSlots = mSubSlots.size();
+            if (tag == TAG_PRIMARY) {
+                return subSlots;
+            }
+
+            return subSlots - getIndexForTag(tag) - 1;
+        }
+
+        public List<StatusBarIconHolder> getHolderList() {
+            ArrayList<StatusBarIconHolder> holders = new ArrayList<>();
+            if (mHolder != null) {
+                holders.add(mHolder);
+            }
+
+            if (mSubSlots != null) {
+                holders.addAll(mSubSlots);
+            }
+
+            return holders;
+        }
+
+        @Override
+        public String toString() {
+            return String.format("(%s) %s", mName, subSlotsString());
+        }
+
+        private String subSlotsString() {
+            if (mSubSlots == null) {
+                return "";
+            }
+
+            return "" + mSubSlots.size() + " subSlots";
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java
new file mode 100644
index 0000000..c5a3a0d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java
@@ -0,0 +1,420 @@
+/*
+ * Copyright (C) 2018 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.phone;
+
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.graphics.Rect;
+import android.os.Handler;
+import android.os.Looper;
+import android.telephony.SubscriptionInfo;
+import android.util.ArraySet;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityEvent;
+import android.widget.ImageView;
+import com.android.settingslib.graph.SignalDrawable;
+import com.android.systemui.Dependency;
+import com.android.systemui.R;
+import com.android.systemui.statusbar.phone.StatusBarIconController;
+import com.android.systemui.statusbar.policy.DarkIconDispatcher;
+import com.android.systemui.statusbar.policy.NetworkController;
+import com.android.systemui.statusbar.policy.NetworkController.IconState;
+import com.android.systemui.statusbar.policy.NetworkControllerImpl;
+import com.android.systemui.statusbar.policy.SecurityController;
+import com.android.systemui.tuner.TunerService.Tunable;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+
+public class StatusBarSignalPolicy implements NetworkControllerImpl.SignalCallback,
+        SecurityController.SecurityControllerCallback, Tunable {
+    private static final String TAG = "StatusBarSignalPolicy";
+
+    private final String mSlotAirplane;
+    private final String mSlotMobile;
+    private final String mSlotWifi;
+    private final String mSlotEthernet;
+    private final String mSlotVpn;
+
+    private final Context mContext;
+    private final StatusBarIconController mIconController;
+    private final NetworkController mNetworkController;
+    private final SecurityController mSecurityController;
+    private final Handler mHandler = Handler.getMain();
+
+    private boolean mBlockAirplane;
+    private boolean mBlockMobile;
+    private boolean mBlockWifi;
+    private boolean mBlockEthernet;
+    private boolean mActivityEnabled;
+    private boolean mForceBlockWifi;
+
+    // Track as little state as possible, and only for padding purposes
+    private boolean mIsAirplaneMode = false;
+    private boolean mWifiVisible = false;
+
+    private ArrayList<MobileIconState> mMobileStates = new ArrayList<MobileIconState>();
+    private WifiIconState mWifiIconState = new WifiIconState();
+
+    public StatusBarSignalPolicy(Context context, StatusBarIconController iconController) {
+        mContext = context;
+
+        mSlotAirplane = mContext.getString(com.android.internal.R.string.status_bar_airplane);
+        mSlotMobile   = mContext.getString(com.android.internal.R.string.status_bar_mobile);
+        mSlotWifi     = mContext.getString(com.android.internal.R.string.status_bar_wifi);
+        mSlotEthernet = mContext.getString(com.android.internal.R.string.status_bar_ethernet);
+        mSlotVpn      = mContext.getString(com.android.internal.R.string.status_bar_vpn);
+
+        mIconController = iconController;
+        mNetworkController = Dependency.get(NetworkController.class);
+        mSecurityController = Dependency.get(SecurityController.class);
+
+        mNetworkController.addCallback(this);
+        mSecurityController.addCallback(this);
+    }
+
+    public void destroy() {
+        mNetworkController.removeCallback(this);
+        mSecurityController.removeCallback(this);
+    }
+
+    private void updateVpn() {
+        boolean vpnVisible = mSecurityController.isVpnEnabled();
+        int vpnIconId = currentVpnIconId(mSecurityController.isVpnBranded());
+
+        mIconController.setIcon(mSlotVpn, vpnIconId, null);
+        mIconController.setIconVisibility(mSlotVpn, vpnVisible);
+    }
+
+    private int currentVpnIconId(boolean isBranded) {
+        return isBranded ? R.drawable.stat_sys_branded_vpn : R.drawable.stat_sys_vpn_ic;
+    }
+
+    private void updateActivityEnabled() {
+        mActivityEnabled = mContext.getResources().getBoolean(R.bool.config_showActivity);
+    }
+
+    /**
+     * From SecurityController
+     */
+    @Override
+    public void onStateChanged() {
+        mHandler.post(this::updateVpn);
+    }
+
+    @Override
+    public void onTuningChanged(String key, String newValue) {
+        if (!StatusBarIconController.ICON_BLACKLIST.equals(key)) {
+            return;
+        }
+        ArraySet<String> blockList = StatusBarIconController.getIconBlacklist(newValue);
+        boolean blockAirplane = blockList.contains(mSlotAirplane);
+        boolean blockMobile = blockList.contains(mSlotMobile);
+        boolean blockWifi = blockList.contains(mSlotWifi);
+        boolean blockEthernet = blockList.contains(mSlotEthernet);
+
+        if (blockAirplane != mBlockAirplane || blockMobile != mBlockMobile
+                || blockEthernet != mBlockEthernet || blockWifi != mBlockWifi) {
+            mBlockAirplane = blockAirplane;
+            mBlockMobile = blockMobile;
+            mBlockEthernet = blockEthernet;
+            mBlockWifi = blockWifi || mForceBlockWifi;
+            // Re-register to get new callbacks.
+            mNetworkController.removeCallback(this);
+        }
+    }
+
+    @Override
+    public void setWifiIndicators(boolean enabled, IconState statusIcon, IconState qsIcon,
+            boolean activityIn, boolean activityOut, String description, boolean isTransient,
+            String statusLabel) {
+
+        boolean visible = statusIcon.visible && !mBlockWifi;
+        boolean in = activityIn && mActivityEnabled && visible;
+        boolean out = activityOut && mActivityEnabled && visible;
+
+        mWifiIconState.visible = visible;
+        mWifiIconState.resId = statusIcon.icon;
+        mWifiIconState.activityIn = in;
+        mWifiIconState.activityOut = out;
+        mWifiIconState.slot = mSlotWifi;
+        mWifiIconState.airplaneSpacerVisible = mIsAirplaneMode;
+        mWifiIconState.contentDescription = statusIcon.contentDescription;
+
+        if (mWifiIconState.visible && mWifiIconState.resId > 0) {
+            mIconController.setSignalIcon(mSlotWifi, mWifiIconState.copy());
+            mIconController.setIconVisibility(mSlotWifi, true);
+        } else {
+            mIconController.setIconVisibility(mSlotWifi, false);
+        }
+    }
+
+    @Override
+    public void setMobileDataIndicators(IconState statusIcon, IconState qsIcon, int statusType,
+            int qsType, boolean activityIn, boolean activityOut, String typeContentDescription,
+            String description, boolean isWide, int subId, boolean roaming) {
+        MobileIconState state = getState(subId);
+        if (state == null) {
+            return;
+        }
+
+        state.visible = statusIcon.visible && !mBlockMobile;
+        state.strengthId = statusIcon.icon;
+        state.typeId = statusType;
+        state.contentDescription = statusIcon.contentDescription;
+        state.typeContentDescription = typeContentDescription;
+        state.roaming = roaming;
+        state.activityIn = activityIn && mActivityEnabled;
+        state.activityOut = activityOut && mActivityEnabled;
+
+        // Always send a copy to maintain value type semantics
+        mIconController.setMobileIcons(mSlotMobile, MobileIconState.copyStates(mMobileStates));
+    }
+
+    private MobileIconState getState(int subId) {
+        for (MobileIconState state : mMobileStates) {
+            if (state.subId == subId) {
+                return state;
+            }
+        }
+        Log.e(TAG, "Unexpected subscription " + subId);
+        return null;
+    }
+
+
+    /**
+     * It is expected that a call to setSubs will be immediately followed by setMobileDataIndicators
+     * so we don't have to update the icon manager at this point, just remove the old ones
+     * @param subs list of mobile subscriptions, displayed as mobile data indicators (max 8)
+     */
+    @Override
+    public void setSubs(List<SubscriptionInfo> subs) {
+        if (hasCorrectSubs(subs)) {
+            return;
+        }
+
+        mIconController.removeAllIconsForSlot(mSlotMobile);
+        mMobileStates.clear();
+        final int n = subs.size();
+        for (int i = 0; i < n; i++) {
+            mMobileStates.add(new MobileIconState(subs.get(i).getSubscriptionId()));
+        }
+    }
+
+    private boolean hasCorrectSubs(List<SubscriptionInfo> subs) {
+        final int N = subs.size();
+        if (N != mMobileStates.size()) {
+            return false;
+        }
+        for (int i = 0; i < N; i++) {
+            if (mMobileStates.get(i).subId != subs.get(i).getSubscriptionId()) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    @Override
+    public void setNoSims(boolean show, boolean simDetected) {
+        // Noop yay!
+    }
+
+
+    @Override
+    public void setEthernetIndicators(IconState state) {
+        boolean visible = state.visible && !mBlockEthernet;
+        int resId = state.icon;
+        String description = state.contentDescription;
+
+        if (resId > 0) {
+            mIconController.setIcon(mSlotEthernet, resId, description);
+            mIconController.setIconVisibility(mSlotEthernet, true);
+        } else {
+            mIconController.setIconVisibility(mSlotEthernet, false);
+        }
+    }
+
+    @Override
+    public void setIsAirplaneMode(IconState icon) {
+        mIsAirplaneMode = icon.visible && !mBlockAirplane;
+        int resId = icon.icon;
+        String description = icon.contentDescription;
+
+        if (mIsAirplaneMode && resId > 0) {
+            mIconController.setIcon(mSlotAirplane, resId, description);
+            mIconController.setIconVisibility(mSlotAirplane, true);
+        } else {
+            mIconController.setIconVisibility(mSlotAirplane, false);
+        }
+    }
+
+    @Override
+    public void setMobileDataEnabled(boolean enabled) {
+        // Don't care.
+    }
+
+    private static abstract class SignalIconState {
+        public boolean visible;
+        public boolean activityOut;
+        public boolean activityIn;
+        public String slot;
+        public String contentDescription;
+
+        @Override
+        public boolean equals(Object o) {
+            // Skipping reference equality bc this should be more of a value type
+            if (o == null || getClass() != o.getClass()) {
+                return false;
+            }
+            SignalIconState that = (SignalIconState) o;
+            return visible == that.visible &&
+                    activityOut == that.activityOut &&
+                    activityIn == that.activityIn &&
+                    Objects.equals(contentDescription, that.contentDescription) &&
+                    Objects.equals(slot, that.slot);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(visible, activityOut, slot);
+        }
+
+        protected void copyTo(SignalIconState other) {
+            other.visible = visible;
+            other.activityIn = activityIn;
+            other.activityOut = activityOut;
+            other.slot = slot;
+            other.contentDescription = contentDescription;
+        }
+    }
+
+    public static class WifiIconState extends SignalIconState{
+        public int resId;
+        public boolean airplaneSpacerVisible;
+        public boolean signalSpacerVisible;
+
+        @Override
+        public boolean equals(Object o) {
+            // Skipping reference equality bc this should be more of a value type
+            if (o == null || getClass() != o.getClass()) {
+                return false;
+            }
+            if (!super.equals(o)) {
+                return false;
+            }
+            WifiIconState that = (WifiIconState) o;
+            return resId == that.resId &&
+                    airplaneSpacerVisible == that.airplaneSpacerVisible &&
+                    signalSpacerVisible == that.signalSpacerVisible;
+        }
+
+        public void copyTo(WifiIconState other) {
+            super.copyTo(other);
+            other.resId = resId;
+            other.airplaneSpacerVisible = airplaneSpacerVisible;
+            other.signalSpacerVisible = signalSpacerVisible;
+        }
+
+        public WifiIconState copy() {
+            WifiIconState newState = new WifiIconState();
+            copyTo(newState);
+            return newState;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(super.hashCode(),
+                    resId, airplaneSpacerVisible, signalSpacerVisible);
+        }
+
+        @Override public String toString() {
+            return "WifiIconState(resId=" + resId + ", visible=" + visible + ")";
+        }
+    }
+
+    /**
+     * A little different. This one delegates to SignalDrawable instead of a specific resId
+     */
+    public static class MobileIconState extends SignalIconState {
+        public int subId;
+        public int strengthId;
+        public int typeId;
+        public boolean roaming;
+        public boolean needsLeadingPadding;
+        public String typeContentDescription;
+
+        private MobileIconState(int subId) {
+            super();
+            this.subId = subId;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (o == null || getClass() != o.getClass()) {
+                return false;
+            }
+            if (!super.equals(o)) {
+                return false;
+            }
+            MobileIconState that = (MobileIconState) o;
+            return subId == that.subId &&
+                    strengthId == that.strengthId &&
+                    typeId == that.typeId &&
+                    roaming == that.roaming &&
+                    needsLeadingPadding == that.needsLeadingPadding &&
+                    Objects.equals(typeContentDescription, that.typeContentDescription);
+        }
+
+        @Override
+        public int hashCode() {
+
+            return Objects
+                    .hash(super.hashCode(), subId, strengthId, typeId, roaming, needsLeadingPadding,
+                            typeContentDescription);
+        }
+
+        public void copyTo(MobileIconState other) {
+            super.copyTo(other);
+            other.subId = subId;
+            other.strengthId = strengthId;
+            other.typeId = typeId;
+            other.roaming = roaming;
+            other.needsLeadingPadding = needsLeadingPadding;
+            other.typeContentDescription = typeContentDescription;
+        }
+
+        private static List<MobileIconState> copyStates(List<MobileIconState> inStates) {
+            ArrayList<MobileIconState> outStates = new ArrayList<>();
+            for (MobileIconState state : inStates) {
+                MobileIconState copy = new MobileIconState(state.subId);
+                state.copyTo(copy);
+                outStates.add(copy);
+            }
+
+            return outStates;
+        }
+
+        @Override public String toString() {
+            return "MobileIconState(subId=" + subId + ", strengthId=" + strengthId + ", roaming="
+                    + roaming + ", visible=" + visible + ")";
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusIconContainer.java
index dab28d6..255e10e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusIconContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusIconContainer.java
@@ -18,13 +18,13 @@
 
 import android.annotation.Nullable;
 import android.content.Context;
-import android.util.ArrayMap;
 import android.util.AttributeSet;
+import android.util.Log;
 
 import android.view.View;
 import com.android.keyguard.AlphaOptimizedLinearLayout;
 import com.android.systemui.R;
-import com.android.systemui.statusbar.StatusBarIconView;
+import com.android.systemui.statusbar.StatusIconDisplayable;
 import com.android.systemui.statusbar.stack.ViewState;
 
 /**
@@ -97,7 +97,7 @@
      * Layout is happening from end -> start
      */
     private void calculateIconTranslations() {
-        float width = getWidth();
+        float width = getWidth() - getPaddingEnd();
         float translationX = width;
         float contentStart = getPaddingStart();
         int childCount = getChildCount();
@@ -109,18 +109,22 @@
         //TODO: Dots
         for (int i = childCount - 1; i >= 0; i--) {
             View child = getChildAt(i);
-            if (!(child instanceof StatusBarIconView)) {
+            if (!(child instanceof StatusIconDisplayable)) {
+                if (DEBUG) Log.d(TAG, "skipping child (wrong type)");
                 continue;
             }
 
+            StatusIconDisplayable iconView = (StatusIconDisplayable) child;
+
             ViewState childState = getViewStateFromChild(child);
             if (childState == null ) {
+                if (DEBUG) Log.d(TAG, "skipping child (" + iconView.getSlot() + ") no ViewState");
                 continue;
             }
 
-            // Rely on StatusBarIcon for truth about visibility
-            if (!((StatusBarIconView) child).getStatusBarIcon().visible) {
+            if (!iconView.isIconVisible() || iconView.isIconBlocked()) {
                 childState.hidden = true;
+                if (DEBUG) Log.d(TAG, "skipping child (" + iconView.getSlot() + ") not visible");
                 continue;
             }
 
@@ -175,8 +179,8 @@
 
             vs.initFrom(child);
             vs.alpha = 1.0f;
-            if (child instanceof StatusBarIconView) {
-                vs.hidden = !((StatusBarIconView)child).getStatusBarIcon().visible;
+            if (child instanceof StatusIconDisplayable) {
+                vs.hidden = !((StatusIconDisplayable)child).isIconVisible();
             } else {
                 vs.hidden = false;
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DarkIconDispatcher.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DarkIconDispatcher.java
index 58944c6..945ed76 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DarkIconDispatcher.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DarkIconDispatcher.java
@@ -35,7 +35,7 @@
 
     // Used to reapply darkness on an object, must have previously been added through
     // addDarkReceiver.
-    void applyDark(ImageView object);
+    void applyDark(DarkReceiver object);
 
     int DEFAULT_ICON_TINT = Color.WHITE;
     Rect sTmpRect = new Rect();
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 4533aa5..4c100cd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
@@ -54,6 +54,7 @@
 import com.android.systemui.settings.CurrentUserTracker;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
 
+import com.android.systemui.statusbar.policy.MobileSignalController.MobileState;
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -848,6 +849,11 @@
                         subs.add(addSignalController(i, i));
                     }
                     mCallbackHandler.setSubs(subs);
+                    for (int i = 0; i < mMobileSignalControllers.size(); i++) {
+                        int key = mMobileSignalControllers.keyAt(i);
+                        MobileSignalController controller = mMobileSignalControllers.get(key);
+                        controller.notifyListeners();
+                    }
                 }
             }
             String nosim = args.getString("nosim");
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconListTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconListTest.java
index 2792d8c..07ac11b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconListTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconListTest.java
@@ -1,17 +1,23 @@
 package com.android.systemui.statusbar;
 
+import static com.android.systemui.statusbar.phone.StatusBarIconController.TAG_PRIMARY;
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertNull;
+import static junit.framework.Assert.assertTrue;
 
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
 
 import android.support.test.runner.AndroidJUnit4;
 import android.test.suitebuilder.annotation.SmallTest;
 
-import com.android.internal.statusbar.StatusBarIcon;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.statusbar.phone.StatusBarIconHolder;
 import com.android.systemui.statusbar.phone.StatusBarIconList;
+import com.android.systemui.statusbar.phone.StatusBarIconList.Slot;
 
+import java.util.ArrayList;
+import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -41,45 +47,125 @@
     @Test
     public void testAddSlotSlidesIcons() {
         StatusBarIconList statusBarIconList = new StatusBarIconList(STATUS_BAR_SLOTS);
-        StatusBarIcon sbIcon = mock(StatusBarIcon.class);
-        statusBarIconList.setIcon(0, sbIcon);
+        StatusBarIconHolder sbHolder = mock(StatusBarIconHolder.class);
+        statusBarIconList.setIcon(0, sbHolder);
         statusBarIconList.getSlotIndex("zzz"); // new content added in front
-        assertNull(statusBarIconList.getIcon(0));
-        assertEquals(sbIcon, statusBarIconList.getIcon(1));
+        assertNull(statusBarIconList.getIcon(0, TAG_PRIMARY));
+        assertEquals(sbHolder, statusBarIconList.getIcon(1, TAG_PRIMARY));
     }
 
     @Test
     public void testGetAndSetIcon() {
         StatusBarIconList statusBarIconList = new StatusBarIconList(STATUS_BAR_SLOTS);
-        StatusBarIcon sbIconA = mock(StatusBarIcon.class);
-        StatusBarIcon sbIconB = mock(StatusBarIcon.class);
-        statusBarIconList.setIcon(0, sbIconA);
-        statusBarIconList.setIcon(1, sbIconB);
-        assertEquals(sbIconA, statusBarIconList.getIcon(0));
-        assertEquals(sbIconB, statusBarIconList.getIcon(1));
-        assertNull(statusBarIconList.getIcon(2)); // icon not set
+        StatusBarIconHolder sbHolderA = mock(StatusBarIconHolder.class);
+        StatusBarIconHolder sbHolderB = mock(StatusBarIconHolder.class);
+        statusBarIconList.setIcon(0, sbHolderA);
+        statusBarIconList.setIcon(1, sbHolderB);
+        assertEquals(sbHolderA, statusBarIconList.getIcon(0, TAG_PRIMARY));
+        assertEquals(sbHolderB, statusBarIconList.getIcon(1, TAG_PRIMARY));
+        assertNull(statusBarIconList.getIcon(2, TAG_PRIMARY)); // icon not set
     }
 
     @Test
     public void testRemoveIcon() {
         StatusBarIconList statusBarIconList = new StatusBarIconList(STATUS_BAR_SLOTS);
-        StatusBarIcon sbIconA = mock(StatusBarIcon.class);
-        StatusBarIcon sbIconB = mock(StatusBarIcon.class);
-        statusBarIconList.setIcon(0, sbIconA);
-        statusBarIconList.setIcon(1, sbIconB);
-        statusBarIconList.removeIcon(0);
-        assertNull(statusBarIconList.getIcon(0)); // icon not set
+        StatusBarIconHolder sbHolderA = mock(StatusBarIconHolder.class);
+        StatusBarIconHolder sbHolderB = mock(StatusBarIconHolder.class);
+        statusBarIconList.setIcon(0, sbHolderA);
+        statusBarIconList.setIcon(1, sbHolderB);
+        statusBarIconList.removeIcon(0, TAG_PRIMARY);
+        assertNull(statusBarIconList.getIcon(0, TAG_PRIMARY)); // icon not set
     }
 
     @Test
-    public void testGetViewIndex() {
+    public void testGetViewIndex_NoMultiples() {
         StatusBarIconList statusBarIconList = new StatusBarIconList(STATUS_BAR_SLOTS);
-        StatusBarIcon sbIcon = mock(StatusBarIcon.class);
-        statusBarIconList.setIcon(2, sbIcon);
-        assertEquals(0, statusBarIconList.getViewIndex(2)); // Icon for item 2 is 0th child view.
-        statusBarIconList.setIcon(0, sbIcon);
-        assertEquals(0, statusBarIconList.getViewIndex(0)); // Icon for item 0 is 0th child view,
-        assertEquals(1, statusBarIconList.getViewIndex(2)); // and item 2 is now 1st child view.
+        StatusBarIconHolder sbHolder = mock(StatusBarIconHolder.class);
+        statusBarIconList.setIcon(2, sbHolder);
+        // Icon for item 2 is 0th child view.
+        assertEquals(0, statusBarIconList.getViewIndex(2, TAG_PRIMARY));
+        statusBarIconList.setIcon(0, sbHolder);
+        // Icon for item 0 is 0th child view,
+        assertEquals(0, statusBarIconList.getViewIndex(0, TAG_PRIMARY));
+        // and item 2 is now 1st child view.
+        assertEquals(1, statusBarIconList.getViewIndex(2, TAG_PRIMARY));
     }
 
+    @Test
+    public void testGetViewIndex_MultipleIconsPerSlot() {
+        StatusBarIconList statusBarIconList = new StatusBarIconList(STATUS_BAR_SLOTS);
+        StatusBarIconHolder sbHolder = mock(StatusBarIconHolder.class);
+
+        statusBarIconList.setIcon(2, sbHolder); // item 2, one icon 0th child
+
+        // All of these can be added to the same slot
+        // no tag bc it defaults to 0
+        StatusBarIconHolder sbHolder2 = mock(StatusBarIconHolder.class);
+        StatusBarIconHolder sbHolder3 = mock(StatusBarIconHolder.class);
+        int sb3Tag = 1;
+        when(sbHolder3.getTag()).thenReturn(sb3Tag);
+        StatusBarIconHolder sbHolder4 = mock(StatusBarIconHolder.class);
+        int sb4Tag = 2;
+        when(sbHolder4.getTag()).thenReturn(sb4Tag);
+
+        // Put a holder at slot 1, verify that it is first
+        statusBarIconList.setIcon(1, sbHolder2);
+        assertEquals(0, statusBarIconList.getViewIndex(1, TAG_PRIMARY));
+
+        // Put another holder at slot 1, verify it's index 0 and the rest come after
+        statusBarIconList.setIcon(1, sbHolder3);
+        assertEquals(0, statusBarIconList.getViewIndex(1, sb3Tag));
+        assertEquals(1, statusBarIconList.getViewIndex(1, TAG_PRIMARY));
+        // First icon should be at the end
+        assertEquals(2, statusBarIconList.getViewIndex(2, TAG_PRIMARY));
+
+        // Put another one in there just for good measure
+        statusBarIconList.setIcon(1, sbHolder4);
+        assertEquals(0, statusBarIconList.getViewIndex(1, sb4Tag));
+        assertEquals(1, statusBarIconList.getViewIndex(1, sb3Tag));
+        assertEquals(2, statusBarIconList.getViewIndex(1, TAG_PRIMARY));
+        assertEquals(3, statusBarIconList.getViewIndex(2, TAG_PRIMARY));
+    }
+
+    /**
+     * StatusBarIconList.Slot tests
+     */
+
+    @Test
+    public void testSlot_OrderIsPreserved() {
+        Slot testSlot = new Slot("test_name", null);
+
+        // no tag bc it defaults to 0
+        StatusBarIconHolder sbHolder1 = mock(StatusBarIconHolder.class);
+        StatusBarIconHolder sbHolder2 = mock(StatusBarIconHolder.class);
+        int sb2Tag = 1;
+        when(sbHolder2.getTag()).thenReturn(sb2Tag);
+        StatusBarIconHolder sbHolder3 = mock(StatusBarIconHolder.class);
+        int sb3Tag = 2;
+        when(sbHolder3.getTag()).thenReturn(sb3Tag);
+
+        ArrayList<StatusBarIconHolder> expected = new ArrayList<>();
+        expected.add(sbHolder1);
+        expected.add(sbHolder2);
+        expected.add(sbHolder3);
+
+
+        // Add 3 icons in the same slot, and verify that the list we get is equal to what we gave
+        for (StatusBarIconHolder holder : expected) {
+            testSlot.addHolder(holder);
+        }
+        assertTrue(listsEqual(expected, testSlot.getHolderList()));
+    }
+
+    private boolean listsEqual(List<StatusBarIconHolder> list1, List<StatusBarIconHolder> list2) {
+        if (list1.size() != list2.size())  return false;
+
+        for (int i = 0; i < list1.size(); i++) {
+            if (!list1.get(i).equals(list2.get(i))) {
+                return false;
+            }
+        }
+
+        return true;
+    }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java
index 5db7792..72b0156 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java
@@ -14,10 +14,17 @@
 
 package com.android.systemui.statusbar.phone;
 
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertTrue;
+
+import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_ICON;
+import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_MOBILE;
+import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_WIFI;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 
+import android.graphics.Rect;
 import android.support.test.filters.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper.RunWithLooper;
@@ -25,9 +32,14 @@
 import android.widget.LinearLayout;
 
 import com.android.internal.statusbar.StatusBarIcon;
+import com.android.systemui.statusbar.StatusIconDisplayable;
 import com.android.systemui.statusbar.StatusBarIconView;
+import com.android.systemui.statusbar.StatusBarMobileView;
+import com.android.systemui.statusbar.StatusBarWifiView;
 import com.android.systemui.statusbar.phone.StatusBarIconController.DarkIconManager;
 import com.android.systemui.statusbar.phone.StatusBarIconController.IconManager;
+import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState;
+import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState;
 import com.android.systemui.statusbar.policy.DarkIconDispatcher;
 import com.android.systemui.utils.leaks.LeakCheckedTest;
 
@@ -50,50 +62,119 @@
     public void testSetCalledOnAdd_IconManager() {
         LinearLayout layout = new LinearLayout(mContext);
         TestIconManager manager = new TestIconManager(layout);
-        StatusBarIcon icon = mock(StatusBarIcon.class);
-
-        manager.onIconAdded(0, "test_slot", false, icon);
-        verify(manager.mMock).set(eq(icon));
+        testCallOnAdd_forManager(manager);
     }
 
     @Test
     public void testSetCalledOnAdd_DarkIconManager() {
         LinearLayout layout = new LinearLayout(mContext);
         TestDarkIconManager manager = new TestDarkIconManager(layout);
-        StatusBarIcon icon = mock(StatusBarIcon.class);
-
-        manager.onIconAdded(0, "test_slot", false, icon);
-        verify(manager.mMock).set(eq(icon));
+        testCallOnAdd_forManager(manager);
     }
 
-    private static class TestDarkIconManager extends DarkIconManager {
 
-        private final StatusBarIconView mMock;
+    private <T extends IconManager & TestableIconManager> void testCallOnAdd_forManager(T manager) {
+        StatusBarIconHolder holder = holderForType(TYPE_ICON);
+        manager.onIconAdded(0, "test_slot", false, holder);
+        assertTrue("Expected StatusBarIconView",
+                (manager.getViewAt(0) instanceof StatusBarIconView));
+
+        holder = holderForType(TYPE_WIFI);
+        manager.onIconAdded(1, "test_wifi", false, holder);
+        assertTrue(manager.getViewAt(1) instanceof StatusBarWifiView);
+
+        holder = holderForType(TYPE_MOBILE);
+        manager.onIconAdded(2, "test_mobile", false, holder);
+        assertTrue(manager.getViewAt(2) instanceof StatusBarMobileView);
+    }
+
+    private StatusBarIconHolder holderForType(int type) {
+        switch (type) {
+            case TYPE_MOBILE:
+                return StatusBarIconHolder.fromMobileIconState(mock(MobileIconState.class));
+
+            case TYPE_WIFI:
+                return StatusBarIconHolder.fromWifiIconState(mock(WifiIconState.class));
+
+            case TYPE_ICON:
+            default:
+                return StatusBarIconHolder.fromIcon(mock(StatusBarIcon.class));
+        }
+    }
+
+    private static class TestDarkIconManager extends DarkIconManager
+            implements TestableIconManager {
 
         public TestDarkIconManager(LinearLayout group) {
             super(group);
-            mMock = mock(StatusBarIconView.class);
         }
 
         @Override
-        protected StatusBarIconView onCreateStatusBarIconView(String slot, boolean blocked) {
-            return mMock;
+        public StatusIconDisplayable getViewAt(int index) {
+            return (StatusIconDisplayable) mGroup.getChildAt(index);
+        }
+
+        @Override
+        protected StatusBarIconView addIcon(int index, String slot, boolean blocked,
+                StatusBarIcon icon) {
+            StatusBarIconView mock = mock(StatusBarIconView.class);
+            mGroup.addView(mock, index);
+
+            return mock;
+        }
+
+        @Override
+        protected StatusBarWifiView addSignalIcon(int index, String slot, WifiIconState state) {
+            StatusBarWifiView mock = mock(StatusBarWifiView.class);
+            mGroup.addView(mock, index);
+            return mock;
+        }
+
+        @Override
+        protected StatusBarMobileView addMobileIcon(int index, String slot, MobileIconState state) {
+            StatusBarMobileView mock = mock(StatusBarMobileView.class);
+            mGroup.addView(mock, index);
+
+            return mock;
         }
     }
 
-    private static class TestIconManager extends IconManager {
-
-        private final StatusBarIconView mMock;
-
+    private static class TestIconManager extends IconManager implements TestableIconManager {
         public TestIconManager(ViewGroup group) {
             super(group);
-            mMock = mock(StatusBarIconView.class);
         }
 
         @Override
-        protected StatusBarIconView onCreateStatusBarIconView(String slot, boolean blocked) {
-            return mMock;
+        public StatusIconDisplayable getViewAt(int index) {
+            return (StatusIconDisplayable) mGroup.getChildAt(index);
+        }
+
+        @Override
+        protected StatusBarIconView addIcon(int index, String slot, boolean blocked,
+                StatusBarIcon icon) {
+            StatusBarIconView mock = mock(StatusBarIconView.class);
+            mGroup.addView(mock, index);
+
+            return mock;
+        }
+
+        @Override
+        protected StatusBarWifiView addSignalIcon(int index, String slot, WifiIconState state) {
+            StatusBarWifiView mock = mock(StatusBarWifiView.class);
+            mGroup.addView(mock, index);
+            return mock;
+        }
+
+        @Override
+        protected StatusBarMobileView addMobileIcon(int index, String slot, MobileIconState state) {
+            StatusBarMobileView mock = mock(StatusBarMobileView.class);
+            mGroup.addView(mock, index);
+
+            return mock;
         }
     }
 
+    private interface TestableIconManager {
+        StatusIconDisplayable getViewAt(int index);
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java
index 6b501af..8e34685 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java
@@ -19,6 +19,9 @@
 import com.android.internal.statusbar.StatusBarIcon;
 import com.android.systemui.statusbar.phone.StatusBarIconController;
 import com.android.systemui.statusbar.phone.StatusBarIconController.IconManager;
+import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState;
+import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState;
+import java.util.List;
 
 public class FakeStatusBarIconController extends BaseLeakChecker<IconManager>
         implements StatusBarIconController {
@@ -53,12 +56,23 @@
     }
 
     @Override
-    public void setIconVisibility(String slotTty, boolean b) {
-
+    public void setSignalIcon(String slot, WifiIconState state) {
     }
 
     @Override
-    public void removeIcon(String slot) {
-
+    public void setMobileIcons(String slot, List<MobileIconState> states) {
     }
+
+    @Override
+    public void setIconVisibility(String slotTty, boolean b) {
+    }
+
+    @Override
+    public void removeIcon(String slot, int tag) {
+    }
+
+    @Override
+    public void removeAllIconsForSlot(String slot) {
+    }
+
 }