Limit status icons and show etc dots

When <= 5 status icons exist, try to show them all (space permitting).
If > 5 exist, show 4 and etc dots.

Test: visual
Change-Id: I4c09344bee75b3cab6256dd69f11337b9b667b0f
Fixes: 73778753
Fixes: 77660387
Bug: 77822905
diff --git a/packages/SystemUI/res/layout/status_bar_mobile_signal_group.xml b/packages/SystemUI/res/layout/status_bar_mobile_signal_group.xml
index d607c8c..c8a5544 100644
--- a/packages/SystemUI/res/layout/status_bar_mobile_signal_group.xml
+++ b/packages/SystemUI/res/layout/status_bar_mobile_signal_group.xml
@@ -23,6 +23,7 @@
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
     android:layout_gravity="center_vertical"
+    android:paddingStart="2dp"
     android:orientation="horizontal">
     <FrameLayout
         android:id="@+id/inout_container"
diff --git a/packages/SystemUI/res/layout/status_bar_wifi_group.xml b/packages/SystemUI/res/layout/status_bar_wifi_group.xml
index 08cef55..482f780 100644
--- a/packages/SystemUI/res/layout/status_bar_wifi_group.xml
+++ b/packages/SystemUI/res/layout/status_bar_wifi_group.xml
@@ -22,61 +22,67 @@
     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" >
+    android:gravity="center_vertical" >
 
-    <FrameLayout
-            android:id="@+id/inout_container"
-            android:layout_height="17dp"
+    <com.android.keyguard.AlphaOptimizedLinearLayout
+        android:id="@+id/wifi_group"
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent"
+        android:paddingStart="2dp"
+        android:gravity="center_vertical"
+    >
+        <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" >
-        <ImageView
-            android:id="@+id/wifi_in"
-            android:layout_height="wrap_content"
+            <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: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: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"
         />
-    </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.keyguard.AlphaOptimizedLinearLayout>
 </com.android.systemui.statusbar.StatusBarWifiView>
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
index 70bfad1..fb94d8a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
@@ -56,6 +56,7 @@
 import com.android.systemui.statusbar.phone.StatusBarIconController;
 import com.android.systemui.statusbar.phone.StatusBarIconController.TintedIconManager;
 import com.android.systemui.statusbar.policy.Clock;
+import com.android.systemui.statusbar.phone.StatusIconContainer;
 import com.android.systemui.statusbar.policy.DarkIconDispatcher;
 import com.android.systemui.statusbar.policy.DarkIconDispatcher.DarkReceiver;
 import com.android.systemui.statusbar.policy.DateView;
@@ -147,7 +148,9 @@
         mDate.setOnClickListener(this);
         mSystemIconsView = findViewById(R.id.quick_status_bar_system_icons);
         mQuickQsStatusIcons = findViewById(R.id.quick_qs_status_icons);
-        mIconManager = new TintedIconManager(findViewById(R.id.statusIcons));
+        StatusIconContainer iconContainer = findViewById(R.id.statusIcons);
+        iconContainer.setShouldRestrictIcons(false);
+        mIconManager = new TintedIconManager(iconContainer);
 
         // Views corresponding to the header info section (e.g. tooltip and next alarm).
         mHeaderTextContainerView = findViewById(R.id.header_text_container);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
index bd6bd12..4fc18ad 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
@@ -163,7 +163,6 @@
         mNumberPain.setColor(context.getColor(R.drawable.notification_number_text_color));
         mNumberPain.setAntiAlias(true);
         setNotification(sbn);
-        maybeUpdateIconScaleDimens();
         setScaleType(ScaleType.CENTER);
         mDensity = context.getResources().getDisplayMetrics().densityDpi;
         if (mNotification != null) {
@@ -171,26 +170,18 @@
                     com.android.internal.R.color.notification_default_color_light));
         }
         reloadDimens();
+        maybeUpdateIconScaleDimens();
     }
 
+    /** Should always be preceded by {@link #reloadDimens()} */
     private void maybeUpdateIconScaleDimens() {
         // We do not resize and scale system icons (on the right), only notification icons (on the
         // left).
         if (mNotification != null || mAlwaysScaleIcon) {
-            updateIconScaleDimens();
+            updateIconScale();
         }
     }
 
-    private void updateIconScaleDimens() {
-        Resources res = mContext.getResources();
-        mStatusBarIconSize = res.getDimensionPixelSize(R.dimen.status_bar_icon_size);
-        mStatusBarIconDrawingSizeDark =
-                res.getDimensionPixelSize(R.dimen.status_bar_icon_drawing_size_dark);
-        mStatusBarIconDrawingSize =
-                res.getDimensionPixelSize(R.dimen.status_bar_icon_drawing_size);
-        updateIconScale();
-    }
-
     private void updateIconScale() {
         final float imageBounds = NotificationUtils.interpolate(
                 mStatusBarIconDrawingSize,
@@ -214,15 +205,21 @@
         int density = newConfig.densityDpi;
         if (density != mDensity) {
             mDensity = density;
+            reloadDimens();
             maybeUpdateIconScaleDimens();
             updateDrawable();
-            reloadDimens();
         }
     }
 
     private void reloadDimens() {
         boolean applyRadius = mDotRadius == mStaticDotRadius;
-        mStaticDotRadius = getResources().getDimensionPixelSize(R.dimen.overflow_dot_radius);
+        Resources res = getResources();
+        mStaticDotRadius = res.getDimensionPixelSize(R.dimen.overflow_dot_radius);
+        mStatusBarIconSize = res.getDimensionPixelSize(R.dimen.status_bar_icon_size);
+        mStatusBarIconDrawingSizeDark =
+                res.getDimensionPixelSize(R.dimen.status_bar_icon_drawing_size_dark);
+        mStatusBarIconDrawingSize =
+                res.getDimensionPixelSize(R.dimen.status_bar_icon_drawing_size);
         if (applyRadius) {
             mDotRadius = mStaticDotRadius;
         }
@@ -240,7 +237,8 @@
         mDozer = new NotificationIconDozeHelper(context);
         mBlocked = false;
         mAlwaysScaleIcon = true;
-        updateIconScaleDimens();
+        reloadDimens();
+        updateIconScale();
         mDensity = context.getResources().getDisplayMetrics().densityDpi;
     }
 
@@ -425,7 +423,7 @@
                 radius = NotificationUtils.interpolate(mDotRadius, getWidth() / 4, fadeOutAmount);
             }
             mDotPaint.setAlpha((int) (alpha * 255));
-            canvas.drawCircle(getWidth() / 2, getHeight() / 2, radius, mDotPaint);
+            canvas.drawCircle(mStatusBarIconSize / 2, getHeight() / 2, radius, mDotPaint);
         }
     }
 
@@ -657,6 +655,7 @@
         mContrastedDrawableColor = contrastedColor;
     }
 
+    @Override
     public void setVisibleState(int state) {
         setVisibleState(state, true /* animate */, null /* endRunnable */);
     }
@@ -872,7 +871,10 @@
 
     @Override
     public void onDarkChanged(Rect area, float darkIntensity, int tint) {
-        setImageTintList(ColorStateList.valueOf(getTint(area, this, tint)));
+        int areaTint = getTint(area, this, tint);
+        ColorStateList color = ColorStateList.valueOf(areaTint);
+        setImageTintList(color);
+        setDecorColor(areaTint);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java
index 51b4239..f4e45812 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java
@@ -197,10 +197,20 @@
     }
 
     @Override
+    public void setDecorColor(int color) {
+        //TODO: May also not be needed
+    }
+
+    @Override
     public boolean isIconVisible() {
         return mState.visible;
     }
 
+    @Override
+    public void setVisibleState(int state) {
+        //TODO: May not be needed. Mobile is always expected to be visible (not a dot)
+    }
+
     @VisibleForTesting
     public MobileIconState getState() {
         return mState;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarWifiView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarWifiView.java
index 62cd16f..0e2714d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarWifiView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarWifiView.java
@@ -16,6 +16,9 @@
 
 package com.android.systemui.statusbar;
 
+import static com.android.systemui.statusbar.StatusBarIconView.STATE_DOT;
+import static com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN;
+import static com.android.systemui.statusbar.StatusBarIconView.STATE_ICON;
 import static com.android.systemui.statusbar.policy.DarkIconDispatcher.getTint;
 import static com.android.systemui.statusbar.policy.DarkIconDispatcher.isInArea;
 
@@ -24,11 +27,17 @@
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
+import android.util.Log;
 import android.view.ContextThemeWrapper;
+import android.view.Gravity;
 import android.view.LayoutInflater;
 import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewGroup.LayoutParams;
+import android.widget.FrameLayout;
 import android.widget.ImageView;
 
+import android.widget.LinearLayout;
 import com.android.keyguard.AlphaOptimizedLinearLayout;
 import com.android.settingslib.Utils;
 import com.android.systemui.R;
@@ -38,10 +47,14 @@
 /**
  * Start small: StatusBarWifiView will be able to layout from a WifiIconState
  */
-public class StatusBarWifiView extends AlphaOptimizedLinearLayout implements DarkReceiver,
+public class StatusBarWifiView extends FrameLayout implements DarkReceiver,
         StatusIconDisplayable {
     private static final String TAG = "StatusBarWifiView";
 
+    /// Used to show etc dots
+    private StatusBarIconView mDotView;
+    /// Contains the main icon layout
+    private LinearLayout mWifiGroup;
     private ImageView mWifiIcon;
     private ImageView mIn;
     private ImageView mOut;
@@ -55,9 +68,12 @@
     private ContextThemeWrapper mDarkContext;
     private ContextThemeWrapper mLightContext;
 
-    public static StatusBarWifiView fromContext(Context context) {
+    public static StatusBarWifiView fromContext(Context context, String slot) {
         LayoutInflater inflater = LayoutInflater.from(context);
-        return (StatusBarWifiView) inflater.inflate(R.layout.status_bar_wifi_group, null);
+        StatusBarWifiView v = (StatusBarWifiView) inflater.inflate(R.layout.status_bar_wifi_group, null);
+        v.setSlot(slot);
+        v.init();
+        return v;
     }
 
     public StatusBarWifiView(Context context) {
@@ -77,12 +93,6 @@
         super(context, attrs, defStyleAttr, defStyleRes);
     }
 
-    @Override
-    protected void onFinishInflate() {
-        super.onFinishInflate();
-        init();
-    }
-
     public void setSlot(String slot) {
         mSlot = slot;
     }
@@ -93,6 +103,12 @@
         mWifiIcon.setImageTintList(list);
         mIn.setImageTintList(list);
         mOut.setImageTintList(list);
+        mDotView.setDecorColor(color);
+    }
+
+    @Override
+    public void setDecorColor(int color) {
+        mDotView.setDecorColor(color);
     }
 
     @Override
@@ -105,18 +121,55 @@
         return mState != null && mState.visible;
     }
 
+    @Override
+    public void setVisibleState(int state) {
+        switch (state) {
+            case STATE_ICON:
+                mWifiGroup.setVisibility(View.VISIBLE);
+                mDotView.setVisibility(View.GONE);
+                break;
+            case STATE_DOT:
+                mWifiGroup.setVisibility(View.GONE);
+                mDotView.setVisibility(View.VISIBLE);
+                break;
+            case STATE_HIDDEN:
+            default:
+                setVisibility(View.GONE);
+                break;
+        }
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+        int width = MeasureSpec.getSize(widthMeasureSpec);
+    }
+
     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);
 
+        mWifiGroup = findViewById(R.id.wifi_group);
         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);
+
+        initDotView();
+    }
+
+    private void initDotView() {
+        mDotView = new StatusBarIconView(mContext, mSlot, null);
+        mDotView.setVisibleState(STATE_DOT);
+
+        int width = mContext.getResources().getDimensionPixelSize(R.dimen.status_bar_icon_size);
+        LayoutParams lp = new LayoutParams(width, width);
+        lp.gravity = Gravity.CENTER_VERTICAL | Gravity.START;
+        addView(mDotView, lp);
     }
 
     public void applyWifiState(WifiIconState state) {
@@ -186,6 +239,8 @@
         }
         mIn.setImageTintList(ColorStateList.valueOf(getTint(area, this, tint)));
         mOut.setImageTintList(ColorStateList.valueOf(getTint(area, this, tint)));
+        mDotView.setDecorColor(tint);
+        mDotView.setIconColor(tint, false);
     }
 
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusIconDisplayable.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusIconDisplayable.java
index ccab0d6..6383816 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusIconDisplayable.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusIconDisplayable.java
@@ -21,6 +21,8 @@
 public interface StatusIconDisplayable extends DarkReceiver {
     String getSlot();
     void setStaticDrawableColor(int color);
+    void setDecorColor(int color);
+    void setVisibleState(int state);
     boolean isIconVisible();
     default boolean isIconBlocked() {
         return false;
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 48540b1..824960e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java
@@ -198,8 +198,7 @@
 
     public void addDemoWifiView(WifiIconState state) {
         Log.d(TAG, "addDemoWifiView: ");
-        StatusBarWifiView view = StatusBarWifiView.fromContext(mContext);
-        view.setSlot(state.slot);
+        StatusBarWifiView view = StatusBarWifiView.fromContext(mContext, state.slot);
 
         int viewIndex = getChildCount();
         // If we have mobile views, put wifi before them
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 b817809..9787d73 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
@@ -205,12 +205,6 @@
     }
 
     @Override
-    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        updateLayoutConsideringCutout();
-        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
-    }
-
-    @Override
     public WindowInsets onApplyWindowInsets(WindowInsets insets) {
         mLayoutState = LAYOUT_NONE;
         if (updateLayoutConsideringCutout()) {
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 94e004b..1ba37a9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
@@ -163,6 +163,7 @@
                 StatusBarIconHolder holder) {
             StatusIconDisplayable view = addHolder(index, slot, blocked, holder);
             view.setStaticDrawableColor(mColor);
+            view.setDecorColor(mColor);
         }
 
         public void setTint(int color) {
@@ -172,6 +173,7 @@
                 if (child instanceof StatusIconDisplayable) {
                     StatusIconDisplayable icon = (StatusIconDisplayable) child;
                     icon.setStaticDrawableColor(mColor);
+                    icon.setDecorColor(mColor);
                 }
             }
         }
@@ -289,8 +291,7 @@
         }
 
         private StatusBarWifiView onCreateStatusBarWifiView(String slot) {
-            StatusBarWifiView view = StatusBarWifiView.fromContext(mContext);
-            view.setSlot(slot);
+            StatusBarWifiView view = StatusBarWifiView.fromContext(mContext, slot);
             return view;
         }
 
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 255e10e..c97c8eb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusIconContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusIconContainer.java
@@ -16,30 +16,57 @@
 
 package com.android.systemui.statusbar.phone;
 
+import static com.android.systemui.statusbar.StatusBarIconView.STATE_ICON;
+import static com.android.systemui.statusbar.StatusBarIconView.STATE_DOT;
+import static com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN;
+
 import android.annotation.Nullable;
 import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Paint.Style;
 import android.util.AttributeSet;
 import android.util.Log;
 
 import android.view.View;
+import android.view.ViewGroup;
 import com.android.keyguard.AlphaOptimizedLinearLayout;
 import com.android.systemui.R;
 import com.android.systemui.statusbar.StatusIconDisplayable;
 import com.android.systemui.statusbar.stack.ViewState;
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
 
 /**
  * A container for Status bar system icons. Limits the number of system icons and handles overflow
- * similar to NotificationIconController. Can be used to layout nested StatusIconContainers
+ * similar to {@link NotificationIconContainer}.
  *
- * Children are expected to be of type StatusBarIconView.
+ * Children are expected to implement {@link StatusIconDisplayable}
  */
 public class StatusIconContainer extends AlphaOptimizedLinearLayout {
 
     private static final String TAG = "StatusIconContainer";
     private static final boolean DEBUG = false;
-    private static final int MAX_ICONS = 5;
+    private static final boolean DEBUG_OVERFLOW = false;
+    // Max 5 status icons including battery
+    private static final int MAX_ICONS = 4;
     private static final int MAX_DOTS = 3;
 
+    private int mDotPadding;
+    private int mStaticDotDiameter;
+    private int mUnderflowWidth;
+    private int mUnderflowStart = 0;
+    // Whether or not we can draw into the underflow space
+    private boolean mNeedsUnderflow;
+    // Individual StatusBarIconViews draw their etc dots centered in this width
+    private int mIconDotFrameWidth;
+    private boolean mShouldRestrictIcons = true;
+    // Used to count which states want to be visible during layout
+    private ArrayList<StatusIconState> mLayoutStates = new ArrayList<>();
+    // So we can count and measure properly
+    private ArrayList<View> mMeasureViews = new ArrayList<>();
+
     public StatusIconContainer(Context context) {
         this(context, null);
     }
@@ -49,6 +76,27 @@
     }
 
     @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        setWillNotDraw(!DEBUG_OVERFLOW);
+        initDimens();
+    }
+
+    public void setShouldRestrictIcons(boolean should) {
+        mShouldRestrictIcons = should;
+    }
+
+    private void initDimens() {
+        // This is the same value that StatusBarIconView uses
+        mIconDotFrameWidth = getResources().getDimensionPixelSize(
+                com.android.internal.R.dimen.status_bar_icon_size);
+        mDotPadding = getResources().getDimensionPixelSize(R.dimen.overflow_icon_dot_padding);
+        int radius = getResources().getDimensionPixelSize(R.dimen.overflow_dot_radius);
+        mStaticDotDiameter = 2 * radius;
+        mUnderflowWidth = mIconDotFrameWidth + 2 * (mStaticDotDiameter + mDotPadding);
+    }
+
+    @Override
     protected void onLayout(boolean changed, int l, int t, int r, int b) {
         float midY = getHeight() / 2.0f;
 
@@ -67,23 +115,80 @@
     }
 
     @Override
+    protected void onDraw(Canvas canvas) {
+        super.onDraw(canvas);
+        if (DEBUG_OVERFLOW) {
+            Paint paint = new Paint();
+            paint.setStyle(Style.STROKE);
+            paint.setColor(Color.RED);
+
+            // Show bounding box
+            canvas.drawRect(getPaddingStart(), 0, getWidth() - getPaddingEnd(), getHeight(), paint);
+
+            // Show etc box
+            paint.setColor(Color.GREEN);
+            canvas.drawRect(
+                    mUnderflowStart, 0, mUnderflowStart + mUnderflowWidth, getHeight(), paint);
+        }
+    }
+
+    @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
-
-        int width = MeasureSpec.getSize(widthMeasureSpec);
-        int widthSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.UNSPECIFIED);
-
+        mMeasureViews.clear();
+        int mode = MeasureSpec.getMode(widthMeasureSpec);
+        final int width = MeasureSpec.getSize(widthMeasureSpec);
         final int count = getChildCount();
-        // Measure all children so that they report the correct width
+        // Collect all of the views which want to be laid out
         for (int i = 0; i < count; i++) {
-            measureChild(getChildAt(i), widthSpec, heightMeasureSpec);
+            StatusIconDisplayable icon = (StatusIconDisplayable) getChildAt(i);
+            if (icon.isIconVisible() && !icon.isIconBlocked()) {
+                mMeasureViews.add((View) icon);
+            }
+        }
+
+        int visibleCount = mMeasureViews.size();
+        int maxVisible = visibleCount <= MAX_ICONS ? MAX_ICONS : MAX_ICONS - 1;
+        int totalWidth = getPaddingStart() + getPaddingEnd();
+        boolean trackWidth = true;
+
+        // Measure all children so that they report the correct width
+        int childWidthSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.UNSPECIFIED);
+        mNeedsUnderflow = mShouldRestrictIcons && visibleCount > MAX_ICONS;
+        for (int i = 0; i < mMeasureViews.size(); i++) {
+            // Walking backwards
+            View child = mMeasureViews.get(visibleCount - i - 1);
+            measureChild(child, childWidthSpec, heightMeasureSpec);
+            if (mShouldRestrictIcons) {
+                if (i < maxVisible && trackWidth) {
+                    totalWidth += getViewTotalMeasuredWidth(child);
+                } else if (trackWidth) {
+                    // We've hit the icon limit; add space for dots
+                    totalWidth += mUnderflowWidth;
+                    trackWidth = false;
+                }
+            } else {
+                totalWidth += getViewTotalMeasuredWidth(child);
+            }
+        }
+
+        if (mode == MeasureSpec.EXACTLY) {
+            if (!mNeedsUnderflow && totalWidth > width) {
+                mNeedsUnderflow = true;
+            }
+            setMeasuredDimension(width, MeasureSpec.getSize(heightMeasureSpec));
+        } else {
+            if (mode == MeasureSpec.AT_MOST && totalWidth > width) {
+                mNeedsUnderflow = true;
+                totalWidth = width;
+            }
+            setMeasuredDimension(totalWidth, MeasureSpec.getSize(heightMeasureSpec));
         }
     }
 
     @Override
     public void onViewAdded(View child) {
         super.onViewAdded(child);
-        ViewState vs = new ViewState();
+        StatusIconState vs = new StatusIconState();
         child.setTag(R.id.status_bar_view_state_tag, vs);
     }
 
@@ -97,6 +202,7 @@
      * Layout is happening from end -> start
      */
     private void calculateIconTranslations() {
+        mLayoutStates.clear();
         float width = getWidth() - getPaddingEnd();
         float translationX = width;
         float contentStart = getPaddingStart();
@@ -106,45 +212,57 @@
         if (DEBUG) android.util.Log.d(TAG, "calculateIconTransitions: start=" + translationX
                 + " width=" + width);
 
-        //TODO: Dots
+        // Collect all of the states which want to be visible
         for (int i = childCount - 1; i >= 0; i--) {
             View child = getChildAt(i);
-            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;
-            }
+            StatusIconState childState = getViewStateFromChild(child);
 
             if (!iconView.isIconVisible() || iconView.isIconBlocked()) {
-                childState.hidden = true;
+                childState.visibleState = STATE_HIDDEN;
                 if (DEBUG) Log.d(TAG, "skipping child (" + iconView.getSlot() + ") not visible");
                 continue;
             }
 
-            childState.xTranslation = translationX - child.getWidth();
+            childState.visibleState = STATE_ICON;
+            childState.xTranslation = translationX - getViewTotalWidth(child);
+            mLayoutStates.add(0, childState);
 
-            if (childState.xTranslation < contentStart) {
-                if (firstUnderflowIndex == -1) {
-                    firstUnderflowIndex = i;
-                }
+            translationX -= getViewTotalWidth(child);
+        }
+
+        // Show either 1-4 dots, or 3 dots + overflow
+        int totalVisible = mLayoutStates.size();
+        int maxVisible = totalVisible <= MAX_ICONS ? MAX_ICONS : MAX_ICONS - 1;
+
+        mUnderflowStart = 0;
+        int visible = 0;
+        firstUnderflowIndex = -1;
+        for (int i = totalVisible - 1; i >= 0; i--) {
+            StatusIconState state = mLayoutStates.get(i);
+            // Allow room for underflow if we found we need it in onMeasure
+            if (mNeedsUnderflow && (state.xTranslation < (contentStart + mUnderflowWidth))||
+                    (mShouldRestrictIcons && visible >= maxVisible)) {
+                firstUnderflowIndex = i;
+                break;
             }
-
-            translationX -= child.getWidth();
+            mUnderflowStart = (int) Math.max(contentStart, state.xTranslation - mUnderflowWidth);
+            visible++;
         }
 
         if (firstUnderflowIndex != -1) {
-            for (int i = 0; i <= firstUnderflowIndex; i++) {
-                View child = getChildAt(i);
-                ViewState vs = getViewStateFromChild(child);
-                if (vs != null) {
-                    vs.hidden = true;
+            int totalDots = 0;
+            int dotWidth = mStaticDotDiameter + mDotPadding;
+            int dotOffset = mUnderflowStart + mUnderflowWidth - mIconDotFrameWidth;
+            for (int i = firstUnderflowIndex; i >= 0; i--) {
+                StatusIconState state = mLayoutStates.get(i);
+                if (totalDots < MAX_DOTS) {
+                    state.xTranslation = dotOffset;
+                    state.visibleState = STATE_DOT;
+                    dotOffset -= dotWidth;
+                    totalDots++;
+                } else {
+                    state.visibleState = STATE_HIDDEN;
                 }
             }
         }
@@ -153,7 +271,7 @@
         if (isLayoutRtl()) {
             for (int i = 0; i < childCount; i++) {
                 View child = getChildAt(i);
-                ViewState state = getViewStateFromChild(child);
+                StatusIconState state = getViewStateFromChild(child);
                 state.xTranslation = width - state.xTranslation - child.getWidth();
             }
         }
@@ -162,7 +280,7 @@
     private void applyIconStates() {
         for (int i = 0; i < getChildCount(); i++) {
             View child = getChildAt(i);
-            ViewState vs = getViewStateFromChild(child);
+            StatusIconState vs = getViewStateFromChild(child);
             if (vs != null) {
                 vs.applyToView(child);
             }
@@ -172,7 +290,7 @@
     private void resetViewStates() {
         for (int i = 0; i < getChildCount(); i++) {
             View child = getChildAt(i);
-            ViewState vs = getViewStateFromChild(child);
+            StatusIconState vs = getViewStateFromChild(child);
             if (vs == null) {
                 continue;
             }
@@ -187,7 +305,29 @@
         }
     }
 
-    private static @Nullable ViewState getViewStateFromChild(View child) {
-        return (ViewState) child.getTag(R.id.status_bar_view_state_tag);
+    private static @Nullable StatusIconState getViewStateFromChild(View child) {
+        return (StatusIconState) child.getTag(R.id.status_bar_view_state_tag);
+    }
+
+    private static int getViewTotalMeasuredWidth(View child) {
+        return child.getMeasuredWidth() + child.getPaddingStart() + child.getPaddingEnd();
+    }
+
+    private static int getViewTotalWidth(View child) {
+        return child.getWidth() + child.getPaddingStart() + child.getPaddingEnd();
+    }
+
+    public static class StatusIconState extends ViewState {
+        /// StatusBarIconView.STATE_*
+        public int visibleState = STATE_ICON;
+
+        @Override
+        public void applyToView(View view) {
+            if (view instanceof  StatusIconDisplayable) {
+                StatusIconDisplayable icon = (StatusIconDisplayable) view;
+                icon.setVisibleState(visibleState);
+            }
+            super.applyToView(view);
+        }
     }
 }