Work on separating out the status bar management

Lots of stuff:
 - Make StatusBarIconController be a permanent dependency
 - Break out dark stuff into DarkIconDispatcher
 - Create StatusBarFragment
   - This bit is a bit ugly for now, but will be better later
 - Other stuff probably

Test: runtest systemui
Change-Id: I4973bc9f944e66af92731bf1edd2b39657f1782f
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index 9245df0..5366da1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -25,7 +25,6 @@
 import android.os.Message;
 import android.support.annotation.VisibleForTesting;
 import android.util.Pair;
-import android.view.KeyEvent;
 
 import com.android.internal.os.SomeArgs;
 import com.android.internal.statusbar.IStatusBar;
@@ -92,6 +91,8 @@
     private final Object mLock = new Object();
     private ArrayList<Callbacks> mCallbacks = new ArrayList<>();
     private Handler mHandler = new H(Looper.getMainLooper());
+    private int mDisable1;
+    private int mDisable2;
 
     /**
      * These methods are called back on the main thread.
@@ -119,9 +120,9 @@
         default void cancelPreloadRecentApps() { }
         default void setWindowState(int window, int state) { }
         default void showScreenPinningRequest(int taskId) { }
-        default void appTransitionPending() { }
+        default void appTransitionPending(boolean forced) { }
         default void appTransitionCancelled() { }
-        default void appTransitionStarting(long startTime, long duration) { }
+        default void appTransitionStarting(long startTime, long duration, boolean forced) { }
         default void appTransitionFinished() { }
         default void showAssistDisclosure() { }
         default void startAssist(Bundle args) { }
@@ -141,6 +142,7 @@
 
     public void addCallbacks(Callbacks callbacks) {
         mCallbacks.add(callbacks);
+        callbacks.disable(mDisable1, mDisable2, false /* animate */);
     }
 
     public void removeCallbacks(Callbacks callbacks) {
@@ -164,6 +166,8 @@
 
     public void disable(int state1, int state2) {
         synchronized (mLock) {
+            mDisable1 = state1;
+            mDisable2 = state2;
             mHandler.removeMessages(MSG_DISABLE);
             mHandler.obtainMessage(MSG_DISABLE, state1, state2, null).sendToTarget();
         }
@@ -315,9 +319,13 @@
     }
 
     public void appTransitionPending() {
+        appTransitionPending(false /* forced */);
+    }
+
+    public void appTransitionPending(boolean forced) {
         synchronized (mLock) {
             mHandler.removeMessages(MSG_APP_TRANSITION_PENDING);
-            mHandler.sendEmptyMessage(MSG_APP_TRANSITION_PENDING);
+            mHandler.obtainMessage(MSG_APP_TRANSITION_PENDING, forced ? 1 : 0, 0).sendToTarget();
         }
     }
 
@@ -329,10 +337,14 @@
     }
 
     public void appTransitionStarting(long startTime, long duration) {
+        appTransitionStarting(startTime, duration, false /* forced */);
+    }
+
+    public void appTransitionStarting(long startTime, long duration, boolean forced) {
         synchronized (mLock) {
             mHandler.removeMessages(MSG_APP_TRANSITION_STARTING);
-            mHandler.obtainMessage(MSG_APP_TRANSITION_STARTING, Pair.create(startTime, duration))
-                    .sendToTarget();
+            mHandler.obtainMessage(MSG_APP_TRANSITION_STARTING, forced ? 1 : 0, 0,
+                    Pair.create(startTime, duration)).sendToTarget();
         }
     }
 
@@ -505,7 +517,7 @@
                     break;
                 case MSG_APP_TRANSITION_PENDING:
                     for (int i = 0; i < mCallbacks.size(); i++) {
-                        mCallbacks.get(i).appTransitionPending();
+                        mCallbacks.get(i).appTransitionPending(msg.arg1 != 0);
                     }
                     break;
                 case MSG_APP_TRANSITION_CANCELLED:
@@ -516,7 +528,8 @@
                 case MSG_APP_TRANSITION_STARTING:
                     for (int i = 0; i < mCallbacks.size(); i++) {
                         Pair<Long, Long> data = (Pair<Long, Long>) msg.obj;
-                        mCallbacks.get(i).appTransitionStarting(data.first, data.second);
+                        mCallbacks.get(i).appTransitionStarting(data.first, data.second,
+                                msg.arg1 != 0);
                     }
                     break;
                 case MSG_APP_TRANSITION_FINISHED:
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java
index 45eb5df..89041f9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java
@@ -43,6 +43,8 @@
 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.DarkIconDispatcher.DarkReceiver;
 import com.android.systemui.statusbar.policy.NetworkController;
 import com.android.systemui.statusbar.policy.NetworkController.IconState;
 import com.android.systemui.statusbar.policy.NetworkControllerImpl;
@@ -54,10 +56,9 @@
 import java.util.List;
 
 // Intimately tied to the design of res/layout/signal_cluster_view.xml
-public class SignalClusterView
-        extends LinearLayout
-        implements NetworkControllerImpl.SignalCallback,
-        SecurityController.SecurityControllerCallback, Tunable {
+public class SignalClusterView extends LinearLayout implements NetworkControllerImpl.SignalCallback,
+        SecurityController.SecurityControllerCallback, Tunable,
+        DarkReceiver {
 
     static final String TAG = "SignalClusterView";
     static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
@@ -581,7 +582,8 @@
         return colorAccent;
     }
 
-    public void setIconTint(int tint, float darkIntensity, Rect tintArea) {
+    @Override
+    public void onDarkChanged(Rect tintArea, float darkIntensity, int tint) {
         boolean changed = tint != mIconTint || darkIntensity != mDarkIntensity
                 || !mTintArea.equals(tintArea);
         mIconTint = tint;
@@ -593,16 +595,16 @@
     }
 
     private void applyIconTint() {
-        setTint(mVpn, StatusBarIconController.getTint(mTintArea, mVpn, mIconTint));
-        setTint(mAirplane, StatusBarIconController.getTint(mTintArea, mAirplane, mIconTint));
+        setTint(mVpn, DarkIconDispatcher.getTint(mTintArea, mVpn, mIconTint));
+        setTint(mAirplane, DarkIconDispatcher.getTint(mTintArea, mAirplane, mIconTint));
         applyDarkIntensity(
-                StatusBarIconController.getDarkIntensity(mTintArea, mNoSims, mDarkIntensity),
+                DarkIconDispatcher.getDarkIntensity(mTintArea, mNoSims, mDarkIntensity),
                 mNoSims, mNoSimsDark);
         applyDarkIntensity(
-                StatusBarIconController.getDarkIntensity(mTintArea, mWifi, mDarkIntensity),
+                DarkIconDispatcher.getDarkIntensity(mTintArea, mWifi, mDarkIntensity),
                 mWifi, mWifiDark);
         applyDarkIntensity(
-                StatusBarIconController.getDarkIntensity(mTintArea, mEthernet, mDarkIntensity),
+                DarkIconDispatcher.getDarkIntensity(mTintArea, mEthernet, mDarkIntensity),
                 mEthernet, mEthernetDark);
         for (int i = 0; i < mPhoneStates.size(); i++) {
             mPhoneStates.get(i).setIconTint(mIconTint, mDarkIntensity, mTintArea);
@@ -740,10 +742,10 @@
 
         public void setIconTint(int tint, float darkIntensity, Rect tintArea) {
             applyDarkIntensity(
-                    StatusBarIconController.getDarkIntensity(tintArea, mMobile, darkIntensity),
+                    DarkIconDispatcher.getDarkIntensity(tintArea, mMobile, darkIntensity),
                     mMobile, mMobileDark);
-            setTint(mMobileType, StatusBarIconController.getTint(tintArea, mMobileType, tint));
-            setTint(mMobileRoaming, StatusBarIconController.getTint(tintArea, mMobileRoaming,
+            setTint(mMobileType, DarkIconDispatcher.getTint(tintArea, mMobileType, tint));
+            setTint(mMobileRoaming, DarkIconDispatcher.getTint(tintArea, mMobileRoaming,
                     tint));
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
index 4161389..f53dad5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
@@ -32,17 +32,19 @@
 import android.view.ViewStub;
 import android.view.WindowManager;
 import android.widget.LinearLayout;
+
 import com.android.systemui.BatteryMeterView;
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
 import com.android.systemui.SwipeHelper;
+import com.android.systemui.fragments.FragmentHostManager;
 import com.android.systemui.recents.Recents;
 import com.android.systemui.recents.misc.SystemServicesProxy;
 import com.android.systemui.recents.misc.SystemServicesProxy.TaskStackListener;
 import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.phone.CollapsedStatusBarFragment;
 import com.android.systemui.statusbar.phone.NavigationBarView;
 import com.android.systemui.statusbar.phone.StatusBar;
-import com.android.systemui.statusbar.phone.PhoneStatusBarView;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.UserSwitcherController;
 
@@ -75,7 +77,6 @@
 
         createBatteryController();
         mCarBatteryController.startListening();
-        mConnectedDeviceSignalController.startListening();
     }
 
     @Override
@@ -87,32 +88,40 @@
     }
 
     @Override
-    protected PhoneStatusBarView makeStatusBarView() {
-        PhoneStatusBarView statusBarView = super.makeStatusBarView();
+    protected void makeStatusBarView() {
+        super.makeStatusBarView();
 
-        mBatteryMeterView = ((BatteryMeterView) statusBarView.findViewById(R.id.battery));
+        FragmentHostManager manager = FragmentHostManager.get(mStatusBarWindow);
+        manager.addTagListener(CollapsedStatusBarFragment.TAG, (tag, fragment) -> {
+            mBatteryMeterView = ((BatteryMeterView) fragment.getView().findViewById(
+                    R.id.battery));
 
-        // By default, the BatteryMeterView should not be visible. It will be toggled visible
-        // when a device has connected by bluetooth.
-        mBatteryMeterView.setVisibility(View.GONE);
+            // By default, the BatteryMeterView should not be visible. It will be toggled
+            // when a device has connected by bluetooth.
+            mBatteryMeterView.setVisibility(View.GONE);
 
-        ViewStub stub = (ViewStub) statusBarView.findViewById(R.id.connected_device_signals_stub);
-        View signalsView = stub.inflate();
+            ViewStub stub = (ViewStub) fragment.getView().findViewById(
+                    R.id.connected_device_signals_stub);
+            View signalsView = stub.inflate();
 
-        // When a ViewStub if inflated, it does not respect the margins on the inflated view.
-        // As a result, manually add the ending margin.
-        ((LinearLayout.LayoutParams) signalsView.getLayoutParams()).setMarginEnd(
-                mContext.getResources().getDimensionPixelOffset(
-                        R.dimen.status_bar_connected_device_signal_margin_end));
+            // When a ViewStub if inflated, it does not respect the margins on the
+            // inflated view.
+            // As a result, manually add the ending margin.
+            ((LinearLayout.LayoutParams) signalsView.getLayoutParams()).setMarginEnd(
+                    mContext.getResources().getDimensionPixelOffset(
+                            R.dimen.status_bar_connected_device_signal_margin_end));
 
-        mConnectedDeviceSignalController = new ConnectedDeviceSignalController(mContext,
-                signalsView);
+            if (mConnectedDeviceSignalController != null) {
+                mConnectedDeviceSignalController.stopListening();
+            }
+            mConnectedDeviceSignalController = new ConnectedDeviceSignalController(mContext,
+                    signalsView);
+            mConnectedDeviceSignalController.startListening();
 
-        if (Log.isLoggable(TAG, Log.DEBUG)) {
-            Log.d(TAG, "makeStatusBarView(). mBatteryMeterView: " + mBatteryMeterView);
-        }
-
-        return statusBarView;
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "makeStatusBarView(). mBatteryMeterView: " + mBatteryMeterView);
+            }
+        });
     }
 
     private BatteryController createBatteryController() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
new file mode 100644
index 0000000..1f56c56
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
@@ -0,0 +1,223 @@
+/*
+ * 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 static com.android.systemui.statusbar.phone.StatusBar.reinflateSignalCluster;
+
+import android.annotation.Nullable;
+import android.app.Fragment;
+import android.app.StatusBarManager;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+
+import com.android.systemui.Dependency;
+import com.android.systemui.Interpolators;
+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;
+import com.android.systemui.statusbar.policy.KeyguardMonitor;
+import com.android.systemui.statusbar.policy.NetworkController;
+
+/**
+ * Contains the collapsed status bar and handles hiding/showing based on disable flags
+ * and keyguard state. Also manages lifecycle to make sure the views it contains are being
+ * updated by the StatusBarIconController and DarkIconManager while it is attached.
+ */
+public class CollapsedStatusBarFragment extends Fragment implements CommandQueue.Callbacks {
+
+    public static final String TAG = "CollapsedStatusBarFragment";
+    private PhoneStatusBarView mStatusBar;
+    private KeyguardMonitor mKeyguardMonitor;
+    private NetworkController mNetworkController;
+    private LinearLayout mSystemIconArea;
+    private View mNotificationIconAreaInner;
+    private int mDisabled1;
+    private StatusBar mStatusBarComponent;
+    private DarkIconManager mDarkIconManager;
+    private SignalClusterView mSignalClusterView;
+
+    @Override
+    public void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        mKeyguardMonitor = Dependency.get(KeyguardMonitor.class);
+        mNetworkController = Dependency.get(NetworkController.class);
+        mStatusBarComponent = SysUiServiceProvider.getComponent(getContext(), StatusBar.class);
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
+            Bundle savedInstanceState) {
+        return inflater.inflate(R.layout.status_bar, container, false);
+    }
+
+    @Override
+    public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
+        super.onViewCreated(view, savedInstanceState);
+        mStatusBar = (PhoneStatusBarView) view;
+        mDarkIconManager = new DarkIconManager((LinearLayout) view.findViewById(R.id.statusIcons));
+        Dependency.get(StatusBarIconController.class).addIconGroup(mDarkIconManager);
+        mSystemIconArea = (LinearLayout) mStatusBar.findViewById(R.id.system_icon_area);
+        mSignalClusterView = reinflateSignalCluster(mStatusBar);
+        Dependency.get(DarkIconDispatcher.class).addDarkReceiver(mSignalClusterView);
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        SysUiServiceProvider.getComponent(getContext(), CommandQueue.class).addCallbacks(this);
+    }
+
+    @Override
+    public void onPause() {
+        super.onPause();
+        SysUiServiceProvider.getComponent(getContext(), CommandQueue.class).removeCallbacks(this);
+    }
+
+    @Override
+    public void onDestroyView() {
+        super.onDestroyView();
+        Dependency.get(DarkIconDispatcher.class).removeDarkReceiver(mSignalClusterView);
+        Dependency.get(StatusBarIconController.class).removeIconGroup(mDarkIconManager);
+    }
+
+    public void initNotificationIconArea(NotificationIconAreaController
+            notificationIconAreaController) {
+        ViewGroup notificationIconArea = (ViewGroup) mStatusBar
+                .findViewById(R.id.notification_icon_area);
+        mNotificationIconAreaInner =
+                notificationIconAreaController.getNotificationInnerAreaView();
+        if (mNotificationIconAreaInner.getParent() != null) {
+            ((ViewGroup) mNotificationIconAreaInner.getParent())
+                    .removeView(mNotificationIconAreaInner);
+        }
+        notificationIconArea.addView(mNotificationIconAreaInner);
+    }
+
+    @Override
+    public void disable(int state1, int state2, boolean animate) {
+        state1 = adjustDisableFlags(state1);
+        final int old1 = mDisabled1;
+        final int diff1 = state1 ^ old1;
+        mDisabled1 = state1;
+        if ((diff1 & StatusBarManager.DISABLE_SYSTEM_INFO) != 0) {
+            if ((state1 & StatusBarManager.DISABLE_SYSTEM_INFO) != 0) {
+                hideSystemIconArea(animate);
+            } else {
+                showSystemIconArea(animate);
+            }
+        }
+        if ((diff1 & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) {
+            if ((state1 & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) {
+                hideNotificationIconArea(animate);
+            } else {
+                showNotificationIconArea(animate);
+            }
+        }
+    }
+
+    protected int adjustDisableFlags(int state) {
+        if (!mStatusBarComponent.isLaunchTransitionFadingAway()
+                && !mKeyguardMonitor.isKeyguardFadingAway()
+                && shouldHideNotificationIcons()) {
+            state |= StatusBarManager.DISABLE_NOTIFICATION_ICONS;
+            state |= StatusBarManager.DISABLE_SYSTEM_INFO;
+        }
+        if (mNetworkController != null && EncryptionHelper.IS_DATA_ENCRYPTED) {
+            if (mNetworkController.hasEmergencyCryptKeeperText()) {
+                state |= StatusBarManager.DISABLE_NOTIFICATION_ICONS;
+            }
+            if (!mNetworkController.isRadioOn()) {
+                state |= StatusBarManager.DISABLE_SYSTEM_INFO;
+            }
+        }
+        return state;
+    }
+
+    private boolean shouldHideNotificationIcons() {
+        return !mStatusBar.isClosed() && mStatusBarComponent.shouldHideNotificationIcons();
+    }
+
+    public void hideSystemIconArea(boolean animate) {
+        animateHide(mSystemIconArea, animate);
+    }
+
+    public void showSystemIconArea(boolean animate) {
+        animateShow(mSystemIconArea, animate);
+    }
+
+    public void hideNotificationIconArea(boolean animate) {
+        animateHide(mNotificationIconAreaInner, animate);
+    }
+
+    public void showNotificationIconArea(boolean animate) {
+        animateShow(mNotificationIconAreaInner, animate);
+    }
+
+    /**
+     * Hides a view.
+     */
+    private void animateHide(final View v, boolean animate) {
+        v.animate().cancel();
+        if (!animate) {
+            v.setAlpha(0f);
+            v.setVisibility(View.INVISIBLE);
+            return;
+        }
+        v.animate()
+                .alpha(0f)
+                .setDuration(160)
+                .setStartDelay(0)
+                .setInterpolator(Interpolators.ALPHA_OUT)
+                .withEndAction(() -> v.setVisibility(View.INVISIBLE));
+    }
+
+    /**
+     * Shows a view, and synchronizes the animation with Keyguard exit animations, if applicable.
+     */
+    private void animateShow(View v, boolean animate) {
+        v.animate().cancel();
+        v.setVisibility(View.VISIBLE);
+        if (!animate) {
+            v.setAlpha(1f);
+            return;
+        }
+        v.animate()
+                .alpha(1f)
+                .setDuration(320)
+                .setInterpolator(Interpolators.ALPHA_IN)
+                .setStartDelay(50)
+
+                // We need to clean up any pending end action from animateHide if we call
+                // both hide and show in the same frame before the animation actually gets started.
+                // cancel() doesn't really remove the end action.
+                .withEndAction(null);
+
+        // Synchronize the motion with the Keyguard fading if necessary.
+        if (mKeyguardMonitor.isKeyguardFadingAway()) {
+            v.animate()
+                    .setDuration(mKeyguardMonitor.getKeyguardFadingAwayDuration())
+                    .setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN)
+                    .setStartDelay(mKeyguardMonitor.getKeyguardFadingAwayDelay())
+                    .start();
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.java
new file mode 100644
index 0000000..020dc25
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.java
@@ -0,0 +1,60 @@
+/*
+ * 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.content.Context;
+import android.content.res.Configuration;
+
+import com.android.systemui.ConfigurationChangedReceiver;
+import com.android.systemui.statusbar.policy.ConfigurationController;
+
+import java.util.ArrayList;
+
+public class ConfigurationControllerImpl implements ConfigurationController,
+        ConfigurationChangedReceiver {
+
+    private final ArrayList<ConfigurationListener> mListeners = new ArrayList<>();
+    private int mDensity;
+    private float mFontScale;
+
+    public ConfigurationControllerImpl(Context context) {
+        Configuration currentConfig = context.getResources().getConfiguration();
+        mFontScale = currentConfig.fontScale;
+        mDensity = currentConfig.densityDpi;
+    }
+
+    @Override
+    public void onConfigurationChanged(Configuration newConfig) {
+        mListeners.forEach(l -> l.onConfigChanged(newConfig));
+        final float fontScale = newConfig.fontScale;
+        final int density = newConfig.densityDpi;
+        if (density != mDensity || mFontScale != fontScale) {
+            mListeners.forEach(l -> l.onDensityOrFontScaleChanged());
+            mDensity = density;
+            mFontScale = fontScale;
+        }
+    }
+
+    @Override
+    public void addCallback(ConfigurationListener listener) {
+        mListeners.add(listener);
+        listener.onDensityOrFontScaleChanged();
+    }
+
+    @Override
+    public void removeCallback(ConfigurationListener listener) {
+        mListeners.remove(listener);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java
new file mode 100644
index 0000000..3f9ae80
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java
@@ -0,0 +1,106 @@
+/*
+ * 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 static com.android.systemui.statusbar.policy.DarkIconDispatcher.getTint;
+
+import android.animation.ArgbEvaluator;
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.graphics.Rect;
+import android.util.ArrayMap;
+import android.widget.ImageView;
+
+import com.android.systemui.R;
+import com.android.systemui.statusbar.policy.DarkIconDispatcher;
+
+public class DarkIconDispatcherImpl implements DarkIconDispatcher {
+
+    private final LightBarTransitionsController mTransitionsController;
+    private final Rect mTintArea = new Rect();
+    private final ArrayMap<Object, DarkReceiver> mReceivers = new ArrayMap<>();
+
+    private int mIconTint = DEFAULT_ICON_TINT;
+    private float mDarkIntensity;
+    private int mDarkModeIconColorSingleTone;
+    private int mLightModeIconColorSingleTone;
+
+    public DarkIconDispatcherImpl(Context context) {
+        mDarkModeIconColorSingleTone = context.getColor(R.color.dark_mode_icon_color_single_tone);
+        mLightModeIconColorSingleTone = context.getColor(R.color.light_mode_icon_color_single_tone);
+
+        mTransitionsController = new LightBarTransitionsController(context,
+                this::setIconTintInternal);
+    }
+
+    public LightBarTransitionsController getTransitionsController() {
+        return mTransitionsController;
+    }
+
+    public void addDarkReceiver(DarkReceiver receiver) {
+        mReceivers.put(receiver, receiver);
+        receiver.onDarkChanged(mTintArea, mDarkIntensity, mIconTint);
+    }
+
+    public void addDarkReceiver(ImageView imageView) {
+        DarkReceiver receiver = (area, darkIntensity, tint) -> imageView.setImageTintList(
+                ColorStateList.valueOf(getTint(mTintArea, imageView, mIconTint)));
+        mReceivers.put(imageView, receiver);
+        receiver.onDarkChanged(mTintArea, mDarkIntensity, mIconTint);
+    }
+
+    public void removeDarkReceiver(DarkReceiver object) {
+        mReceivers.remove(object);
+    }
+
+    public void removeDarkReceiver(ImageView object) {
+        mReceivers.remove(object);
+    }
+
+    public void applyDark(ImageView object) {
+        mReceivers.get(object).onDarkChanged(mTintArea, mDarkIntensity, mIconTint);
+    }
+
+    /**
+     * Sets the dark area so {@link #setIconsDark} only affects the icons in the specified area.
+     *
+     * @param darkArea the area in which icons should change it's tint, in logical screen
+     *                 coordinates
+     */
+    public void setIconsDarkArea(Rect darkArea) {
+        if (darkArea == null && mTintArea.isEmpty()) {
+            return;
+        }
+        if (darkArea == null) {
+            mTintArea.setEmpty();
+        } else {
+            mTintArea.set(darkArea);
+        }
+        applyIconTint();
+    }
+
+    private void setIconTintInternal(float darkIntensity) {
+        mDarkIntensity = darkIntensity;
+        mIconTint = (int) ArgbEvaluator.getInstance().evaluate(darkIntensity,
+                mLightModeIconColorSingleTone, mDarkModeIconColorSingleTone);
+        applyIconTint();
+    }
+
+    private void applyIconTint() {
+        for (int i = 0; i < mReceivers.size(); i++) {
+            mReceivers.valueAt(i).onDarkChanged(mTintArea, mDarkIntensity, mIconTint);
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
index 7c458898..9d699cf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
@@ -22,6 +22,7 @@
 import com.android.systemui.Dependency;
 import com.android.systemui.Dumpable;
 import com.android.systemui.statusbar.policy.BatteryController;
+import com.android.systemui.statusbar.policy.DarkIconDispatcher;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -36,7 +37,7 @@
 
     private static final float NAV_BAR_INVERSION_SCRIM_ALPHA_THRESHOLD = 0.1f;
 
-    private final StatusBarIconController mStatusBarIconController;
+    private final DarkIconDispatcher mStatusBarIconController;
     private final BatteryController mBatteryController;
     private FingerprintUnlockController mFingerprintUnlockController;
 
@@ -65,8 +66,8 @@
     private final Rect mLastFullscreenBounds = new Rect();
     private final Rect mLastDockedBounds = new Rect();
 
-    public LightBarController(StatusBarIconController statusBarIconController) {
-        mStatusBarIconController = statusBarIconController;
+    public LightBarController() {
+        mStatusBarIconController = Dependency.get(DarkIconDispatcher.class);
         mBatteryController = Dependency.get(BatteryController.class);
         mBatteryController.addCallback(this);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarTransitionsController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarTransitionsController.java
index 07f37ab..6bd959f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarTransitionsController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarTransitionsController.java
@@ -17,13 +17,19 @@
 package com.android.systemui.statusbar.phone;
 
 import android.animation.ValueAnimator;
+import android.content.Context;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.SystemClock;
 import android.util.TimeUtils;
 
 import com.android.systemui.Dumpable;
+import com.android.systemui.Dependency;
 import com.android.systemui.Interpolators;
+import com.android.systemui.SysUiServiceProvider;
+import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.CommandQueue.Callbacks;
+import com.android.systemui.statusbar.policy.KeyguardMonitor;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -31,13 +37,14 @@
 /**
  * Class to control all aspects about light bar changes.
  */
-public class LightBarTransitionsController implements Dumpable {
+public class LightBarTransitionsController implements Dumpable, Callbacks {
 
     public static final long DEFAULT_TINT_ANIMATION_DURATION = 120;
     private static final String EXTRA_DARK_INTENSITY = "dark_intensity";
 
     private final Handler mHandler;
     private final DarkIntensityApplier mApplier;
+    private final KeyguardMonitor mKeyguardMonitor;
 
     private boolean mTransitionDeferring;
     private long mTransitionDeferringStartTime;
@@ -56,9 +63,17 @@
         }
     };
 
-    public LightBarTransitionsController(DarkIntensityApplier applier) {
+    public LightBarTransitionsController(Context context, DarkIntensityApplier applier) {
         mApplier = applier;
         mHandler = new Handler();
+        mKeyguardMonitor = Dependency.get(KeyguardMonitor.class);
+        SysUiServiceProvider.getComponent(context, CommandQueue.class)
+                .addCallbacks(this);
+    }
+
+    public void destroy(Context context) {
+        SysUiServiceProvider.getComponent(context, CommandQueue.class)
+                .removeCallbacks(this);
     }
 
     public void saveState(Bundle outState) {
@@ -71,10 +86,15 @@
         setIconTintInternal(savedInstanceState.getFloat(EXTRA_DARK_INTENSITY, 0));
     }
 
-    public void appTransitionPending() {
+    @Override
+    public void appTransitionPending(boolean forced) {
+        if (mKeyguardMonitor.isKeyguardGoingAway() && !forced) {
+            return;
+        }
         mTransitionPending = true;
     }
 
+    @Override
     public void appTransitionCancelled() {
         if (mTransitionPending && mTintChangePending) {
             mTintChangePending = false;
@@ -83,7 +103,11 @@
         mTransitionPending = false;
     }
 
-    public void appTransitionStarting(long startTime, long duration) {
+    @Override
+    public void appTransitionStarting(long startTime, long duration, boolean forced) {
+        if (mKeyguardMonitor.isKeyguardGoingAway() && !forced) {
+            return;
+        }
         if (mTransitionPending && mTintChangePending) {
             mTintChangePending = false;
             animateIconTint(mPendingDarkIntensity,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
index 808cd21..99f8aaf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
@@ -118,7 +118,6 @@
 
     private int mSystemUiVisibility;
     private LightBarController mLightBarController;
-    private boolean mKeyguardGoingAway;
 
     public boolean mHomeBlockedThisTouch;
 
@@ -195,6 +194,7 @@
     @Override
     public void onDestroyView() {
         super.onDestroyView();
+        mNavigationBarView.getLightTransitionsController().destroy(getContext());
         getContext().unregisterReceiver(mBroadcastReceiver);
     }
 
@@ -287,31 +287,6 @@
         }
     }
 
-    @Override
-    public void appTransitionPending() {
-        mNavigationBarView.getLightTransitionsController().appTransitionPending();
-    }
-
-    @Override
-    public void appTransitionCancelled() {
-        mNavigationBarView.getLightTransitionsController().appTransitionCancelled();
-    }
-
-    @Override
-    public void appTransitionStarting(long startTime, long duration) {
-        if (mKeyguardGoingAway) return;
-        doAppTransitionStarting(startTime, duration);
-    }
-
-    /**
-     * Calls appTransitionStarting for the nav bar regardless of whether keyguard is going away.
-     * public so StatusBar can force this when needed.
-     */
-    public void doAppTransitionStarting(long startTime, long duration) {
-        mNavigationBarView.getLightTransitionsController().appTransitionStarting(startTime,
-                duration);
-    }
-
     // Injected from StatusBar at creation.
     public void setCurrentSysuiVisibility(int systemUiVisibility) {
         mSystemUiVisibility = systemUiVisibility;
@@ -612,10 +587,6 @@
                 delay + StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE);
     }
 
-    public void setKeyguardGoingAway(boolean keyguardGoingAway) {
-        mKeyguardGoingAway = keyguardGoingAway;
-    }
-
     public BarTransitions getBarTransitions() {
         return mNavigationBarView.getBarTransitions();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java
index 3be5e57..cb925d5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java
@@ -38,7 +38,8 @@
         mView = view;
         mBarService = IStatusBarService.Stub.asInterface(
                 ServiceManager.getService(Context.STATUS_BAR_SERVICE));
-        mLightTransitionsController = new LightBarTransitionsController(this::applyDarkIntensity);
+        mLightTransitionsController = new LightBarTransitionsController(view.getContext(),
+                this::applyDarkIntensity);
     }
 
     public void init() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
index 0386398..6d7ab47 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
@@ -17,6 +17,8 @@
 import com.android.systemui.statusbar.NotificationShelf;
 import com.android.systemui.statusbar.StatusBarIconView;
 import com.android.systemui.statusbar.notification.NotificationUtils;
+import com.android.systemui.statusbar.policy.DarkIconDispatcher;
+import com.android.systemui.statusbar.policy.DarkIconDispatcher.DarkReceiver;
 import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
 
 import java.util.ArrayList;
@@ -26,7 +28,7 @@
  * A controller for the space in the status bar to the left of the system icons. This area is
  * normally reserved for notifications.
  */
-public class NotificationIconAreaController {
+public class NotificationIconAreaController implements DarkReceiver {
     private final NotificationColorUtil mNotificationColorUtil;
 
     private int mIconSize;
@@ -64,11 +66,12 @@
         mNotificationIcons = (NotificationIconContainer) mNotificationIconArea.findViewById(
                 R.id.notificationIcons);
 
-        NotificationShelf shelf = mStatusBar.getNotificationShelf();
+        mNotificationScrollLayout = mStatusBar.getNotificationScrollLayout();
+    }
+
+    public void setupShelf(NotificationShelf shelf) {
         mShelfIcons = shelf.getShelfIcons();
         shelf.setCollapsedIcons(mNotificationIcons);
-
-        mNotificationScrollLayout = mStatusBar.getNotificationScrollLayout();
     }
 
     public void onDensityOrFontScaleChanged(Context context) {
@@ -102,23 +105,18 @@
     }
 
     /**
-     * See {@link StatusBarIconController#setIconsDarkArea}.
+     * See {@link com.android.systemui.statusbar.policy.DarkIconDispatcher#setIconsDarkArea}.
+     * Sets the color that should be used to tint any icons in the notification area.
      *
      * @param tintArea the area in which to tint the icons, specified in screen coordinates
+     * @param darkIntensity
      */
-    public void setTintArea(Rect tintArea) {
+    public void onDarkChanged(Rect tintArea, float darkIntensity, int iconTint) {
         if (tintArea == null) {
             mTintArea.setEmpty();
         } else {
             mTintArea.set(tintArea);
         }
-        applyNotificationIconsTint();
-    }
-
-    /**
-     * Sets the color that should be used to tint any icons in the notification area.
-     */
-    public void setIconTint(int iconTint) {
         mIconTint = iconTint;
         applyNotificationIconsTint();
     }
@@ -231,7 +229,7 @@
             boolean colorize = !isPreL || NotificationUtils.isGrayscale(v, mNotificationColorUtil);
             if (colorize) {
                 v.setImageTintList(ColorStateList.valueOf(
-                        StatusBarIconController.getTint(mTintArea, v, mIconTint)));
+                        DarkIconDispatcher.getTint(mTintArea, v, mIconTint)));
             }
             v.setIconTint(mIconTint);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java
index 87a3848..23d3816 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java
@@ -166,6 +166,10 @@
         if (DEBUG) LOG("onPanelPeeked");
     }
 
+    public boolean isClosed() {
+        return mState == STATE_CLOSED;
+    }
+
     public void onPanelCollapsed() {
         if (DEBUG) LOG("onPanelCollapsed");
     }
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 1044ecf..93f874d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
@@ -38,24 +38,35 @@
 import com.android.internal.telephony.TelephonyIntents;
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
+import com.android.systemui.SysUiServiceProvider;
 import com.android.systemui.qs.tiles.DndTile;
 import com.android.systemui.qs.tiles.RotationLockTile;
+import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.CommandQueue.Callbacks;
 import com.android.systemui.statusbar.policy.BluetoothController;
 import com.android.systemui.statusbar.policy.BluetoothController.Callback;
 import com.android.systemui.statusbar.policy.CastController;
 import com.android.systemui.statusbar.policy.CastController.CastDevice;
 import com.android.systemui.statusbar.policy.DataSaverController;
+import com.android.systemui.statusbar.policy.DataSaverController.Listener;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
 import com.android.systemui.statusbar.policy.HotspotController;
+import com.android.systemui.statusbar.policy.KeyguardMonitor;
 import com.android.systemui.statusbar.policy.NextAlarmController;
 import com.android.systemui.statusbar.policy.RotationLockController;
+import com.android.systemui.statusbar.policy.RotationLockController.RotationLockControllerCallback;
 import com.android.systemui.statusbar.policy.UserInfoController;
+import com.android.systemui.statusbar.policy.ZenModeController;
 
 /**
  * This class contains all of the policy about which icons are installed in the status
  * bar at boot time.  It goes through the normal API for icons, even though it probably
  * strictly doesn't need to.
  */
-public class PhoneStatusBarPolicy implements Callback, RotationLockController.RotationLockControllerCallback, DataSaverController.Listener {
+public class PhoneStatusBarPolicy implements Callback, Callbacks,
+        RotationLockControllerCallback, Listener,
+        ZenModeController.Callback, DeviceProvisionedListener, KeyguardMonitor.Callback {
     private static final String TAG = "PhoneStatusBarPolicy";
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
@@ -82,7 +93,9 @@
     private final StatusBarIconController mIconController;
     private final RotationLockController mRotationLockController;
     private final DataSaverController mDataSaver;
-    private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
+    private final ZenModeController mZenController;
+    private final DeviceProvisionedController mProvisionedController;
+    private final KeyguardMonitor mKeyguardMonitor;
 
     // Assume it's all good unless we hear otherwise.  We don't always seem
     // to get broadcasts that it *is* there.
@@ -106,13 +119,15 @@
         mCast = Dependency.get(CastController.class);
         mHotspot = Dependency.get(HotspotController.class);
         mBluetooth = Dependency.get(BluetoothController.class);
-        mBluetooth.addCallback(this);
         mNextAlarm = Dependency.get(NextAlarmController.class);
         mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
         mUserInfoController = Dependency.get(UserInfoController.class);
         mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
         mRotationLockController = Dependency.get(RotationLockController.class);
         mDataSaver = Dependency.get(DataSaverController.class);
+        mZenController = Dependency.get(ZenModeController.class);
+        mProvisionedController = Dependency.get(DeviceProvisionedController.class);
+        mKeyguardMonitor = Dependency.get(KeyguardMonitor.class);
 
         mSlotCast = context.getString(com.android.internal.R.string.status_bar_cast);
         mSlotHotspot = context.getString(com.android.internal.R.string.status_bar_hotspot);
@@ -127,7 +142,6 @@
         mSlotHeadset = context.getString(com.android.internal.R.string.status_bar_headset);
         mSlotDataSaver = context.getString(com.android.internal.R.string.status_bar_data_saver);
 
-        mRotationLockController.addCallback(this);
 
         // listen for broadcasts
         IntentFilter filter = new IntentFilter();
@@ -158,7 +172,6 @@
         // Alarm clock
         mIconController.setIcon(mSlotAlarmClock, R.drawable.stat_sys_alarm, null);
         mIconController.setIconVisibility(mSlotAlarmClock, false);
-        mNextAlarm.addCallback(mNextAlarmCallback);
 
         // zen
         mIconController.setIcon(mSlotZen, R.drawable.stat_sys_zen_important, null);
@@ -172,13 +185,11 @@
         // cast
         mIconController.setIcon(mSlotCast, R.drawable.stat_sys_cast, null);
         mIconController.setIconVisibility(mSlotCast, false);
-        mCast.addCallback(mCastCallback);
 
         // hotspot
         mIconController.setIcon(mSlotHotspot, R.drawable.stat_sys_hotspot,
                 mContext.getString(R.string.accessibility_status_bar_hotspot));
         mIconController.setIconVisibility(mSlotHotspot, mHotspot.isHotspotEnabled());
-        mHotspot.addCallback(mHotspotCallback);
 
         // managed profile
         mIconController.setIcon(mSlotManagedProfile, R.drawable.stat_sys_managed_profile_status,
@@ -189,15 +200,36 @@
         mIconController.setIcon(mSlotDataSaver, R.drawable.stat_sys_data_saver,
                 context.getString(R.string.accessibility_data_saver_on));
         mIconController.setIconVisibility(mSlotDataSaver, false);
+
+        mRotationLockController.addCallback(this);
+        mBluetooth.addCallback(this);
+        mProvisionedController.addCallback(this);
+        mZenController.addCallback(this);
+        mCast.addCallback(mCastCallback);
+        mHotspot.addCallback(mHotspotCallback);
+        mNextAlarm.addCallback(mNextAlarmCallback);
         mDataSaver.addCallback(this);
+        mKeyguardMonitor.addCallback(this);
+
+        SysUiServiceProvider.getComponent(mContext, CommandQueue.class).addCallbacks(this);
     }
 
-    public void setStatusBarKeyguardViewManager(
-            StatusBarKeyguardViewManager statusBarKeyguardViewManager) {
-        mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
+    public void destroy() {
+        mRotationLockController.removeCallback(this);
+        mBluetooth.removeCallback(this);
+        mProvisionedController.removeCallback(this);
+        mZenController.removeCallback(this);
+        mCast.removeCallback(mCastCallback);
+        mHotspot.removeCallback(mHotspotCallback);
+        mNextAlarm.removeCallback(mNextAlarmCallback);
+        mDataSaver.removeCallback(this);
+        mKeyguardMonitor.removeCallback(this);
+        SysUiServiceProvider.getComponent(mContext, CommandQueue.class).removeCallbacks(this);
+        mContext.unregisterReceiver(mIntentReceiver);
     }
 
-    public void setZenMode(int zen) {
+    @Override
+    public void onZenChanged(int zen) {
         mZen = zen;
         updateVolumeZen();
     }
@@ -394,7 +426,7 @@
         if (DEBUG) Log.v(TAG, "updateManagedProfile: mManagedProfileFocused: "
                 + mManagedProfileFocused);
         final boolean showIcon;
-        if (mManagedProfileFocused && !mStatusBarKeyguardViewManager.isShowing()) {
+        if (mManagedProfileFocused && !mKeyguardMonitor.isShowing()) {
             showIcon = true;
             mIconController.setIcon(mSlotManagedProfile,
                     R.drawable.stat_sys_managed_profile_status,
@@ -471,15 +503,20 @@
         }
     };
 
-    public void appTransitionStarting(long startTime, long duration) {
+    @Override
+    public void appTransitionStarting(long startTime, long duration, boolean forced) {
         updateManagedProfile();
     }
 
-    public void notifyKeyguardShowingChanged() {
+    @Override
+    public void onKeyguardShowingChanged() {
         updateManagedProfile();
     }
 
-    public void setCurrentUserSetup(boolean userSetup) {
+    @Override
+    public void onUserSetupChanged() {
+        boolean userSetup = mProvisionedController.isUserSetup(
+                mProvisionedController.getCurrentUser());
         if (mCurrentUserSetup == userSetup) return;
         mCurrentUserSetup = userSetup;
         updateAlarm();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
index 7e08812..b52c26f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
@@ -24,9 +24,13 @@
 import android.view.ViewGroup;
 import android.view.accessibility.AccessibilityEvent;
 
+import com.android.systemui.BatteryMeterView;
 import com.android.systemui.DejankUtils;
+import com.android.systemui.Dependency;
 import com.android.systemui.EventLogTags;
 import com.android.systemui.R;
+import com.android.systemui.statusbar.policy.DarkIconDispatcher;
+import com.android.systemui.statusbar.policy.DarkIconDispatcher.DarkReceiver;
 
 public class PhoneStatusBarView extends PanelBar {
     private static final String TAG = "PhoneStatusBarView";
@@ -48,6 +52,7 @@
             }
         }
     };
+    private DarkReceiver mBattery;
 
     public PhoneStatusBarView(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -70,6 +75,20 @@
     @Override
     public void onFinishInflate() {
         mBarTransitions.init();
+        mBattery = (DarkReceiver) findViewById(R.id.battery);
+    }
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        // Always have Battery meters in the status bar observe the dark/light modes.
+        Dependency.get(DarkIconDispatcher.class).addDarkReceiver(mBattery);
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        Dependency.get(DarkIconDispatcher.class).removeDarkReceiver(mBattery);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java
index 4307a2e..457ed6c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java
@@ -178,7 +178,7 @@
         SignalClusterView cluster = (SignalClusterView) findViewById(R.id.signal_cluster);
         int colorForeground = Utils.getColorAttr(getContext(), android.R.attr.colorForeground);
         float intensity = colorForeground == Color.WHITE ? 0 : 1;
-        cluster.setIconTint(colorForeground, intensity, new Rect(0, 0, 0, 0));
+        cluster.onDarkChanged(new Rect(0, 0, 0, 0), intensity, colorForeground);
         BatteryMeterView battery = (BatteryMeterView) findViewById(R.id.battery);
         int colorSecondary = Utils.getColorAttr(getContext(), android.R.attr.textColorSecondary);
         battery.setRawColors(colorForeground, colorSecondary);
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 942cf0e..433dc3f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -173,13 +173,16 @@
 import com.android.systemui.statusbar.SignalClusterView;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.notification.VisualStabilityManager;
+import com.android.systemui.statusbar.phone.StatusBarIconController.IconManager;
 import com.android.systemui.statusbar.phone.UnlockMethodCache.OnUnlockMethodChangedListener;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback;
 import com.android.systemui.statusbar.policy.BrightnessMirrorController;
+import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
+import com.android.systemui.statusbar.policy.DarkIconDispatcher;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
-import com.android.systemui.statusbar.policy.EncryptionHelper;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.statusbar.policy.KeyguardMonitor;
 import com.android.systemui.statusbar.policy.KeyguardMonitorImpl;
@@ -504,9 +507,6 @@
                 }
                 updateQsExpansionEnabled();
             }
-            if (mIconPolicy != null) {
-                mIconPolicy.setCurrentUserSetup(mUserSetup);
-            }
         }
     };
 
@@ -712,6 +712,8 @@
     private BatteryController mBatteryController;
     private LogMaker mStatusBarStateLog;
     private LockscreenGestureLogger mLockscreenGestureLogger = new LockscreenGestureLogger();
+    private NotificationIconAreaController mNotificationIconAreaController;
+    private ConfigurationListener mDensityChangeListener;
 
     private void recycleAllVisibilityObjects(ArraySet<NotificationVisibility> array) {
         final int N = array.size();
@@ -809,8 +811,6 @@
         final Configuration currentConfig = mContext.getResources().getConfiguration();
         mLocale = currentConfig.locale;
         mLayoutDirection = TextUtils.getLayoutDirectionFromLocale(mLocale);
-        mFontScale = currentConfig.fontScale;
-        mDensity = currentConfig.densityDpi;
 
         mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
         mKeyguardManager = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
@@ -847,7 +847,7 @@
         int N = iconSlots.size();
         int viewIndex = 0;
         for (int i=0; i < N; i++) {
-            setIcon(iconSlots.get(i), icons.get(i));
+            mCommandQueue.setIcon(iconSlots.get(i), icons.get(i));
         }
 
         // Set up the initial notification state.
@@ -914,7 +914,6 @@
 
         // Lastly, call to the icon policy to install/update all the icons.
         mIconPolicy = new PhoneStatusBarPolicy(mContext, mIconController);
-        mIconPolicy.setCurrentUserSetup(mUserSetup);
         mSettingsObserver.onChange(false); // set up
 
         mHeadsUpObserver.onChange(true); // set up
@@ -938,18 +937,25 @@
 
         mScreenPinningRequest = new ScreenPinningRequest(mContext);
         mFalsingManager = FalsingManager.getInstance(mContext);
+
         Dependency.get(ActivityStarterDelegate.class).setActivityStarterImpl(this);
+
+        mDensityChangeListener = new ConfigurationListener() {
+            @Override
+            public void onDensityOrFontScaleChanged() {
+                StatusBar.this.onDensityOrFontScaleChanged();
+            }
+        };
+        Dependency.get(ConfigurationController.class).addCallback(mDensityChangeListener);
     }
 
     protected void createIconController() {
-        mIconController = new StatusBarIconController(
-                mContext, mStatusBarView, mKeyguardStatusBar, this);
     }
 
     // ================================================================================
     // Constructing the view
     // ================================================================================
-    protected PhoneStatusBarView makeStatusBarView() {
+    protected void makeStatusBarView() {
         final Context context = mContext;
         updateDisplaySize(); // populates mDisplayMetrics
         updateResources();
@@ -958,14 +964,37 @@
         mStatusBarWindow.setService(this);
         mStatusBarWindow.setOnTouchListener(getStatusBarWindowTouchListener());
 
+        // TODO: Deal with the ugliness that comes from having some of the statusbar broken out
+        // into fragments, but the rest here, it leaves some awkward lifecycle and whatnot.
         mNotificationPanel = (NotificationPanelView) mStatusBarWindow.findViewById(
                 R.id.notification_panel);
+        mStackScroller = (NotificationStackScrollLayout) mStatusBarWindow.findViewById(
+                R.id.notification_stack_scroller);
         mNotificationPanel.setStatusBar(this);
         mNotificationPanel.setGroupManager(mGroupManager);
+        mKeyguardStatusBar = (KeyguardStatusBarView) mStatusBarWindow.findViewById(R.id.keyguard_header);
 
-        mStatusBarView = (PhoneStatusBarView) mStatusBarWindow.findViewById(R.id.status_bar);
-        mStatusBarView.setBar(this);
-        mStatusBarView.setPanel(mNotificationPanel);
+        mNotificationIconAreaController = SystemUIFactory.getInstance()
+                .createNotificationIconAreaController(context, this);
+        inflateShelf();
+        mNotificationIconAreaController.setupShelf(mNotificationShelf);
+        Dependency.get(DarkIconDispatcher.class).addDarkReceiver(mNotificationIconAreaController);
+        FragmentHostManager.get(mStatusBarWindow)
+                .addTagListener(CollapsedStatusBarFragment.TAG, (tag, fragment) -> {
+                    CollapsedStatusBarFragment statusBarFragment = (CollapsedStatusBarFragment) fragment;
+                    statusBarFragment.initNotificationIconArea(mNotificationIconAreaController);
+                    mStatusBarView = (PhoneStatusBarView) fragment.getView();
+                    mStatusBarView.setBar(this);
+                    mStatusBarView.setPanel(mNotificationPanel);
+                    mStatusBarView.setScrimController(mScrimController);
+                    setAreThereNotifications();
+                }).getFragmentManager()
+                .beginTransaction()
+                .replace(R.id.status_bar_container, new CollapsedStatusBarFragment(), CollapsedStatusBarFragment.TAG)
+                .commit();
+        Dependency.get(StatusBarIconController.class).addIconGroup(
+                new IconManager((ViewGroup) mKeyguardStatusBar.findViewById(R.id.statusIcons)));
+        mIconController = Dependency.get(StatusBarIconController.class);
 
         if (!ActivityManager.isHighEndGfx()) {
             mStatusBarWindow.setBackground(null);
@@ -1003,8 +1032,6 @@
         // figure out which pixel-format to use for the status bar.
         mPixelFormat = PixelFormat.OPAQUE;
 
-        mStackScroller = (NotificationStackScrollLayout) mStatusBarWindow.findViewById(
-                R.id.notification_stack_scroller);
         mStackScroller.setLongPressListener(getNotificationLongClicker());
         mStackScroller.setStatusBar(this);
         mStackScroller.setGroupManager(mGroupManager);
@@ -1012,7 +1039,6 @@
         mGroupManager.setOnGroupChangeListener(mStackScroller);
         mVisualStabilityManager.setVisibilityLocationProvider(mStackScroller);
 
-        inflateShelf();
         inflateEmptyShadeView();
         inflateDismissView();
         mExpandedContents = mStackScroller;
@@ -1025,7 +1051,6 @@
             mLockscreenWallpaper = new LockscreenWallpaper(mContext, this, mHandler);
         }
 
-        mKeyguardStatusBar = (KeyguardStatusBarView) mStatusBarWindow.findViewById(R.id.keyguard_header);
         mKeyguardStatusView =
                 (KeyguardStatusView) mStatusBarWindow.findViewById(R.id.keyguard_status_view);
         mKeyguardBottomArea =
@@ -1039,8 +1064,6 @@
         // set the initial view visibility
         setAreThereNotifications();
 
-        createIconController();
-
         // TODO: Find better place for this callback.
         mBatteryController.addCallback(new BatteryStateChangeCallback() {
             @Override
@@ -1057,7 +1080,7 @@
             }
         });
 
-        mLightBarController = new LightBarController(mIconController);
+        mLightBarController = new LightBarController();
         if (mNavigationBar != null) {
             mNavigationBar.setLightBarController(mLightBarController);
         }
@@ -1081,7 +1104,6 @@
         }
         mHeadsUpManager.addListener(mScrimController);
         mStackScroller.setScrimController(mScrimController);
-        mStatusBarView.setScrimController(mScrimController);
         mDozeScrimController = new DozeScrimController(mScrimController, context, mStackScroller,
                 mNotificationPanel);
 
@@ -1187,8 +1209,6 @@
 
         // Private API call to make the shadows look better for Recents
         ThreadedRenderer.overrideProperty("ambientRatio", String.valueOf(1.5f));
-
-        return mStatusBarView;
     }
 
     protected void createNavigationBar() {
@@ -1262,12 +1282,13 @@
         }
         // end old BaseStatusBar.onDensityOrFontScaleChanged().
         mScrimController.onDensityOrFontScaleChanged();
-        mStatusBarView.onDensityOrFontScaleChanged();
+        // TODO: Remove this.
+        if (mStatusBarView != null) mStatusBarView.onDensityOrFontScaleChanged();
         if (mBrightnessMirrorController != null) {
             mBrightnessMirrorController.onDensityOrFontScaleChanged();
         }
         inflateSignalClusters();
-        mIconController.onDensityOrFontScaleChanged();
+        mNotificationIconAreaController.onDensityOrFontScaleChanged(mContext);
         inflateDismissView();
         updateClearAll();
         inflateEmptyShadeView();
@@ -1283,12 +1304,11 @@
     }
 
     private void inflateSignalClusters() {
-        SignalClusterView signalClusterView = reinflateSignalCluster(mStatusBarView);
-        mIconController.setSignalCluster(signalClusterView);
         reinflateSignalCluster(mKeyguardStatusBar);
     }
 
-    private SignalClusterView reinflateSignalCluster(View view) {
+    public static SignalClusterView reinflateSignalCluster(View view) {
+        Context context = view.getContext();
         SignalClusterView signalCluster =
                 (SignalClusterView) view.findViewById(R.id.signal_cluster);
         if (signalCluster != null) {
@@ -1297,12 +1317,12 @@
                 ViewGroup viewParent = (ViewGroup) parent;
                 int index = viewParent.indexOfChild(signalCluster);
                 viewParent.removeView(signalCluster);
-                SignalClusterView newCluster = (SignalClusterView) LayoutInflater.from(mContext)
+                SignalClusterView newCluster = (SignalClusterView) LayoutInflater.from(context)
                         .inflate(R.layout.signal_cluster_view, viewParent, false);
                 ViewGroup.MarginLayoutParams layoutParams =
                         (ViewGroup.MarginLayoutParams) viewParent.getLayoutParams();
                 layoutParams.setMarginsRelative(
-                        mContext.getResources().getDimensionPixelSize(
+                        context.getResources().getDimensionPixelSize(
                                 R.dimen.signal_cluster_margin_start),
                         0, 0, 0);
                 newCluster.setLayoutParams(layoutParams);
@@ -1426,9 +1446,6 @@
             updateNotifications();
         }
         // end old BaseStatusBar.setZenMode().
-        if (mIconPolicy != null) {
-            mIconPolicy.setZenMode(mode);
-        }
     }
 
     protected void startKeyguard() {
@@ -1445,7 +1462,6 @@
         mKeyguardIndicationController.setUserInfoController(
                 Dependency.get(UserInfoController.class));
         mFingerprintUnlockController.setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager);
-        mIconPolicy.setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager);
         mRemoteInputController.addCallback(mStatusBarKeyguardViewManager);
 
         mRemoteInputController.addCallback(new RemoteInputController.Callback() {
@@ -1526,16 +1542,6 @@
         }
     }
 
-    @Override
-    public void setIcon(String slot, StatusBarIcon icon) {
-        mIconController.setIcon(slot, icon);
-    }
-
-    @Override
-    public void removeIcon(String slot) {
-        mIconController.removeIcon(slot);
-    }
-
     public UserHandle getCurrentUserHandle() {
         return new UserHandle(mCurrentUserId);
     }
@@ -1898,7 +1904,7 @@
         updateQsExpansionEnabled();
 
         // Let's also update the icons
-        mIconController.updateNotificationIcons(mNotificationData);
+        mNotificationIconAreaController.updateNotificationIcons(mNotificationData);
     }
 
     /**
@@ -2079,24 +2085,26 @@
                     hasActiveNotifications() + " clearable=" + clearable);
         }
 
-        final View nlo = mStatusBarView.findViewById(R.id.notification_lights_out);
-        final boolean showDot = hasActiveNotifications() && !areLightsOn();
-        if (showDot != (nlo.getAlpha() == 1.0f)) {
-            if (showDot) {
-                nlo.setAlpha(0f);
-                nlo.setVisibility(View.VISIBLE);
+        if (mStatusBarView != null) {
+            final View nlo = mStatusBarView.findViewById(R.id.notification_lights_out);
+            final boolean showDot = hasActiveNotifications() && !areLightsOn();
+            if (showDot != (nlo.getAlpha() == 1.0f)) {
+                if (showDot) {
+                    nlo.setAlpha(0f);
+                    nlo.setVisibility(View.VISIBLE);
+                }
+                nlo.animate()
+                        .alpha(showDot ? 1 : 0)
+                        .setDuration(showDot ? 750 : 250)
+                        .setInterpolator(new AccelerateInterpolator(2.0f))
+                        .setListener(showDot ? null : new AnimatorListenerAdapter() {
+                            @Override
+                            public void onAnimationEnd(Animator _a) {
+                                nlo.setVisibility(View.GONE);
+                            }
+                        })
+                        .start();
             }
-            nlo.animate()
-                .alpha(showDot?1:0)
-                .setDuration(showDot?750:250)
-                .setInterpolator(new AccelerateInterpolator(2.0f))
-                .setListener(showDot ? null : new AnimatorListenerAdapter() {
-                    @Override
-                    public void onAnimationEnd(Animator _a) {
-                        nlo.setVisibility(View.GONE);
-                    }
-                })
-                .start();
         }
 
         findAndUpdateMediaNotifications();
@@ -2420,26 +2428,6 @@
                 && mFalsingManager.isReportingEnabled() ? View.VISIBLE : View.INVISIBLE);
     }
 
-    protected int adjustDisableFlags(int state) {
-        if (!mLaunchTransitionFadingAway && !mKeyguardFadingAway && shouldHideNotificationIcons()) {
-            state |= StatusBarManager.DISABLE_NOTIFICATION_ICONS;
-            state |= StatusBarManager.DISABLE_SYSTEM_INFO;
-        }
-        if (mNetworkController != null && EncryptionHelper.IS_DATA_ENCRYPTED) {
-            if (mNetworkController.hasEmergencyCryptKeeperText()) {
-                state |= StatusBarManager.DISABLE_NOTIFICATION_ICONS;
-            }
-            if (!mNetworkController.isRadioOn()) {
-                state |= StatusBarManager.DISABLE_SYSTEM_INFO;
-            }
-        }
-        return state;
-    }
-
-    private boolean shouldHideNotificationIcons() {
-        return mExpandedVisible && mNotificationPanel.shouldHideNotificationIcons();
-    }
-
     /**
      * State is one or more of the DISABLE constants from StatusBarManager.
      */
@@ -2448,7 +2436,6 @@
         animate &= mStatusBarWindowState != WINDOW_STATE_HIDDEN;
         mDisabledUnmodified1 = state1;
         mDisabledUnmodified2 = state2;
-        state1 = adjustDisableFlags(state1);
         final int old1 = mDisabled1;
         final int diff1 = state1 ^ old1;
         mDisabled1 = state1;
@@ -2490,28 +2477,13 @@
         flagdbg.append(">");
         Log.d(TAG, flagdbg.toString());
 
-        if ((diff1 & StatusBarManager.DISABLE_SYSTEM_INFO) != 0) {
-            if ((state1 & StatusBarManager.DISABLE_SYSTEM_INFO) != 0) {
-                mIconController.hideSystemIconArea(animate);
-            } else {
-                mIconController.showSystemIconArea(animate);
-            }
-        }
-
-        if ((diff1 & StatusBarManager.DISABLE_CLOCK) != 0) {
-            boolean visible = (state1 & StatusBarManager.DISABLE_CLOCK) == 0;
-            mIconController.setClockVisibilityByPolicy(visible);
-        }
         if ((diff1 & StatusBarManager.DISABLE_EXPAND) != 0) {
             if ((state1 & StatusBarManager.DISABLE_EXPAND) != 0) {
                 animateCollapsePanels();
             }
         }
 
-        if ((diff1 & (StatusBarManager.DISABLE_HOME
-                        | StatusBarManager.DISABLE_RECENT
-                        | StatusBarManager.DISABLE_BACK
-                        | StatusBarManager.DISABLE_SEARCH)) != 0) {
+        if ((diff1 & StatusBarManager.DISABLE_RECENT) != 0) {
             if ((state1 & StatusBarManager.DISABLE_RECENT) != 0) {
                 // close recents if it's visible
                 mHandler.removeMessages(MSG_HIDE_RECENT_APPS);
@@ -2519,14 +2491,6 @@
             }
         }
 
-        if ((diff1 & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) {
-            if ((state1 & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) {
-                mIconController.hideNotificationIconArea(animate);
-            } else {
-                mIconController.showNotificationIconArea(animate);
-            }
-        }
-
         if ((diff1 & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) != 0) {
             mDisableNotificationAlerts =
                     (state1 & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) != 0;
@@ -2740,10 +2704,6 @@
         mFalsingManager.onScreenOff();
     }
 
-    public NotificationShelf getNotificationShelf() {
-        return mNotificationShelf;
-    }
-
     public NotificationStackScrollLayout getNotificationScrollLayout() {
         return mStackScroller;
     }
@@ -2757,6 +2717,14 @@
         updateNotifications();
     }
 
+    public boolean isLaunchTransitionFadingAway() {
+        return mLaunchTransitionFadingAway;
+    }
+
+    public boolean shouldHideNotificationIcons() {
+        return mNotificationPanel.shouldHideNotificationIcons();
+    }
+
     /**
      * All changes to the status bar and notifications funnel through here and are batched.
      */
@@ -3162,7 +3130,8 @@
 
     void checkBarModes() {
         if (mDemoMode) return;
-        checkBarMode(mStatusBarMode, mStatusBarWindowState, getStatusBarTransitions());
+        if (mStatusBarView != null) checkBarMode(mStatusBarMode, mStatusBarWindowState,
+                getStatusBarTransitions());
         if (mNavigationBar != null) mNavigationBar.checkNavBarModes();
         mNoAnimationOnNextBarModeChange = false;
     }
@@ -3183,7 +3152,9 @@
     }
 
     private void finishBarAnimations() {
-        mStatusBarView.getBarTransitions().finishAnimations();
+        if (mStatusBarView != null) {
+            mStatusBarView.getBarTransitions().finishAnimations();
+        }
         if (mNavigationBar != null) {
             mNavigationBar.finishBarAnimations();
         }
@@ -3363,8 +3334,6 @@
                 mNotificationData.dump(pw, "  ");
             }
 
-            mIconController.dump(pw);
-
             if (false) {
                 pw.println("see the logcat for a dump of the views we have created.");
                 // must happen on ui thread
@@ -3617,15 +3586,6 @@
     protected void onConfigurationChanged(Configuration newConfig) {
         updateResources();
         updateDisplaySize(); // populates mDisplayMetrics
-        // Begin old BaseStatusBar.onConfigurationChanged
-        final float fontScale = newConfig.fontScale;
-        final int density = newConfig.densityDpi;
-        if (density != mDensity || mFontScale != fontScale) {
-            onDensityOrFontScaleChanged();
-            mDensity = density;
-            mFontScale = fontScale;
-        }
-        // End old BaseStatusBar.onConfigurationChanged
 
         if (DEBUG) {
             Log.v(TAG, "configuration changed: " + mContext.getResources().getConfiguration());
@@ -3946,17 +3906,12 @@
         mContext.unregisterReceiver(mDemoReceiver);
         mAssistManager.destroy();
 
-        final SignalClusterView signalCluster =
-                (SignalClusterView) mStatusBarView.findViewById(R.id.signal_cluster);
-        final SignalClusterView signalClusterKeyguard =
-                (SignalClusterView) mKeyguardStatusBar.findViewById(R.id.signal_cluster);
-        final SignalClusterView signalClusterQs =
-                (SignalClusterView) mHeader.findViewById(R.id.signal_cluster);
         if (mQSPanel != null && mQSPanel.getHost() != null) {
             mQSPanel.getHost().destroy();
         }
         Dependency.get(ActivityStarterDelegate.class).setActivityStarterImpl(null);
         mDeviceProvisionedController.removeCallback(mUserSetupObserver);
+        Dependency.get(ConfigurationController.class).removeCallback(mDensityChangeListener);
     }
 
     private boolean mDemoModeAllowed;
@@ -3989,7 +3944,7 @@
             mBatteryController.dispatchDemoCommand(command, args);
         }
         if (modeChange || command.equals(COMMAND_STATUS)) {
-            mIconController.dispatchDemoCommand(command, args);
+            ((StatusBarIconControllerImpl) mIconController).dispatchDemoCommand(command, args);
         }
         if (mNetworkController != null && (modeChange || command.equals(COMMAND_NETWORK))) {
             mNetworkController.dispatchDemoCommand(command, args);
@@ -4132,14 +4087,8 @@
                                 onLaunchTransitionFadingEnded();
                             }
                         });
-                mIconController.getTransitionsController().appTransitionStarting(
-                        SystemClock.uptimeMillis(),
-                        LightBarTransitionsController.DEFAULT_TINT_ANIMATION_DURATION);
-                if (mNavigationBar != null) {
-                    mNavigationBar.doAppTransitionStarting(
-                            SystemClock.uptimeMillis(),
-                            LightBarTransitionsController.DEFAULT_TINT_ANIMATION_DURATION);
-                }
+                mCommandQueue.appTransitionStarting(SystemClock.uptimeMillis(),
+                        LightBarTransitionsController.DEFAULT_TINT_ANIMATION_DURATION, true);
             }
         };
         if (mNotificationPanel.isLaunchTransitionRunning()) {
@@ -4264,11 +4213,8 @@
         // Treat Keyguard exit animation as an app transition to achieve nice transition for status
         // bar.
         mKeyguardGoingAway = true;
-        mIconController.getTransitionsController().appTransitionPending();
-        if (mNavigationBar != null) {
-            mNavigationBar.setKeyguardGoingAway(true);
-            mNavigationBar.appTransitionPending();
-        }
+        mKeyguardMonitor.notifyKeyguardGoingAway(true);
+        mCommandQueue.appTransitionPending(true);
     }
 
     /**
@@ -4283,16 +4229,14 @@
         mKeyguardFadingAwayDelay = delay;
         mKeyguardFadingAwayDuration = fadeoutDuration;
         mWaitingForKeyguardExit = false;
-        mIconController.getTransitionsController().appTransitionStarting(
-                startTime + fadeoutDuration
+        mCommandQueue.appTransitionStarting(startTime + fadeoutDuration
                         - LightBarTransitionsController.DEFAULT_TINT_ANIMATION_DURATION,
-                LightBarTransitionsController.DEFAULT_TINT_ANIMATION_DURATION);
+                LightBarTransitionsController.DEFAULT_TINT_ANIMATION_DURATION, true);
         recomputeDisableFlags(fadeoutDuration > 0 /* animate */);
-        if (mNavigationBar != null) {
-            mNavigationBar.doAppTransitionStarting(
+        mCommandQueue.appTransitionStarting(
                     startTime - LightBarTransitionsController.DEFAULT_TINT_ANIMATION_DURATION,
-                    LightBarTransitionsController.DEFAULT_TINT_ANIMATION_DURATION);
-        }
+                    LightBarTransitionsController.DEFAULT_TINT_ANIMATION_DURATION, true);
+        mKeyguardMonitor.notifyKeyguardFadingAway(delay, fadeoutDuration);
     }
 
     public boolean isKeyguardFadingAway() {
@@ -4305,9 +4249,7 @@
     public void finishKeyguardFadingAway() {
         mKeyguardFadingAway = false;
         mKeyguardGoingAway = false;
-        if (mNavigationBar != null) {
-            mNavigationBar.setKeyguardGoingAway(false);
-        }
+        mKeyguardMonitor.notifyKeyguardDoneFading();
     }
 
     public void stopWaitingForKeyguardExit() {
@@ -4357,7 +4299,6 @@
         } else {
             mScrimController.setKeyguardShowing(false);
         }
-        mIconPolicy.notifyKeyguardShowingChanged();
         mNotificationPanel.setBarState(mState, mKeyguardFadingAway, goingToFullShade);
         updateDozingState();
         updatePublicMode();
@@ -4945,33 +4886,11 @@
     }
 
     @Override
-    public void appTransitionPending() {
-        // Use own timings when Keyguard is going away, see keyguardGoingAway and
-        // setKeyguardFadingAway
-        if (!mKeyguardFadingAway) {
-            mIconController.getTransitionsController().appTransitionPending();
-        }
-    }
-
-    @Override
     public void appTransitionCancelled() {
-        mIconController.getTransitionsController().appTransitionCancelled();
         EventBus.getDefault().send(new AppTransitionFinishedEvent());
     }
 
     @Override
-    public void appTransitionStarting(long startTime, long duration) {
-        // Use own timings when Keyguard is going away, see keyguardGoingAway and
-        // setKeyguardFadingAway.
-        if (!mKeyguardGoingAway) {
-            mIconController.getTransitionsController().appTransitionStarting(startTime, duration);
-        }
-        if (mIconPolicy != null) {
-            mIconPolicy.appTransitionStarting(startTime, duration);
-        }
-    }
-
-    @Override
     public void appTransitionFinished() {
         EventBus.getDefault().send(new AppTransitionFinishedEvent());
     }
@@ -5180,7 +5099,6 @@
     private boolean mVisibleToUser;
 
     private Locale mLocale;
-    private float mFontScale;
 
     protected boolean mUseHeadsUp = false;
     protected boolean mHeadsUpTicker = false;
@@ -5197,7 +5115,6 @@
     private final SparseBooleanArray mUsersAllowingNotifications = new SparseBooleanArray();
 
     private UserManager mUserManager;
-    private int mDensity;
 
     protected KeyguardManager mKeyguardManager;
     private LockPatternUtils mLockPatternUtils;
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 41f8a91..c339da7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
@@ -1,502 +1,48 @@
 /*
- * Copyright (C) 2015 The Android Open Source Project
+ * 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
+ * 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
+ * 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.animation.ArgbEvaluator;
 import android.content.Context;
-import android.content.res.ColorStateList;
-import android.content.res.Resources;
-import android.graphics.Color;
-import android.graphics.Rect;
-import android.graphics.drawable.Icon;
-import android.os.Bundle;
-import android.os.UserHandle;
 import android.text.TextUtils;
 import android.util.ArraySet;
-import android.util.TypedValue;
 import android.view.Gravity;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
-import android.widget.TextView;
+
 import com.android.internal.statusbar.StatusBarIcon;
-import com.android.systemui.BatteryMeterView;
 import com.android.systemui.Dependency;
-import com.android.systemui.FontSizeUtils;
-import com.android.systemui.Interpolators;
 import com.android.systemui.R;
-import com.android.systemui.SystemUIFactory;
-import com.android.systemui.statusbar.NotificationData;
-import com.android.systemui.statusbar.NotificationShelf;
-import com.android.systemui.statusbar.SignalClusterView;
 import com.android.systemui.statusbar.StatusBarIconView;
-import com.android.systemui.tuner.TunerService;
-import com.android.systemui.tuner.TunerService.Tunable;
+import com.android.systemui.statusbar.policy.DarkIconDispatcher;
 
-import java.io.PrintWriter;
-import java.util.ArrayList;
+public interface StatusBarIconController {
 
-/**
- * Controls everything regarding the icons in the status bar and on Keyguard, including, but not
- * limited to: notification icons, signal cluster, additional status icons, and clock in the status
- * bar.
- */
-public class StatusBarIconController extends StatusBarIconList implements Tunable {
+    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 static final String ICON_BLACKLIST = "icon_blacklist";
-    public static final int DEFAULT_ICON_TINT = Color.WHITE;
-
-    private Context mContext;
-    private StatusBar mStatusBar;
-    private DemoStatusIcons mDemoStatusIcons;
-
-    private LinearLayout mSystemIconArea;
-    private LinearLayout mStatusIcons;
-    private SignalClusterView mSignalCluster;
-    private LinearLayout mStatusIconsKeyguard;
-
-    private NotificationIconAreaController mNotificationIconAreaController;
-    private View mNotificationIconAreaInner;
-    private NotificationShelf mNotificationShelf;
-
-    private BatteryMeterView mBatteryMeterView;
-    private BatteryMeterView mBatteryMeterViewKeyguard;
-    private TextView mClock;
-
-    private int mIconSize;
-    private int mIconHPadding;
-
-    private int mIconTint = DEFAULT_ICON_TINT;
-    private float mDarkIntensity;
-    private final Rect mTintArea = new Rect();
-    private static final Rect sTmpRect = new Rect();
-    private static final int[] sTmpInt2 = new int[2];
-
-    private int mDarkModeIconColorSingleTone;
-    private int mLightModeIconColorSingleTone;
-
-    private final LightBarTransitionsController mTransitionsController;
-
-    private boolean mClockVisibleByPolicy = true;
-    private boolean mClockVisibleByUser = true;
-
-    private final ArraySet<String> mIconBlacklist = new ArraySet<>();
-
-    public StatusBarIconController(Context context, View statusBar, View keyguardStatusBar,
-            StatusBar phoneStatusBar) {
-        super(context.getResources().getStringArray(
-                com.android.internal.R.array.config_statusBarIcons));
-        mContext = context;
-        mStatusBar = phoneStatusBar;
-        mSystemIconArea = (LinearLayout) statusBar.findViewById(R.id.system_icon_area);
-        mStatusIcons = (LinearLayout) statusBar.findViewById(R.id.statusIcons);
-        mSignalCluster = (SignalClusterView) statusBar.findViewById(R.id.signal_cluster);
-
-        mNotificationShelf = phoneStatusBar.getNotificationShelf();
-        mNotificationIconAreaController = SystemUIFactory.getInstance()
-                .createNotificationIconAreaController(context, phoneStatusBar);
-        mNotificationIconAreaInner =
-                mNotificationIconAreaController.getNotificationInnerAreaView();
-
-        ViewGroup notificationIconArea =
-                (ViewGroup) statusBar.findViewById(R.id.notification_icon_area);
-        notificationIconArea.addView(mNotificationIconAreaInner);
-
-        mStatusIconsKeyguard = (LinearLayout) keyguardStatusBar.findViewById(R.id.statusIcons);
-
-        mBatteryMeterView = (BatteryMeterView) statusBar.findViewById(R.id.battery);
-        mBatteryMeterViewKeyguard = (BatteryMeterView) keyguardStatusBar.findViewById(R.id.battery);
-        scaleBatteryMeterViews(context);
-
-        mClock = (TextView) statusBar.findViewById(R.id.clock);
-        mDarkModeIconColorSingleTone = context.getColor(R.color.dark_mode_icon_color_single_tone);
-        mLightModeIconColorSingleTone = context.getColor(R.color.light_mode_icon_color_single_tone);
-        loadDimens();
-
-        Dependency.get(TunerService.class).addTunable(this, ICON_BLACKLIST);
-
-        mTransitionsController = new LightBarTransitionsController(this::setIconTintInternal);
-    }
-
-    public void setSignalCluster(SignalClusterView signalCluster) {
-        mSignalCluster = signalCluster;
-    }
-
-    public LightBarTransitionsController getTransitionsController() {
-        return mTransitionsController;
-    }
-
-    /**
-     * Looks up the scale factor for status bar icons and scales the battery view by that amount.
-     */
-    private void scaleBatteryMeterViews(Context context) {
-        Resources res = context.getResources();
-        TypedValue typedValue = new TypedValue();
-
-        res.getValue(R.dimen.status_bar_icon_scale_factor, typedValue, true);
-        float iconScaleFactor = typedValue.getFloat();
-
-        int batteryHeight = res.getDimensionPixelSize(R.dimen.status_bar_battery_icon_height);
-        int batteryWidth = res.getDimensionPixelSize(R.dimen.status_bar_battery_icon_width);
-        int marginBottom = res.getDimensionPixelSize(R.dimen.battery_margin_bottom);
-
-        LinearLayout.LayoutParams scaledLayoutParams = new LinearLayout.LayoutParams(
-                (int) (batteryWidth * iconScaleFactor), (int) (batteryHeight * iconScaleFactor));
-        scaledLayoutParams.setMarginsRelative(0, 0, 0, marginBottom);
-
-        mBatteryMeterView.setLayoutParams(scaledLayoutParams);
-        mBatteryMeterViewKeyguard.setLayoutParams(scaledLayoutParams);
-    }
-
-    @Override
-    public void onTuningChanged(String key, String newValue) {
-        if (!ICON_BLACKLIST.equals(key)) {
-            return;
-        }
-        mIconBlacklist.clear();
-        mIconBlacklist.addAll(getIconBlacklist(newValue));
-        ArrayList<StatusBarIconView> views = new ArrayList<StatusBarIconView>();
-        // Get all the current views.
-        for (int i = 0; i < mStatusIcons.getChildCount(); i++) {
-            views.add((StatusBarIconView) mStatusIcons.getChildAt(i));
-        }
-        // Remove all the icons.
-        for (int i = views.size() - 1; i >= 0; i--) {
-            removeIcon(views.get(i).getSlot());
-        }
-        // Add them all back
-        for (int i = 0; i < views.size(); i++) {
-            setIcon(views.get(i).getSlot(), views.get(i).getStatusBarIcon());
-        }
-
-        setClockVisibleByUser(!StatusBarIconController.getIconBlacklist(newValue)
-                .contains("clock"));
-        updateClockVisibility();
-    }
-    private void loadDimens() {
-        mIconSize = mContext.getResources().getDimensionPixelSize(
-                com.android.internal.R.dimen.status_bar_icon_size);
-        mIconHPadding = mContext.getResources().getDimensionPixelSize(
-                R.dimen.status_bar_icon_padding);
-    }
-
-    private void addSystemIcon(int index, StatusBarIcon icon) {
-        String slot = getSlot(index);
-        int viewIndex = getViewIndex(index);
-        boolean blocked = mIconBlacklist.contains(slot);
-        StatusBarIconView view = new StatusBarIconView(mContext, slot, null, blocked);
-        view.set(icon);
-
-        LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
-                ViewGroup.LayoutParams.WRAP_CONTENT, mIconSize);
-        lp.setMargins(mIconHPadding, 0, mIconHPadding, 0);
-        mStatusIcons.addView(view, viewIndex, lp);
-
-        view = new StatusBarIconView(mContext, slot, null, blocked);
-        view.set(icon);
-        mStatusIconsKeyguard.addView(view, viewIndex, new LinearLayout.LayoutParams(
-                ViewGroup.LayoutParams.WRAP_CONTENT, mIconSize));
-        applyIconTint();
-    }
-
-    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);
-        } else {
-            icon.icon = Icon.createWithResource(mContext, resourceId);
-            icon.contentDescription = contentDescription;
-            handleSet(index, icon);
-        }
-    }
-
-    public void setExternalIcon(String slot) {
-        int viewIndex = getViewIndex(getSlotIndex(slot));
-        int height = mContext.getResources().getDimensionPixelSize(
-                R.dimen.status_bar_icon_drawing_size);
-        ImageView imageView = (ImageView) mStatusIcons.getChildAt(viewIndex);
-        imageView.setScaleType(ImageView.ScaleType.FIT_CENTER);
-        imageView.setAdjustViewBounds(true);
-        setHeightAndCenter(imageView, height);
-        imageView = (ImageView) mStatusIconsKeyguard.getChildAt(viewIndex);
-        imageView.setScaleType(ImageView.ScaleType.FIT_CENTER);
-        imageView.setAdjustViewBounds(true);
-        setHeightAndCenter(imageView, height);
-    }
-
-    private void setHeightAndCenter(ImageView imageView, int height) {
-        ViewGroup.LayoutParams params = imageView.getLayoutParams();
-        params.height = height;
-        if (params instanceof LinearLayout.LayoutParams) {
-            ((LinearLayout.LayoutParams) params).gravity = Gravity.CENTER_VERTICAL;
-        }
-        imageView.setLayoutParams(params);
-    }
-
-    public void setIcon(String slot, StatusBarIcon icon) {
-        setIcon(getSlotIndex(slot), icon);
-    }
-
-    public void removeIcon(String slot) {
-        int index = getSlotIndex(slot);
-        removeIcon(index);
-    }
-
-    public void setIconVisibility(String slot, boolean visibility) {
-        int index = getSlotIndex(slot);
-        StatusBarIcon icon = getIcon(index);
-        if (icon == null || icon.visible == visibility) {
-            return;
-        }
-        icon.visible = visibility;
-        handleSet(index, icon);
-    }
-
-    @Override
-    public void removeIcon(int index) {
-        if (getIcon(index) == null) {
-            return;
-        }
-        super.removeIcon(index);
-        int viewIndex = getViewIndex(index);
-        mStatusIcons.removeViewAt(viewIndex);
-        mStatusIconsKeyguard.removeViewAt(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, StatusBarIcon icon) {
-        int viewIndex = getViewIndex(index);
-        StatusBarIconView view = (StatusBarIconView) mStatusIcons.getChildAt(viewIndex);
-        view.set(icon);
-        view = (StatusBarIconView) mStatusIconsKeyguard.getChildAt(viewIndex);
-        view.set(icon);
-        applyIconTint();
-    }
-
-    public void updateNotificationIcons(NotificationData notificationData) {
-        mNotificationIconAreaController.updateNotificationIcons(notificationData);
-    }
-
-    public void hideSystemIconArea(boolean animate) {
-        animateHide(mSystemIconArea, animate);
-    }
-
-    public void showSystemIconArea(boolean animate) {
-        animateShow(mSystemIconArea, animate);
-    }
-
-    public void hideNotificationIconArea(boolean animate) {
-        animateHide(mNotificationIconAreaInner, animate);
-    }
-
-    public void showNotificationIconArea(boolean animate) {
-        animateShow(mNotificationIconAreaInner, animate);
-    }
-
-    public void setClockVisibleByUser(boolean visible) {
-        mClockVisibleByUser = visible;
-        updateClockVisibility();
-    }
-
-    public void setClockVisibilityByPolicy(boolean visible) {
-        mClockVisibleByPolicy = visible;
-        updateClockVisibility();
-    }
-
-    private void updateClockVisibility() {
-        int visibility = (mClockVisibleByPolicy && mClockVisibleByUser)
-                ? View.VISIBLE : View.GONE;
-        mClock.setVisibility(visibility);
-    }
-
-    public void dump(PrintWriter pw) {
-        int N = mStatusIcons.getChildCount();
-        pw.println("  icon views: " + N);
-        for (int i=0; i<N; i++) {
-            StatusBarIconView ic = (StatusBarIconView) mStatusIcons.getChildAt(i);
-            pw.println("    [" + i + "] icon=" + ic);
-        }
-        super.dump(pw);
-    }
-
-    public void dispatchDemoCommand(String command, Bundle args) {
-        if (mDemoStatusIcons == null) {
-            mDemoStatusIcons = new DemoStatusIcons(mStatusIcons, mIconSize);
-        }
-        mDemoStatusIcons.dispatchDemoCommand(command, args);
-    }
-
-    /**
-     * Hides a view.
-     */
-    private void animateHide(final View v, boolean animate) {
-        v.animate().cancel();
-        if (!animate) {
-            v.setAlpha(0f);
-            v.setVisibility(View.INVISIBLE);
-            return;
-        }
-        v.animate()
-                .alpha(0f)
-                .setDuration(160)
-                .setStartDelay(0)
-                .setInterpolator(Interpolators.ALPHA_OUT)
-                .withEndAction(new Runnable() {
-                    @Override
-                    public void run() {
-                        v.setVisibility(View.INVISIBLE);
-                    }
-                });
-    }
-
-    /**
-     * Shows a view, and synchronizes the animation with Keyguard exit animations, if applicable.
-     */
-    private void animateShow(View v, boolean animate) {
-        v.animate().cancel();
-        v.setVisibility(View.VISIBLE);
-        if (!animate) {
-            v.setAlpha(1f);
-            return;
-        }
-        v.animate()
-                .alpha(1f)
-                .setDuration(320)
-                .setInterpolator(Interpolators.ALPHA_IN)
-                .setStartDelay(50)
-
-                // We need to clean up any pending end action from animateHide if we call
-                // both hide and show in the same frame before the animation actually gets started.
-                // cancel() doesn't really remove the end action.
-                .withEndAction(null);
-
-        // Synchronize the motion with the Keyguard fading if necessary.
-        if (mStatusBar.isKeyguardFadingAway()) {
-            v.animate()
-                    .setDuration(mStatusBar.getKeyguardFadingAwayDuration())
-                    .setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN)
-                    .setStartDelay(mStatusBar.getKeyguardFadingAwayDelay())
-                    .start();
-        }
-    }
-
-    /**
-     * Sets the dark area so {@link #setIconsDark} only affects the icons in the specified area.
-     *
-     * @param darkArea the area in which icons should change it's tint, in logical screen
-     *                 coordinates
-     */
-    public void setIconsDarkArea(Rect darkArea) {
-        if (darkArea == null && mTintArea.isEmpty()) {
-            return;
-        }
-        if (darkArea == null) {
-            mTintArea.setEmpty();
-        } else {
-            mTintArea.set(darkArea);
-        }
-        applyIconTint();
-        mNotificationIconAreaController.setTintArea(darkArea);
-    }
-
-    private void setIconTintInternal(float darkIntensity) {
-        mDarkIntensity = darkIntensity;
-        mIconTint = (int) ArgbEvaluator.getInstance().evaluate(darkIntensity,
-                mLightModeIconColorSingleTone, mDarkModeIconColorSingleTone);
-        mNotificationIconAreaController.setIconTint(mIconTint);
-        applyIconTint();
-    }
-
-    /**
-     * @return the tint to apply to {@param view} depending on the desired tint {@param color} and
-     *         the screen {@param tintArea} in which to apply that tint
-     */
-    public static int getTint(Rect tintArea, View view, int color) {
-        if (isInArea(tintArea, view)) {
-            return color;
-        } else {
-            return DEFAULT_ICON_TINT;
-        }
-    }
-
-    /**
-     * @return the dark intensity to apply to {@param view} depending on the desired dark
-     *         {@param intensity} and the screen {@param tintArea} in which to apply that intensity
-     */
-    public static float getDarkIntensity(Rect tintArea, View view, float intensity) {
-        if (isInArea(tintArea, view)) {
-            return intensity;
-        } else {
-            return 0f;
-        }
-    }
-
-    /**
-     * @return true if more than half of the {@param view} area are in {@param area}, false
-     *         otherwise
-     */
-    private static boolean isInArea(Rect area, View view) {
-        if (area.isEmpty()) {
-            return true;
-        }
-        sTmpRect.set(area);
-        view.getLocationOnScreen(sTmpInt2);
-        int left = sTmpInt2[0];
-
-        int intersectStart = Math.max(left, area.left);
-        int intersectEnd = Math.min(left + view.getWidth(), area.right);
-        int intersectAmount = Math.max(0, intersectEnd - intersectStart);
-
-        boolean coversFullStatusBar = area.top <= 0;
-        boolean majorityOfWidth = 2 * intersectAmount > view.getWidth();
-        return majorityOfWidth && coversFullStatusBar;
-    }
-
-    private void applyIconTint() {
-        for (int i = 0; i < mStatusIcons.getChildCount(); i++) {
-            StatusBarIconView v = (StatusBarIconView) mStatusIcons.getChildAt(i);
-            v.setImageTintList(ColorStateList.valueOf(getTint(mTintArea, v, mIconTint)));
-        }
-        mSignalCluster.setIconTint(mIconTint, mDarkIntensity, mTintArea);
-        mBatteryMeterView.setDarkIntensity(
-                isInArea(mTintArea, mBatteryMeterView) ? mDarkIntensity : 0);
-        mClock.setTextColor(getTint(mTintArea, mClock, mIconTint));
-    }
 
     public static ArraySet<String> getIconBlacklist(String blackListStr) {
-        ArraySet<String> ret = new ArraySet<String>();
+        ArraySet<String> ret = new ArraySet<>();
         if (blackListStr == null) {
             blackListStr = "rotate,headset";
         }
@@ -509,34 +55,110 @@
         return ret;
     }
 
-    public void onDensityOrFontScaleChanged() {
-        loadDimens();
-        mNotificationIconAreaController.onDensityOrFontScaleChanged(mContext);
-        updateClock();
-        for (int i = 0; i < mStatusIcons.getChildCount(); i++) {
-            View child = mStatusIcons.getChildAt(i);
+
+    /**
+     * Version of ViewGroup that observers state from the DarkIconDispatcher.
+     */
+    public static class DarkIconManager extends IconManager {
+        private final DarkIconDispatcher mDarkIconDispatcher;
+        private int mIconHPadding;
+
+        public DarkIconManager(LinearLayout linearLayout) {
+            super(linearLayout);
+            mIconHPadding = mContext.getResources().getDimensionPixelSize(
+                    R.dimen.status_bar_icon_padding);
+            mDarkIconDispatcher = Dependency.get(DarkIconDispatcher.class);
+        }
+
+        @Override
+        protected void onIconAdded(int index, String slot, boolean blocked, StatusBarIcon icon) {
+            StatusBarIconView view = new StatusBarIconView(mContext, slot, null, blocked);
             LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
                     ViewGroup.LayoutParams.WRAP_CONTENT, mIconSize);
             lp.setMargins(mIconHPadding, 0, mIconHPadding, 0);
-            child.setLayoutParams(lp);
+            mGroup.addView(view, index, lp);
+            mDarkIconDispatcher.addDarkReceiver(view);
         }
-        for (int i = 0; i < mStatusIconsKeyguard.getChildCount(); i++) {
-            View child = mStatusIconsKeyguard.getChildAt(i);
-            LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
-                    ViewGroup.LayoutParams.WRAP_CONTENT, mIconSize);
-            child.setLayoutParams(lp);
+
+        @Override
+        protected void destroy() {
+            for (int i = 0; i < mGroup.getChildCount(); i++) {
+                mDarkIconDispatcher.removeDarkReceiver((ImageView) mGroup.getChildAt(i));
+            }
+            mGroup.removeAllViews();
         }
-        scaleBatteryMeterViews(mContext);
+
+        @Override
+        protected void onRemoveIcon(int viewIndex) {
+            mDarkIconDispatcher.removeDarkReceiver((ImageView) mGroup.getChildAt(viewIndex));
+            super.onRemoveIcon(viewIndex);
+        }
+
+        @Override
+        public void onSetIcon(int viewIndex, StatusBarIcon icon) {
+            super.onSetIcon(viewIndex, icon);
+            mDarkIconDispatcher.applyDark((ImageView) mGroup.getChildAt(viewIndex));
+        }
     }
 
-    private void updateClock() {
-        FontSizeUtils.updateFontSize(mClock, R.dimen.status_bar_clock_size);
-        mClock.setPaddingRelative(
-                mContext.getResources().getDimensionPixelSize(
-                        R.dimen.status_bar_clock_starting_padding),
-                0,
-                mContext.getResources().getDimensionPixelSize(
-                        R.dimen.status_bar_clock_end_padding),
-                0);
+    /**
+     * Turns info from StatusBarIconController into ImageViews in a ViewGroup.
+     */
+    public static class IconManager {
+        protected final ViewGroup mGroup;
+        protected final Context mContext;
+        protected final int mIconSize;
+
+        public IconManager(ViewGroup group) {
+            mGroup = group;
+            mContext = group.getContext();
+            mIconSize = mContext.getResources().getDimensionPixelSize(
+                    com.android.internal.R.dimen.status_bar_icon_size);
+        }
+
+        protected void onIconAdded(int index, String slot, boolean blocked, StatusBarIcon icon) {
+            StatusBarIconView view = new StatusBarIconView(mContext, slot, null, blocked);
+            view.set(icon);
+            mGroup.addView(view, index, new LinearLayout.LayoutParams(
+                    ViewGroup.LayoutParams.WRAP_CONTENT, mIconSize));
+        }
+
+        protected void destroy() {
+            mGroup.removeAllViews();
+        }
+
+        protected void onIconExternal(int viewIndex, int height) {
+            ImageView imageView = (ImageView) mGroup.getChildAt(viewIndex);
+            imageView.setScaleType(ImageView.ScaleType.FIT_CENTER);
+            imageView.setAdjustViewBounds(true);
+            setHeightAndCenter(imageView, height);
+        }
+
+        protected void onDensityOrFontScaleChanged() {
+            for (int i = 0; i < mGroup.getChildCount(); i++) {
+                View child = mGroup.getChildAt(i);
+                LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
+                        ViewGroup.LayoutParams.WRAP_CONTENT, mIconSize);
+                child.setLayoutParams(lp);
+            }
+        }
+
+        private void setHeightAndCenter(ImageView imageView, int height) {
+            ViewGroup.LayoutParams params = imageView.getLayoutParams();
+            params.height = height;
+            if (params instanceof LinearLayout.LayoutParams) {
+                ((LinearLayout.LayoutParams) params).gravity = Gravity.CENTER_VERTICAL;
+            }
+            imageView.setLayoutParams(params);
+        }
+
+        protected void onRemoveIcon(int viewIndex) {
+            mGroup.removeViewAt(viewIndex);
+        }
+
+        public void onSetIcon(int viewIndex, StatusBarIcon icon) {
+            StatusBarIconView view = (StatusBarIconView) mGroup.getChildAt(viewIndex);
+            view.set(icon);
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
new file mode 100644
index 0000000..70b92ad
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
@@ -0,0 +1,230 @@
+/*
+ * 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.content.Context;
+import android.graphics.drawable.Icon;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.text.TextUtils;
+import android.util.ArraySet;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+
+import com.android.internal.statusbar.StatusBarIcon;
+import com.android.systemui.Dependency;
+import com.android.systemui.Dumpable;
+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.policy.ConfigurationController;
+import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
+import com.android.systemui.statusbar.policy.DarkIconDispatcher;
+import com.android.systemui.tuner.TunerService;
+import com.android.systemui.tuner.TunerService.Tunable;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+
+/**
+ * Receives the callbacks from CommandQueue related to icons and tracks the state of
+ * all the icons. Dispatches this state to any IconManagers that are currently
+ * registered with it.
+ */
+public class StatusBarIconControllerImpl extends StatusBarIconList implements Tunable,
+        ConfigurationListener, Dumpable, CommandQueue.Callbacks, StatusBarIconController {
+
+    private final DarkIconDispatcher mDarkIconDispatcher;
+
+    private Context mContext;
+    private DemoStatusIcons mDemoStatusIcons;
+
+    private final ArrayList<IconManager> mIconGroups = new ArrayList<>();
+
+    private final ArraySet<String> mIconBlacklist = new ArraySet<>();
+
+    public StatusBarIconControllerImpl(Context context) {
+        super(context.getResources().getStringArray(
+                com.android.internal.R.array.config_statusBarIcons));
+        Dependency.get(ConfigurationController.class).addCallback(this);
+        mDarkIconDispatcher = Dependency.get(DarkIconDispatcher.class);
+        mContext = context;
+
+        loadDimens();
+
+        SysUiServiceProvider.getComponent(context, CommandQueue.class)
+                .addCallbacks(this);
+        Dependency.get(TunerService.class).addTunable(this, ICON_BLACKLIST);
+    }
+
+    @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);
+            }
+        }
+    }
+
+    @Override
+    public void removeIconGroup(IconManager group) {
+        group.destroy();
+        mIconGroups.remove(group);
+    }
+
+    @Override
+    public void onTuningChanged(String key, String newValue) {
+        if (!ICON_BLACKLIST.equals(key)) {
+            return;
+        }
+        mIconBlacklist.clear();
+        mIconBlacklist.addAll(StatusBarIconController.getIconBlacklist(newValue));
+        ArrayList<StatusBarIcon> current = new ArrayList<>(mIcons);
+        ArrayList<String> currentSlots = new ArrayList<>(mSlots);
+        // Remove all the icons.
+        for (int i = current.size() - 1; i >= 0; i--) {
+            removeIcon(currentSlots.get(i));
+        }
+        // Add them all back
+        for (int i = 0; i < current.size(); i++) {
+            setIcon(currentSlots.get(i), current.get(i));
+        }
+    }
+
+    private void loadDimens() {
+    }
+
+    private void addSystemIcon(int index, StatusBarIcon icon) {
+        String slot = getSlot(index);
+        int viewIndex = getViewIndex(index);
+        boolean blocked = mIconBlacklist.contains(slot);
+
+        mIconGroups.forEach(l -> l.onIconAdded(viewIndex, slot, blocked, icon));
+    }
+
+    @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);
+        } else {
+            icon.icon = Icon.createWithResource(mContext, resourceId);
+            icon.contentDescription = contentDescription;
+            handleSet(index, icon);
+        }
+    }
+
+    @Override
+    public void setExternalIcon(String slot) {
+        int viewIndex = getViewIndex(getSlotIndex(slot));
+        int height = mContext.getResources().getDimensionPixelSize(
+                R.dimen.status_bar_icon_drawing_size);
+        mIconGroups.forEach(l -> l.onIconExternal(viewIndex, height));
+    }
+
+    @Override
+    public void setIcon(String slot, StatusBarIcon icon) {
+        setIcon(getSlotIndex(slot), icon);
+    }
+
+    @Override
+    public void removeIcon(String slot) {
+        int index = getSlotIndex(slot);
+        removeIcon(index);
+    }
+
+    public void setIconVisibility(String slot, boolean visibility) {
+        int index = getSlotIndex(slot);
+        StatusBarIcon icon = getIcon(index);
+        if (icon == null || icon.visible == visibility) {
+            return;
+        }
+        icon.visible = visibility;
+        handleSet(index, icon);
+    }
+
+    @Override
+    public void removeIcon(int index) {
+        if (getIcon(index) == null) {
+            return;
+        }
+        super.removeIcon(index);
+        int viewIndex = getViewIndex(index);
+        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, StatusBarIcon icon) {
+        int viewIndex = getViewIndex(index);
+        mIconGroups.forEach(l -> l.onSetIcon(viewIndex, icon));
+    }
+
+    @Override
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        // TODO: Dump info about all icon groups?
+        ViewGroup statusIcons = mIconGroups.get(0).mGroup;
+        int N = statusIcons.getChildCount();
+        pw.println("  icon views: " + N);
+        for (int i = 0; i < N; i++) {
+            StatusBarIconView ic = (StatusBarIconView) statusIcons.getChildAt(i);
+            pw.println("    [" + i + "] icon=" + ic);
+        }
+        super.dump(pw);
+    }
+
+    public void dispatchDemoCommand(String command, Bundle args) {
+        if (mDemoStatusIcons == null) {
+            // TODO: Rework how we handle demo mode.
+            int iconSize = mContext.getResources().getDimensionPixelSize(
+                    com.android.internal.R.dimen.status_bar_icon_size);
+            mDemoStatusIcons = new DemoStatusIcons((LinearLayout) mIconGroups.get(0).mGroup,
+                    iconSize);
+        }
+        mDemoStatusIcons.dispatchDemoCommand(command, args);
+    }
+
+    @Override
+    public void onDensityOrFontScaleChanged() {
+        loadDimens();
+    }
+}
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 660672d..f600908 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconList.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconList.java
@@ -22,8 +22,8 @@
 import java.util.ArrayList;
 
 public class StatusBarIconList {
-    private ArrayList<String> mSlots = new ArrayList<>();
-    private ArrayList<StatusBarIcon> mIcons = new ArrayList<>();
+    protected ArrayList<String> mSlots = new ArrayList<>();
+    protected ArrayList<StatusBarIcon> mIcons = new ArrayList<>();
 
     public StatusBarIconList(String[] slots) {
         final int N = slots.length;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
index ffc0d97..bb0748c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
@@ -19,11 +19,14 @@
 import libcore.icu.LocaleData;
 
 import android.app.ActivityManager;
+import android.app.StatusBarManager;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.res.Configuration;
 import android.content.res.TypedArray;
+import android.graphics.Rect;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.SystemClock;
@@ -35,12 +38,18 @@
 import android.text.style.RelativeSizeSpan;
 import android.util.AttributeSet;
 import android.view.Display;
+import android.view.View;
 import android.widget.TextView;
 
 import com.android.systemui.DemoMode;
 import com.android.systemui.Dependency;
+import com.android.systemui.FontSizeUtils;
 import com.android.systemui.R;
+import com.android.systemui.SysUiServiceProvider;
+import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.phone.StatusBarIconController;
+import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
+import com.android.systemui.statusbar.policy.DarkIconDispatcher.DarkReceiver;
 import com.android.systemui.tuner.TunerService;
 import com.android.systemui.tuner.TunerService.Tunable;
 
@@ -52,10 +61,14 @@
 /**
  * Digital clock for the status bar.
  */
-public class Clock extends TextView implements DemoMode, Tunable {
+public class Clock extends TextView implements DemoMode, Tunable, CommandQueue.Callbacks,
+        DarkReceiver, ConfigurationListener {
 
     public static final String CLOCK_SECONDS = "clock_seconds";
 
+    private boolean mClockVisibleByPolicy = true;
+    private boolean mClockVisibleByUser = true;
+
     private boolean mAttached;
     private Calendar mCalendar;
     private String mClockFormatString;
@@ -110,6 +123,8 @@
                     null, Dependency.get(Dependency.TIME_TICK_HANDLER));
             Dependency.get(TunerService.class).addTunable(this, CLOCK_SECONDS,
                     StatusBarIconController.ICON_BLACKLIST);
+            SysUiServiceProvider.getComponent(getContext(), CommandQueue.class).addCallbacks(this);
+            Dependency.get(DarkIconDispatcher.class).addDarkReceiver(this);
         }
 
         // NOTE: It's safe to do these after registering the receiver since the receiver always runs
@@ -130,6 +145,9 @@
             getContext().unregisterReceiver(mIntentReceiver);
             mAttached = false;
             Dependency.get(TunerService.class).removeTunable(this);
+            SysUiServiceProvider.getComponent(getContext(), CommandQueue.class)
+                    .removeCallbacks(this);
+            Dependency.get(DarkIconDispatcher.class).removeDarkReceiver(this);
         }
     }
 
@@ -158,6 +176,22 @@
         }
     };
 
+    public void setClockVisibleByUser(boolean visible) {
+        mClockVisibleByUser = visible;
+        updateClockVisibility();
+    }
+
+    public void setClockVisibilityByPolicy(boolean visible) {
+        mClockVisibleByPolicy = visible;
+        updateClockVisibility();
+    }
+
+    private void updateClockVisibility() {
+        int visibility = (mClockVisibleByPolicy && mClockVisibleByUser)
+                ? View.VISIBLE : View.GONE;
+        setVisibility(visibility);
+    }
+
     final void updateClock() {
         if (mDemoMode) return;
         mCalendar.setTimeInMillis(System.currentTimeMillis());
@@ -170,9 +204,38 @@
         if (CLOCK_SECONDS.equals(key)) {
             mShowSeconds = newValue != null && Integer.parseInt(newValue) != 0;
             updateShowSeconds();
+        } else {
+            setClockVisibleByUser(!StatusBarIconController.getIconBlacklist(newValue)
+                    .contains("clock"));
+            updateClockVisibility();
         }
     }
 
+    @Override
+    public void disable(int state1, int state2, boolean animate) {
+        boolean clockVisibleByPolicy = (state1 & StatusBarManager.DISABLE_CLOCK) == 0;
+        if (clockVisibleByPolicy != mClockVisibleByPolicy) {
+            setClockVisibilityByPolicy(clockVisibleByPolicy);
+        }
+    }
+
+    @Override
+    public void onDarkChanged(Rect area, float darkIntensity, int tint) {
+        setTextColor(DarkIconDispatcher.getTint(area, this, tint));
+    }
+
+    @Override
+    public void onDensityOrFontScaleChanged() {
+        FontSizeUtils.updateFontSize(this, R.dimen.status_bar_clock_size);
+        setPaddingRelative(
+                mContext.getResources().getDimensionPixelSize(
+                        R.dimen.status_bar_clock_starting_padding),
+                0,
+                mContext.getResources().getDimensionPixelSize(
+                        R.dimen.status_bar_clock_end_padding),
+                0);
+    }
+
     private void updateShowSeconds() {
         if (mShowSeconds) {
             // Wait until we have a display to start trying to show seconds.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationController.java
new file mode 100644
index 0000000..788fda8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationController.java
@@ -0,0 +1,31 @@
+/*
+ * 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.policy;
+
+import android.content.res.Configuration;
+
+import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
+
+/**
+ * Common listener for configuration or subsets of configuration changes (like density or
+ * font scaling), providing easy static dependence on these events.
+ */
+public interface ConfigurationController extends CallbackController<ConfigurationListener> {
+
+    interface ConfigurationListener {
+        default void onConfigChanged(Configuration newConfig) {}
+        default void onDensityOrFontScaleChanged() {}
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DarkIconDispatcher.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DarkIconDispatcher.java
new file mode 100644
index 0000000..58944c6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DarkIconDispatcher.java
@@ -0,0 +1,92 @@
+/*
+ * 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.policy;
+
+import android.graphics.Color;
+import android.graphics.Rect;
+import android.view.View;
+import android.widget.ImageView;
+
+import com.android.systemui.statusbar.phone.LightBarTransitionsController;
+
+public interface DarkIconDispatcher {
+
+    void setIconsDarkArea(Rect r);
+    LightBarTransitionsController getTransitionsController();
+
+    void addDarkReceiver(DarkReceiver receiver);
+    void addDarkReceiver(ImageView imageView);
+
+    // Must have been previously been added through one of the addDarkReceive methods above.
+    void removeDarkReceiver(DarkReceiver object);
+    void removeDarkReceiver(ImageView object);
+
+    // Used to reapply darkness on an object, must have previously been added through
+    // addDarkReceiver.
+    void applyDark(ImageView object);
+
+    int DEFAULT_ICON_TINT = Color.WHITE;
+    Rect sTmpRect = new Rect();
+    int[] sTmpInt2 = new int[2];
+
+    /**
+     * @return the tint to apply to {@param view} depending on the desired tint {@param color} and
+     *         the screen {@param tintArea} in which to apply that tint
+     */
+    static int getTint(Rect tintArea, View view, int color) {
+        if (isInArea(tintArea, view)) {
+            return color;
+        } else {
+            return DEFAULT_ICON_TINT;
+        }
+    }
+
+    /**
+     * @return the dark intensity to apply to {@param view} depending on the desired dark
+     *         {@param intensity} and the screen {@param tintArea} in which to apply that intensity
+     */
+    static float getDarkIntensity(Rect tintArea, View view, float intensity) {
+        if (isInArea(tintArea, view)) {
+            return intensity;
+        } else {
+            return 0f;
+        }
+    }
+
+    /**
+     * @return true if more than half of the {@param view} area are in {@param area}, false
+     *         otherwise
+     */
+    static boolean isInArea(Rect area, View view) {
+        if (area.isEmpty()) {
+            return true;
+        }
+        sTmpRect.set(area);
+        view.getLocationOnScreen(sTmpInt2);
+        int left = sTmpInt2[0];
+
+        int intersectStart = Math.max(left, area.left);
+        int intersectEnd = Math.min(left + view.getWidth(), area.right);
+        int intersectAmount = Math.max(0, intersectEnd - intersectStart);
+
+        boolean coversFullStatusBar = area.top <= 0;
+        boolean majorityOfWidth = 2 * intersectAmount > view.getWidth();
+        return majorityOfWidth && coversFullStatusBar;
+    }
+
+    interface DarkReceiver {
+        void onDarkChanged(Rect area, float darkIntensity, int tint);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardMonitor.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardMonitor.java
index de47267..728005d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardMonitor.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardMonitor.java
@@ -21,8 +21,12 @@
     boolean isSecure();
     boolean canSkipBouncer();
     boolean isShowing();
+    boolean isKeyguardFadingAway();
+    boolean isKeyguardGoingAway();
+    long getKeyguardFadingAwayDuration();
+    long getKeyguardFadingAwayDelay();
 
     public interface Callback {
-        void onKeyguardChanged();
+        void onKeyguardShowingChanged();
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardMonitorImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardMonitorImpl.java
index 769f93f..821e635 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardMonitorImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardMonitorImpl.java
@@ -18,13 +18,10 @@
 
 import android.app.ActivityManager;
 import android.content.Context;
-import android.os.RemoteException;
-import android.view.WindowManagerGlobal;
 
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
 import com.android.systemui.settings.CurrentUserTracker;
-import com.android.systemui.statusbar.policy.KeyguardMonitor.Callback;
 
 import java.util.ArrayList;
 
@@ -44,6 +41,10 @@
     private boolean mCanSkipBouncer;
 
     private boolean mListening;
+    private boolean mKeyguardFadingAway;
+    private long mKeyguardFadingAwayDelay;
+    private long mKeyguardFadingAwayDuration;
+    private boolean mKeyguardGoingAway;
 
     public KeyguardMonitorImpl(Context context) {
         mContext = context;
@@ -115,8 +116,42 @@
     }
 
     private void notifyKeyguardChanged() {
-        for (Callback callback : mCallbacks) {
-            callback.onKeyguardChanged();
-        }
+        mCallbacks.forEach(Callback::onKeyguardShowingChanged);
+    }
+
+    public void notifyKeyguardFadingAway(long delay, long fadeoutDuration) {
+        mKeyguardFadingAway = true;
+        mKeyguardFadingAwayDelay = delay;
+        mKeyguardFadingAwayDuration = fadeoutDuration;
+        mCallbacks.forEach(Callback::onKeyguardShowingChanged);
+    }
+
+    public void notifyKeyguardDoneFading() {
+        mKeyguardFadingAway = false;
+        mCallbacks.forEach(Callback::onKeyguardShowingChanged);
+    }
+
+    @Override
+    public boolean isKeyguardFadingAway() {
+        return mKeyguardFadingAway;
+    }
+
+    @Override
+    public boolean isKeyguardGoingAway() {
+        return mKeyguardGoingAway;
+    }
+
+    @Override
+    public long getKeyguardFadingAwayDelay() {
+        return mKeyguardFadingAwayDelay;
+    }
+
+    @Override
+    public long getKeyguardFadingAwayDuration() {
+        return mKeyguardFadingAwayDuration;
+    }
+
+    public void notifyKeyguardGoingAway(boolean keyguardGoingAway) {
+        mKeyguardGoingAway = keyguardGoingAway;
     }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
index 7a32bf1..6df4a21 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
@@ -850,7 +850,7 @@
 
     private final KeyguardMonitor.Callback mCallback = new KeyguardMonitor.Callback() {
         @Override
-        public void onKeyguardChanged() {
+        public void onKeyguardShowingChanged() {
             notifyAdapters();
         }
     };
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeController.java
index f195f7a..8777aa6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeController.java
@@ -36,14 +36,14 @@
     int getCurrentUser();
     boolean isVolumeRestricted();
 
-    public static class Callback {
-        public void onZenChanged(int zen) {}
-        public void onConditionsChanged(Condition[] conditions) {}
-        public void onNextAlarmChanged() {}
-        public void onZenAvailableChanged(boolean available) {}
-        public void onEffectsSupressorChanged() {}
-        public void onManualRuleChanged(ZenRule rule) {}
-        public void onConfigChanged(ZenModeConfig config) {}
+    public static interface Callback {
+        default void onZenChanged(int zen) {}
+        default void onConditionsChanged(Condition[] conditions) {}
+        default void onNextAlarmChanged() {}
+        default void onZenAvailableChanged(boolean available) {}
+        default void onEffectsSupressorChanged() {}
+        default void onManualRuleChanged(ZenRule rule) {}
+        default void onConfigChanged(ZenModeConfig config) {}
     }
 
 }
\ No newline at end of file