Merge "Adjust QS Header in landscape"
diff --git a/api/current.txt b/api/current.txt
index 47a5242..a41a878 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -32110,6 +32110,7 @@
     field public static final java.lang.String ACTION_DISPLAY_SETTINGS = "android.settings.DISPLAY_SETTINGS";
     field public static final java.lang.String ACTION_DREAM_SETTINGS = "android.settings.DREAM_SETTINGS";
     field public static final java.lang.String ACTION_HOME_SETTINGS = "android.settings.HOME_SETTINGS";
+    field public static final java.lang.String ACTION_IGNORE_BACKGROUND_DATA_RESTRICTIONS_SETTINGS = "android.settings.IGNORE_BACKGROUND_DATA_RESTRICTIONS_SETTINGS";
     field public static final java.lang.String ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS = "android.settings.IGNORE_BATTERY_OPTIMIZATION_SETTINGS";
     field public static final java.lang.String ACTION_INPUT_METHOD_SETTINGS = "android.settings.INPUT_METHOD_SETTINGS";
     field public static final java.lang.String ACTION_INPUT_METHOD_SUBTYPE_SETTINGS = "android.settings.INPUT_METHOD_SUBTYPE_SETTINGS";
diff --git a/api/system-current.txt b/api/system-current.txt
index edcd9e6..54435ae5 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -34520,6 +34520,7 @@
     field public static final java.lang.String ACTION_DISPLAY_SETTINGS = "android.settings.DISPLAY_SETTINGS";
     field public static final java.lang.String ACTION_DREAM_SETTINGS = "android.settings.DREAM_SETTINGS";
     field public static final java.lang.String ACTION_HOME_SETTINGS = "android.settings.HOME_SETTINGS";
+    field public static final java.lang.String ACTION_IGNORE_BACKGROUND_DATA_RESTRICTIONS_SETTINGS = "android.settings.IGNORE_BACKGROUND_DATA_RESTRICTIONS_SETTINGS";
     field public static final java.lang.String ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS = "android.settings.IGNORE_BATTERY_OPTIMIZATION_SETTINGS";
     field public static final java.lang.String ACTION_INPUT_METHOD_SETTINGS = "android.settings.INPUT_METHOD_SETTINGS";
     field public static final java.lang.String ACTION_INPUT_METHOD_SUBTYPE_SETTINGS = "android.settings.INPUT_METHOD_SUBTYPE_SETTINGS";
diff --git a/api/test-current.txt b/api/test-current.txt
index 4dbad61..d51fca3 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -32123,6 +32123,7 @@
     field public static final java.lang.String ACTION_DISPLAY_SETTINGS = "android.settings.DISPLAY_SETTINGS";
     field public static final java.lang.String ACTION_DREAM_SETTINGS = "android.settings.DREAM_SETTINGS";
     field public static final java.lang.String ACTION_HOME_SETTINGS = "android.settings.HOME_SETTINGS";
+    field public static final java.lang.String ACTION_IGNORE_BACKGROUND_DATA_RESTRICTIONS_SETTINGS = "android.settings.IGNORE_BACKGROUND_DATA_RESTRICTIONS_SETTINGS";
     field public static final java.lang.String ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS = "android.settings.IGNORE_BATTERY_OPTIMIZATION_SETTINGS";
     field public static final java.lang.String ACTION_INPUT_METHOD_SETTINGS = "android.settings.INPUT_METHOD_SETTINGS";
     field public static final java.lang.String ACTION_INPUT_METHOD_SUBTYPE_SETTINGS = "android.settings.INPUT_METHOD_SUBTYPE_SETTINGS";
diff --git a/cmds/am/src/com/android/commands/am/Am.java b/cmds/am/src/com/android/commands/am/Am.java
index 9d81c43..a74a1ca 100644
--- a/cmds/am/src/com/android/commands/am/Am.java
+++ b/cmds/am/src/com/android/commands/am/Am.java
@@ -1774,18 +1774,33 @@
             System.err.println("Error: invalid input bounds");
             return;
         }
-        resizeStack(stackId, bounds, 0, false);
+        resizeStack(stackId, bounds, 0);
     }
 
     private void runStackResizeAnimated() throws Exception {
         String stackIdStr = nextArgRequired();
         int stackId = Integer.valueOf(stackIdStr);
-        final Rect bounds = getBounds();
-        if (bounds == null) {
-            System.err.println("Error: invalid input bounds");
-            return;
+        final Rect bounds;
+        if ("null".equals(mArgs.peekNextArg())) {
+            bounds = null;
+        } else {
+            bounds = getBounds();
+            if (bounds == null) {
+                System.err.println("Error: invalid input bounds");
+                return;
+            }
         }
-        resizeStack(stackId, bounds, 0, true);
+        resizeStackUnchecked(stackId, bounds, 0, true);
+    }
+
+    private void resizeStackUnchecked(int stackId, Rect bounds, int delayMs, boolean animate) {
+        try {
+            mAm.resizeStack(stackId, bounds, false, false, animate);
+            Thread.sleep(delayMs);
+        } catch (RemoteException e) {
+            showError("Error: resizing stack " + e);
+        } catch (InterruptedException e) {
+        }
     }
 
     private void runStackResizeDocked() throws Exception {
@@ -1802,20 +1817,13 @@
         }
     }
 
-    private void resizeStack(int stackId, Rect bounds, int delayMs, boolean animate)
+    private void resizeStack(int stackId, Rect bounds, int delayMs)
             throws Exception {
         if (bounds == null) {
             showError("Error: invalid input bounds");
             return;
         }
-
-        try {
-            mAm.resizeStack(stackId, bounds, false, false, animate);
-            Thread.sleep(delayMs);
-        } catch (RemoteException e) {
-            showError("Error: resizing stack " + e);
-        } catch (InterruptedException e) {
-        }
+        resizeStackUnchecked(stackId, bounds, delayMs, false);
     }
 
     private void runStackPositionTask() throws Exception {
@@ -1924,7 +1932,7 @@
             maxChange = Math.min(stepSize, currentPoint - minPoint);
             currentPoint -= maxChange;
             setBoundsSide(bounds, side, currentPoint);
-            resizeStack(DOCKED_STACK_ID, bounds, delayMs, false);
+            resizeStack(DOCKED_STACK_ID, bounds, delayMs);
         }
 
         System.out.println("Growing docked stack side=" + side);
@@ -1932,7 +1940,7 @@
             maxChange = Math.min(stepSize, maxPoint - currentPoint);
             currentPoint += maxChange;
             setBoundsSide(bounds, side, currentPoint);
-            resizeStack(DOCKED_STACK_ID, bounds, delayMs, false);
+            resizeStack(DOCKED_STACK_ID, bounds, delayMs);
         }
 
         System.out.println("Back to Original size side=" + side);
@@ -1940,7 +1948,7 @@
             maxChange = Math.min(stepSize, currentPoint - startPoint);
             currentPoint -= maxChange;
             setBoundsSide(bounds, side, currentPoint);
-            resizeStack(DOCKED_STACK_ID, bounds, delayMs, false);
+            resizeStack(DOCKED_STACK_ID, bounds, delayMs);
         }
     }
 
diff --git a/core/java/android/os/ShellCommand.java b/core/java/android/os/ShellCommand.java
index 54d1090..fc804e5 100644
--- a/core/java/android/os/ShellCommand.java
+++ b/core/java/android/os/ShellCommand.java
@@ -234,6 +234,16 @@
         }
     }
 
+    public String peekNextArg() {
+        if (mCurArgData != null) {
+            return mCurArgData;
+        } else if (mArgPos < mArgs.length) {
+            return mArgs[mArgPos];
+        } else {
+            return null;
+        }
+    }
+
     /**
      * Return the next argument on the command line, whatever it is; if there are
      * no arguments left, throws an IllegalArgumentException to report this to the user.
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 5535eaa..dfc8601 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -670,14 +670,14 @@
             "android.settings.IGNORE_BATTERY_OPTIMIZATION_SETTINGS";
 
     /**
-     * Activity Action: Ask the user to allow an to ignore battery optimizations (that is,
+     * Activity Action: Ask the user to allow an app to ignore battery optimizations (that is,
      * put them on the whitelist of apps shown by
      * {@link #ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS}).  For an app to use this, it also
      * must hold the {@link android.Manifest.permission#REQUEST_IGNORE_BATTERY_OPTIMIZATIONS}
      * permission.
      * <p><b>Note:</b> most applications should <em>not</em> use this; there are many facilities
      * provided by the platform for applications to operate correctly in the various power
-     * saving mode.  This is only for unusual applications that need to deeply control their own
+     * saving modes.  This is only for unusual applications that need to deeply control their own
      * execution, at the potential expense of the user's battery life.  Note that these applications
      * greatly run the risk of showing to the user as high power consumers on their device.</p>
      * <p>
@@ -695,6 +695,24 @@
             "android.settings.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS";
 
     /**
+     * Activity Action: Show screen for controlling which apps can ignore background data
+     * restrictions.
+     * <p>
+     * Input: if the Intent's data URI is set with an application name (using the "package" schema,
+     * like "package:com.my.app"), then when the screen is displayed it will focus on such app. If
+     * the data is not set, it will just open the screen.
+     * <p>
+     * Output: Nothing.
+     * <p>
+     * Applications can also use {@link android.net.ConnectivityManager#getRestrictBackgroundStatus
+     * ConnectivityManager#getRestrictBackgroundStatus()} to determine the status of the background
+     * data restrictions for them.
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_IGNORE_BACKGROUND_DATA_RESTRICTIONS_SETTINGS =
+            "android.settings.IGNORE_BACKGROUND_DATA_RESTRICTIONS_SETTINGS";
+
+    /**
      * @hide
      * Activity Action: Show the "app ops" settings screen.
      * <p>
diff --git a/core/java/android/widget/PopupWindow.java b/core/java/android/widget/PopupWindow.java
index 584df08..8fa71a2 100644
--- a/core/java/android/widget/PopupWindow.java
+++ b/core/java/android/widget/PopupWindow.java
@@ -43,6 +43,7 @@
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.View;
+import android.view.View.OnAttachStateChangeListener;
 import android.view.View.OnTouchListener;
 import android.view.ViewGroup;
 import android.view.ViewParent;
@@ -164,7 +165,20 @@
         com.android.internal.R.attr.state_above_anchor
     };
 
+    private final OnAttachStateChangeListener mOnAnchorRootDetachedListener =
+            new OnAttachStateChangeListener() {
+                @Override
+                public void onViewAttachedToWindow(View v) {}
+
+                @Override
+                public void onViewDetachedFromWindow(View v) {
+                    mIsAnchorRootAttached = false;
+                }
+            };
+
     private WeakReference<View> mAnchor;
+    private WeakReference<View> mAnchorRoot;
+    private boolean mIsAnchorRootAttached;
 
     private final OnScrollChangedListener mOnScrollChangedListener = new OnScrollChangedListener() {
         @Override
@@ -1037,7 +1051,7 @@
 
         TransitionManager.endTransitions(mDecorView);
 
-        unregisterForScrollChanged();
+        unregisterForViewTreeChanges();
 
         mIsShowing = true;
         mIsDropdown = false;
@@ -1120,7 +1134,7 @@
 
         TransitionManager.endTransitions(mDecorView);
 
-        registerForScrollChanged(anchor, xoff, yoff, gravity);
+        registerForViewTreeChanges(anchor, xoff, yoff, gravity);
 
         mIsShowing = true;
         mIsDropdown = true;
@@ -1633,14 +1647,23 @@
         mIsShowing = false;
         mIsTransitioningToDismiss = true;
 
+        // This method may be called as part of window detachment, in which
+        // case the anchor view (and its root) will still return true from
+        // isAttachedToWindow() during execution of this method; however, we
+        // can expect the OnAttachStateChangeListener to have been called prior
+        // to executing this method, so we can rely on that instead.
         final Transition exitTransition = mExitTransition;
-        if (exitTransition != null && decorView.isLaidOut()) {
+        if (!mIsAnchorRootAttached && exitTransition != null && decorView.isLaidOut()) {
             // The decor view is non-interactive during exit transitions.
             final LayoutParams p = (LayoutParams) decorView.getLayoutParams();
             p.flags |= LayoutParams.FLAG_NOT_TOUCHABLE;
             p.flags |= LayoutParams.FLAG_NOT_FOCUSABLE;
             mWindowManager.updateViewLayout(decorView, p);
 
+            // Once we start dismissing the decor view, all state (including
+            // the anchor root) needs to be moved to the decor view since we
+            // may open another popup while it's busy exiting.
+            final View anchorRoot = mAnchorRoot != null ? mAnchorRoot.get() : null;
             final Rect epicenter = getTransitionEpicenter();
             exitTransition.setEpicenterCallback(new EpicenterCallback() {
                 @Override
@@ -1648,18 +1671,19 @@
                     return epicenter;
                 }
             });
-            decorView.startExitTransition(exitTransition, new TransitionListenerAdapter() {
-                @Override
-                public void onTransitionEnd(Transition transition) {
-                    dismissImmediate(decorView, contentHolder, contentView);
-                }
-            });
+            decorView.startExitTransition(exitTransition, anchorRoot,
+                    new TransitionListenerAdapter() {
+                        @Override
+                        public void onTransitionEnd(Transition transition) {
+                            dismissImmediate(decorView, contentHolder, contentView);
+                        }
+                    });
         } else {
             dismissImmediate(decorView, contentHolder, contentView);
         }
 
         // Clears the anchor view.
-        unregisterForScrollChanged();
+        unregisterForViewTreeChanges();
 
         if (mOnDismissListener != null) {
             mOnDismissListener.onDismiss();
@@ -1925,7 +1949,7 @@
         final WeakReference<View> oldAnchor = mAnchor;
         final boolean needsUpdate = updateLocation && (mAnchorXoff != xoff || mAnchorYoff != yoff);
         if (oldAnchor == null || oldAnchor.get() != anchor || (needsUpdate && !mIsDropdown)) {
-            registerForScrollChanged(anchor, xoff, yoff, mAnchoredGravity);
+            registerForViewTreeChanges(anchor, xoff, yoff, mAnchoredGravity);
         } else if (needsUpdate) {
             // No need to register again if this is a DropDown, showAsDropDown already did.
             mAnchorXoff = xoff;
@@ -1969,27 +1993,38 @@
         public void onDismiss();
     }
 
-    private void unregisterForScrollChanged() {
-        final WeakReference<View> anchorRef = mAnchor;
-        final View anchor = anchorRef == null ? null : anchorRef.get();
+    private void unregisterForViewTreeChanges() {
+        final View anchor = mAnchor != null ? mAnchor.get() : null;
         if (anchor != null) {
             final ViewTreeObserver vto = anchor.getViewTreeObserver();
             vto.removeOnScrollChangedListener(mOnScrollChangedListener);
         }
 
+        final View anchorRoot = mAnchorRoot != null ? mAnchorRoot.get() : null;
+        if (anchorRoot != null) {
+            anchorRoot.removeOnAttachStateChangeListener(mOnAnchorRootDetachedListener);
+        }
+
         mAnchor = null;
+        mAnchorRoot = null;
+        mIsAnchorRootAttached = false;
     }
 
-    private void registerForScrollChanged(View anchor, int xoff, int yoff, int gravity) {
-        unregisterForScrollChanged();
-
-        mAnchor = new WeakReference<>(anchor);
+    private void registerForViewTreeChanges(View anchor, int xoff, int yoff, int gravity) {
+        unregisterForViewTreeChanges();
 
         final ViewTreeObserver vto = anchor.getViewTreeObserver();
         if (vto != null) {
             vto.addOnScrollChangedListener(mOnScrollChangedListener);
         }
 
+        final View anchorRoot = anchor.getRootView();
+        anchorRoot.addOnAttachStateChangeListener(mOnAnchorRootDetachedListener);
+
+        mAnchor = new WeakReference<>(anchor);
+        mAnchorRoot = new WeakReference<>(anchorRoot);
+        mIsAnchorRootAttached = anchorRoot.isAttachedToWindow();
+
         mAnchorXoff = xoff;
         mAnchorYoff = yoff;
         mAnchoredGravity = gravity;
@@ -2109,16 +2144,23 @@
          * its {@code onTransitionEnd} method called even if the transition
          * never starts; however, it may be called with a {@code null} argument.
          */
-        public void startExitTransition(Transition transition, final TransitionListener listener) {
+        public void startExitTransition(Transition transition, final View anchorRoot,
+                final TransitionListener listener) {
             if (transition == null) {
                 return;
             }
 
+            // The anchor view's window may go away while we're executing our
+            // transition, in which case we need to end the transition
+            // immediately and execute the listener to remove the popup.
+            anchorRoot.addOnAttachStateChangeListener(mOnAnchorRootDetachedListener);
+
             // The exit listener MUST be called for cleanup, even if the
             // transition never starts or ends. Stash it for later.
             mPendingExitListener = new TransitionListenerAdapter() {
                 @Override
                 public void onTransitionEnd(Transition transition) {
+                    anchorRoot.removeOnAttachStateChangeListener(mOnAnchorRootDetachedListener);
                     listener.onTransitionEnd(transition);
 
                     // The listener was called. Our job here is done.
@@ -2153,6 +2195,19 @@
                 mPendingExitListener.onTransitionEnd(null);
             }
         }
+
+        private final OnAttachStateChangeListener mOnAnchorRootDetachedListener =
+                new OnAttachStateChangeListener() {
+                    @Override
+                    public void onViewAttachedToWindow(View v) {}
+
+                    @Override
+                    public void onViewDetachedFromWindow(View v) {
+                        v.removeOnAttachStateChangeListener(this);
+
+                        TransitionManager.endTransitions(PopupDecorView.this);
+                    }
+                };
     }
 
     private class PopupBackgroundView extends FrameLayout {
diff --git a/packages/SystemUI/res/layout/qs_add_tile_layout.xml b/packages/SystemUI/res/layout/qs_add_tile_layout.xml
deleted file mode 100644
index 962b00e..0000000
--- a/packages/SystemUI/res/layout/qs_add_tile_layout.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-     Copyright (C) 2015 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<FrameLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_height="wrap_content"
-    android:layout_width="wrap_content"
-    android:paddingTop="20dp"
-    android:paddingStart="7dp"
-    android:paddingEnd="7dp">
-    <LinearLayout
-        android:layout_height="wrap_content"
-        android:layout_width="80dp"
-        android:orientation="vertical">
-        <ImageView
-            android:id="@+id/tile_icon"
-            android:layout_gravity="center"
-            android:layout_width="@dimen/qs_tile_icon_size"
-            android:layout_height="@dimen/qs_tile_icon_size" />
-        <TextView
-            android:id="@+id/tile_label"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_gravity="center|bottom"
-            android:paddingTop="10dp"
-            android:gravity="center"
-            android:textSize="@dimen/qs_tile_text_size" />
-    </LinearLayout>
-</FrameLayout>
diff --git a/packages/SystemUI/res/layout/qs_customize_layout.xml b/packages/SystemUI/res/layout/qs_customize_divider.xml
similarity index 63%
rename from packages/SystemUI/res/layout/qs_customize_layout.xml
rename to packages/SystemUI/res/layout/qs_customize_divider.xml
index 0b8e02f..71ad85b 100644
--- a/packages/SystemUI/res/layout/qs_customize_layout.xml
+++ b/packages/SystemUI/res/layout/qs_customize_divider.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-     Copyright (C) 2015 The Android Open Source Project
+     Copyright (C) 2016 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.
@@ -14,18 +14,16 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<com.android.systemui.qs.customize.NonPagedTileLayout
+
+<TextView
     xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/tiles_container"
+    android:id="@android:id/title"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
-    android:orientation="vertical">
-
-    <view
-        class="com.android.systemui.qs.PagedTileLayout$TilePage"
-        android:id="@+id/tile_page"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content" />
-
-</com.android.systemui.qs.customize.NonPagedTileLayout>
-
+    android:paddingTop="20dp"
+    android:paddingStart="16dp"
+    android:paddingEnd="8dp"
+    android:paddingBottom="13dp"
+    android:textAppearance="@android:style/TextAppearance.Material.Body2"
+    android:textColor="?android:attr/colorAccent"
+    android:text="@string/drag_to_add_tiles" />
diff --git a/packages/SystemUI/res/layout/qs_customize_panel.xml b/packages/SystemUI/res/layout/qs_customize_panel.xml
index e56431b..73a92d9 100644
--- a/packages/SystemUI/res/layout/qs_customize_panel.xml
+++ b/packages/SystemUI/res/layout/qs_customize_panel.xml
@@ -22,85 +22,53 @@
     android:background="@drawable/qs_customizer_background"
     android:gravity="center_horizontal">
 
-    <FrameLayout
+    <LinearLayout
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:background="@drawable/notification_header_bg">
+        android:paddingTop="28dp"
+        android:paddingEnd="8dp">
 
-        <LinearLayout
-            android:id="@+id/drag_buttons"
-            android:layout_width="match_parent"
-            android:layout_height="fill_parent"
-            android:orientation="horizontal">
-            <FrameLayout
-                android:layout_width="0dp"
-                android:layout_height="fill_parent"
-                android:layout_weight="1">
-                <com.android.systemui.qs.customize.DropButton
-                    android:id="@+id/info_button"
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:layout_gravity="center"
-                    android:gravity="center"
-                    android:drawableStart="@drawable/ic_info"
-                    android:drawablePadding="10dp"
-                    android:textAppearance="?android:attr/textAppearanceMedium"
-                    android:textColor="@android:color/white"
-                    android:text="@string/qs_customize_info" />
-            </FrameLayout>
-            <FrameLayout
-                android:layout_width="0dp"
-                android:layout_height="fill_parent"
-                android:layout_weight="1">
-                <com.android.systemui.qs.customize.DropButton
-                    android:id="@+id/remove_button"
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:layout_gravity="center"
-                    android:gravity="center"
-                    android:drawableStart="@drawable/ic_close_white"
-                    android:drawablePadding="10dp"
-                    android:textAppearance="?android:attr/textAppearanceMedium"
-                    android:textColor="@android:color/white"
-                    android:text="@string/qs_customize_remove" />
-            </FrameLayout>
-        </LinearLayout>
+        <ImageView
+            android:id="@+id/close"
+            android:layout_width="56dp"
+            android:layout_height="56dp"
+            android:padding="16dp"
+            android:src="@drawable/ic_close_white" />
 
-        <Toolbar
-            android:id="@*android:id/action_bar"
-            android:layout_width="match_parent"
+        <Space
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:layout_weight="1" />
+
+        <Button
+            android:id="@+id/save"
+            android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:navigationContentDescription="@*android:string/action_bar_up_description"
-            android:background="@drawable/notification_header_bg"
-            style="?android:attr/toolbarStyle" />
-    </FrameLayout>
+            android:paddingStart="12dp"
+            android:paddingEnd="12dp"
+            android:background="?android:attr/selectableItemBackground"
+            android:textAppearance="@android:style/TextAppearance.Material.Widget.Button.Inverse"
+            android:textColor="?android:attr/colorAccent"
+            android:text="@string/save" />
 
-    <com.android.systemui.tuner.AutoScrollView
+        <Button
+            android:id="@+id/reset"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:paddingStart="12dp"
+            android:paddingEnd="12dp"
+            android:background="?android:attr/selectableItemBackground"
+            android:textAppearance="@android:style/TextAppearance.Material.Widget.Button.Inverse"
+            android:textColor="?android:attr/colorAccent"
+            android:text="@*android:string/reset" />
+
+    </LinearLayout>
+
+    <android.support.v7.widget.RecyclerView
+        android:id="@android:id/list"
         android:layout_width="@dimen/notification_panel_width"
         android:layout_height="0dp"
-        android:layout_weight="1"
-        android:paddingTop="12dp"
-        android:paddingBottom="8dp"
-        android:elevation="2dp">
-
-        <com.android.systemui.qs.customize.CustomQSPanel
-            android:id="@+id/quick_settings_panel"
-            android:background="#0000"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content" />
-
-    </com.android.systemui.tuner.AutoScrollView>
-
-    <com.android.systemui.qs.customize.FloatingActionButton
-        android:id="@+id/fab"
-        android:clickable="true"
-        android:layout_width="@dimen/fab_size"
-        android:layout_height="@dimen/fab_size"
-        android:layout_gravity="bottom|end"
-        android:layout_marginEnd="@dimen/fab_margin"
-        android:layout_marginBottom="@dimen/fab_margin"
-        android:elevation="@dimen/fab_elevation"
-        android:background="@drawable/fab_background" />
+        android:layout_weight="1" />
 
     <View
         android:layout_width="match_parent"
diff --git a/packages/SystemUI/res/layout/qs_customize_tile_frame.xml b/packages/SystemUI/res/layout/qs_customize_tile_frame.xml
new file mode 100644
index 0000000..aaa84fd
--- /dev/null
+++ b/packages/SystemUI/res/layout/qs_customize_tile_frame.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2016 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.
+-->
+
+<FrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_height="wrap_content"
+    android:layout_width="wrap_content"
+    android:paddingStart="8dp"
+    android:paddingEnd="8dp"
+    android:paddingBottom="16dp" />
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index c6c448d0..9f10520 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1400,4 +1400,7 @@
     <!-- SysUI Tuner: Label for preview area in navigation bar tuner [CHAR LIMIT=NONE] -->
     <string name="preview">Preview</string>
 
+    <!-- Label for area where tiles can be dragged out of [CHAR LIMIT=60] -->
+    <string name="drag_to_add_tiles">Drag to add tiles</string>
+
 </resources>
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTile.java b/packages/SystemUI/src/com/android/systemui/qs/QSTile.java
index 35000d3..d79f4d4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTile.java
@@ -76,7 +76,7 @@
 
     private String mTileSpec;
 
-    abstract protected TState newTileState();
+    public abstract TState newTileState();
     abstract protected void handleClick();
     abstract protected void handleUpdateState(TState state, Object arg);
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/BlankCustomTile.java b/packages/SystemUI/src/com/android/systemui/qs/customize/BlankCustomTile.java
deleted file mode 100644
index 36bed0d..0000000
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/BlankCustomTile.java
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.qs.customize;
-
-import android.content.ComponentName;
-import android.content.pm.PackageManager;
-import android.content.pm.ServiceInfo;
-import android.graphics.drawable.Drawable;
-
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.MetricsProto.MetricsEvent;
-import com.android.systemui.R;
-import com.android.systemui.qs.QSTile;
-
-public class BlankCustomTile extends QSTile<QSTile.State> {
-    public static final String PREFIX = "custom(";
-
-    private final ComponentName mComponent;
-
-    private BlankCustomTile(Host host, String action) {
-        super(host);
-        mComponent = ComponentName.unflattenFromString(action);
-    }
-
-    public static QSTile<?> create(Host host, String spec) {
-        if (spec == null || !spec.startsWith(PREFIX) || !spec.endsWith(")")) {
-            throw new IllegalArgumentException("Bad custom tile spec: " + spec);
-        }
-        final String action = spec.substring(PREFIX.length(), spec.length() - 1);
-        if (action.isEmpty()) {
-            throw new IllegalArgumentException("Empty custom tile spec action");
-        }
-        return new BlankCustomTile(host, action);
-    }
-
-    @Override
-    public void setListening(boolean listening) {
-    }
-
-    @Override
-    protected State newTileState() {
-        return new State();
-    }
-
-    @Override
-    protected void handleUserSwitch(int newUserId) {
-        super.handleUserSwitch(newUserId);
-    }
-
-    @Override
-    protected void handleClick() {
-        MetricsLogger.action(mContext, getMetricsCategory(), mComponent.getPackageName());
-    }
-
-    @Override
-    protected void handleLongClick() {
-    }
-
-    @Override
-    protected void handleUpdateState(State state, Object arg) {
-        try {
-            PackageManager pm = mContext.getPackageManager();
-            ServiceInfo info = pm.getServiceInfo(mComponent, 0);
-            Drawable drawable = info.loadIcon(pm);
-            drawable.setTint(mContext.getColor(R.color.qs_tile_tint_active));
-            state.icon = new DrawableIcon(drawable);
-            state.label = info.loadLabel(pm).toString();
-            state.contentDescription = state.label;
-        } catch (Exception e) {
-        }
-    }
-
-    @Override
-    public int getMetricsCategory() {
-        return MetricsEvent.QS_INTENT;
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/CustomQSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/customize/CustomQSPanel.java
deleted file mode 100644
index 286748b..0000000
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/CustomQSPanel.java
+++ /dev/null
@@ -1,204 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.systemui.qs.customize;
-
-import android.app.ActivityManager;
-import android.content.ClipData;
-import android.content.Context;
-import android.content.Intent;
-import android.os.Handler;
-import android.os.UserHandle;
-import android.provider.Settings.Secure;
-import android.text.TextUtils;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-
-import com.android.systemui.R;
-import com.android.systemui.qs.QSPanel;
-import com.android.systemui.qs.QSTile;
-import com.android.systemui.qs.external.CustomTile;
-import com.android.systemui.qs.external.TileLifecycleManager;
-import com.android.systemui.statusbar.phone.QSTileHost;
-import com.android.systemui.tuner.TunerService;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-
-/**
- * A version of QSPanel that allows tiles to be dragged around rather than
- * clicked on.  Dragging starting and receiving is handled in the NonPagedTileLayout,
- * and the saving/ordering is handled by the CustomQSTileHost.
- */
-public class CustomQSPanel extends QSPanel {
-    
-    private static final String TAG = "CustomQSPanel";
-    private static final boolean DEBUG = false;
-
-    private List<String> mSavedTiles = Collections.emptyList();
-    private ArrayList<String> mStash;
-    private List<String> mTiles = new ArrayList<>();
-
-    private ArrayList<QSTile<?>> mCurrentTiles = new ArrayList<>();
-
-    public CustomQSPanel(Context context, AttributeSet attrs) {
-        super(context, attrs);
-        mTileLayout = (QSTileLayout) LayoutInflater.from(mContext)
-                .inflate(R.layout.qs_customize_layout, mQsContainer, false);
-        mQsContainer.addView((View) mTileLayout, 1 /* Between brightness and footer */);
-        ((NonPagedTileLayout) mTileLayout).setCustomQsPanel(this);
-        removeView(mFooter.getView());
-
-        if (DEBUG) Log.d(TAG, "new CustomQSPanel", new Throwable());
-        TunerService.get(mContext).addTunable(this, QSTileHost.TILES_SETTING);
-    }
-
-    @Override
-    protected void showDetail(boolean show, Record r) {
-        // No detail here.
-    }
-
-    @Override
-    protected void onDetachedFromWindow() {
-        // Don't allow the super to unregister the tunable.
-    }
-
-    @Override
-    public void onTuningChanged(String key, String newValue) {
-        if (key.equals(QS_SHOW_BRIGHTNESS)) {
-            // No Brightness for you.
-            super.onTuningChanged(key, "0");
-        }
-        if (QSTileHost.TILES_SETTING.equals(key)) {
-            mSavedTiles = Collections.unmodifiableList(
-                    QSTileHost.loadTileSpecs(mContext, newValue));
-            if (DEBUG) Log.d(TAG, "New saved tiles " + TextUtils.join(",", mSavedTiles));
-        }
-    }
-
-    @Override
-    protected void createCustomizePanel() {
-        // Already in CustomizePanel.
-    }
-
-    public void tileSelected(QSTile<?> tile, ClipData currentClip) {
-        String sourceSpec = getSpec(currentClip);
-        String destSpec = tile.getTileSpec();
-        if (!sourceSpec.equals(destSpec)) {
-            moveTo(sourceSpec, destSpec);
-        }
-    }
-
-    public ClipData getClip(QSTile<?> tile) {
-        String tileSpec = tile.getTileSpec();
-        // TODO: Something better than plain text.
-        // TODO: Once using something better than plain text, stop listening to non-QS drag events.
-        return ClipData.newPlainText(tileSpec, tileSpec);
-    }
-
-    public String getSpec(ClipData data) {
-        return data.getItemAt(0).getText().toString();
-    }
-
-    public void setSavedTiles() {
-        if (DEBUG) Log.d(TAG, "setSavedTiles " + TextUtils.join(",", mSavedTiles));
-        setTiles(mSavedTiles);
-    }
-
-    public void saveCurrentTiles() {
-        mHost.changeTiles(mSavedTiles, mTiles);
-    }
-
-    public void stashCurrentTiles() {
-        mStash = new ArrayList<>(mTiles);
-    }
-
-    public void unstashTiles() {
-        setTiles(mStash);
-    }
-
-    @Override
-    public void setTiles(Collection<QSTile<?>> tiles) {
-        setTilesInternal();
-    }
-
-    private void setTilesInternal() {
-        if (DEBUG) Log.d(TAG, "Set tiles internal");
-        for (int i = 0; i < mCurrentTiles.size(); i++) {
-            mCurrentTiles.get(i).destroy();
-        }
-        mCurrentTiles.clear();
-        for (int i = 0; i < mTiles.size(); i++) {
-            if (mTiles.get(i).startsWith(CustomTile.PREFIX)) {
-                QSTile<?> tile = BlankCustomTile.create(mHost, mTiles.get(i));
-                tile.setTileSpec(mTiles.get(i));
-                mCurrentTiles.add(tile);
-            } else {
-                QSTile<?> tile = mHost.createTile(mTiles.get(i));
-                if (tile != null) {
-                    tile.setTileSpec(mTiles.get(i));
-                    mCurrentTiles.add(tile);
-                } else {
-                    if (DEBUG) Log.d(TAG, "Skipping " + mTiles.get(i));
-                }
-            }
-        }
-        super.setTiles(mCurrentTiles);
-    }
-
-    public void addTile(String spec) {
-        if (DEBUG) Log.d(TAG, "addTile " + spec);
-        mTiles.add(spec);
-        setTilesInternal();
-    }
-
-    public void moveTo(String from, String to) {
-        if (DEBUG) Log.d(TAG, "moveTo " + from + " " + to);
-        int fromIndex = mTiles.indexOf(from);
-        if (fromIndex < 0) {
-            Log.e(TAG, "Unknown from tile " + from);
-            return;
-        }
-        int index = mTiles.indexOf(to);
-        if (index < 0) {
-            Log.e(TAG, "Unknown to tile " + to);
-            return;
-        }
-        mTiles.remove(fromIndex);
-        mTiles.add(index, from);
-        setTilesInternal();
-    }
-
-    public void remove(String spec) {
-        if (!mTiles.remove(spec)) {
-            Log.e(TAG, "Unknown remove spec " + spec);
-        }
-        setTilesInternal();
-    }
-
-    public void setTiles(List<String> tiles) {
-        if (DEBUG) Log.d(TAG, "Set tiles " + TextUtils.join(",", tiles));
-        mTiles = new ArrayList<>(tiles);
-        setTilesInternal();
-    }
-
-    public Collection<QSTile<?>> getTiles() {
-        return mCurrentTiles;
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/DropButton.java b/packages/SystemUI/src/com/android/systemui/qs/customize/DropButton.java
deleted file mode 100644
index 3135408..0000000
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/DropButton.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
-*
-* Licensed under the Apache License, Version 2.0 (the "License");
-* you may not use this file except in compliance with the License.
-* You may obtain a copy of the License at
-*
-*      http://www.apache.org/licenses/LICENSE-2.0
-*
-* Unless required by applicable law or agreed to in writing, software
-* distributed under the License is distributed on an "AS IS" BASIS,
-* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-* See the License for the specific language governing permissions and
-* limitations under the License.
-*/
-package com.android.systemui.qs.customize;
-
-import android.content.ClipData;
-import android.content.Context;
-import android.util.AttributeSet;
-import android.view.DragEvent;
-import android.view.View;
-import android.view.View.OnDragListener;
-import android.widget.TextView;
-
-public class DropButton extends TextView implements OnDragListener {
-
-    private OnDropListener mListener;
-
-    public DropButton(Context context, AttributeSet attrs) {
-        super(context, attrs);
-    }
-
-    @Override
-    protected void onAttachedToWindow() {
-        super.onAttachedToWindow();
-        // TODO: Don't do this, instead make this view the right size...
-        ((View) getParent()).setOnDragListener(this);
-    }
-
-    public void setOnDropListener(OnDropListener listener) {
-        mListener = listener;
-    }
-
-    private void setHovering(boolean hovering) {
-        setAlpha(hovering ? .3f : 1);
-    }
-
-    @Override
-    public boolean onDrag(View v, DragEvent event) {
-        switch (event.getAction()) {
-            case DragEvent.ACTION_DRAG_ENTERED:
-                setHovering(true);
-                break;
-            case DragEvent.ACTION_DROP:
-                if (mListener != null) {
-                    mListener.onDrop(this, event.getClipData());
-                }
-            case DragEvent.ACTION_DRAG_EXITED:
-            case DragEvent.ACTION_DRAG_ENDED:
-                setHovering(false);
-                break;
-        }
-        return true;
-    }
-
-    public interface OnDropListener {
-        void onDrop(View v, ClipData data);
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/FloatingActionButton.java b/packages/SystemUI/src/com/android/systemui/qs/customize/FloatingActionButton.java
deleted file mode 100644
index 8791a10..0000000
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/FloatingActionButton.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.qs.customize;
-
-import android.animation.AnimatorInflater;
-import android.content.Context;
-import android.graphics.Outline;
-import android.util.AttributeSet;
-import android.view.View;
-import android.view.ViewOutlineProvider;
-import android.widget.ImageView;
-
-import com.android.systemui.R;
-
-public class FloatingActionButton extends ImageView {
-
-    public FloatingActionButton(Context context, AttributeSet attrs) {
-        super(context, attrs);
-        setScaleType(ScaleType.CENTER);
-        setStateListAnimator(AnimatorInflater.loadStateListAnimator(context, R.anim.fab_elevation));
-        setOutlineProvider(new ViewOutlineProvider() {
-            @Override
-            public void getOutline(View view, Outline outline) {
-                outline.setOval(0, 0, getWidth(), getHeight());
-            }
-        });
-        setClipToOutline(true);
-    }
-
-    @Override
-    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
-        super.onSizeChanged(w, h, oldw, oldh);
-        invalidateOutline();
-    }
-}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/NonPagedTileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/customize/NonPagedTileLayout.java
deleted file mode 100644
index 98c7be4..0000000
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/NonPagedTileLayout.java
+++ /dev/null
@@ -1,184 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.systemui.qs.customize;
-
-import android.content.ClipData;
-import android.content.Context;
-import android.graphics.Rect;
-import android.util.AttributeSet;
-import android.view.DragEvent;
-import android.view.LayoutInflater;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.View.OnTouchListener;
-import android.widget.LinearLayout;
-
-import com.android.systemui.R;
-import com.android.systemui.qs.PagedTileLayout;
-import com.android.systemui.qs.PagedTileLayout.TilePage;
-import com.android.systemui.qs.QSPanel.QSTileLayout;
-import com.android.systemui.qs.QSPanel.TileRecord;
-import com.android.systemui.qs.QSTile;
-
-import java.util.ArrayList;
-
-/**
- * Similar to PagedTileLayout, except that instead of pages it lays them out
- * vertically and expects to be inside a ScrollView.
- * @see CustomQSPanel
- */
-public class NonPagedTileLayout extends LinearLayout implements QSTileLayout, OnTouchListener {
-
-    private final ArrayList<TilePage> mPages = new ArrayList<>();
-    private final ArrayList<TileRecord> mTiles = new ArrayList<TileRecord>();
-    private CustomQSPanel mPanel;
-    private final Rect mHitRect = new Rect();
-
-    private ClipData mCurrentClip;
-    private View mCurrentView;
-
-    public NonPagedTileLayout(Context context, AttributeSet attrs) {
-        super(context, attrs);
-    }
-
-    @Override
-    protected void onFinishInflate() {
-        super.onFinishInflate();
-        TilePage page = (PagedTileLayout.TilePage) findViewById(R.id.tile_page);
-        page.setMaxRows(3 /* First page only gets 3 */);
-        mPages.add(page);
-    }
-
-    public void setCustomQsPanel(CustomQSPanel qsPanel) {
-        mPanel = qsPanel;
-    }
-
-    @Override
-    public void addTile(TileRecord record) {
-        mTiles.add(record);
-        distributeTiles();
-        if (record.tileView.getTag() == record.tile) {
-            return;
-        }
-        record.tileView.setTag(record.tile);
-        record.tileView.setVisibility(View.VISIBLE);
-        record.tileView.init(null, null);
-        record.tileView.setOnTouchListener(this);
-        if (mCurrentClip != null && mCurrentClip.getItemAt(0)
-                .getText().toString().equals(record.tile.getTileSpec())) {
-            record.tileView.setAlpha(.3f);
-            mCurrentView = record.tileView;
-        }
-    }
-
-    @Override
-    public void removeTile(TileRecord tile) {
-        if (mTiles.remove(tile)) {
-            distributeTiles();
-        }
-    }
-
-    private void distributeTiles() {
-        final int NP = mPages.size();
-        for (int i = 0; i < NP; i++) {
-            mPages.get(i).removeAllViews();
-        }
-        int index = 0;
-        final int NT = mTiles.size();
-        for (int i = 0; i < NT; i++) {
-            TileRecord tile = mTiles.get(i);
-            mPages.get(index).addTile(tile);
-            // Keep everything in one layout for now.
-            if (false && mPages.get(index).isFull()) {
-                if (++index == mPages.size()) {
-                    LayoutInflater inflater = LayoutInflater.from(mContext);
-                    inflater.inflate(R.layout.horizontal_divider, this);
-                    mPages.add((TilePage) inflater.inflate(R.layout.qs_paged_page, this, false));
-                    addView(mPages.get(mPages.size() - 1));
-                }
-            }
-        }
-    }
-
-    @Override
-    public int getOffsetTop(TileRecord tile) {
-        // No touch feedback, so this isn't required.
-        return 0;
-    }
-
-    @Override
-    public boolean updateResources() {
-        return false;
-    }
-
-    @Override
-    public boolean onDragEvent(DragEvent event) {
-        switch (event.getAction()) {
-            case DragEvent.ACTION_DRAG_LOCATION:
-                float x = event.getX();
-                float y = event.getY();
-                final int NP = mPages.size();
-                for (int i = 0; i < NP; i++) {
-                    TilePage page = mPages.get(i);
-                    if (contains(page, x, y)) {
-                        x -= page.getLeft();
-                        y -= page.getTop();
-                        final int NC = page.getChildCount();
-                        for (int j = 0; j < NC; j++) {
-                            View child = page.getChildAt(j);
-                            if (contains(child, x, y)) {
-                                mPanel.tileSelected((QSTile<?>) child.getTag(), mCurrentClip);
-                            }
-                        }
-                        break;
-                    }
-                }
-                break;
-            case DragEvent.ACTION_DRAG_ENDED:
-                onDragEnded();
-                break;
-        }
-        return true;
-    }
-
-    @Override
-    public boolean onTouch(View v, MotionEvent event) {
-        switch (event.getAction()) {
-            case MotionEvent.ACTION_DOWN:
-                // Stash the current tiles, in case the drop is on info, that we can restore
-                // the previous state.
-                mPanel.stashCurrentTiles();
-                mCurrentView = v;
-                mCurrentClip = mPanel.getClip((QSTile<?>) v.getTag());
-                View.DragShadowBuilder shadow = new View.DragShadowBuilder(v);
-                ((View) getParent().getParent()).startDrag(mCurrentClip, shadow, null, 0);
-                v.setAlpha(.3f);
-                return true;
-        }
-        return false;
-    }
-
-    public void onDragEnded() {
-        mCurrentView.setAlpha(1f);
-        mCurrentView = null;
-        mCurrentClip = null;
-    }
-
-    private boolean contains(View v, float x, float y) {
-        v.getHitRect(mHitRect);
-        return mHitRect.contains((int) x, (int) y);
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
index a6c7fe4..edcccac 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
@@ -16,38 +16,25 @@
 package com.android.systemui.qs.customize;
 
 import android.animation.Animator;
-import android.content.ClipData;
+import android.animation.Animator.AnimatorListener;
 import android.content.Context;
-import android.content.DialogInterface;
-import android.content.DialogInterface.OnCancelListener;
-import android.content.DialogInterface.OnDismissListener;
+import android.support.v7.widget.DefaultItemAnimator;
+import android.support.v7.widget.GridLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.support.v7.widget.helper.ItemTouchHelper;
 import android.util.AttributeSet;
-import android.util.Log;
-import android.util.TypedValue;
 import android.view.ContextThemeWrapper;
-import android.view.DragEvent;
-import android.view.LayoutInflater;
-import android.view.Menu;
-import android.view.MenuItem;
 import android.view.View;
 import android.view.View.OnClickListener;
-import android.view.ViewGroup;
-import android.view.WindowManager;
 import android.widget.LinearLayout;
-import android.widget.ListView;
-import android.widget.Toolbar;
-import android.widget.Toolbar.OnMenuItemClickListener;
-
 import com.android.systemui.R;
 import com.android.systemui.qs.QSDetailClipper;
-import com.android.systemui.qs.QSTile.Host.Callback;
-import com.android.systemui.qs.customize.DropButton.OnDropListener;
-import com.android.systemui.qs.customize.TileAdapter.TileSelectedListener;
+import com.android.systemui.qs.QSTile;
 import com.android.systemui.statusbar.phone.PhoneStatusBar;
 import com.android.systemui.statusbar.phone.QSTileHost;
-import com.android.systemui.statusbar.phone.SystemUIDialog;
 
 import java.util.ArrayList;
+import java.util.List;
 
 /**
  * Allows full-screen customization of QS, through show() and hide().
@@ -55,26 +42,19 @@
  * This adds itself to the status bar window, so it can appear on top of quick settings and
  * *someday* do fancy animations to get into/out of it.
  */
-public class QSCustomizer extends LinearLayout implements OnMenuItemClickListener, Callback,
-        OnDropListener, OnClickListener, Animator.AnimatorListener, TileSelectedListener,
-        OnCancelListener, OnDismissListener {
+public class QSCustomizer extends LinearLayout implements AnimatorListener, OnClickListener {
 
-    private static final int MENU_SAVE = Menu.FIRST;
-    private static final int MENU_RESET = Menu.FIRST + 1;
     private final QSDetailClipper mClipper;
 
     private PhoneStatusBar mPhoneStatusBar;
 
-    private Toolbar mToolbar;
-    private ViewGroup mDragButtons;
-    private CustomQSPanel mQsPanel;
-
     private boolean isShown;
-    private DropButton mInfoButton;
-    private DropButton mRemoveButton;
-    private FloatingActionButton mFab;
-    private SystemUIDialog mDialog;
     private QSTileHost mHost;
+    private RecyclerView mRecyclerView;
+    private TileAdapter mTileAdapter;
+    private View mClose;
+    private View mSave;
+    private View mReset;
 
     public QSCustomizer(Context context, AttributeSet attrs) {
         super(new ContextThemeWrapper(context, android.R.style.Theme_Material), attrs);
@@ -83,59 +63,42 @@
 
     public void setHost(QSTileHost host) {
         mHost = host;
-        mHost.addCallback(this);
         mPhoneStatusBar = host.getPhoneStatusBar();
-        mQsPanel.setTiles(mHost.getTiles());
-        mQsPanel.setHost(mHost);
-        mQsPanel.setSavedTiles();
     }
 
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
-        mToolbar = (Toolbar) findViewById(com.android.internal.R.id.action_bar);
-        TypedValue value = new TypedValue();
-        mContext.getTheme().resolveAttribute(android.R.attr.homeAsUpIndicator, value, true);
-        mToolbar.setNavigationIcon(
-                getResources().getDrawable(R.drawable.ic_close_white, mContext.getTheme()));
-        mToolbar.setNavigationOnClickListener(new OnClickListener() {
-            @Override
-            public void onClick(View v) {
-                hide(0, 0);
-            }
-        });
-        mToolbar.setOnMenuItemClickListener(this);
-        mToolbar.getMenu().add(Menu.NONE, MENU_SAVE, 0, mContext.getString(R.string.save))
-                .setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
-        mToolbar.getMenu().add(Menu.NONE, MENU_RESET, 0,
-                mContext.getString(com.android.internal.R.string.reset));
+        mClose = findViewById(R.id.close);
+        mSave = findViewById(R.id.save);
+        mReset = findViewById(R.id.reset);
+        mClose.setOnClickListener(this);
+        mSave.setOnClickListener(this);
+        mReset.setOnClickListener(this);
 
-        mQsPanel = (CustomQSPanel) findViewById(R.id.quick_settings_panel);
-
-        mDragButtons = (ViewGroup) findViewById(R.id.drag_buttons);
-        setDragging(false);
-
-        mInfoButton = (DropButton) findViewById(R.id.info_button);
-        mInfoButton.setOnDropListener(this);
-        mRemoveButton = (DropButton) findViewById(R.id.remove_button);
-        mRemoveButton.setOnDropListener(this);
-
-        mFab = (FloatingActionButton) findViewById(R.id.fab);
-        mFab.setImageResource(R.drawable.ic_add);
-        mFab.setOnClickListener(this);
+        mRecyclerView = (RecyclerView) findViewById(android.R.id.list);
+        mTileAdapter = new TileAdapter(getContext());
+        mRecyclerView.setAdapter(mTileAdapter);
+        new ItemTouchHelper(mTileAdapter.getCallback()).attachToRecyclerView(mRecyclerView);
+        GridLayoutManager layout = new GridLayoutManager(getContext(), 3);
+        layout.setSpanSizeLookup(mTileAdapter.getSizeLookup());
+        mRecyclerView.setLayoutManager(layout);
+        mRecyclerView.addItemDecoration(mTileAdapter.getItemDecoration());
+        DefaultItemAnimator animator = new DefaultItemAnimator();
+        animator.setMoveDuration(TileAdapter.MOVE_DURATION);
+        mRecyclerView.setItemAnimator(animator);
     }
 
     public void show(int x, int y) {
         isShown = true;
-        mQsPanel.setSavedTiles();
         mPhoneStatusBar.getStatusBarWindow().addView(this);
-        mQsPanel.setListening(true);
+        setTileSpecs();
         mClipper.animateCircularClip(x, y, true, this);
+        new TileQueryHelper(mContext, mHost).setListener(mTileAdapter);
     }
 
     public void hide(int x, int y) {
         isShown = false;
-        mQsPanel.setListening(false);
         mClipper.animateCircularClip(x, y, false, this);
     }
 
@@ -149,109 +112,35 @@
         for (String tile : defTiles.split(",")) {
             tiles.add(tile);
         }
-        mQsPanel.setTiles(tiles);
+        mTileAdapter.setTileSpecs(tiles);
     }
 
-    private void setDragging(boolean dragging) {
-        mToolbar.setVisibility(!dragging ? View.VISIBLE : View.INVISIBLE);
+    private void setTileSpecs() {
+        List<String> specs = new ArrayList<>();
+        for (QSTile tile : mHost.getTiles()) {
+            specs.add(tile.getTileSpec());
+        }
+        mTileAdapter.setTileSpecs(specs);
     }
 
     private void save() {
-        Log.d("CustomQSPanel", "Save!");
-        mQsPanel.saveCurrentTiles();
-        // TODO: At save button.
-        hide(0, 0);
-    }
-
-    @Override
-    public boolean onMenuItemClick(MenuItem item) {
-        switch (item.getItemId()) {
-            case MENU_SAVE:
-                Log.d("CustomQSPanel", "Save...");
-                save();
-                break;
-            case MENU_RESET:
-                reset();
-                break;
-        }
-        return true;
-    }
-
-    @Override
-    public void onTileSelected(String spec) {
-        if (mDialog != null) {
-            mQsPanel.addTile(spec);
-            mDialog.dismiss();
-        }
-    }
-
-    @Override
-    public void onTilesChanged() {
-        mQsPanel.setTiles(mHost.getTiles());
-    }
-
-    public boolean onDragEvent(DragEvent event) {
-        switch (event.getAction()) {
-            case DragEvent.ACTION_DRAG_STARTED:
-                setDragging(true);
-                break;
-            case DragEvent.ACTION_DRAG_ENDED:
-                setDragging(false);
-                break;
-        }
-        return true;
-    }
-
-    public void onDrop(View v, ClipData data) {
-        if (v == mRemoveButton) {
-            mQsPanel.remove(mQsPanel.getSpec(data));
-        } else if (v == mInfoButton) {
-            mQsPanel.unstashTiles();
-            SystemUIDialog dialog = new SystemUIDialog(mContext);
-            dialog.setTitle(mQsPanel.getSpec(data));
-            dialog.setPositiveButton(R.string.ok, null);
-            dialog.show();
-        }
+        mTileAdapter.saveSpecs(mHost);
+        hide((int) mSave.getX() + mSave.getWidth() / 2, (int) mSave.getY() + mSave.getHeight() / 2);
     }
 
     @Override
     public void onClick(View v) {
-        if (mFab == v) {
-            mDialog = new SystemUIDialog(mContext,
-                    android.R.style.Theme_Material_Dialog);
-            View view = LayoutInflater.from(mContext).inflate(R.layout.qs_add_tiles_list, null);
-            ListView listView = (ListView) view.findViewById(android.R.id.list);
-            TileAdapter adapter = new TileAdapter(mContext, mQsPanel.getTiles(), mHost);
-            adapter.setListener(this);
-            listView.setDivider(null);
-            listView.setDividerHeight(0);
-            listView.setAdapter(adapter);
-            listView.setEmptyView(view.findViewById(R.id.empty_text));
-            mDialog.setView(view);
-            mDialog.setOnDismissListener(this);
-            mDialog.setOnCancelListener(this);
-            mDialog.show();
-            // Too lazy to figure out what this will be now, but it should probably be something
-            // besides just a dialog.
-            // For now, just make it big.
-            WindowManager.LayoutParams params = mDialog.getWindow().getAttributes();
-            params.width = WindowManager.LayoutParams.MATCH_PARENT;
-            params.height = WindowManager.LayoutParams.WRAP_CONTENT;
-            mDialog.getWindow().setAttributes(params);
+        if (v == mClose) {
+            hide((int) mClose.getX() + mClose.getWidth() / 2,
+                    (int) mClose.getY() + mClose.getHeight() / 2);
+        } else if (v == mSave) {
+            save();
+        } else if (v == mReset) {
+            reset();
         }
     }
 
     @Override
-    public void onDismiss(DialogInterface dialog) {
-        mDialog = null;
-    }
-
-    @Override
-    public void onCancel(DialogInterface dialog) {
-        mDialog = null;
-    }
-
-    @Override
     public void onAnimationEnd(Animator animation) {
         if (!isShown) {
             mPhoneStatusBar.getStatusBarWindow().removeView(this);
@@ -274,4 +163,4 @@
     public void onAnimationRepeat(Animator animation) {
         // Don't care.
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
index b72789e..fb3818c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
@@ -1,262 +1,293 @@
 /*
- * Copyright (C) 2015 The Android Open Source Project
+ * Copyright (C) 2016 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.qs.customize;
 
-import android.content.ComponentName;
 import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.ResolveInfo;
-import android.graphics.drawable.Drawable;
-import android.os.AsyncTask;
-import android.os.Handler;
-import android.os.Looper;
-import android.service.quicksettings.TileService;
-import android.util.Log;
+import android.graphics.Canvas;
+import android.graphics.drawable.ColorDrawable;
+import android.support.v4.view.ViewCompat;
+import android.support.v7.widget.GridLayoutManager.SpanSizeLookup;
+import android.support.v7.widget.RecyclerView;
+import android.support.v7.widget.RecyclerView.ItemDecoration;
+import android.support.v7.widget.RecyclerView.State;
+import android.support.v7.widget.RecyclerView.ViewHolder;
+import android.support.v7.widget.helper.ItemTouchHelper;
+import android.support.v7.widget.helper.ItemTouchHelper.Callback;
 import android.view.LayoutInflater;
 import android.view.View;
-import android.view.View.OnClickListener;
 import android.view.ViewGroup;
-import android.widget.BaseAdapter;
-import android.widget.GridLayout;
-import android.widget.ImageView;
-import android.widget.TextView;
-
+import android.widget.FrameLayout;
 import com.android.systemui.R;
-import com.android.systemui.qs.QSTile;
-import com.android.systemui.qs.QSTile.Icon;
-import com.android.systemui.qs.external.CustomTile;
+import com.android.systemui.qs.QSIconView;
+import com.android.systemui.qs.QSTileView;
+import com.android.systemui.qs.customize.TileAdapter.Holder;
+import com.android.systemui.qs.customize.TileQueryHelper.TileInfo;
+import com.android.systemui.qs.customize.TileQueryHelper.TileStateListener;
 import com.android.systemui.statusbar.phone.QSTileHost;
 
 import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
 import java.util.List;
 
-public class TileAdapter extends BaseAdapter {
+public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileStateListener {
 
-    private static final String TAG = "TileAdapter";
+    private static final long DRAG_LENGTH = 100;
+    private static final float DRAG_SCALE = 1.2f;
+    public static final long MOVE_DURATION = 150;
 
-    private final ArrayList<TileGroup> mGroups = new ArrayList<>();
+    private static final int TYPE_TILE = 0;
+    private static final int TYPE_EDIT = 1;
+
     private final Context mContext;
 
-    private TileSelectedListener mListener;
-    private ArrayList<String> mCurrentTiles;
+    private final List<TileInfo> mTiles = new ArrayList<>();
+    private int mDividerIndex;
+    private List<String> mCurrentSpecs;
+    private List<TileInfo> mOtherTiles;
+    private List<TileInfo> mAllTiles;
 
-    public TileAdapter(Context context, Collection<QSTile<?>> currentTiles, QSTileHost host) {
+    private Holder mCurrentDrag;
+
+    public TileAdapter(Context context) {
         mContext = context;
-        addSystemTiles(currentTiles, host);
-        // TODO: Live?
-    }
-
-    private void addSystemTiles(Collection<QSTile<?>> currentTiles, QSTileHost host) {
-        try {
-            ArrayList<String> tileSpecs = new ArrayList<>();
-            for (QSTile<?> tile : currentTiles) {
-                tileSpecs.add(tile.getTileSpec());
-            }
-            mCurrentTiles = tileSpecs;
-            final TileGroup group = new TileGroup("com.android.settings", mContext);
-            boolean hasColorMod = host.getDisplayController().isEnabled();
-            String possible = mContext.getString(R.string.quick_settings_tiles_default)
-                    + ",hotspot,inversion,saver" + (hasColorMod ? ",colors" : "");
-            String[] possibleTiles = possible.split(",");
-            for (int i = 0; i < possibleTiles.length; i++) {
-                final String spec = possibleTiles[i];
-                if (spec.startsWith("q")) {
-                    // Quick tiles can't be customized.
-                    continue;
-                }
-                if (tileSpecs.contains(spec)) {
-                    Log.d(TAG, "Skipping " + spec);
-                    continue;
-                }
-                Log.d(TAG, "Trying " + spec);
-                final QSTile<?> tile = host.createTile(spec);
-                if (tile == null) {
-                    continue;
-                }
-                // Bad, bad, very bad.
-                tile.setListening(true);
-                tile.clearState();
-                tile.refreshState();
-                tile.setListening(false);
-                new Handler(host.getLooper()).post(new Runnable() {
-                    @Override
-                    public void run() {
-                        group.addTile(spec, tile.getState().icon, tile.getState().label, mContext);
-                    }
-                });
-            }
-            // Error: Badness (10000).
-            // Serialize this work after the host's looper's queue is empty.
-            new Handler(host.getLooper()).post(new Runnable() {
-                @Override
-                public void run() {
-                    new Handler(Looper.getMainLooper()).post(new Runnable() {
-                        @Override
-                        public void run() {
-                            if (group.mTiles.size() > 0) {
-                                mGroups.add(group);
-                                notifyDataSetChanged();
-                            }
-                            new QueryTilesTask().execute();
-                        }
-                    });
-                }
-            });
-        } catch (NameNotFoundException e) {
-            Log.e(TAG, "Couldn't load system tiles", e);
-        }
-    }
-
-    public void setListener(TileSelectedListener listener) {
-        mListener = listener;
-    }
-
-    @Override
-    public int getCount() {
-        return mGroups.size();
-    }
-
-    @Override
-    public Object getItem(int position) {
-        return mGroups.get(position);
+        setHasStableIds(true);
     }
 
     @Override
     public long getItemId(int position) {
-        return position;
+        return mTiles.get(position) != null ? mAllTiles.indexOf(mTiles.get(position)) : -1;
+    }
+
+    public Callback getCallback() {
+        return mCallbacks;
+    }
+
+    public ItemDecoration getItemDecoration() {
+        return mDecoration;
+    }
+
+    public void saveSpecs(QSTileHost host) {
+        List<String> newSpecs = new ArrayList<>();
+        for (int i = 0; mTiles.get(i) != null; i++) {
+            newSpecs.add(mTiles.get(i).spec);
+        }
+        host.changeTiles(mCurrentSpecs, newSpecs);
+        setTileSpecs(newSpecs);
+    }
+
+    public void setTileSpecs(List<String> currentSpecs) {
+        mCurrentSpecs = currentSpecs;
+        recalcSpecs();
     }
 
     @Override
-    public View getView(int position, View convertView, ViewGroup parent) {
-        return mGroups.get(position).getView(mContext, convertView, parent, mListener);
+    public void onTilesChanged(List<TileInfo> tiles) {
+        mAllTiles = tiles;
+        recalcSpecs();
     }
 
-    private static class TileGroup {
-        private final ArrayList<TileInfo> mTiles = new ArrayList<>();
-        private CharSequence mLabel;
-        private Drawable mIcon;
-
-        public TileGroup(String pkg, Context context) throws NameNotFoundException {
-            PackageManager pm = context.getPackageManager();
-            ApplicationInfo info = pm.getApplicationInfo(pkg, 0);
-            mLabel = info.loadLabel(pm);
-            mIcon = info.loadIcon(pm);
-            Log.d(TAG, "Added " + mLabel);
+    private void recalcSpecs() {
+        if (mCurrentSpecs == null || mAllTiles == null) {
+            return;
         }
-
-        private void addTile(String spec, Drawable icon, CharSequence label) {
-            TileInfo info = new TileInfo();
-            info.label = label;
-            info.drawable = icon;
-            info.spec = spec;
-            mTiles.add(info);
+        mOtherTiles = new ArrayList<TileInfo>(mAllTiles);
+        mTiles.clear();
+        for (int i = 0; i < mCurrentSpecs.size(); i++) {
+            mTiles.add(getAndRemoveOther(mCurrentSpecs.get(i)));
         }
+        mTiles.add(null);
+        mTiles.addAll(mOtherTiles);
+        mDividerIndex = mTiles.indexOf(null);
+        notifyDataSetChanged();
+    }
 
-        private void addTile(String spec, Icon icon, CharSequence label, Context context) {
-            addTile(spec, icon != null ? icon.getDrawable(context) : null, label);
-        }
-
-        private View getView(Context context, View convertView, ViewGroup parent,
-                final TileSelectedListener listener) {
-            if (convertView == null) {
-                convertView = LayoutInflater.from(context).inflate(R.layout.tile_listing, parent,
-                        false);
+    private TileInfo getAndRemoveOther(String s) {
+        for (int i = 0; i < mOtherTiles.size(); i++) {
+            if (mOtherTiles.get(i).spec.equals(s)) {
+                return mOtherTiles.remove(i);
             }
-            ((TextView) convertView.findViewById(android.R.id.title)).setText(mLabel);
-            ((ImageView) convertView.findViewById(android.R.id.icon)).setImageDrawable(mIcon);
-            GridLayout grid = (GridLayout) convertView.findViewById(R.id.tile_grid);
-            final int N = mTiles.size();
-            if (grid.getChildCount() != N) {
-                grid.removeAllViews();
+        }
+        return null;
+    }
+
+    @Override
+    public int getItemViewType(int position) {
+        if (mTiles.get(position) == null) {
+            return TYPE_EDIT;
+        }
+        return TYPE_TILE;
+    }
+
+    @Override
+    public Holder onCreateViewHolder(ViewGroup parent, int viewType) {
+        final Context context = parent.getContext();
+        LayoutInflater inflater = LayoutInflater.from(context);
+        if (viewType == 1) {
+            return new Holder(inflater.inflate(R.layout.qs_customize_divider, parent, false));
+        }
+        FrameLayout frame = (FrameLayout) inflater.inflate(R.layout.qs_customize_tile_frame, parent,
+                false);
+        frame.addView(new QSTileView(context, new QSIconView(context)));
+        return new Holder(frame);
+    }
+
+    @Override
+    public int getItemCount() {
+        return mTiles.size();
+    }
+
+    @Override
+    public void onBindViewHolder(Holder holder, int position) {
+        if (holder.getItemViewType() == TYPE_EDIT) return;
+
+        TileInfo info = mTiles.get(position);
+        holder.mTileView.onStateChanged(info.state);
+    }
+
+    public SpanSizeLookup getSizeLookup() {
+        return mSizeLookup;
+    }
+
+    public class Holder extends ViewHolder {
+        private QSTileView mTileView;
+
+        public Holder(View itemView) {
+            super(itemView);
+            if (itemView instanceof FrameLayout) {
+                mTileView = (QSTileView) ((FrameLayout) itemView).getChildAt(0);
             }
-            for (int i = 0; i < N; i++) {
-                if (grid.getChildCount() <= i) {
-                    grid.addView(createTile(context));
-                }
-                View view = grid.getChildAt(i);
-                final TileInfo tileInfo = mTiles.get(i);
-                ((ImageView) view.findViewById(R.id.tile_icon)).setImageDrawable(tileInfo.drawable);
-                ((TextView) view.findViewById(R.id.tile_label)).setText(tileInfo.label);
-                view.setClickable(true);
-                view.setOnClickListener(new OnClickListener() {
-                    @Override
-                    public void onClick(View v) {
-                        listener.onTileSelected(tileInfo.spec);
-                    }
-                });
-            }
-            return convertView;
         }
 
-        private View createTile(Context context) {
-            return LayoutInflater.from(context).inflate(R.layout.qs_add_tile_layout, null);
+        public void startDrag() {
+            itemView.animate()
+                    .setDuration(DRAG_LENGTH)
+                    .scaleX(DRAG_SCALE)
+                    .scaleY(DRAG_SCALE);
+            mTileView.findViewById(R.id.tile_label).animate()
+                    .setDuration(DRAG_LENGTH)
+                    .alpha(0);
+        }
+
+        public void stopDrag() {
+            itemView.animate()
+                    .setDuration(DRAG_LENGTH)
+                    .scaleX(1)
+                    .scaleY(1);
+            mTileView.findViewById(R.id.tile_label).animate()
+                    .setDuration(DRAG_LENGTH)
+                    .alpha(1);
         }
     }
 
-    private static class TileInfo {
-        private String spec;
-        private Drawable drawable;
-        private CharSequence label;
-    }
-
-    private class QueryTilesTask extends AsyncTask<Void, Void, Collection<TileGroup>> {
+    private final SpanSizeLookup mSizeLookup = new SpanSizeLookup() {
         @Override
-        protected Collection<TileGroup> doInBackground(Void... params) {
-            HashMap<String, TileGroup> pkgMap = new HashMap<>();
-            PackageManager pm = mContext.getPackageManager();
-            // TODO: Handle userness.
-            List<ResolveInfo> services = pm.queryIntentServices(
-                    new Intent(TileService.ACTION_QS_TILE), 0);
-            for (ResolveInfo info : services) {
-                String packageName = info.serviceInfo.packageName;
-                ComponentName componentName = new ComponentName(packageName, info.serviceInfo.name);
-                String spec = CustomTile.toSpec(componentName);
-                if (mCurrentTiles.contains(spec)) {
+        public int getSpanSize(int position) {
+            return getItemViewType(position) == TYPE_EDIT ? 3 : 1;
+        }
+    };
+
+    private final ItemDecoration mDecoration = new ItemDecoration() {
+        // TODO: Move this to resource.
+        private final ColorDrawable mDrawable = new ColorDrawable(0xff384248);
+
+        @Override
+        public void onDraw(Canvas c, RecyclerView parent, State state) {
+            super.onDraw(c, parent, state);
+
+            final int childCount = parent.getChildCount();
+            final int width = parent.getWidth();
+            final int bottom = parent.getBottom();
+            for (int i = 0; i < childCount; i++) {
+                final View child = parent.getChildAt(i);
+                final ViewHolder holder = parent.getChildViewHolder(child);
+                if (holder.getAdapterPosition() < mDividerIndex) {
                     continue;
                 }
-                try {
-                    TileGroup group = pkgMap.get(packageName);
-                    if (group == null) {
-                        group = new TileGroup(packageName, mContext);
-                        pkgMap.put(packageName, group);
-                    }
-                    Drawable icon = info.serviceInfo.loadIcon(pm);
-                    CharSequence label = info.serviceInfo.loadLabel(pm);
-                    group.addTile(spec, icon, label != null ? label.toString() : "null");
-                } catch (NameNotFoundException e) {
-                    Log.w(TAG, "Couldn't find resolved package... " + packageName, e);
-                }
+
+                final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
+                        .getLayoutParams();
+                final int top = child.getTop() + params.topMargin +
+                        Math.round(ViewCompat.getTranslationY(child));
+                // Draw full width, in case there aren't tiles all the way across.
+                mDrawable.setBounds(0, top, width, bottom);
+                mDrawable.draw(c);
+                break;
             }
-            return pkgMap.values();
+        }
+    };
+
+    private final ItemTouchHelper.Callback mCallbacks = new ItemTouchHelper.Callback() {
+
+        @Override
+        public boolean isLongPressDragEnabled() {
+            return true;
         }
 
         @Override
-        protected void onPostExecute(Collection<TileGroup> result) {
-            mGroups.addAll(result);
-            notifyDataSetChanged();
+        public boolean isItemViewSwipeEnabled() {
+            return false;
         }
-    }
 
-    public interface TileSelectedListener {
-        void onTileSelected(String spec);
-    }
+        @Override
+        public void onSelectedChanged(ViewHolder viewHolder, int actionState) {
+            super.onSelectedChanged(viewHolder, actionState);
+            if (mCurrentDrag != null) {
+                mCurrentDrag.stopDrag();
+            }
+            if (viewHolder != null) {
+                mCurrentDrag = (Holder) viewHolder;
+                mCurrentDrag.startDrag();
+            }
+        }
+
+        @Override
+        public int getMovementFlags(RecyclerView recyclerView, ViewHolder viewHolder) {
+            if (viewHolder.getItemViewType() == TYPE_EDIT) {
+                return makeMovementFlags(0, 0);
+            }
+            int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN | ItemTouchHelper.RIGHT
+                    | ItemTouchHelper.LEFT;
+            return makeMovementFlags(dragFlags, 0);
+        }
+
+        @Override
+        public boolean onMove(RecyclerView recyclerView, ViewHolder viewHolder, ViewHolder target) {
+            int from = viewHolder.getAdapterPosition();
+            int to = target.getAdapterPosition();
+            if (to > mDividerIndex) {
+                if (from < mDividerIndex) {
+                    to = mDividerIndex;
+                } else {
+                    return false;
+                }
+            }
+            if (target.getItemViewType() == TYPE_EDIT && from < mDividerIndex) {
+                to++;
+            }
+            move(from, to, mTiles);
+            mDividerIndex = mTiles.indexOf(null);
+            notifyItemMoved(from, to);
+            return true;
+        }
+
+        private <T> void move(int from, int to, List<T> list) {
+            list.add(from > to ? to : to + 1, list.get(from));
+            list.remove(from > to ? from + 1 : from);
+        }
+
+        @Override
+        public void onSwiped(ViewHolder viewHolder, int direction) {
+        }
+    };
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
new file mode 100644
index 0000000..6840e08
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.customize;
+
+import android.app.ActivityManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.graphics.drawable.Drawable;
+import android.os.AsyncTask;
+import android.os.Handler;
+import android.os.Looper;
+import android.service.quicksettings.TileService;
+import com.android.systemui.R;
+import com.android.systemui.qs.QSTile;
+import com.android.systemui.qs.QSTile.DrawableIcon;
+import com.android.systemui.qs.external.CustomTile;
+import com.android.systemui.statusbar.phone.QSTileHost;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+public class TileQueryHelper {
+
+    private static final String TAG = "TileQueryHelper";
+
+    private final ArrayList<TileInfo> mTiles = new ArrayList<>();
+    private final Context mContext;
+    private TileStateListener mListener;
+
+    public TileQueryHelper(Context context, QSTileHost host) {
+        mContext = context;
+        addSystemTiles(host);
+        // TODO: Live?
+    }
+
+    private void addSystemTiles(QSTileHost host) {
+        boolean hasColorMod = host.getDisplayController().isEnabled();
+        String possible = mContext.getString(R.string.quick_settings_tiles_default)
+                + ",hotspot,inversion,saver" + (hasColorMod ? ",colors" : "");
+        String[] possibleTiles = possible.split(",");
+        final Handler qsHandler = new Handler(host.getLooper());
+        final Handler mainHandler = new Handler(Looper.getMainLooper());
+        for (int i = 0; i < possibleTiles.length; i++) {
+            final String spec = possibleTiles[i];
+            final QSTile<?> tile = host.createTile(spec);
+            if (tile == null) {
+                continue;
+            }
+            tile.setListening(true);
+            tile.clearState();
+            tile.refreshState();
+            tile.setListening(false);
+            qsHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    final QSTile.State state = tile.newTileState();
+                    tile.getState().copyTo(state);
+                    mainHandler.post(new Runnable() {
+                        @Override
+                        public void run() {
+                            addTile(spec, state, mTiles);
+                            mListener.onTilesChanged(mTiles);
+                        }
+                    });
+                }
+            });
+        }
+        qsHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                new QueryTilesTask().execute();
+            }
+        });
+    }
+
+    public void setListener(TileStateListener listener) {
+        mListener = listener;
+    }
+
+    private static void addTile(String spec, QSTile.State state, List<TileInfo> tiles) {
+        TileInfo info = new TileInfo();
+        info.state = state;
+        info.spec = spec;
+        tiles.add(info);
+    }
+
+    private static void addTile(String spec, Drawable drawable, CharSequence label, Context context,
+            List<TileInfo> tiles) {
+        QSTile.State state = new QSTile.State();
+        state.label = label;
+        state.contentDescription = label;
+        state.icon = new DrawableIcon(drawable);
+        addTile(spec, state, tiles);
+    }
+
+    public static class TileInfo {
+        public String spec;
+        public QSTile.State state;
+    }
+
+    private class QueryTilesTask extends AsyncTask<Void, Void, Collection<TileInfo>> {
+        @Override
+        protected Collection<TileInfo> doInBackground(Void... params) {
+            List<TileInfo> tiles = new ArrayList<>();
+            PackageManager pm = mContext.getPackageManager();
+            List<ResolveInfo> services = pm.queryIntentServicesAsUser(
+                    new Intent(TileService.ACTION_QS_TILE), 0, ActivityManager.getCurrentUser());
+            for (ResolveInfo info : services) {
+                String packageName = info.serviceInfo.packageName;
+                ComponentName componentName = new ComponentName(packageName, info.serviceInfo.name);
+                String spec = CustomTile.toSpec(componentName);
+                Drawable icon = info.serviceInfo.loadIcon(pm);
+                if (icon != null) {
+                    icon.mutate();
+                    icon.setTint(mContext.getColor(android.R.color.white));
+                }
+                CharSequence label = info.serviceInfo.loadLabel(pm);
+                addTile(spec, icon, label != null ? label.toString() : "null", mContext, tiles);
+            }
+            return tiles;
+        }
+
+        @Override
+        protected void onPostExecute(Collection<TileInfo> result) {
+            mTiles.addAll(result);
+            mListener.onTilesChanged(mTiles);
+        }
+    }
+
+    public interface TileStateListener {
+        void onTilesChanged(List<TileInfo> tiles);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
index df3b5de..3cd9e67 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
@@ -151,7 +151,7 @@
     }
 
     @Override
-    protected State newTileState() {
+    public State newTileState() {
         return new State();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java
index d78d6ff..5222e61 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java
@@ -51,7 +51,7 @@
     }
 
     @Override
-    protected BooleanState newTileState() {
+    public BooleanState newTileState() {
         return new BooleanState();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BatteryTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BatteryTile.java
index 64b3a6c..cd3e3b2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BatteryTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BatteryTile.java
@@ -54,7 +54,7 @@
     }
 
     @Override
-    protected State newTileState() {
+    public State newTileState() {
         return new QSTile.State();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
index 874fc3e..1dce053 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
@@ -56,7 +56,7 @@
     }
 
     @Override
-    protected BooleanState newTileState() {
+    public BooleanState newTileState() {
         return new BooleanState();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
index 18eb7a1..15e082a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
@@ -60,7 +60,7 @@
     }
 
     @Override
-    protected BooleanState newTileState() {
+    public BooleanState newTileState() {
         return new BooleanState();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
index aacdbc9..c3a2ebe 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
@@ -54,7 +54,7 @@
     }
 
     @Override
-    protected SignalState newTileState() {
+    public SignalState newTileState() {
         return new SignalState();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java
index 6e843e9..e98734c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java
@@ -54,7 +54,7 @@
     }
 
     @Override
-    protected BooleanState newTileState() {
+    public BooleanState newTileState() {
         return new BooleanState();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java
index 1aeb0fe..c6a98b4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java
@@ -30,7 +30,7 @@
     }
 
     @Override
-    protected BooleanState newTileState() {
+    public BooleanState newTileState() {
         return new BooleanState();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
index f99a3e4..58872ec 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
@@ -94,7 +94,7 @@
     }
 
     @Override
-    protected BooleanState newTileState() {
+    public BooleanState newTileState() {
         return new BooleanState();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java
index 1d9f15b..f06634e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java
@@ -47,7 +47,7 @@
     }
 
     @Override
-    protected BooleanState newTileState() {
+    public BooleanState newTileState() {
         return new BooleanState();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
index 2f37943..943b502 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
@@ -44,7 +44,7 @@
     }
 
     @Override
-    protected BooleanState newTileState() {
+    public BooleanState newTileState() {
         return new BooleanState();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/IntentTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/IntentTile.java
index e1dc9f2..bdf95d8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/IntentTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/IntentTile.java
@@ -75,7 +75,7 @@
     }
 
     @Override
-    protected State newTileState() {
+    public State newTileState() {
         return new State();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java
index 8328897..9f41f9a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java
@@ -45,7 +45,7 @@
     }
 
     @Override
-    protected BooleanState newTileState() {
+    public BooleanState newTileState() {
         return new BooleanState();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java
index f920d48..c94cf5a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java
@@ -46,7 +46,7 @@
     }
 
     @Override
-    protected BooleanState newTileState() {
+    public BooleanState newTileState() {
         return new BooleanState();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserTile.java
index 1565b6f..ba7ea4d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserTile.java
@@ -37,7 +37,7 @@
     }
 
     @Override
-    protected State newTileState() {
+    public State newTileState() {
         return new QSTile.State();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
index 42296f2..ac4dfd5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
@@ -60,7 +60,7 @@
     }
 
     @Override
-    protected SignalState newTileState() {
+    public SignalState newTileState() {
         return new SignalState();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java
index 508490f..a94973c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java
@@ -53,7 +53,7 @@
     }
 
     @Override
-    protected BooleanState newTileState() {
+    public BooleanState newTileState() {
         return new BooleanState();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/ColorMatrixTile.java b/packages/SystemUI/src/com/android/systemui/tuner/ColorMatrixTile.java
index 7b06393..d8cf2e2 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/ColorMatrixTile.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/ColorMatrixTile.java
@@ -56,7 +56,7 @@
     }
 
     @Override
-    protected State newTileState() {
+    public State newTileState() {
         return new State();
     }
 
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 544f255..7f8099e 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -192,7 +192,7 @@
  * enforcement.
  */
 public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
-    private static final String TAG = "NetworkPolicy";
+    static final String TAG = "NetworkPolicy";
     private static final boolean LOGD = false;
     private static final boolean LOGV = false;
 
@@ -1689,6 +1689,7 @@
             }
             writePolicy = true;
         }
+        updateRulesForGlobalChangeLocked(true);
 
         // Remove associated UID policies
         int[] uids = new int[0];
@@ -1862,8 +1863,8 @@
         Slog.i(TAG, "adding uid " + uid + " to restrict background whitelist");
         synchronized (mRulesLock) {
             mRestrictBackgroundWhitelistUids.append(uid, true);
+            updateRulesForGlobalChangeLocked(true);
             writePolicyLocked();
-            // TODO: call other update methods like updateNetworkRulesLocked?
         }
         mHandler.obtainMessage(MSG_RESTRICT_BACKGROUND_WHITELIST_CHANGED, uid, 0).sendToTarget();
     }
@@ -1878,9 +1879,10 @@
         mHandler.obtainMessage(MSG_RESTRICT_BACKGROUND_WHITELIST_CHANGED, uid, 0).sendToTarget();
     }
 
-    private void removeRestrictBackgroundWhitelistedUidLocked(int uid, boolean writePolicy) {
+    private void removeRestrictBackgroundWhitelistedUidLocked(int uid, boolean updateNow) {
         mRestrictBackgroundWhitelistUids.delete(uid);
-        if (writePolicy) {
+        if (updateNow) {
+            updateRulesForGlobalChangeLocked(true);
             writePolicyLocked();
         }
     }
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerShellCommand.java b/services/core/java/com/android/server/net/NetworkPolicyManagerShellCommand.java
index 5830b0e..281c3d0 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerShellCommand.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerShellCommand.java
@@ -16,19 +16,24 @@
 
 package com.android.server.net;
 
-import java.io.PrintWriter;
+import static com.android.server.net.NetworkPolicyManagerService.TAG;
 
-import android.content.Intent;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
 import android.net.INetworkPolicyManager;
+import android.net.NetworkPolicy;
 import android.os.Binder;
 import android.os.RemoteException;
 import android.os.ShellCommand;
+import android.util.Log;
 
-public class NetworkPolicyManagerShellCommand extends ShellCommand {
+class NetworkPolicyManagerShellCommand extends ShellCommand {
 
     final INetworkPolicyManager mInterface;
 
-    NetworkPolicyManagerShellCommand(NetworkPolicyManagerService service) {
+    NetworkPolicyManagerShellCommand(INetworkPolicyManager service) {
         mInterface = service;
     }
 
@@ -66,16 +71,24 @@
         pw.println("  help");
         pw.println("    Print this help text.");
         pw.println("");
-        pw.println("  get restrict-background");
-        pw.println("    Gets the global restrict background usage status.");
-        pw.println("  set restrict-background BOOLEAN");
-        pw.println("    Sets the global restrict background usage status.");
-        pw.println("  list restrict-background-whitelist");
-        pw.println("    Prints UID that are whitelisted for restrict background usage.");
         pw.println("  add restrict-background-whitelist UID");
         pw.println("    Adds a UID to the whitelist for restrict background usage.");
+        pw.println("  get metered-network ID");
+        pw.println("    Checks whether the given non-mobile network is metered or not.");
+        pw.println("  get restrict-background");
+        pw.println("    Gets the global restrict background usage status.");
+        pw.println("  list metered-networks [BOOLEAN]");
+        pw.println("    Lists all non-mobile networks and whether they are metered or not.");
+        pw.println("    If a boolean argument is passed, filters just the metered (or unmetered)");
+        pw.println("    networks.");
+        pw.println("  list restrict-background-whitelist");
+        pw.println("    Lists UIDs that are whitelisted for restrict background usage.");
         pw.println("  remove restrict-background-whitelist UID");
         pw.println("    Removes a UID from the whitelist for restrict background usage.");
+        pw.println("  set metered-network ID BOOLEAN");
+        pw.println("    Toggles whether the given non-mobile network is metered.");
+        pw.println("  set restrict-background BOOLEAN");
+        pw.println("    Sets the global restrict background usage status.");
     }
 
     private int runGet() throws RemoteException {
@@ -86,6 +99,8 @@
             return -1;
         }
         switch(type) {
+            case "metered-network":
+                return getNonMobileMeteredNetwork();
             case "restrict-background":
                 return getRestrictBackground();
         }
@@ -101,6 +116,8 @@
             return -1;
         }
         switch(type) {
+            case "metered-network":
+                return setNonMobileMeteredNetwork();
             case "restrict-background":
                 return setRestrictBackground();
         }
@@ -116,6 +133,8 @@
             return -1;
         }
         switch(type) {
+            case "metered-networks":
+                return listNonMobileMeteredNetworks();
             case "restrict-background-whitelist":
                 return runListRestrictBackgroundWhitelist();
         }
@@ -196,7 +215,12 @@
       if (uid < 0) {
           return uid;
       }
-      mInterface.addRestrictBackgroundWhitelistedUid(uid);
+      final long token = Binder.clearCallingIdentity();
+      try {
+          mInterface.addRestrictBackgroundWhitelistedUid(uid);
+      } finally {
+          Binder.restoreCallingIdentity(token);
+      }
       return 0;
     }
 
@@ -205,10 +229,95 @@
         if (uid < 0) {
             return uid;
         }
-        mInterface.removeRestrictBackgroundWhitelistedUid(uid);
+        final long token = Binder.clearCallingIdentity();
+        try {
+            mInterface.removeRestrictBackgroundWhitelistedUid(uid);
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
         return 0;
     }
 
+    private int listNonMobileMeteredNetworks() throws RemoteException {
+        final PrintWriter pw = getOutPrintWriter();
+        final String arg = getNextArg();
+        final Boolean filter = arg == null ? null : Boolean.valueOf(arg);
+        for (NetworkPolicy policy : getNonMobilePolicies()) {
+            if (filter != null && filter.booleanValue() != policy.metered) {
+                continue;
+            }
+            pw.print(getNetworkId(policy));
+            pw.print(';');
+            pw.println(policy.metered);
+        }
+        return 0;
+    }
+
+    private int getNonMobileMeteredNetwork() throws RemoteException {
+        final PrintWriter pw = getOutPrintWriter();
+        final String id = getNextArg();
+        if (id == null) {
+            pw.println("Error: didn't specify ID");
+            return -1;
+        }
+        final List<NetworkPolicy> policies = getNonMobilePolicies();
+        for (NetworkPolicy policy: policies) {
+            if (id.equals(getNetworkId(policy))) {
+                pw.println(policy.metered);
+                return 0;
+            }
+        }
+        return 0;
+    }
+
+    private int setNonMobileMeteredNetwork() throws RemoteException {
+        final PrintWriter pw = getOutPrintWriter();
+        final String id = getNextArg();
+        if (id == null) {
+            pw.println("Error: didn't specify ID");
+            return -1;
+        }
+        final String arg = getNextArg();
+        if (arg == null) {
+            pw.println("Error: didn't specify BOOLEAN");
+            return -1;
+        }
+        final boolean metered = Boolean.valueOf(arg);
+        final NetworkPolicy[] policies = mInterface.getNetworkPolicies(null);
+        boolean changed = false;
+        for (NetworkPolicy policy : policies) {
+            if (policy.template.isMatchRuleMobile() || policy.metered == metered) {
+                continue;
+            }
+            final String networkId = getNetworkId(policy);
+            if (id.equals(networkId)) {
+                Log.i(TAG, "Changing " + networkId + " metered status to " + metered);
+                policy.metered = metered;
+                changed = true;
+            }
+        }
+        if (changed) {
+            mInterface.setNetworkPolicies(policies);
+        }
+        return 0;
+    }
+
+    private List<NetworkPolicy> getNonMobilePolicies() throws RemoteException {
+        final NetworkPolicy[] policies = mInterface.getNetworkPolicies(null);
+        final List<NetworkPolicy> nonMobilePolicies = new ArrayList<NetworkPolicy>(policies.length);
+        for (NetworkPolicy policy: policies) {
+            if (!policy.template.isMatchRuleMobile()) {
+                nonMobilePolicies.add(policy);
+            }
+        }
+        return nonMobilePolicies;
+    }
+
+    private String getNetworkId(NetworkPolicy policy) {
+        // ids are typically enclosed on double quotes (")
+        return policy.template.getNetworkId().replaceAll("^\"|\"$", "");
+    }
+
     private int getNextBooleanArg() {
         final PrintWriter pw = getOutPrintWriter();
         final String arg = getNextArg();
diff --git a/services/core/java/com/android/server/wm/BoundsAnimationController.java b/services/core/java/com/android/server/wm/BoundsAnimationController.java
index 5f97478..1bfdcce 100644
--- a/services/core/java/com/android/server/wm/BoundsAnimationController.java
+++ b/services/core/java/com/android/server/wm/BoundsAnimationController.java
@@ -22,6 +22,7 @@
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 
 import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
 import android.animation.ValueAnimator;
 import android.graphics.Rect;
 import android.util.ArrayMap;
@@ -50,13 +51,15 @@
         private final Rect mFrom;
         private final Rect mTo;
         private final Rect mTmpRect;
+        private final boolean mMoveToFullScreen;
 
-        BoundsAnimator(AnimateBoundsUser target, Rect from, Rect to) {
+        BoundsAnimator(AnimateBoundsUser target, Rect from, Rect to, boolean moveToFullScreen) {
             super();
             mTarget = target;
             mFrom = from;
             mTo = to;
             mTmpRect = new Rect();
+            mMoveToFullScreen = moveToFullScreen;
             addUpdateListener(this);
             addListener(this);
         }
@@ -88,6 +91,9 @@
         @Override
         public void onAnimationEnd(Animator animation) {
             finishAnimation();
+            if (mMoveToFullScreen) {
+                mTarget.moveToFullscreen();
+            }
         }
 
         @Override
@@ -125,14 +131,25 @@
          * necessary cleanup.
          */
         void finishBoundsAnimation();
+
+        void moveToFullscreen();
+
+        void getFullScreenBounds(Rect bounds);
     }
 
-    void animateBounds(AnimateBoundsUser target, Rect from, Rect to) {
+    void animateBounds(final AnimateBoundsUser target, Rect from, Rect to) {
+        boolean moveToFullscreen = false;
+        if (to == null) {
+            to = new Rect();
+            target.getFullScreenBounds(to);
+            moveToFullscreen = true;
+        }
+
         final BoundsAnimator existing = mRunningAnimations.get(target);
         if (existing != null) {
             existing.cancel();
         }
-        BoundsAnimator animator = new BoundsAnimator(target, from, to);
+        BoundsAnimator animator = new BoundsAnimator(target, from, to, moveToFullscreen);
         mRunningAnimations.put(target, animator);
         animator.setFloatValues(0f, 1f);
         animator.setDuration(DEFAULT_APP_TRANSITION_DURATION);
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index 4659131..ecc1364 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -927,4 +927,18 @@
             }
         }
     }
+
+    @Override
+    public void moveToFullscreen() {
+        try {
+            mService.mActivityManager.moveTasksToFullscreenStack(mStackId, true);
+        } catch (RemoteException e) {
+            e.printStackTrace();
+        }
+    }
+
+    @Override
+    public void getFullScreenBounds(Rect bounds) {
+        getDisplayContent().getContentRect(bounds);
+    }
 }