Merge "Add 'more settings' and 'done' to guts."
diff --git a/api/current.txt b/api/current.txt
index 3854b7a..76e44cf 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -4082,11 +4082,14 @@
   }
 
   public class DatePickerDialog extends android.app.AlertDialog implements android.widget.DatePicker.OnDateChangedListener android.content.DialogInterface.OnClickListener {
+    ctor public DatePickerDialog(android.content.Context);
+    ctor public DatePickerDialog(android.content.Context, int);
     ctor public DatePickerDialog(android.content.Context, android.app.DatePickerDialog.OnDateSetListener, int, int, int);
     ctor public DatePickerDialog(android.content.Context, int, android.app.DatePickerDialog.OnDateSetListener, int, int, int);
     method public android.widget.DatePicker getDatePicker();
     method public void onClick(android.content.DialogInterface, int);
     method public void onDateChanged(android.widget.DatePicker, int, int, int);
+    method public void setOnDateSetListener(android.app.DatePickerDialog.OnDateSetListener);
     method public void updateDate(int, int, int);
   }
 
diff --git a/api/system-current.txt b/api/system-current.txt
index 2990516..27a16bf 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -4202,11 +4202,14 @@
   }
 
   public class DatePickerDialog extends android.app.AlertDialog implements android.widget.DatePicker.OnDateChangedListener android.content.DialogInterface.OnClickListener {
+    ctor public DatePickerDialog(android.content.Context);
+    ctor public DatePickerDialog(android.content.Context, int);
     ctor public DatePickerDialog(android.content.Context, android.app.DatePickerDialog.OnDateSetListener, int, int, int);
     ctor public DatePickerDialog(android.content.Context, int, android.app.DatePickerDialog.OnDateSetListener, int, int, int);
     method public android.widget.DatePicker getDatePicker();
     method public void onClick(android.content.DialogInterface, int);
     method public void onDateChanged(android.widget.DatePicker, int, int, int);
+    method public void setOnDateSetListener(android.app.DatePickerDialog.OnDateSetListener);
     method public void updateDate(int, int, int);
   }
 
diff --git a/api/test-current.txt b/api/test-current.txt
index 83d12fc..cee8fad 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -4082,11 +4082,14 @@
   }
 
   public class DatePickerDialog extends android.app.AlertDialog implements android.widget.DatePicker.OnDateChangedListener android.content.DialogInterface.OnClickListener {
+    ctor public DatePickerDialog(android.content.Context);
+    ctor public DatePickerDialog(android.content.Context, int);
     ctor public DatePickerDialog(android.content.Context, android.app.DatePickerDialog.OnDateSetListener, int, int, int);
     ctor public DatePickerDialog(android.content.Context, int, android.app.DatePickerDialog.OnDateSetListener, int, int, int);
     method public android.widget.DatePicker getDatePicker();
     method public void onClick(android.content.DialogInterface, int);
     method public void onDateChanged(android.widget.DatePicker, int, int, int);
+    method public void setOnDateSetListener(android.app.DatePickerDialog.OnDateSetListener);
     method public void updateDate(int, int, int);
   }
 
diff --git a/core/java/android/app/DatePickerDialog.java b/core/java/android/app/DatePickerDialog.java
index 3fbbdff..8d7f347 100644
--- a/core/java/android/app/DatePickerDialog.java
+++ b/core/java/android/app/DatePickerDialog.java
@@ -16,6 +16,9 @@
 
 package android.app;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.StyleRes;
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.DialogInterface.OnClickListener;
@@ -35,95 +38,126 @@
 
 /**
  * A simple dialog containing an {@link android.widget.DatePicker}.
- *
- * <p>See the <a href="{@docRoot}guide/topics/ui/controls/pickers.html">Pickers</a>
- * guide.</p>
+ * <p>
+ * See the <a href="{@docRoot}guide/topics/ui/controls/pickers.html">Pickers</a>
+ * guide.
  */
 public class DatePickerDialog extends AlertDialog implements OnClickListener,
         OnDateChangedListener {
-
     private static final String YEAR = "year";
     private static final String MONTH = "month";
     private static final String DAY = "day";
 
     private final DatePicker mDatePicker;
-    private final OnDateSetListener mDateSetListener;
     private final Calendar mCalendar;
 
+    private OnDateSetListener mDateSetListener;
+
     private boolean mTitleNeedsUpdate = true;
 
     /**
-     * The callback used to indicate the user is done filling in the date.
+     * Creates a new date picker dialog for the current date using the parent
+     * context's default date picker dialog theme.
+     *
+     * @param context the parent context
      */
-    public interface OnDateSetListener {
-
-        /**
-         * @param view The view associated with this listener.
-         * @param year The year that was set.
-         * @param monthOfYear The month that was set (0-11) for compatibility
-         *  with {@link java.util.Calendar}.
-         * @param dayOfMonth The day of the month that was set.
-         */
-        void onDateSet(DatePicker view, int year, int monthOfYear, int dayOfMonth);
+    public DatePickerDialog(Context context) {
+        this(context, 0);
     }
 
     /**
-     * @param context The context the dialog is to run in.
-     * @param callBack How the parent is notified that the date is set.
-     * @param year The initial year of the dialog.
-     * @param monthOfYear The initial month of the dialog.
-     * @param dayOfMonth The initial day of the dialog.
+     * Creates a new date picker dialog for the current date.
+     *
+     * @param context the parent context
+     * @param themeResId the resource ID of the theme against which to inflate
+     *                   this dialog, or {@code 0} to use the parent
+     *                   {@code context}'s default alert dialog theme
      */
-    public DatePickerDialog(Context context,
-            OnDateSetListener callBack,
-            int year,
-            int monthOfYear,
-            int dayOfMonth) {
-        this(context, 0, callBack, year, monthOfYear, dayOfMonth);
-    }
-
-    static int resolveDialogTheme(Context context, int resid) {
-        if (resid == 0) {
-            final TypedValue outValue = new TypedValue();
-            context.getTheme().resolveAttribute(R.attr.datePickerDialogTheme, outValue, true);
-            return outValue.resourceId;
-        } else {
-            return resid;
-        }
-    }
-
-    /**
-     * @param context The context the dialog is to run in.
-     * @param theme the theme to apply to this dialog
-     * @param listener How the parent is notified that the date is set.
-     * @param year The initial year of the dialog.
-     * @param monthOfYear The initial month of the dialog.
-     * @param dayOfMonth The initial day of the dialog.
-     */
-    public DatePickerDialog(Context context, int theme, OnDateSetListener listener, int year,
-            int monthOfYear, int dayOfMonth) {
-        super(context, resolveDialogTheme(context, theme));
-
-        mDateSetListener = listener;
-        mCalendar = Calendar.getInstance();
+    public DatePickerDialog(Context context, @StyleRes int themeResId) {
+        super(context, resolveDialogTheme(context, themeResId));
 
         final Context themeContext = getContext();
         final LayoutInflater inflater = LayoutInflater.from(themeContext);
         final View view = inflater.inflate(R.layout.date_picker_dialog, null);
         setView(view);
+
         setButton(BUTTON_POSITIVE, themeContext.getString(R.string.ok), this);
         setButton(BUTTON_NEGATIVE, themeContext.getString(R.string.cancel), this);
         setButtonPanelLayoutHint(LAYOUT_HINT_SIDE);
 
+        mCalendar = Calendar.getInstance();
+
+        final int year = mCalendar.get(Calendar.YEAR);
+        final int monthOfYear = mCalendar.get(Calendar.MONTH);
+        final int dayOfMonth = mCalendar.get(Calendar.DAY_OF_MONTH);
         mDatePicker = (DatePicker) view.findViewById(R.id.datePicker);
         mDatePicker.init(year, monthOfYear, dayOfMonth, this);
         mDatePicker.setValidationCallback(mValidationCallback);
     }
 
+    /**
+     * Creates a new date picker dialog for the specified date using the parent
+     * context's default date picker dialog theme.
+     *
+     * @param context the parent context
+     * @param listener the listener to call when the user sets the date
+     * @param year the initially selected year
+     * @param month the initially selected month (0-11 for compatibility with
+     *              {@link Calendar#MONTH})
+     * @param dayOfMonth the initially selected day of month (1-31, depending
+     *                   on month)
+     */
+    public DatePickerDialog(@Nullable Context context, @Nullable OnDateSetListener listener,
+            int year, int month, int dayOfMonth) {
+        this(context, 0, listener, year, month, dayOfMonth);
+    }
+
+    /**
+     * Creates a new date picker dialog for the specified date.
+     *
+     * @param context the parent context
+     * @param themeResId the resource ID of the theme against which to inflate
+     *                   this dialog, or {@code 0} to use the parent
+     *                   {@code context}'s default alert dialog theme
+     * @param listener the listener to call when the user sets the date
+     * @param year the initially selected year
+     * @param month the initially selected month (0-11 for compatibility with
+     *              {@link Calendar#MONTH})
+     * @param dayOfMonth the initially selected day of month (1-31, depending
+     *                   on month)
+     */
+    public DatePickerDialog(@NonNull Context context, @StyleRes int themeResId,
+            @Nullable OnDateSetListener listener, int year, int month, int dayOfMonth) {
+        this(context, themeResId);
+
+        mDateSetListener = listener;
+
+        mDatePicker.updateDate(year, month, dayOfMonth);
+    }
+
+    static int resolveDialogTheme(@NonNull Context context, @StyleRes int themeResId) {
+        if (themeResId == 0) {
+            final TypedValue outValue = new TypedValue();
+            context.getTheme().resolveAttribute(R.attr.datePickerDialogTheme, outValue, true);
+            return outValue.resourceId;
+        } else {
+            return themeResId;
+        }
+    }
+
     @Override
-    public void onDateChanged(DatePicker view, int year, int month, int day) {
-        mDatePicker.init(year, month, day, this);
-        updateTitle(year, month, day);
+    public void onDateChanged(DatePicker view, int year, int month, int dayOfMonth) {
+        mDatePicker.init(year, month, dayOfMonth, this);
+        updateTitle(year, month, dayOfMonth);
+    }
+
+    /**
+     * Sets the listener to call when the user sets the date.
+     *
+     * @param listener the listener to call when the user sets the date
+     */
+    public void setOnDateSetListener(@Nullable OnDateSetListener listener) {
+        mDateSetListener = listener;
     }
 
     @Override
@@ -145,10 +179,11 @@
     }
 
     /**
-     * Gets the {@link DatePicker} contained in this dialog.
+     * Returns the {@link DatePicker} contained in this dialog.
      *
-     * @return The calendar view.
+     * @return the date picker
      */
+    @NonNull
     public DatePicker getDatePicker() {
         return mDatePicker;
     }
@@ -156,20 +191,22 @@
     /**
      * Sets the current date.
      *
-     * @param year The date year.
-     * @param monthOfYear The date month.
-     * @param dayOfMonth The date day of month.
+     * @param year the year
+     * @param month the month (0-11 for compatibility with
+     *              {@link Calendar#MONTH})
+     * @param dayOfMonth the day of month (1-31, depending on month)
      */
-    public void updateDate(int year, int monthOfYear, int dayOfMonth) {
-        mDatePicker.updateDate(year, monthOfYear, dayOfMonth);
+    public void updateDate(int year, int month, int dayOfMonth) {
+        mDatePicker.updateDate(year, month, dayOfMonth);
     }
 
-    private void updateTitle(int year, int month, int day) {
+    private void updateTitle(int year, int month, int dayOfMonth) {
         if (!mDatePicker.getCalendarViewShown()) {
             mCalendar.set(Calendar.YEAR, year);
             mCalendar.set(Calendar.MONTH, month);
-            mCalendar.set(Calendar.DAY_OF_MONTH, day);
-            String title = DateUtils.formatDateTime(mContext,
+            mCalendar.set(Calendar.DAY_OF_MONTH, dayOfMonth);
+
+            final String title = DateUtils.formatDateTime(mContext,
                     mCalendar.getTimeInMillis(),
                     DateUtils.FORMAT_SHOW_DATE
                     | DateUtils.FORMAT_SHOW_WEEKDAY
@@ -177,12 +214,12 @@
                     | DateUtils.FORMAT_ABBREV_MONTH
                     | DateUtils.FORMAT_ABBREV_WEEKDAY);
             setTitle(title);
+
             mTitleNeedsUpdate = true;
-        } else {
-            if (mTitleNeedsUpdate) {
-                mTitleNeedsUpdate = false;
-                setTitle(R.string.date_picker_dialog_title);
-            }
+        } else if (mTitleNeedsUpdate) {
+            setTitle(R.string.date_picker_dialog_title);
+
+            mTitleNeedsUpdate = false;
         }
     }
 
@@ -213,4 +250,19 @@
             }
         }
     };
+
+    /**
+     * The listener used to indicate the user has finished selecting a date.
+     */
+    public interface OnDateSetListener {
+        /**
+         * @param view the picker associated with the dialog
+         * @param year the selected year
+         * @param month the selected month (0-11 for compatibility with
+         *              {@link Calendar#MONTH})
+         * @param dayOfMonth th selected day of the month (1-31, depending on
+         *                   month)
+         */
+        void onDateSet(DatePicker view, int year, int month, int dayOfMonth);
+    }
 }
diff --git a/core/java/android/security/net/config/ApplicationConfig.java b/core/java/android/security/net/config/ApplicationConfig.java
index 71d9d5d..4de36cd 100644
--- a/core/java/android/security/net/config/ApplicationConfig.java
+++ b/core/java/android/security/net/config/ApplicationConfig.java
@@ -120,6 +120,32 @@
         return mTrustManager;
     }
 
+    /**
+     * Returns {@code true} if cleartext traffic is permitted for this application, which is the
+     * case only if all configurations permit cleartext traffic. For finer-grained policy use
+     * {@link #isCleartextTrafficPermitted(String)}.
+     */
+    public boolean isCleartextTrafficPermitted() {
+        ensureInitialized();
+        if (mConfigs != null) {
+            for (Pair<Domain, NetworkSecurityConfig> entry : mConfigs) {
+                if (!entry.second.isCleartextTrafficPermitted()) {
+                    return false;
+                }
+            }
+        }
+
+        return mDefaultConfig.isCleartextTrafficPermitted();
+    }
+
+    /**
+     * Returns {@code true} if cleartext traffic is permitted for this application when connecting
+     * to {@code hostname}.
+     */
+    public boolean isCleartextTrafficPermitted(String hostname) {
+        return getConfigForHostname(hostname).isCleartextTrafficPermitted();
+    }
+
     private void ensureInitialized() {
         synchronized(mLock) {
             if (mInitialized) {
diff --git a/core/java/android/security/net/config/ConfigNetworkSecurityPolicy.java b/core/java/android/security/net/config/ConfigNetworkSecurityPolicy.java
new file mode 100644
index 0000000..e7d17c2
--- /dev/null
+++ b/core/java/android/security/net/config/ConfigNetworkSecurityPolicy.java
@@ -0,0 +1,40 @@
+/**
+ * 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 android.security.net.config;
+
+/**
+ * {@link libcore.net.NetworkSecurityPolicy} based on an {@link ApplicationConfig}.
+ *
+ * @hide
+ */
+public class ConfigNetworkSecurityPolicy extends libcore.net.NetworkSecurityPolicy {
+    private final ApplicationConfig mConfig;
+
+    public ConfigNetworkSecurityPolicy(ApplicationConfig config) {
+        mConfig = config;
+    }
+
+    @Override
+    public boolean isCleartextTrafficPermitted() {
+        return mConfig.isCleartextTrafficPermitted();
+    }
+
+    @Override
+    public boolean isCleartextTrafficPermitted(String hostname) {
+        return mConfig.isCleartextTrafficPermitted(hostname);
+    }
+}
diff --git a/core/java/android/security/net/config/NetworkSecurityConfigProvider.java b/core/java/android/security/net/config/NetworkSecurityConfigProvider.java
index 5ebc7ac..0f66873 100644
--- a/core/java/android/security/net/config/NetworkSecurityConfigProvider.java
+++ b/core/java/android/security/net/config/NetworkSecurityConfigProvider.java
@@ -40,5 +40,6 @@
             throw new RuntimeException("Failed to install provider as highest priority provider."
                     + " Provider was installed at position " + pos);
         }
+        libcore.net.NetworkSecurityPolicy.setInstance(new ConfigNetworkSecurityPolicy(config));
     }
 }
diff --git a/core/java/android/service/quicksettings/TileService.java b/core/java/android/service/quicksettings/TileService.java
index d8787b4..55fe4cd 100644
--- a/core/java/android/service/quicksettings/TileService.java
+++ b/core/java/android/service/quicksettings/TileService.java
@@ -26,17 +26,17 @@
 import android.view.WindowManager;
 
 /**
- * A QSTileService provides the user a tile that can be added to Quick Settings.
+ * A TileService provides the user a tile that can be added to Quick Settings.
  * Quick Settings is a space provided that allows the user to change settings and
  * take quick actions without leaving the context of their current app.
  *
- * <p>The lifecycle of a QSTileService is different from some other services in
+ * <p>The lifecycle of a TileService is different from some other services in
  * that it may be unbound during parts of its lifecycle.  Any of the following
  * lifecycle events can happen indepently in a separate binding/creation of the
  * service.</p>
  *
  * <ul>
- * <li>When a tile is added by the user its QSTileService will be bound to and
+ * <li>When a tile is added by the user its TileService will be bound to and
  * {@link #onTileAdded()} will be called.</li>
  *
  * <li>When a tile should be up to date and listing will be indicated by
@@ -45,10 +45,10 @@
  * <li>When the user removes a tile from Quick Settings {@link #onStopListening()}
  * will be called.</li>
  * </ul>
- * <p>QSTileService will be detected by tiles that match the {@value #ACTION_QS_TILE}
+ * <p>TileService will be detected by tiles that match the {@value #ACTION_QS_TILE}
  * and require the permission "android.permission.BIND_QUICK_SETTINGS_TILE".
  * The label and icon for the service will be used as the default label and
- * icon for the tile. Here is an example QSTileService declaration.</p>
+ * icon for the tile. Here is an example TileService declaration.</p>
  * <pre class="prettyprint">
  * {@literal
  * <service
@@ -67,7 +67,7 @@
 public class TileService extends Service {
 
     /**
-     * Action that identifies a Service as being a QSTileService.
+     * Action that identifies a Service as being a TileService.
      */
     public static final String ACTION_QS_TILE = "android.service.quicksettings.action.QS_TILE";
 
diff --git a/core/java/android/view/LayoutInflater.java b/core/java/android/view/LayoutInflater.java
index aa29636..914bd56 100644
--- a/core/java/android/view/LayoutInflater.java
+++ b/core/java/android/view/LayoutInflater.java
@@ -555,6 +555,26 @@
         }
     }
 
+    private static final ClassLoader BOOT_CLASS_LOADER = LayoutInflater.class.getClassLoader();
+
+    private final boolean verifyClassLoader(Constructor<? extends View> constructor) {
+        final ClassLoader constructorLoader = constructor.getDeclaringClass().getClassLoader();
+        if (constructorLoader == BOOT_CLASS_LOADER) {
+            // fast path for boot class loader (most common case?) - always ok
+            return true;
+        }
+        // in all normal cases (no dynamic code loading), we will exit the following loop on the
+        // first iteration (i.e. when the declaring classloader is the contexts class loader).
+        ClassLoader cl = mContext.getClassLoader();
+        do {
+            if (constructorLoader == cl) {
+                return true;
+            }
+            cl = cl.getParent();
+        } while (cl != null);
+        return false;
+    }
+
     /**
      * Low-level function for instantiating a view by name. This attempts to
      * instantiate a view class of the given <var>name</var> found in this
@@ -575,6 +595,10 @@
     public final View createView(String name, String prefix, AttributeSet attrs)
             throws ClassNotFoundException, InflateException {
         Constructor<? extends View> constructor = sConstructorMap.get(name);
+        if (constructor != null && !verifyClassLoader(constructor)) {
+            constructor = null;
+            sConstructorMap.remove(name);
+        }
         Class<? extends View> clazz = null;
 
         try {
diff --git a/core/java/android/widget/PopupWindow.java b/core/java/android/widget/PopupWindow.java
index 19e290b..7e98193 100644
--- a/core/java/android/widget/PopupWindow.java
+++ b/core/java/android/widget/PopupWindow.java
@@ -1467,10 +1467,14 @@
         }
 
         if (mClipToScreen) {
+            final int winOffsetX = mScreenLocation[0] - mDrawingLocation[0];
+            final int winOffsetY = mScreenLocation[1] - mDrawingLocation[1];
+            p.x += winOffsetX;
+            p.y += winOffsetY;
             final int displayFrameWidth = displayFrame.right - displayFrame.left;
             final int right = p.x + p.width;
-            if (right > displayFrameWidth) {
-                p.x -= right - displayFrameWidth;
+            if (right > displayFrame.right) {
+                p.x -= right - displayFrame.right;
             }
 
             if (p.x < displayFrame.left) {
@@ -1479,10 +1483,9 @@
             }
 
             if (mOverlapAnchor) {
-                final int displayFrameHeight = displayFrame.bottom - displayFrame.top;
                 final int bottom = p.y + p.height;
                 if (bottom > displayFrame.bottom) {
-                    p.y -= bottom - displayFrameHeight;
+                    p.y -= bottom - displayFrame.bottom;
                 }
             } else {
                 if (onTop) {
@@ -1494,6 +1497,8 @@
                     p.y = Math.max(p.y, displayFrame.top);
                 }
             }
+            p.x -= winOffsetX;
+            p.y -= winOffsetY;
         }
 
         p.gravity |= Gravity.DISPLAY_CLIP_VERTICAL;
diff --git a/core/java/com/android/internal/logging/MetricsLogger.java b/core/java/com/android/internal/logging/MetricsLogger.java
index bfc56db..5fc7448 100644
--- a/core/java/com/android/internal/logging/MetricsLogger.java
+++ b/core/java/com/android/internal/logging/MetricsLogger.java
@@ -36,6 +36,7 @@
     public static final int NOTIFICATION_TOPIC_NOTIFICATION = 263;
     public static final int ACTION_DEFAULT_SMS_APP_CHANGED = 264;
     public static final int QS_COLOR_MATRIX = 265;
+    public static final int QS_CUSTOM = 266;
 
     /**
      * Logged when the user docks a window from recents by longpressing a task and dragging it to
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 1090f90..01edb8e 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1901,7 +1901,7 @@
 
     <!-- Allows an application to bind to third party quick settings tiles.
          <p>Should only be requested by the System, should be required by
-         QSTileService declarations.-->
+         TileService declarations.-->
     <permission android:name="android.permission.BIND_QUICK_SETTINGS_TILE"
         android:protectionLevel="signature" />
 
diff --git a/core/res/res/layout/text_drag_thumbnail.xml b/core/res/res/layout/text_drag_thumbnail.xml
index 63d2c05..b1a58cd 100644
--- a/core/res/res/layout/text_drag_thumbnail.xml
+++ b/core/res/res/layout/text_drag_thumbnail.xml
@@ -1,6 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-/* apps/common/assets/default/default/skins/StatusBar.xml
 **
 ** Copyright 2010, The Android Open Source Project
 **
diff --git a/libs/hwui/JankTracker.cpp b/libs/hwui/JankTracker.cpp
index eb9b55f..c305f65 100644
--- a/libs/hwui/JankTracker.cpp
+++ b/libs/hwui/JankTracker.cpp
@@ -243,6 +243,7 @@
     dprintf(fd, "\nTotal frames rendered: %u", data->totalFrameCount);
     dprintf(fd, "\nJanky frames: %u (%.2f%%)", data->jankFrameCount,
             (float) data->jankFrameCount / (float) data->totalFrameCount * 100.0f);
+    dprintf(fd, "\n50th percentile: %ums", findPercentile(data, 50));
     dprintf(fd, "\n90th percentile: %ums", findPercentile(data, 90));
     dprintf(fd, "\n95th percentile: %ums", findPercentile(data, 95));
     dprintf(fd, "\n99th percentile: %ums", findPercentile(data, 99));
diff --git a/libs/hwui/Properties.cpp b/libs/hwui/Properties.cpp
index 0669596..083aeb7 100644
--- a/libs/hwui/Properties.cpp
+++ b/libs/hwui/Properties.cpp
@@ -53,6 +53,8 @@
 ProfileType Properties::sProfileType = ProfileType::None;
 bool Properties::sDisableProfileBars = false;
 
+bool Properties::waitForGpuCompletion = false;
+
 static int property_get_int(const char* key, int defaultValue) {
     char buf[PROPERTY_VALUE_MAX] = {'\0',};
 
diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h
index 1dde7e0..88f1dbc 100644
--- a/libs/hwui/Properties.h
+++ b/libs/hwui/Properties.h
@@ -285,6 +285,9 @@
 
     static ProfileType getProfileType();
 
+    // Should be used only by test apps
+    static bool waitForGpuCompletion;
+
 private:
     static ProfileType sProfileType;
     static bool sDisableProfileBars;
diff --git a/libs/hwui/renderthread/EglManager.cpp b/libs/hwui/renderthread/EglManager.cpp
index 78df297..466fef9d 100644
--- a/libs/hwui/renderthread/EglManager.cpp
+++ b/libs/hwui/renderthread/EglManager.cpp
@@ -29,8 +29,6 @@
 
 #define GLES_VERSION 2
 
-#define WAIT_FOR_GPU_COMPLETION 0
-
 // Android-specific addition that is used to show when frames began in systrace
 EGLAPI void EGLAPIENTRY eglBeginFrame(EGLDisplay dpy, EGLSurface surface);
 
@@ -179,7 +177,10 @@
 }
 
 void EglManager::createContext() {
-    EGLint attribs[] = { EGL_CONTEXT_CLIENT_VERSION, GLES_VERSION, EGL_NONE };
+    EGLint attribs[] = {
+            EGL_CONTEXT_CLIENT_VERSION, GLES_VERSION,
+            EGL_NONE
+    };
     mEglContext = eglCreateContext(mEglDisplay, mEglConfig, EGL_NO_CONTEXT, attribs);
     LOG_ALWAYS_FATAL_IF(mEglContext == EGL_NO_CONTEXT,
         "Failed to create context, error = %s", egl_error_str());
@@ -318,12 +319,10 @@
 
 bool EglManager::swapBuffers(const Frame& frame, const SkRect& screenDirty) {
 
-#if WAIT_FOR_GPU_COMPLETION
-    {
+    if (CC_UNLIKELY(Properties::waitForGpuCompletion)) {
         ATRACE_NAME("Finishing GPU work");
         fence();
     }
-#endif
 
     EGLint rects[4];
     frame.map(screenDirty, rects);
diff --git a/libs/hwui/tests/common/TestScene.h b/libs/hwui/tests/common/TestScene.h
index df8d194..706f2ff 100644
--- a/libs/hwui/tests/common/TestScene.h
+++ b/libs/hwui/tests/common/TestScene.h
@@ -37,6 +37,7 @@
 public:
     struct Options {
         int count = 0;
+        int reportFrametimeWeight = 0;
     };
 
     template <class T>
diff --git a/libs/hwui/tests/macrobench/TestSceneRunner.cpp b/libs/hwui/tests/macrobench/TestSceneRunner.cpp
index 8261220..a843e92 100644
--- a/libs/hwui/tests/macrobench/TestSceneRunner.cpp
+++ b/libs/hwui/tests/macrobench/TestSceneRunner.cpp
@@ -38,6 +38,30 @@
     }
 };
 
+template<class T>
+class ModifiedMovingAverage {
+public:
+    ModifiedMovingAverage(int weight) : mWeight(weight) {}
+
+    T add(T today) {
+        if (!mHasValue) {
+            mAverage = today;
+        } else {
+            mAverage = (((mWeight - 1) * mAverage) + today) / mWeight;
+        }
+        return mAverage;
+    }
+
+    T average() {
+        return mAverage;
+    }
+
+private:
+    bool mHasValue = false;
+    int mWeight;
+    T mAverage;
+};
+
 void run(const TestScene::Info& info, const TestScene::Options& opts) {
     // Switch to the real display
     gDisplay = getBuiltInDisplay();
@@ -67,22 +91,35 @@
     proxy->setLightCenter((Vector3){lightX, dp(-200.0f), dp(800.0f)});
 
     // Do a few cold runs then reset the stats so that the caches are all hot
-    for (int i = 0; i < 3; i++) {
+    for (int i = 0; i < 5; i++) {
         testContext.waitForVsync();
         nsecs_t vsync = systemTime(CLOCK_MONOTONIC);
         UiFrameInfoBuilder(proxy->frameInfo()).setVsync(vsync, vsync);
         proxy->syncAndDrawFrame();
     }
+
     proxy->resetProfileInfo();
+    proxy->fence();
+
+    ModifiedMovingAverage<double> avgMs(opts.reportFrametimeWeight);
 
     for (int i = 0; i < opts.count; i++) {
         testContext.waitForVsync();
-
-        ATRACE_NAME("UI-Draw Frame");
         nsecs_t vsync = systemTime(CLOCK_MONOTONIC);
-        UiFrameInfoBuilder(proxy->frameInfo()).setVsync(vsync, vsync);
-        scene->doFrame(i);
-        proxy->syncAndDrawFrame();
+        {
+            ATRACE_NAME("UI-Draw Frame");
+            UiFrameInfoBuilder(proxy->frameInfo()).setVsync(vsync, vsync);
+            scene->doFrame(i);
+            proxy->syncAndDrawFrame();
+        }
+        proxy->fence();
+        nsecs_t done = systemTime(CLOCK_MONOTONIC);
+        if (opts.reportFrametimeWeight) {
+            avgMs.add((done - vsync) / 1000000.0);
+            if (i % 10 == 9) {
+                printf("Average frametime %.3fms\n", avgMs.average());
+            }
+        }
     }
 
     proxy->dumpProfileInfo(STDOUT_FILENO, 0);
diff --git a/libs/hwui/tests/macrobench/main.cpp b/libs/hwui/tests/macrobench/main.cpp
index 619713c..1616a95 100644
--- a/libs/hwui/tests/macrobench/main.cpp
+++ b/libs/hwui/tests/macrobench/main.cpp
@@ -17,6 +17,7 @@
 #include "tests/common/TestScene.h"
 
 #include "protos/hwui.pb.h"
+#include "Properties.h"
 
 #include <getopt.h>
 #include <stdio.h>
@@ -25,26 +26,38 @@
 #include <unordered_map>
 #include <vector>
 
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <errno.h>
+
 using namespace android;
 using namespace android::uirenderer;
 using namespace android::uirenderer::test;
 
-static int gFrameCount = 150;
 static int gRepeatCount = 1;
 static std::vector<TestScene::Info> gRunTests;
+static TestScene::Options gOpts;
 
 void run(const TestScene::Info& info, const TestScene::Options& opts);
 
 static void printHelp() {
-    printf("\
-USAGE: hwuitest [OPTIONS] <TESTNAME>\n\
-\n\
-OPTIONS:\n\
-  -c, --count=NUM      NUM loops a test should run (example, number of frames)\n\
-  -r, --runs=NUM       Repeat the test(s) NUM times\n\
-  -h, --help           Display this help\n\
-  --list               List all tests\n\
-\n");
+    printf(R"(
+USAGE: hwuitest [OPTIONS] <TESTNAME>
+
+OPTIONS:
+  -c, --count=NUM      NUM loops a test should run (example, number of frames)
+  -r, --runs=NUM       Repeat the test(s) NUM times
+  -h, --help           Display this help
+  --list               List all tests
+  --wait-for-gpu       Set this to wait for the GPU before producing the
+                       next frame. Note that without locked clocks this will
+                       pathologically bad performance due to large idle time
+  --report-frametime[=weight] If set, the test will print to stdout the
+                       moving average frametime. Weight is optional, default is 10
+  --cpuset=name        Adds the test to the specified cpuset before running
+                       Not supported on all devices and needs root
+)");
 }
 
 static void listTests() {
@@ -77,11 +90,56 @@
     }
 }
 
+static void moveToCpuSet(const char* cpusetName) {
+    if (access("/dev/cpuset/tasks", F_OK)) {
+        fprintf(stderr, "don't have access to cpusets, skipping...\n");
+        return;
+    }
+    static const int BUF_SIZE = 100;
+    char buffer[BUF_SIZE];
+
+    if (snprintf(buffer, BUF_SIZE, "/dev/cpuset/%s/tasks", cpusetName) >= BUF_SIZE) {
+        fprintf(stderr, "Error, cpusetName too large to fit in buffer '%s'\n", cpusetName);
+        return;
+    }
+    int fd = open(buffer, O_WRONLY | O_CLOEXEC);
+    if (fd == -1) {
+        fprintf(stderr, "Error opening file %d\n", errno);
+        return;
+    }
+    pid_t pid = getpid();
+
+    int towrite = snprintf(buffer, BUF_SIZE, "%ld", (long) pid);
+    if (towrite >= BUF_SIZE) {
+        fprintf(stderr, "Buffer wasn't large enough?\n");
+    } else {
+        if (write(fd, buffer, towrite) != towrite) {
+            fprintf(stderr, "Failed to write, errno=%d", errno);
+        }
+    }
+    close(fd);
+}
+
+// For options that only exist in long-form. Anything in the
+// 0-255 range is reserved for short options (which just use their ASCII value)
+namespace LongOpts {
+enum {
+    Reserved = 255,
+    List,
+    WaitForGpu,
+    ReportFrametime,
+    CpuSet,
+};
+}
+
 static const struct option LONG_OPTIONS[] = {
     { "frames", required_argument, nullptr, 'f' },
     { "repeat", required_argument, nullptr, 'r' },
     { "help", no_argument, nullptr, 'h' },
-    { "list", no_argument, nullptr, 'l' },
+    { "list", no_argument, nullptr, LongOpts::List },
+    { "wait-for-gpu", no_argument, nullptr, LongOpts::WaitForGpu },
+    { "report-frametime", optional_argument, nullptr, LongOpts::ReportFrametime },
+    { "cpuset", required_argument, nullptr, LongOpts::CpuSet },
     { 0, 0, 0, 0 }
 };
 
@@ -89,8 +147,6 @@
 
 void parseOptions(int argc, char* argv[]) {
     int c;
-    // temporary variable
-    int count;
     bool error = false;
     opterr = 0;
 
@@ -110,31 +166,53 @@
             // (although none of the current LONG_OPTIONS do this...)
             break;
 
-        case 'l':
+        case LongOpts::List:
             listTests();
             exit(EXIT_SUCCESS);
             break;
 
         case 'c':
-            count = atoi(optarg);
-            if (!count) {
+            gOpts.count = atoi(optarg);
+            if (!gOpts.count) {
                 fprintf(stderr, "Invalid frames argument '%s'\n", optarg);
                 error = true;
-            } else {
-                gFrameCount = (count > 0 ? count : INT_MAX);
             }
             break;
 
         case 'r':
-            count = atoi(optarg);
-            if (!count) {
+            gRepeatCount = atoi(optarg);
+            if (!gRepeatCount) {
                 fprintf(stderr, "Invalid repeat argument '%s'\n", optarg);
                 error = true;
             } else {
-                gRepeatCount = (count > 0 ? count : INT_MAX);
+                gRepeatCount = (gRepeatCount > 0 ? gRepeatCount : INT_MAX);
             }
             break;
 
+        case LongOpts::ReportFrametime:
+            if (optarg) {
+                gOpts.reportFrametimeWeight = atoi(optarg);
+                if (!gOpts.reportFrametimeWeight) {
+                    fprintf(stderr, "Invalid report frametime weight '%s'\n", optarg);
+                    error = true;
+                }
+            } else {
+                gOpts.reportFrametimeWeight = 10;
+            }
+            break;
+
+        case LongOpts::WaitForGpu:
+            Properties::waitForGpuCompletion = true;
+            break;
+
+        case LongOpts::CpuSet:
+            if (!optarg) {
+                error = true;
+                break;
+            }
+            moveToCpuSet(optarg);
+            break;
+
         case 'h':
             printHelp();
             exit(EXIT_SUCCESS);
@@ -172,13 +250,14 @@
 }
 
 int main(int argc, char* argv[]) {
+    // set defaults
+    gOpts.count = 150;
+
     parseOptions(argc, argv);
 
-    TestScene::Options opts;
-    opts.count = gFrameCount;
     for (int i = 0; i < gRepeatCount; i++) {
         for (auto&& test : gRunTests) {
-            run(test, opts);
+            run(test, gOpts);
         }
     }
     printf("Success!\n");
diff --git a/libs/hwui/tests/scripts/prep_volantis.sh b/libs/hwui/tests/scripts/prep_volantis.sh
new file mode 100755
index 0000000..09d4869
--- /dev/null
+++ b/libs/hwui/tests/scripts/prep_volantis.sh
@@ -0,0 +1,54 @@
+#!/bin/bash
+
+# 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.
+
+adb root
+adb wait-for-device
+adb shell stop mpdecision
+adb shell stop perfd
+adb shell stop
+for pid in $( adb shell ps | awk '{ if ( $9 == "surfaceflinger" ) { print $2 } }' ); do
+    adb shell kill $pid
+done
+adb shell setprop debug.egl.traceGpuCompletion 1
+adb shell daemonize surfaceflinger
+sleep 3
+adb shell setprop service.bootanim.exit 1
+
+# cpu possible frequencies
+# 204000 229500 255000 280500 306000 331500 357000 382500 408000 433500 459000
+# 484500 510000 535500 561000 586500 612000 637500 663000 688500 714000 739500
+# 765000 790500 816000 841500 867000 892500 918000 943500 969000 994500 1020000
+# 1122000 1224000 1326000 1428000 1530000 1632000 1734000 1836000 1938000
+# 2014500 2091000 2193000 2295000 2397000 2499000
+
+S=1326000
+echo "set cpu $cpu to $S hz";
+adb shell "echo userspace > /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor"
+adb shell "echo $S > /sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq"
+adb shell "echo $S > /sys/devices/system/cpu/cpu0/cpufreq/scaling_min_freq"
+adb shell "echo $S > /sys/devices/system/cpu/cpu0/cpufreq/scaling_setspeed"
+
+#disable hotplug
+adb shell "echo 0 > /sys/devices/system/cpu/cpuquiet/tegra_cpuquiet/enable"
+
+# gbus possible rates
+# 72000 108000 180000 252000 324000 396000 468000 540000 612000 648000
+# 684000 708000 756000 804000 852000 (kHz)
+
+S=324000000
+echo "set gpu to $s hz"
+adb shell "echo 1 > /d/clock/override.gbus/state"
+adb shell "echo $S > /d/clock/override.gbus/rate"
diff --git a/packages/PrintSpooler/src/com/android/printspooler/model/NotificationController.java b/packages/PrintSpooler/src/com/android/printspooler/model/NotificationController.java
index 82fd512..3dc5d7e 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/model/NotificationController.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/model/NotificationController.java
@@ -158,11 +158,14 @@
             builder.addAction(secondAction);
         }
 
-        if (printJob.getState() == PrintJobInfo.STATE_STARTED) {
+        if (printJob.getState() == PrintJobInfo.STATE_STARTED
+                || printJob.getState() == PrintJobInfo.STATE_QUEUED) {
             float progress = printJob.getProgress();
             if (progress >= 0) {
-                builder.setProgress(Integer.MAX_VALUE, (int)(Integer.MAX_VALUE * progress),
+                builder.setProgress(Integer.MAX_VALUE, (int) (Integer.MAX_VALUE * progress),
                         false);
+            } else {
+                builder.setProgress(Integer.MAX_VALUE, 0, true);
             }
         }
 
diff --git a/packages/SettingsLib/res/values-bn-rBD/arrays.xml b/packages/SettingsLib/res/values-bn-rBD/arrays.xml
index c26615e..b863934 100644
--- a/packages/SettingsLib/res/values-bn-rBD/arrays.xml
+++ b/packages/SettingsLib/res/values-bn-rBD/arrays.xml
@@ -60,25 +60,25 @@
   </string-array>
   <string-array name="select_logd_size_titles">
     <item msgid="8665206199209698501">"বন্ধ আছে"</item>
-    <item msgid="1593289376502312923">"64K"</item>
-    <item msgid="487545340236145324">"256K"</item>
-    <item msgid="2423528675294333831">"1M"</item>
-    <item msgid="180883774509476541">"4M"</item>
-    <item msgid="2803199102589126938">"16M"</item>
+    <item msgid="1593289376502312923">"৬৪K"</item>
+    <item msgid="487545340236145324">"২৫৬K"</item>
+    <item msgid="2423528675294333831">"১M"</item>
+    <item msgid="180883774509476541">"৪M"</item>
+    <item msgid="2803199102589126938">"১৬M"</item>
   </string-array>
   <string-array name="select_logd_size_lowram_titles">
     <item msgid="6089470720451068364">"বন্ধ আছে"</item>
-    <item msgid="4622460333038586791">"64K"</item>
-    <item msgid="2212125625169582330">"256K"</item>
-    <item msgid="1704946766699242653">"1M"</item>
+    <item msgid="4622460333038586791">"৬৪K"</item>
+    <item msgid="2212125625169582330">"২৫৬K"</item>
+    <item msgid="1704946766699242653">"১M"</item>
   </string-array>
   <string-array name="select_logd_size_summaries">
     <item msgid="6921048829791179331">"বন্ধ আছে"</item>
-    <item msgid="2969458029344750262">"লগ বাফার প্রতি 64K"</item>
-    <item msgid="1342285115665698168">"লগ বাফার প্রতি 256K"</item>
-    <item msgid="1314234299552254621">"লগ বাফার প্রতি 1M"</item>
-    <item msgid="3606047780792894151">"লগ বাফার প্রতি 4M"</item>
-    <item msgid="5431354956856655120">"লগ বাফার প্রতি 16M"</item>
+    <item msgid="2969458029344750262">"লগ বাফার প্রতি ৬৪K"</item>
+    <item msgid="1342285115665698168">"লগ বাফার প্রতি ২৫৬K"</item>
+    <item msgid="1314234299552254621">"লগ বাফার প্রতি ১M"</item>
+    <item msgid="3606047780792894151">"লগ বাফার প্রতি ৪M"</item>
+    <item msgid="5431354956856655120">"লগ বাফার প্রতি ১৬M"</item>
   </string-array>
   <string-array name="window_animation_scale_entries">
     <item msgid="8134156599370824081">"অ্যানিমেশন বন্ধ করুন"</item>
diff --git a/packages/SettingsLib/res/values-ky-rKG/arrays.xml b/packages/SettingsLib/res/values-ky-rKG/arrays.xml
index db4979a..34c713e 100644
--- a/packages/SettingsLib/res/values-ky-rKG/arrays.xml
+++ b/packages/SettingsLib/res/values-ky-rKG/arrays.xml
@@ -74,11 +74,11 @@
   </string-array>
   <string-array name="select_logd_size_summaries">
     <item msgid="6921048829791179331">"Өчүк"</item>
-    <item msgid="2969458029344750262">"64K каттоо буфери үчүн"</item>
-    <item msgid="1342285115665698168">"256K каттоо буфери үчүн"</item>
-    <item msgid="1314234299552254621">"1M каттоо буфери үчүн"</item>
-    <item msgid="3606047780792894151">"4M каттоо буфери үчүн"</item>
-    <item msgid="5431354956856655120">"16M каттоо буфери үчүн"</item>
+    <item msgid="2969458029344750262">"Буфер: 64КБ ашпашы керек"</item>
+    <item msgid="1342285115665698168">"Буфер: 256КБ ашпашы керек"</item>
+    <item msgid="1314234299552254621">"Буфер: 1М ашпашы керек"</item>
+    <item msgid="3606047780792894151">"Буфер: 4М ашпашы керек"</item>
+    <item msgid="5431354956856655120">"Буфер: 16М ашпашы керек"</item>
   </string-array>
   <string-array name="window_animation_scale_entries">
     <item msgid="8134156599370824081">"Анимацияны өчүрүү"</item>
diff --git a/packages/SettingsLib/res/values-ne-rNP/arrays.xml b/packages/SettingsLib/res/values-ne-rNP/arrays.xml
index 707f1dd..7468982 100644
--- a/packages/SettingsLib/res/values-ne-rNP/arrays.xml
+++ b/packages/SettingsLib/res/values-ne-rNP/arrays.xml
@@ -63,8 +63,8 @@
     <item msgid="1593289376502312923">"६४के"</item>
     <item msgid="487545340236145324">"२५६के"</item>
     <item msgid="2423528675294333831">"१एम"</item>
-    <item msgid="180883774509476541">"4एम"</item>
-    <item msgid="2803199102589126938">"16एम"</item>
+    <item msgid="180883774509476541">"४एम"</item>
+    <item msgid="2803199102589126938">"१६एम"</item>
   </string-array>
   <string-array name="select_logd_size_lowram_titles">
     <item msgid="6089470720451068364">"निष्क्रिय गर्नुहोस्"</item>
@@ -74,11 +74,11 @@
   </string-array>
   <string-array name="select_logd_size_summaries">
     <item msgid="6921048829791179331">"निष्क्रिय गर्नुहोस्"</item>
-    <item msgid="2969458029344750262">"64के प्रति लग बफर"</item>
-    <item msgid="1342285115665698168">"256के प्रति लग बफर"</item>
-    <item msgid="1314234299552254621">"1एम प्रति लग बफर"</item>
-    <item msgid="3606047780792894151">"4एम प्रति लग बफर"</item>
-    <item msgid="5431354956856655120">"16एम प्रति लग बफर"</item>
+    <item msgid="2969458029344750262">"६४के प्रति लग बफर"</item>
+    <item msgid="1342285115665698168">"२५६के प्रति लग बफर"</item>
+    <item msgid="1314234299552254621">"१एम प्रति लग बफर"</item>
+    <item msgid="3606047780792894151">"४एम प्रति लग बफर"</item>
+    <item msgid="5431354956856655120">"१६एम प्रति लग बफर"</item>
   </string-array>
   <string-array name="window_animation_scale_entries">
     <item msgid="8134156599370824081">"सजीविकरण बन्द"</item>
diff --git a/packages/SettingsLib/res/values-zh-rCN/arrays.xml b/packages/SettingsLib/res/values-zh-rCN/arrays.xml
index 5426d23..d1d8937 100644
--- a/packages/SettingsLib/res/values-zh-rCN/arrays.xml
+++ b/packages/SettingsLib/res/values-zh-rCN/arrays.xml
@@ -58,22 +58,28 @@
     <item msgid="3878793616631049349">"仅使用 HDCP 检查 DRM 内容"</item>
     <item msgid="45075631231212732">"始终使用 HDCP 检查"</item>
   </string-array>
-    <!-- no translation found for select_logd_size_titles:0 (8665206199209698501) -->
-    <!-- no translation found for select_logd_size_titles:1 (1593289376502312923) -->
-    <!-- no translation found for select_logd_size_titles:2 (487545340236145324) -->
-    <!-- no translation found for select_logd_size_titles:3 (2423528675294333831) -->
-    <!-- no translation found for select_logd_size_titles:4 (180883774509476541) -->
-    <!-- no translation found for select_logd_size_titles:5 (2803199102589126938) -->
-    <!-- no translation found for select_logd_size_lowram_titles:0 (6089470720451068364) -->
-    <!-- no translation found for select_logd_size_lowram_titles:1 (4622460333038586791) -->
-    <!-- no translation found for select_logd_size_lowram_titles:2 (2212125625169582330) -->
-    <!-- no translation found for select_logd_size_lowram_titles:3 (1704946766699242653) -->
-    <!-- no translation found for select_logd_size_summaries:0 (6921048829791179331) -->
-    <!-- no translation found for select_logd_size_summaries:1 (2969458029344750262) -->
-    <!-- no translation found for select_logd_size_summaries:2 (1342285115665698168) -->
-    <!-- no translation found for select_logd_size_summaries:3 (1314234299552254621) -->
-    <!-- no translation found for select_logd_size_summaries:4 (3606047780792894151) -->
-    <!-- no translation found for select_logd_size_summaries:5 (5431354956856655120) -->
+  <string-array name="select_logd_size_titles">
+    <item msgid="8665206199209698501">"关闭"</item>
+    <item msgid="1593289376502312923">"64K"</item>
+    <item msgid="487545340236145324">"256K"</item>
+    <item msgid="2423528675294333831">"1M"</item>
+    <item msgid="180883774509476541">"4M"</item>
+    <item msgid="2803199102589126938">"16M"</item>
+  </string-array>
+  <string-array name="select_logd_size_lowram_titles">
+    <item msgid="6089470720451068364">"关闭"</item>
+    <item msgid="4622460333038586791">"64K"</item>
+    <item msgid="2212125625169582330">"256K"</item>
+    <item msgid="1704946766699242653">"1M"</item>
+  </string-array>
+  <string-array name="select_logd_size_summaries">
+    <item msgid="6921048829791179331">"关闭"</item>
+    <item msgid="2969458029344750262">"每个日志缓冲区 64K"</item>
+    <item msgid="1342285115665698168">"每个日志缓冲区 256K"</item>
+    <item msgid="1314234299552254621">"每个日志缓冲区 1M"</item>
+    <item msgid="3606047780792894151">"每个日志缓冲区 4M"</item>
+    <item msgid="5431354956856655120">"每个日志缓冲区 16M"</item>
+  </string-array>
   <string-array name="window_animation_scale_entries">
     <item msgid="8134156599370824081">"关闭动画"</item>
     <item msgid="6624864048416710414">"动画缩放 0.5x"</item>
diff --git a/packages/SystemUI/res/layout/assist_orb.xml b/packages/SystemUI/res/layout/assist_orb.xml
index ab0a0a5b..0036ed6 100644
--- a/packages/SystemUI/res/layout/assist_orb.xml
+++ b/packages/SystemUI/res/layout/assist_orb.xml
@@ -1,6 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-/* apps/common/assets/default/default/skins/StatusBar.xml
 **
 ** Copyright 2012, The Android Open Source Project
 **
diff --git a/packages/SystemUI/res/layout/mobile_signal_group.xml b/packages/SystemUI/res/layout/mobile_signal_group.xml
index 6ae5cf3..a20ec8e 100644
--- a/packages/SystemUI/res/layout/mobile_signal_group.xml
+++ b/packages/SystemUI/res/layout/mobile_signal_group.xml
@@ -1,6 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-/* apps/common/assets/default/default/skins/StatusBar.xml
 **
 ** Copyright 2011, The Android Open Source Project
 **
diff --git a/packages/SystemUI/res/layout/navigation_bar.xml b/packages/SystemUI/res/layout/navigation_bar.xml
index d58664f..8498a4f 100644
--- a/packages/SystemUI/res/layout/navigation_bar.xml
+++ b/packages/SystemUI/res/layout/navigation_bar.xml
@@ -1,6 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-/* apps/common/assets/default/default/skins/StatusBar.xml
 **
 ** Copyright 2011, The Android Open Source Project
 **
diff --git a/packages/SystemUI/res/layout/recents_task_view.xml b/packages/SystemUI/res/layout/recents_task_view.xml
index 2b82b05..5b44c17 100644
--- a/packages/SystemUI/res/layout/recents_task_view.xml
+++ b/packages/SystemUI/res/layout/recents_task_view.xml
@@ -34,9 +34,10 @@
             android:layout_gravity="bottom|right"
             android:layout_marginRight="15dp"
             android:layout_marginBottom="15dp"
-            android:translationZ="2dp"
+            android:translationZ="4dp"
             android:contentDescription="@string/recents_lock_to_app_button_label"
-            android:background="@drawable/recents_lock_to_task_button_bg">
+            android:background="@drawable/recents_lock_to_task_button_bg"
+            android:visibility="invisible">
             <ImageView
                 android:layout_width="@dimen/recents_lock_to_app_icon_size"
                 android:layout_height="@dimen/recents_lock_to_app_icon_size"
diff --git a/packages/SystemUI/res/layout/signal_cluster_view.xml b/packages/SystemUI/res/layout/signal_cluster_view.xml
index f8bd6fd..198e658 100644
--- a/packages/SystemUI/res/layout/signal_cluster_view.xml
+++ b/packages/SystemUI/res/layout/signal_cluster_view.xml
@@ -1,6 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-/* apps/common/assets/default/default/skins/StatusBar.xml
 **
 ** Copyright 2011, The Android Open Source Project
 **
diff --git a/packages/SystemUI/res/layout/status_bar.xml b/packages/SystemUI/res/layout/status_bar.xml
index a5b3a83..aaa5a09 100644
--- a/packages/SystemUI/res/layout/status_bar.xml
+++ b/packages/SystemUI/res/layout/status_bar.xml
@@ -1,6 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-/* apps/common/assets/default/default/skins/StatusBar.xml
 **
 ** Copyright 2006, The Android Open Source Project
 **
diff --git a/packages/SystemUI/res/layout/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml
index bfa13e2..12cf137 100644
--- a/packages/SystemUI/res/layout/status_bar_expanded.xml
+++ b/packages/SystemUI/res/layout/status_bar_expanded.xml
@@ -1,6 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-/* apps/common/assets/default/default/skins/StatusBar.xml
 **
 ** Copyright 2006, The Android Open Source Project
 **
diff --git a/packages/SystemUI/res/layout/status_bar_no_recent_apps.xml b/packages/SystemUI/res/layout/status_bar_no_recent_apps.xml
index 1675773..adfaed13 100644
--- a/packages/SystemUI/res/layout/status_bar_no_recent_apps.xml
+++ b/packages/SystemUI/res/layout/status_bar_no_recent_apps.xml
@@ -1,6 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-/* apps/common/assets/default/default/skins/StatusBar.xml
 **
 ** Copyright 2011, The Android Open Source Project
 **
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSIconView.java b/packages/SystemUI/src/com/android/systemui/qs/QSIconView.java
index b56ad76..01eb5f9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSIconView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSIconView.java
@@ -75,7 +75,8 @@
             iv.setTag(R.id.qs_icon_tag, state.icon);
             if (d instanceof Animatable) {
                 Animatable a = (Animatable) d;
-                if (state.icon instanceof QSTile.AnimationIcon && !iv.isShown()) {
+                a.start();
+                if (!iv.isShown()) {
                     a.stop(); // skip directly to end state
                 }
             }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTile.java b/packages/SystemUI/src/com/android/systemui/qs/QSTile.java
index 39f0c55..1a36abd 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTile.java
@@ -19,8 +19,6 @@
 import android.app.PendingIntent;
 import android.content.Context;
 import android.content.Intent;
-import android.graphics.drawable.Animatable;
-import android.graphics.drawable.AnimatedVectorDrawable;
 import android.graphics.drawable.Drawable;
 import android.os.Handler;
 import android.os.Looper;
@@ -29,9 +27,20 @@
 import android.util.SparseArray;
 import android.view.View;
 import android.view.ViewGroup;
-
 import com.android.systemui.qs.QSTile.State;
-import com.android.systemui.statusbar.policy.*;
+import com.android.systemui.statusbar.policy.BatteryController;
+import com.android.systemui.statusbar.policy.BluetoothController;
+import com.android.systemui.statusbar.policy.CastController;
+import com.android.systemui.statusbar.policy.FlashlightController;
+import com.android.systemui.statusbar.policy.HotspotController;
+import com.android.systemui.statusbar.policy.KeyguardMonitor;
+import com.android.systemui.statusbar.policy.Listenable;
+import com.android.systemui.statusbar.policy.LocationController;
+import com.android.systemui.statusbar.policy.NetworkController;
+import com.android.systemui.statusbar.policy.RotationLockController;
+import com.android.systemui.statusbar.policy.UserInfoController;
+import com.android.systemui.statusbar.policy.UserSwitcherController;
+import com.android.systemui.statusbar.policy.ZenModeController;
 
 import java.util.Collection;
 import java.util.Objects;
@@ -389,11 +398,7 @@
 
         @Override
         public Drawable getDrawable(Context context) {
-            Drawable d = context.getDrawable(mResId);
-            if (d instanceof Animatable) {
-                ((Animatable) d).start();
-            }
-            return d;
+            return context.getDrawable(mResId);
         }
 
         @Override
@@ -408,41 +413,14 @@
     }
 
     protected class AnimationIcon extends ResourceIcon {
-        private boolean mAllowAnimation;
-
         public AnimationIcon(int resId) {
             super(resId);
         }
 
-        public void setAllowAnimation(boolean allowAnimation) {
-            mAllowAnimation = allowAnimation;
-        }
-
         @Override
         public Drawable getDrawable(Context context) {
             // workaround: get a clean state for every new AVD
-            final AnimatedVectorDrawable d = (AnimatedVectorDrawable) context.getDrawable(mResId)
-                    .getConstantState().newDrawable();
-            d.start();
-            if (mAllowAnimation) {
-                mAllowAnimation = false;
-            } else {
-                d.stop(); // skip directly to end state
-            }
-            return d;
-        }
-    }
-
-    protected enum UserBoolean {
-        USER_TRUE(true, true),
-        USER_FALSE(true, false),
-        BACKGROUND_TRUE(false, true),
-        BACKGROUND_FALSE(false, false);
-        public final boolean value;
-        public final boolean userInitiated;
-        private UserBoolean(boolean userInitiated, boolean value) {
-            this.value = value;
-            this.userInitiated = userInitiated;
+            return context.getDrawable(mResId).getConstantState().newDrawable();
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileServiceWrapper.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileServiceWrapper.java
deleted file mode 100644
index a5e1fd5..0000000
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileServiceWrapper.java
+++ /dev/null
@@ -1,76 +0,0 @@
-package com.android.systemui.qs;
-
-import android.os.IBinder;
-import android.service.quicksettings.IQSTileService;
-import android.service.quicksettings.Tile;
-import android.util.Log;
-
-
-public class QSTileServiceWrapper implements IQSTileService {
-    private static final String TAG = "IQSTileServiceWrapper";
-
-    private final IQSTileService mService;
-    
-    public QSTileServiceWrapper(IQSTileService service) {
-        mService = service;
-    }
-
-    @Override
-    public IBinder asBinder() {
-        return mService.asBinder();
-    }
-
-    @Override
-    public void setQSTile(Tile tile) {
-        try {
-            mService.setQSTile(tile);
-        } catch (Exception e) {
-            Log.d(TAG, "Caught exception from QSTileService", e);
-        }
-    }
-
-    @Override
-    public void onTileAdded() {
-        try {
-            mService.onTileAdded();
-        } catch (Exception e) {
-            Log.d(TAG, "Caught exception from QSTileService", e);
-        }
-    }
-
-    @Override
-    public void onTileRemoved() {
-        try {
-            mService.onTileRemoved();
-        } catch (Exception e) {
-            Log.d(TAG, "Caught exception from QSTileService", e);
-        }
-    }
-
-    @Override
-    public void onStartListening() {
-        try {
-            mService.onStartListening();
-        } catch (Exception e) {
-            Log.d(TAG, "Caught exception from QSTileService", e);
-        }
-    }
-
-    @Override
-    public void onStopListening() {
-        try {
-            mService.onStopListening();
-        } catch (Exception e) {
-            Log.d(TAG, "Caught exception from QSTileService", e);
-        }
-    }
-
-    @Override
-    public void onClick(IBinder token) {
-        try {
-            mService.onClick(token);
-        } catch (Exception e) {
-            Log.d(TAG, "Caught exception from QSTileService", e);
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/CustomQSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/customize/CustomQSPanel.java
index 87c29735..5ac63bc 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/CustomQSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/CustomQSPanel.java
@@ -35,8 +35,8 @@
 import com.android.systemui.R;
 import com.android.systemui.qs.QSPanel;
 import com.android.systemui.qs.QSTile;
-import com.android.systemui.qs.QSTileServiceWrapper;
-import com.android.systemui.qs.tiles.CustomTile;
+import com.android.systemui.qs.external.QSTileServiceWrapper;
+import com.android.systemui.qs.external.CustomTile;
 import com.android.systemui.statusbar.phone.QSTileHost;
 import com.android.systemui.tuner.TunerService;
 
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 6706c7a..a6a7143 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
@@ -41,7 +41,7 @@
 import com.android.systemui.R;
 import com.android.systemui.qs.QSTile;
 import com.android.systemui.qs.QSTile.Icon;
-import com.android.systemui.qs.tiles.CustomTile;
+import com.android.systemui.qs.external.CustomTile;
 import com.android.systemui.statusbar.phone.QSTileHost;
 import com.android.systemui.tuner.QSPagingSwitch;
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CustomTile.java b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
similarity index 71%
rename from packages/SystemUI/src/com/android/systemui/qs/tiles/CustomTile.java
rename to packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
index bb74f34..e622e11 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CustomTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
@@ -11,23 +11,17 @@
  * 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.
+ * limitations under the License
  */
+package com.android.systemui.qs.external;
 
-package com.android.systemui.qs.tiles;
-
-import android.app.ActivityManager;
-import android.app.Service;
 import android.content.ComponentName;
-import android.content.Intent;
-import android.content.ServiceConnection;
 import android.content.pm.PackageManager;
 import android.content.pm.ServiceInfo;
 import android.graphics.drawable.Drawable;
 import android.os.Binder;
 import android.os.IBinder;
 import android.os.RemoteException;
-import android.os.UserHandle;
 import android.service.quicksettings.IQSTileService;
 import android.service.quicksettings.Tile;
 import android.util.Log;
@@ -36,7 +30,6 @@
 import android.view.WindowManagerGlobal;
 import com.android.internal.logging.MetricsLogger;
 import com.android.systemui.qs.QSTile;
-import com.android.systemui.qs.QSTileServiceWrapper;
 import com.android.systemui.statusbar.phone.QSTileHost;
 
 public class CustomTile extends QSTile<QSTile.State> {
@@ -52,8 +45,9 @@
     private final Tile mTile;
     private final IWindowManager mWindowManager;
     private final IBinder mToken = new Binder();
+    private final IQSTileService mService;
+    private final TileServiceManager mServiceManager;
 
-    private QSTileServiceWrapper mService;
     private boolean mListening;
     private boolean mBound;
     private boolean mIsTokenGranted;
@@ -63,7 +57,9 @@
         super(host);
         mWindowManager = WindowManagerGlobal.getWindowManagerService();
         mComponent = ComponentName.unflattenFromString(action);
-        mTile = new Tile(mComponent, host);
+        mServiceManager = host.getTileServices().getTileWrapper(this);
+        mService = mServiceManager.getTileService();
+        mTile = new Tile(mComponent, host.getTileServices());
         try {
             PackageManager pm = mContext.getPackageManager();
             ServiceInfo info = pm.getServiceInfo(mComponent, 0);
@@ -96,42 +92,32 @@
     public void setListening(boolean listening) {
         if (mListening == listening) return;
         mListening = listening;
-        if (listening) {
-            mHandler.removeCallbacks(mUnbind);
-            if (!mBound) {
-                // TODO: Guarantee re-bind on user-switch.
-                mContext.bindServiceAsUser(new Intent().setComponent(mComponent),
-                        mServiceConnection, Service.BIND_AUTO_CREATE,
-                        new UserHandle(ActivityManager.getCurrentUser()));
-                mBound = true;
+        try {
+            if (listening) {
+                mServiceManager.setBindRequested(true);
+                mService.setQSTile(mTile);
+                mService.onStartListening();
             } else {
-                if (mService != null) {
-                    mService.onStartListening();
-                } else {
-                    Log.d(TAG, "Can't start service listening");
-                }
-            }
-        } else {
-            if (mService != null) {
                 mService.onStopListening();
-            }
-            if (mIsTokenGranted && !mIsShowingDialog) {
-                try {
-                    if (DEBUG) Log.d(TAG, "Removing token");
-                    mWindowManager.removeWindowToken(mToken);
-                } catch (RemoteException e) {
+                if (mIsTokenGranted && !mIsShowingDialog) {
+                    try {
+                        if (DEBUG) Log.d(TAG, "Removing token");
+                        mWindowManager.removeWindowToken(mToken);
+                    } catch (RemoteException e) {
+                    }
+                    mIsTokenGranted = false;
                 }
-                mIsTokenGranted = false;
+                mIsShowingDialog = false;
+                mServiceManager.setBindRequested(false);
             }
-            mIsShowingDialog = false;
-            mHandler.postDelayed(mUnbind, UNBIND_DELAY);
+        } catch (RemoteException e) {
+            // Called through wrapper, won't happen here.
         }
     }
 
     @Override
     protected void handleDestroy() {
         super.handleDestroy();
-        mHandler.removeCallbacks(mUnbind);
         if (mIsTokenGranted) {
             try {
                 if (DEBUG) Log.d(TAG, "Removing token");
@@ -139,7 +125,6 @@
             } catch (RemoteException e) {
             }
         }
-        mUnbind.run();
     }
 
     @Override
@@ -161,7 +146,11 @@
                 mIsTokenGranted = true;
             } catch (RemoteException e) {
             }
-            mService.onClick(mToken);
+            try {
+                mService.onClick(mToken);
+            } catch (RemoteException e) {
+                // Called through wrapper, won't happen here.
+            }
         } else {
             Log.e(TAG, "Click with no service " + getTileSpec());
         }
@@ -187,34 +176,9 @@
 
     @Override
     public int getMetricsCategory() {
-        return MetricsLogger.QS_INTENT;
+        return MetricsLogger.QS_CUSTOM;
     }
 
-    private final ServiceConnection mServiceConnection = new ServiceConnection() {
-        @Override
-        public void onServiceConnected(ComponentName name, IBinder service) {
-            mService = new QSTileServiceWrapper(IQSTileService.Stub.asInterface(service));
-            if (mListening) {
-                mService.setQSTile(mTile);
-                mService.onStartListening();
-            } else {
-                mService.onStopListening();
-            }
-        }
-
-        @Override
-        public void onServiceDisconnected(ComponentName name) {
-        }
-    };
-
-    private final Runnable mUnbind = new Runnable() {
-        @Override
-        public void run() {
-            mContext.unbindService(mServiceConnection);
-            mBound = false;
-        }
-    };
-
     public static ComponentName getComponentFromSpec(String spec) {
         final String action = spec.substring(PREFIX.length(), spec.length() - 1);
         if (action.isEmpty()) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/QSTileServiceWrapper.java b/packages/SystemUI/src/com/android/systemui/qs/external/QSTileServiceWrapper.java
new file mode 100644
index 0000000..d656686
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/QSTileServiceWrapper.java
@@ -0,0 +1,96 @@
+/*
+ * 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.external;
+
+import android.os.IBinder;
+import android.service.quicksettings.IQSTileService;
+import android.service.quicksettings.Tile;
+import android.util.Log;
+
+
+public class QSTileServiceWrapper {
+    private static final String TAG = "IQSTileServiceWrapper";
+
+    private final IQSTileService mService;
+
+    public QSTileServiceWrapper(IQSTileService service) {
+        mService = service;
+    }
+
+    public IBinder asBinder() {
+        return mService.asBinder();
+    }
+
+    public boolean setQSTile(Tile tile) {
+        try {
+            mService.setQSTile(tile);
+            return true;
+        } catch (Exception e) {
+            Log.d(TAG, "Caught exception from TileService", e);
+            return false;
+        }
+    }
+
+    public boolean onTileAdded() {
+        try {
+            mService.onTileAdded();
+            return true;
+        } catch (Exception e) {
+            Log.d(TAG, "Caught exception from TileService", e);
+            return false;
+        }
+    }
+
+    public boolean onTileRemoved() {
+        try {
+            mService.onTileRemoved();
+            return true;
+        } catch (Exception e) {
+            Log.d(TAG, "Caught exception from TileService", e);
+            return false;
+        }
+    }
+
+    public boolean onStartListening() {
+        try {
+            mService.onStartListening();
+            return true;
+        } catch (Exception e) {
+            Log.d(TAG, "Caught exception from TileService", e);
+            return false;
+        }
+    }
+
+    public boolean onStopListening() {
+        try {
+            mService.onStopListening();
+            return true;
+        } catch (Exception e) {
+            Log.d(TAG, "Caught exception from TileService", e);
+            return false;
+        }
+    }
+
+    public boolean onClick(IBinder token) {
+        try {
+            mService.onClick(token);
+            return true;
+        } catch (Exception e) {
+            Log.d(TAG, "Caught exception from TileService", e);
+            return false;
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
new file mode 100644
index 0000000..500ee19
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
@@ -0,0 +1,331 @@
+/*
+ * 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.external;
+
+import android.app.AppGlobals;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.ServiceConnection;
+import android.content.pm.PackageManager;
+import android.content.pm.ServiceInfo;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.service.quicksettings.IQSTileService;
+import android.service.quicksettings.Tile;
+import android.support.annotation.VisibleForTesting;
+import android.util.ArraySet;
+import android.util.Log;
+import libcore.util.Objects;
+
+import java.util.Set;
+
+/**
+ * Manages the lifecycle of a TileService.
+ * <p>
+ * Will keep track of all calls on the IQSTileService interface and will relay those calls to the
+ * TileService as soon as it is bound.  It will only bind to the service when it is allowed to
+ * ({@link #setBindService(boolean)}) and when the service is available.
+ */
+public class TileLifecycleManager extends BroadcastReceiver implements
+        IQSTileService, ServiceConnection, IBinder.DeathRecipient {
+    public static final boolean DEBUG = false;
+
+    private static final String TAG = "TileLifecycleManager";
+
+    private static final int MSG_ON_ADDED = 0;
+    private static final int MSG_ON_REMOVED = 1;
+    private static final int MSG_ON_CLICK = 2;
+
+    // Bind retry control.
+    private static final int MAX_BIND_RETRIES = 5;
+    private static final int BIND_RETRY_DELAY = 1000;
+
+    private final Context mContext;
+    private final Handler mHandler;
+    private final Intent mIntent;
+    private final UserHandle mUser;
+
+    private Set<Integer> mQueuedMessages = new ArraySet<>();
+    private QSTileServiceWrapper mWrapper;
+    private boolean mListening;
+    private Tile mTile;
+    private IBinder mClickBinder;
+
+    private int mBindTryCount;
+    private boolean mBound;
+    @VisibleForTesting
+    boolean mReceiverRegistered;
+
+    public TileLifecycleManager(Handler handler, Context context, Intent intent, UserHandle user) {
+        mContext = context;
+        mHandler = handler;
+        mIntent = intent;
+        mUser = user;
+    }
+
+    public boolean hasPendingClick() {
+        synchronized (mQueuedMessages) {
+            return mQueuedMessages.contains(MSG_ON_CLICK);
+        }
+    }
+
+    public void setBindService(boolean bind) {
+        mBound = bind;
+        if (bind) {
+            if (mBindTryCount == MAX_BIND_RETRIES) {
+                // Too many failures, give up on this tile until an update.
+                startPackageListening();
+                return;
+            }
+            if (!checkComponentState()) {
+                return;
+            }
+            if (DEBUG) Log.d(TAG, "Binding service " + mIntent);
+            mBindTryCount++;
+            mContext.bindServiceAsUser(mIntent, this,
+                    Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE,
+                    mUser);
+        } else {
+            if (DEBUG) Log.d(TAG, "Unbinding service " + mIntent);
+            // Give it another chance next time it needs to be bound, out of kindness.
+            mBindTryCount = 0;
+            mContext.unbindService(this);
+        }
+    }
+
+    @Override
+    public void onServiceConnected(ComponentName name, IBinder service) {
+        if (DEBUG) Log.d(TAG, "onServiceConnected " + name);
+        // Got a connection, set the binding count to 0.
+        mBindTryCount = 0;
+        mWrapper = new QSTileServiceWrapper(Stub.asInterface(service));
+        try {
+            service.linkToDeath(this, 0);
+        } catch (RemoteException e) {
+        }
+        handlePendingMessages();
+    }
+
+    @Override
+    public void onServiceDisconnected(ComponentName name) {
+        if (DEBUG) Log.d(TAG, "onServiceDisconnected " + name);
+        mWrapper = null;
+    }
+
+    private void handlePendingMessages() {
+        // This ordering is laid out manually to make sure we preserve the TileService
+        // lifecycle.
+        ArraySet<Integer> queue;
+        synchronized (mQueuedMessages) {
+            queue = new ArraySet<>(mQueuedMessages);
+            mQueuedMessages.clear();
+        }
+        if (queue.contains(MSG_ON_ADDED)) {
+            if (DEBUG) Log.d(TAG, "Handling pending onAdded");
+            onTileAdded();
+        }
+        if (mListening) {
+            if (DEBUG) Log.d(TAG, "Handling pending onStartListening");
+            setQSTile(mTile);
+            onStartListening();
+        }
+        if (queue.contains(MSG_ON_CLICK)) {
+            if (DEBUG) Log.d(TAG, "Handling pending onClick");
+            if (!mListening) {
+                Log.w(TAG, "Managed to get click on non-listening state...");
+                // Skipping click since lost click privileges.
+            } else {
+                onClick(mClickBinder);
+            }
+        }
+        if (queue.contains(MSG_ON_REMOVED)) {
+            if (DEBUG) Log.d(TAG, "Handling pending onRemoved");
+            if (mListening) {
+                Log.w(TAG, "Managed to get remove in listening state...");
+                onStopListening();
+            }
+            onTileRemoved();
+        }
+    }
+
+    public void handleDestroy() {
+        if (DEBUG) Log.d(TAG, "handleDestroy");
+        if (mReceiverRegistered) {
+            stopPackageListening();
+        }
+    }
+
+    private void handleDeath() {
+        if (mWrapper == null) return;
+        mWrapper = null;
+        if (!mBound) return;
+        if (DEBUG) Log.d(TAG, "handleDeath");
+        if (checkComponentState()) {
+            mHandler.postDelayed(new Runnable() {
+                @Override
+                public void run() {
+                    if (mBound) {
+                        // Retry binding.
+                        setBindService(true);
+                    }
+                }
+            }, BIND_RETRY_DELAY);
+        }
+    }
+
+    @Override
+    public void setQSTile(Tile tile) {
+        if (DEBUG) Log.d(TAG, "setQSTile " + tile);
+        mTile = tile;
+        if (mWrapper != null && !mWrapper.setQSTile(tile)) {
+            handleDeath();
+        }
+    }
+
+    private boolean checkComponentState() {
+        PackageManager pm = mContext.getPackageManager();
+        if (!isPackageAvailable(pm) || !isComponentAvailable(pm)) {
+            startPackageListening();
+            return false;
+        }
+        return true;
+    }
+
+    private void startPackageListening() {
+        if (DEBUG) Log.d(TAG, "startPackageListening");
+        IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
+        filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
+        filter.addDataScheme("package");
+        mContext.registerReceiverAsUser(this, mUser, filter, null, mHandler);
+        mReceiverRegistered = true;
+    }
+
+    private void stopPackageListening() {
+        if (DEBUG) Log.d(TAG, "stopPackageListening");
+        mContext.unregisterReceiver(this);
+        mReceiverRegistered = false;
+    }
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        if (DEBUG) Log.d(TAG, "onReceive: " + intent);
+        Uri data = intent.getData();
+        String pkgName = data.getEncodedSchemeSpecificPart();
+        if (!Objects.equal(pkgName, mIntent.getComponent().getPackageName())) {
+            return;
+        }
+        stopPackageListening();
+        if (mBound) {
+            // Trying to bind again will check the state of the package before bothering to bind.
+            if (DEBUG) Log.d(TAG, "Trying to rebind");
+            setBindService(true);
+        }
+    }
+
+    private boolean isComponentAvailable(PackageManager pm) {
+        String packageName = mIntent.getComponent().getPackageName();
+        try {
+            ServiceInfo si = AppGlobals.getPackageManager().getServiceInfo(mIntent.getComponent(),
+                    0, mUser.getIdentifier());
+            if (DEBUG && si == null) Log.d(TAG, "Can't find component " + mIntent.getComponent());
+            return si != null;
+        } catch (RemoteException e) {
+            // Shouldn't happen.
+        }
+        return false;
+    }
+
+    private boolean isPackageAvailable(PackageManager pm) {
+        String packageName = mIntent.getComponent().getPackageName();
+        try {
+            pm.getPackageInfoAsUser(packageName, 0, mUser.getIdentifier());
+            return true;
+        } catch (PackageManager.NameNotFoundException e) {
+            if (DEBUG) Log.d(TAG, "Package not available: " + packageName, e);
+            else Log.d(TAG, "Package not available: " + packageName);
+        }
+        return false;
+    }
+
+    private void queueMessage(int message) {
+        synchronized (mQueuedMessages) {
+            mQueuedMessages.add(message);
+        }
+    }
+
+    @Override
+    public void onTileAdded() {
+        if (DEBUG) Log.d(TAG, "onTileAdded");
+        if (mWrapper == null || !mWrapper.onTileAdded()) {
+            queueMessage(MSG_ON_ADDED);
+            handleDeath();
+        }
+    }
+
+    @Override
+    public void onTileRemoved() {
+        if (DEBUG) Log.d(TAG, "onTileRemoved");
+        if (mWrapper == null || !mWrapper.onTileRemoved()) {
+            queueMessage(MSG_ON_REMOVED);
+            handleDeath();
+        }
+    }
+
+    @Override
+    public void onStartListening() {
+        if (DEBUG) Log.d(TAG, "onStartListening");
+        mListening = true;
+        if (mWrapper != null && !mWrapper.onStartListening()) {
+            handleDeath();
+        }
+    }
+
+    @Override
+    public void onStopListening() {
+        if (DEBUG) Log.d(TAG, "onStopListening");
+        mListening = false;
+        if (mWrapper != null && !mWrapper.onStopListening()) {
+            handleDeath();
+        }
+    }
+
+    @Override
+    public void onClick(IBinder iBinder) {
+        if (DEBUG) Log.d(TAG, "onClick " + iBinder);
+        if (mWrapper == null || !mWrapper.onClick(iBinder)) {
+            mClickBinder = iBinder;
+            queueMessage(MSG_ON_CLICK);
+            handleDeath();
+        }
+    }
+
+    @Override
+    public IBinder asBinder() {
+        return mWrapper != null ? mWrapper.asBinder() : null;
+    }
+
+    @Override
+    public void binderDied() {
+        if (DEBUG) Log.d(TAG, "binderDeath");
+        handleDeath();
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java
new file mode 100644
index 0000000..ca589df
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java
@@ -0,0 +1,159 @@
+/*
+ * 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.external;
+
+import android.app.ActivityManager;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.os.Handler;
+import android.os.UserHandle;
+import android.service.quicksettings.IQSTileService;
+import android.support.annotation.VisibleForTesting;
+import android.util.Log;
+
+/**
+ * Manages the priority which lets {@link TileServices} make decisions about which tiles
+ * to bind.  Also holds on to and manages the {@link TileLifecycleManager}, informing it
+ * of when it is allowed to bind based on decisions frome the {@link TileServices}.
+ */
+public class TileServiceManager {
+
+    private static final long MIN_BIND_TIME = 5000;
+    private static final long UNBIND_DELAY = 30000;
+
+    public static final boolean DEBUG = true;
+
+    private static final String TAG = "TileServiceManager";
+
+    private final TileServices mServices;
+    private final TileLifecycleManager mStateManager;
+    private final Handler mHandler;
+    private boolean mBindRequested;
+    private boolean mBindAllowed;
+    private boolean mBound;
+    private int mPriority;
+    private boolean mJustBound;
+    private long mLastUpdate;
+
+    TileServiceManager(TileServices tileServices, Handler handler, ComponentName component) {
+        this(tileServices, handler, new TileLifecycleManager(handler,
+                tileServices.getContext(), new Intent().setComponent(component),
+                new UserHandle(ActivityManager.getCurrentUser())));
+    }
+
+    @VisibleForTesting
+    TileServiceManager(TileServices tileServices, Handler handler,
+            TileLifecycleManager tileLifecycleManager) {
+        mServices = tileServices;
+        mHandler = handler;
+        mStateManager = tileLifecycleManager;
+    }
+
+    public IQSTileService getTileService() {
+        return mStateManager;
+    }
+
+    public void setBindRequested(boolean bindRequested) {
+        if (mBindRequested == bindRequested) return;
+        mBindRequested = bindRequested;
+        if (mBindAllowed && mBindRequested && !mBound) {
+            bindService();
+        } else {
+            mServices.recalculateBindAllowance();
+        }
+        if (mBound && !mBindRequested) {
+            // TODO: Schedule unbind.
+        }
+    }
+
+    public void setLastUpdate(long lastUpdate) {
+        mLastUpdate = lastUpdate;
+        mServices.recalculateBindAllowance();
+    }
+
+    public void handleDestroy() {
+        mStateManager.handleDestroy();
+    }
+
+    public void setBindAllowed(boolean allowed) {
+        if (mBindAllowed == allowed) return;
+        mBindAllowed = allowed;
+        if (!mBindAllowed && mBound) {
+            unbindService();
+        } else if (mBindAllowed && mBindRequested && !mBound) {
+            bindService();
+        }
+    }
+
+    private void bindService() {
+        if (mBound) {
+            Log.e(TAG, "Service already bound");
+            return;
+        }
+        mBound = true;
+        mJustBound = true;
+        mHandler.postDelayed(mJustBoundOver, MIN_BIND_TIME);
+        mStateManager.setBindService(true);
+    }
+
+    private void unbindService() {
+        if (!mBound) {
+            Log.e(TAG, "Service not bound");
+            return;
+        }
+        mBound = false;
+        mJustBound = false;
+        mStateManager.setBindService(false);
+    }
+
+    public void calculateBindPriority(long currentTime) {
+        if (mStateManager.hasPendingClick()) {
+            // Pending click is the most important thing, need to put this service at the top of
+            // the list to be bound.
+            mPriority = Integer.MAX_VALUE;
+        } else if (mJustBound) {
+            // If we just bound, lets not thrash on binding/unbinding too much, this is second most
+            // important.
+            mPriority = Integer.MAX_VALUE - 1;
+        } else if (!mBindRequested) {
+            // Don't care about binding right now, put us last.
+            mPriority = Integer.MIN_VALUE;
+        } else {
+            // Order based on whether this was just updated.
+            long timeSinceUpdate = currentTime - mLastUpdate;
+            // Fit compare into integer space for simplicity. Make sure to leave MAX_VALUE and
+            // MAX_VALUE - 1 for the more important states above.
+            if (timeSinceUpdate > Integer.MAX_VALUE - 2) {
+                mPriority = Integer.MAX_VALUE - 2;
+            } else {
+                mPriority = (int) timeSinceUpdate;
+            }
+        }
+    }
+
+    public int getBindPriority() {
+        return mPriority;
+    }
+
+    @VisibleForTesting
+    final Runnable mJustBoundOver = new Runnable() {
+        @Override
+        public void run() {
+            mJustBound = false;
+            mServices.recalculateBindAllowance();
+        }
+    };
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
new file mode 100644
index 0000000..d110d97
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
@@ -0,0 +1,156 @@
+/*
+ * 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.external;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.Looper;
+import android.service.quicksettings.IQSService;
+import android.service.quicksettings.Tile;
+import android.util.ArrayMap;
+import com.android.systemui.statusbar.phone.QSTileHost;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+
+/**
+ * Runs the day-to-day operations of which tiles should be bound and when.
+ */
+public class TileServices extends IQSService.Stub {
+    static final int DEFAULT_MAX_BOUND = 3;
+    static final int REDUCED_MAX_BOUND = 1;
+
+    private final ArrayMap<CustomTile, TileServiceManager> mServices = new ArrayMap<>();
+    private final ArrayMap<ComponentName, CustomTile> mTiles = new ArrayMap<>();
+    private final Context mContext;
+    private final Handler mHandler;
+    private final QSTileHost mHost;
+
+    private int mMaxBound = DEFAULT_MAX_BOUND;
+
+    public TileServices(QSTileHost host, Looper looper) {
+        mHost = host;
+        mContext = mHost.getContext();
+        mHandler = new Handler(looper);
+    }
+
+    public Context getContext() {
+        return mContext;
+    }
+
+    public TileServiceManager getTileWrapper(CustomTile tile) {
+        ComponentName component = tile.getComponent();
+        TileServiceManager service = onCreateTileService(component);
+        synchronized (mServices) {
+            mServices.put(tile, service);
+            mTiles.put(component, tile);
+        }
+        return service;
+    }
+
+    protected TileServiceManager onCreateTileService(ComponentName component) {
+        return new TileServiceManager(this, mHandler, component);
+    }
+
+    public void freeService(CustomTile tile, TileServiceManager service) {
+        synchronized (mServices) {
+            service.setBindAllowed(false);
+            mServices.remove(tile);
+            mTiles.remove(tile.getComponent());
+        }
+    }
+
+    public void setMemoryPressure(boolean memoryPressure) {
+        mMaxBound = memoryPressure ? REDUCED_MAX_BOUND : DEFAULT_MAX_BOUND;
+        recalculateBindAllowance();
+    }
+
+    public void recalculateBindAllowance() {
+        final ArrayList<TileServiceManager> services;
+        synchronized (mServices) {
+            services = new ArrayList<>(mServices.values());
+        }
+        final int N = services.size();
+        if (N > mMaxBound) {
+            long currentTime = System.currentTimeMillis();
+            // Precalculate the priority of services for binding.
+            for (int i = 0; i < N; i++) {
+                services.get(i).calculateBindPriority(currentTime);
+            }
+            // Sort them so we can bind the most important first.
+            Collections.sort(services, SERVICE_SORT);
+        }
+        int i;
+        // Allow mMaxBound items to bind.
+        for (i = 0; i < mMaxBound && i < N; i++) {
+            services.get(i).setBindAllowed(true);
+        }
+        // The rest aren't allowed to bind for now.
+        while (i < N) {
+            services.get(i).setBindAllowed(false);
+            i++;
+        }
+    }
+
+    private void verifyCaller(String packageName) {
+        try {
+            int uid = mContext.getPackageManager().getPackageUid(packageName,
+                    Binder.getCallingUserHandle().getIdentifier());
+            if (Binder.getCallingUid() != uid) {
+                throw new SecurityException("Component outside caller's uid");
+            }
+        } catch (PackageManager.NameNotFoundException e) {
+            throw new SecurityException(e);
+        }
+    }
+
+    @Override
+    public void updateQsTile(Tile tile) {
+        verifyCaller(tile.getComponentName().getPackageName());
+        CustomTile customTile = getTileForComponent(tile.getComponentName());
+        if (customTile != null) {
+            mServices.get(customTile).setLastUpdate(System.currentTimeMillis());
+            customTile.updateState(tile);
+            customTile.refreshState();
+        }
+    }
+
+    @Override
+    public void onShowDialog(Tile tile) {
+        verifyCaller(tile.getComponentName().getPackageName());
+        CustomTile customTile = getTileForComponent(tile.getComponentName());
+        if (customTile != null) {
+            customTile.onDialogShown();
+            mHost.collapsePanels();
+        }
+    }
+
+    private CustomTile getTileForComponent(ComponentName component) {
+        return mTiles.get(component);
+    }
+
+    private static final Comparator<TileServiceManager> SERVICE_SORT =
+            new Comparator<TileServiceManager>() {
+        @Override
+        public int compare(TileServiceManager left, TileServiceManager right) {
+            return -Integer.compare(left.getBindPriority(), right.getBindPriority());
+        }
+    };
+}
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 fc802dd..c696f88 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java
@@ -58,8 +58,6 @@
     public void handleClick() {
         MetricsLogger.action(mContext, getMetricsCategory(), !mState.value);
         setEnabled(!mState.value);
-        mEnable.setAllowAnimation(true);
-        mDisable.setAllowAnimation(true);
     }
 
     private void setEnabled(boolean enabled) {
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 f73ee35..23a15b9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java
@@ -71,8 +71,6 @@
     protected void handleClick() {
         MetricsLogger.action(mContext, getMetricsCategory(), !mState.value);
         mSetting.setValue(mState.value ? 0 : 1);
-        mEnable.setAllowAnimation(true);
-        mDisable.setAllowAnimation(true);
     }
 
     @Override
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 d96f735..4f9f46d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
@@ -107,8 +107,6 @@
                     Toast.LENGTH_LONG).show();
             return;
         }
-        mDisable.setAllowAnimation(true);
-        mDisableTotalSilence.setAllowAnimation(true);
         MetricsLogger.action(mContext, getMetricsCategory(), !mState.value);
         if (mState.value) {
             mController.setZen(Global.ZEN_MODE_OFF, null, TAG);
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 12c1298..39d9da1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java
@@ -64,7 +64,7 @@
         }
         MetricsLogger.action(mContext, getMetricsCategory(), !mState.value);
         boolean newState = !mState.value;
-        refreshState(newState ? UserBoolean.USER_TRUE : UserBoolean.USER_FALSE);
+        refreshState(newState);
         mFlashlightController.setFlashlight(newState);
     }
 
@@ -73,8 +73,8 @@
         // TODO: Flashlight available handling...
 //        state.visible = mFlashlightController.isAvailable();
         state.label = mHost.getContext().getString(R.string.quick_settings_flashlight_label);
-        if (arg instanceof UserBoolean) {
-            boolean value = ((UserBoolean) arg).value;
+        if (arg instanceof Boolean) {
+            boolean value = (Boolean) arg;
             if (value == state.value) {
                 return;
             }
@@ -83,7 +83,6 @@
             state.value = mFlashlightController.isEnabled();
         }
         final AnimationIcon icon = state.value ? mEnable : mDisable;
-        icon.setAllowAnimation(arg instanceof UserBoolean && ((UserBoolean) arg).userInitiated);
         state.icon = icon;
         int onOrOffId = state.value
                 ? R.string.accessibility_quick_settings_flashlight_on
@@ -107,12 +106,12 @@
 
     @Override
     public void onFlashlightChanged(boolean enabled) {
-        refreshState(enabled ? UserBoolean.BACKGROUND_TRUE : UserBoolean.BACKGROUND_FALSE);
+        refreshState(enabled);
     }
 
     @Override
     public void onFlashlightError() {
-        refreshState(UserBoolean.BACKGROUND_FALSE);
+        refreshState(false);
     }
 
     @Override
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 250d567..55aa32b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
@@ -59,8 +59,6 @@
         final boolean isEnabled = (Boolean) mState.value;
         MetricsLogger.action(mContext, getMetricsCategory(), !isEnabled);
         mController.setHotspotEnabled(!isEnabled);
-        mEnable.setAllowAnimation(true);
-        mDisable.setAllowAnimation(true);
     }
 
     @Override
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 08540f6..e79aabf 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java
@@ -67,8 +67,6 @@
                     mHost.openPanels();
                     MetricsLogger.action(mContext, getMetricsCategory(), !wasEnabled);
                     mController.setLocationEnabled(!wasEnabled);
-                    mEnable.setAllowAnimation(true);
-                    mDisable.setAllowAnimation(true);
                 }
             });
             return;
@@ -76,8 +74,6 @@
         final boolean wasEnabled = (Boolean) mState.value;
         MetricsLogger.action(mContext, getMetricsCategory(), !wasEnabled);
         mController.setLocationEnabled(!wasEnabled);
-        mEnable.setAllowAnimation(true);
-        mDisable.setAllowAnimation(true);
     }
 
     @Override
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 d85cf60..7bce54b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java
@@ -63,15 +63,14 @@
         MetricsLogger.action(mContext, getMetricsCategory(), !mState.value);
         final boolean newState = !mState.value;
         mController.setRotationLocked(newState);
-        refreshState(newState ? UserBoolean.USER_TRUE : UserBoolean.USER_FALSE);
+        refreshState(newState);
     }
 
     @Override
     protected void handleUpdateState(BooleanState state, Object arg) {
         if (mController == null) return;
-        final boolean rotationLocked = arg != null ? ((UserBoolean) arg).value
+        final boolean rotationLocked = arg != null ? (Boolean) arg
                 : mController.isRotationLocked();
-        final boolean userInitiated = arg != null ? ((UserBoolean) arg).userInitiated : false;
         // TODO: Handle accessibility rotation lock and whatnot.
 //        state.visible = mController.isRotationLockAffordanceVisible();
         if (state.value == rotationLocked && state.contentDescription != null) {
@@ -80,18 +79,15 @@
         }
         state.value = rotationLocked;
         final boolean portrait = isCurrentOrientationLockPortrait();
-        final AnimationIcon icon;
         if (rotationLocked) {
             final int label = portrait ? R.string.quick_settings_rotation_locked_portrait_label
                     : R.string.quick_settings_rotation_locked_landscape_label;
             state.label = mContext.getString(label);
-            icon = portrait ? mAutoToPortrait : mAutoToLandscape;
+            state.icon = portrait ? mAutoToPortrait : mAutoToLandscape;
         } else {
             state.label = mContext.getString(R.string.quick_settings_rotation_unlocked_label);
-            icon = portrait ? mPortraitToAuto : mLandscapeToAuto;
+            state.icon = portrait ? mPortraitToAuto : mLandscapeToAuto;
         }
-        icon.setAllowAnimation(userInitiated);
-        state.icon = icon;
         state.contentDescription = getAccessibilityString(rotationLocked,
                 R.string.accessibility_rotation_lock_on_portrait,
                 R.string.accessibility_rotation_lock_on_landscape,
@@ -145,8 +141,7 @@
     private final RotationLockControllerCallback mCallback = new RotationLockControllerCallback() {
         @Override
         public void onRotationLockStateChanged(boolean rotationLocked, boolean affordanceVisible) {
-            refreshState(rotationLocked ? UserBoolean.BACKGROUND_TRUE
-                    : UserBoolean.BACKGROUND_FALSE);
+            refreshState(rotationLocked);
         }
     };
 }
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 07915f8..255f29f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java
@@ -93,12 +93,7 @@
     }
 
     private void refreshQuietModeState(boolean backgroundRefresh) {
-        if (backgroundRefresh) {
-            refreshState(isWorkModeEnabled() ? UserBoolean.BACKGROUND_TRUE
-                    : UserBoolean.BACKGROUND_FALSE);
-        } else {
-            refreshState(isWorkModeEnabled() ? UserBoolean.USER_TRUE : UserBoolean.USER_FALSE);
-        }
+        refreshState(isWorkModeEnabled());
     }
 
     @Override
@@ -108,28 +103,22 @@
             return;
         }
 
-        final boolean userInitialized;
-        if (arg instanceof UserBoolean) {
-            state.value = ((UserBoolean) arg).value;
-            userInitialized = ((UserBoolean) arg).userInitiated;
+        if (arg instanceof Boolean) {
+            state.value = (Boolean) arg;
         } else {
             state.value = isWorkModeEnabled();
-            userInitialized = false;
         }
 
-        final AnimationIcon icon;
         state.label = mContext.getString(R.string.quick_settings_work_mode_label);
         if (state.value) {
-            icon = mEnable;
+            state.icon = mEnable;
             state.contentDescription =  mContext.getString(
                     R.string.accessibility_quick_settings_work_mode_on);
         } else {
-            icon = mDisable;
+            state.icon = mDisable;
             state.contentDescription =  mContext.getString(
                     R.string.accessibility_quick_settings_work_mode_off);
         }
-        icon.setAllowAnimation(userInitialized);
-        state.icon = icon;
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
index 7547570f..cfbd1cb 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
@@ -74,9 +74,6 @@
     public int svelteLevel;
     public int searchBarSpaceHeightPx;
 
-    /** Dev options and global settings */
-    public boolean lockToAppEnabled;
-
     public RecentsConfiguration(Context context) {
         // Load only resources that can not change after the first load either through developer
         // settings or via multi window
@@ -106,12 +103,7 @@
     /**
      * Updates the configuration based on the current state of the system
      */
-    void update(Context context, SystemServicesProxy ssp, Rect windowRect) {
-        // Only update resources that can change after the first load, either through developer
-        // settings or via multi window
-        lockToAppEnabled = !ssp.hasFreeformWorkspaceSupport() &&
-                ssp.getSystemSetting(context, Settings.System.LOCK_TO_APP_ENABLED) != 0;
-
+    void update(Rect windowRect) {
         // Recompute some values based on the given state, since we can not rely on the resource
         // system to get certain values.
         boolean isLandscape = windowRect.width() > windowRect.height();
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
index 949fb86..2b988a9 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
@@ -572,7 +572,7 @@
         Rect windowRect = ssp.getWindowRect();
 
         // Update the configuration for the current state
-        config.update(mContext, ssp, ssp.getWindowRect());
+        config.update(windowRect);
 
         if (!RecentsDebugFlags.Static.DisableSearchBar && tryAndBindSearchWidget) {
             // Try and pre-emptively bind the search widget on startup to ensure that we
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
index 7a92b2a..d6262ac 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
@@ -155,7 +155,7 @@
             // Add the task to the stack
             Task task = new Task(taskKey, t.affiliatedTaskId, t.affiliatedTaskColor, icon,
                     thumbnail, title, contentDescription, activityColor, !isStackTask,
-                    (i == (taskCount - 1)), config.lockToAppEnabled, t.bounds, t.taskDescription);
+                    t.bounds, t.taskDescription);
 
             allTasks.add(task);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
index 34a0e52..d030fc1 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
@@ -131,8 +131,6 @@
      */
     public boolean isLaunchTarget;
     public boolean isHistorical;
-    public boolean lockToThisTask;
-    public boolean lockToTaskEnabled;
 
     private ArrayList<TaskCallbacks> mCallbacks = new ArrayList<>();
 
@@ -142,8 +140,8 @@
 
     public Task(TaskKey key, int affiliationTaskId, int affiliationColor, Drawable icon,
                 Bitmap thumbnail, String title, String contentDescription, int colorPrimary,
-                boolean isHistorical, boolean lockToThisTask, boolean lockToTaskEnabled,
-                Rect bounds, ActivityManager.TaskDescription taskDescription) {
+                boolean isHistorical, Rect bounds,
+                ActivityManager.TaskDescription taskDescription) {
         boolean isInAffiliationGroup = (affiliationTaskId != key.id);
         boolean hasAffiliationGroupColor = isInAffiliationGroup && (affiliationColor != 0);
         this.key = key;
@@ -159,8 +157,6 @@
         this.bounds = bounds;
         this.taskDescription = taskDescription;
         this.isHistorical = isHistorical;
-        this.lockToThisTask = lockToTaskEnabled && lockToThisTask;
-        this.lockToTaskEnabled = lockToTaskEnabled;
     }
 
     /** Copies the other task. */
@@ -178,8 +174,6 @@
         this.bounds = o.bounds;
         this.isLaunchTarget = o.isLaunchTarget;
         this.isHistorical = o.isHistorical;
-        this.lockToThisTask = o.lockToThisTask;
-        this.lockToTaskEnabled = o.lockToTaskEnabled;
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
index 6f003ab..5e720cb 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
@@ -427,8 +427,6 @@
                 removeGroup(group);
             }
         }
-        // Update the lock-to-app state
-        t.lockToThisTask = false;
     }
 
     /** Removes a task */
@@ -437,9 +435,6 @@
             boolean wasFrontMostTask = (getStackFrontMostTask() == t);
             removeTaskImpl(mStackTaskList, t);
             Task newFrontMostTask = getStackFrontMostTask();
-            if (newFrontMostTask != null && newFrontMostTask.lockToTaskEnabled) {
-                newFrontMostTask.lockToThisTask = true;
-            }
             if (mCb != null) {
                 // Notify that a task has been removed
                 mCb.onStackTaskRemoved(this, t, wasFrontMostTask, newFrontMostTask);
@@ -524,10 +519,21 @@
         mAffinitiesGroups.clear();
     }
 
-    /** Gets the front task */
+    /**
+     * Gets the front-most task in the stack.
+     */
     public Task getStackFrontMostTask() {
-        if (mStackTaskList.size() == 0) return null;
-        return mStackTaskList.getTasks().get(mStackTaskList.size() - 1);
+        ArrayList<Task> stackTasks = mStackTaskList.getTasks();
+        if (stackTasks.isEmpty()) {
+            return null;
+        }
+        for (int i = stackTasks.size() - 1; i >= 0; i--) {
+            Task task = stackTasks.get(i);
+            if (!task.isFreeformTask()) {
+                return task;
+            }
+        }
+        return null;
     }
 
     /** Gets the task keys */
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
index 830d607..94fae13 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -28,6 +28,7 @@
 import android.graphics.drawable.GradientDrawable;
 import android.os.Bundle;
 import android.os.Parcelable;
+import android.provider.Settings;
 import android.util.IntProperty;
 import android.util.Log;
 import android.util.Property;
@@ -149,7 +150,9 @@
     List<TaskView> mImmutableTaskViews = new ArrayList<>();
     List<TaskView> mTmpTaskViews = new ArrayList<>();
     LayoutInflater mInflater;
+
     boolean mTouchExplorationEnabled;
+    boolean mScreenPinningEnabled;
 
     Interpolator mFastOutSlowInInterpolator;
 
@@ -224,6 +227,8 @@
     protected void onAttachedToWindow() {
         SystemServicesProxy ssp = Recents.getSystemServices();
         mTouchExplorationEnabled = ssp.isTouchExplorationEnabled();
+        mScreenPinningEnabled = ssp.getSystemSetting(getContext(),
+                Settings.System.LOCK_TO_APP_ENABLED) != 0;
         EventBus.getDefault().register(this, RecentsActivity.EVENT_BUS_PRIORITY + 1);
         super.onAttachedToWindow();
     }
@@ -1023,8 +1028,7 @@
                         launchTargetTask);
                 hideTask = launchTargetTask.isFreeformTask() && task.isFreeformTask();
             }
-            tv.prepareEnterRecentsAnimation(task.isLaunchTarget, hideTask, occludesLaunchTarget,
-                    offscreenY);
+            tv.prepareEnterRecentsAnimation(hideTask, occludesLaunchTarget, offscreenY);
         }
 
         // If the enter animation started already and we haven't completed a layout yet, do the
@@ -1087,6 +1091,7 @@
                 ctx.currentTaskRect = mLayoutAlgorithm.mTaskRect;
                 ctx.currentTaskOccludesLaunchTarget = (launchTargetTask != null) &&
                         launchTargetTask.group.isTaskAboveTask(task, launchTargetTask);
+                ctx.isScreenPinningEnabled = mScreenPinningEnabled;
                 ctx.updateListener = mRequestUpdateClippingListener;
                 mLayoutAlgorithm.getStackTransform(task, mStackScroller.getStackScroll(),
                         ctx.currentTaskTransform, null);
@@ -1271,13 +1276,11 @@
             requestSynchronizeStackViewsWithModel(DEFAULT_SYNC_STACK_DURATION);
         }
 
-        // Update the new front most task
-        if (newFrontMostTask != null) {
+        // Update the new front most task's action button
+        if (mScreenPinningEnabled && newFrontMostTask != null) {
             TaskView frontTv = getChildViewForTask(newFrontMostTask);
             if (frontTv != null) {
-                frontTv.onTaskBound(newFrontMostTask);
-                frontTv.fadeInActionButton(getResources().getInteger(
-                        R.integer.recents_task_enter_from_app_duration));
+                frontTv.showActionButton(true /* fadeIn */, DEFAULT_SYNC_STACK_DURATION);
             }
         }
 
@@ -1304,7 +1307,7 @@
 
     @Override
     public void prepareViewToEnterPool(TaskView tv) {
-        Task task = tv.getTask();
+        final Task task = tv.getTask();
 
         // Report that this tasks's data is no longer being used
         Recents.getTaskLoader().unloadTaskData(task);
@@ -1314,14 +1317,13 @@
         // Update the task views list after removing the task view
         updateTaskViewsList();
 
-        // Reset the view properties
+        // Reset the view properties and view state
         tv.resetViewProperties();
-
-        // Reset the focused view state
         tv.setFocusedState(false, false /* animated */, false /* requestViewFocus */);
-
-        // Reset the clip state of the task view
         tv.setClipViewInStack(false);
+        if (mScreenPinningEnabled) {
+            tv.hideActionButton();
+        }
     }
 
     @Override
@@ -1362,6 +1364,11 @@
         if (mFocusedTask == task) {
             tv.setFocusedState(true, false /* animated */, false /* requestViewFocus */);
         }
+
+        // Restore the action button visibility if it is the front most task view
+        if (mScreenPinningEnabled && tv.getTask() == mStack.getStackFrontMostTask()) {
+            tv.showActionButton(false /* fadeIn */, 0 /* fadeInDuration */);
+        }
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
index a3e8b2d..14909c5 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
@@ -138,6 +138,7 @@
             setBackground(new FakeShadowDrawable(res, config));
         }
         setOutlineProvider(mViewBounds);
+        setOnLongClickListener(this);
     }
 
     /** Set callback */
@@ -175,8 +176,10 @@
             public void getOutline(View view, Outline outline) {
                 // Set the outline to match the FAB background
                 outline.setOval(0, 0, mActionButtonView.getWidth(), mActionButtonView.getHeight());
+                outline.setAlpha(0.35f);
             }
         });
+        mActionButtonView.setOnClickListener(this);
         mActionButtonTranslationZ = mActionButtonView.getTranslationZ();
     }
 
@@ -293,8 +296,8 @@
 
     /** Prepares this task view for the enter-recents animations.  This is called earlier in the
      * first layout because the actual animation into recents may take a long time. */
-    void prepareEnterRecentsAnimation(boolean isTaskViewLaunchTargetTask, boolean hideTask,
-            boolean occludesLaunchTarget, int offscreenY) {
+    void prepareEnterRecentsAnimation(boolean hideTask, boolean occludesLaunchTarget,
+            int offscreenY) {
         RecentsConfiguration config = Recents.getConfiguration();
         RecentsActivityLaunchState launchState = config.getLaunchState();
         int initialDim = getDim();
@@ -303,7 +306,7 @@
         } else if (launchState.launchedHasConfigurationChanged) {
             // Just load the views as-is
         } else if (launchState.launchedFromAppWithThumbnail) {
-            if (isTaskViewLaunchTargetTask) {
+            if (mTask.isLaunchTarget) {
                 // Set the dim to 0 so we can animate it in
                 initialDim = 0;
                 // Hide the action button
@@ -342,11 +345,15 @@
         if (launchState.launchedFromAppWithThumbnail) {
             if (mTask.isLaunchTarget) {
                 ctx.postAnimationTrigger.increment();
-                // Immediately start the dim animation
+                // Start the dim animation
                 animateDimToProgress(taskViewEnterFromAppDuration,
                         ctx.postAnimationTrigger.decrementOnAnimationEnd());
-                // Animate the action button in
-                fadeInActionButton(taskViewEnterFromAppDuration);
+
+                // Start the action button animation
+                if (ctx.isScreenPinningEnabled) {
+                    showActionButton(true /* fadeIn */,
+                            taskViewEnterFromAppDuration /* fadeInDuration */);
+                }
             } else {
                 // Animate the task up if it was occluding the launch target
                 if (ctx.currentTaskOccludesLaunchTarget) {
@@ -413,17 +420,6 @@
         animate().cancel();
     }
 
-    public void fadeInActionButton(int duration) {
-        // Hide the action button
-        mActionButtonView.setAlpha(0f);
-
-        // Animate the action button in
-        mActionButtonView.animate().alpha(1f)
-                .setDuration(duration)
-                .setInterpolator(PhoneStatusBar.ALPHA_IN)
-                .start();
-    }
-
     /** Animates this task view as it leaves recents by pressing home. */
     void startExitToHomeAnimation(ViewAnimation.TaskViewExitContext ctx) {
         int taskViewExitToHomeDuration = getResources().getInteger(
@@ -468,8 +464,9 @@
                     .alpha(0f)
                     .setStartDelay(0)
                     .setDuration(taskViewExitToAppDuration)
-                    .setInterpolator(mFastOutLinearInInterpolator)
+                    .setInterpolator(PhoneStatusBar.ALPHA_OUT)
                     .withEndAction(postAnimRunnable)
+                    .withLayer()
                     .start();
         } else {
             // Hide the dismiss button
@@ -644,8 +641,6 @@
         setDim(getDimFromTaskProgress());
     }
 
-    /**** View focus state ****/
-
     /**
      * Explicitly sets the focused state of this task.
      */
@@ -672,19 +667,42 @@
         }
     }
 
+    /**
+     * Shows the action button.
+     * @param fadeIn whether or not to animate the action button in.
+     * @param fadeInDuration the duration of the action button animation, only used if
+     *                       {@param fadeIn} is true.
+     */
+    public void showActionButton(boolean fadeIn, int fadeInDuration) {
+        mActionButtonView.setVisibility(View.VISIBLE);
+
+        if (fadeIn) {
+            if (mActionButtonView.getAlpha() < 1f) {
+                mActionButtonView.setAlpha(0f);
+                mActionButtonView.animate().alpha(1f)
+                        .setDuration(fadeInDuration)
+                        .setInterpolator(PhoneStatusBar.ALPHA_IN)
+                        .withLayer()
+                        .start();
+            }
+        } else {
+            mActionButtonView.setAlpha(1f);
+        }
+    }
+
+    /**
+     * Immediately hides the action button.
+     */
+    public void hideActionButton() {
+        mActionButtonView.setVisibility(View.INVISIBLE);
+    }
+
     /**** TaskCallbacks Implementation ****/
 
     /** Binds this task view to the task */
     public void onTaskBound(Task t) {
         mTask = t;
         mTask.addCallback(this);
-
-        // Hide the action button if lock to app is disabled for this view
-        int lockButtonVisibility = (!t.lockToTaskEnabled || !t.lockToThisTask) ? GONE : VISIBLE;
-        if (mActionButtonView.getVisibility() != lockButtonVisibility) {
-            mActionButtonView.setVisibility(lockButtonVisibility);
-            requestLayout();
-        }
     }
 
     @Override
@@ -693,10 +711,6 @@
             // Bind each of the views to the new task data
             mThumbnailView.rebindToTask(mTask);
             mHeaderView.rebindToTask(mTask);
-
-            // Rebind any listeners
-            mActionButtonView.setOnClickListener(this);
-            setOnLongClickListener(this);
         }
         mTaskDataLoaded = true;
     }
@@ -708,8 +722,6 @@
             mTask.removeCallback(this);
             mThumbnailView.unbindFromTask();
             mHeaderView.unbindFromTask();
-            // Unbind any listeners
-            mActionButtonView.setOnClickListener(null);
         }
         mTaskDataLoaded = false;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/ViewAnimation.java b/packages/SystemUI/src/com/android/systemui/recents/views/ViewAnimation.java
index e1d80fd..eaef51c 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/ViewAnimation.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/ViewAnimation.java
@@ -33,6 +33,8 @@
 
         // These following properties are updated for each task view we start the enter animation on
 
+        // Whether or not screen pinning is enabled
+        boolean isScreenPinningEnabled;
         // Whether or not the current task occludes the launch target
         boolean currentTaskOccludesLaunchTarget;
         // The task rect for the current stack
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java
index e6d837a..90a688f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java
@@ -18,30 +18,25 @@
 
 import android.app.ActivityManager;
 import android.app.PendingIntent;
-import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
-import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.res.Resources;
-import android.os.Binder;
 import android.os.HandlerThread;
 import android.os.Looper;
 import android.os.Process;
-import android.os.RemoteException;
 import android.provider.Settings;
-import android.service.quicksettings.IQSService;
-import android.service.quicksettings.Tile;
 import android.text.TextUtils;
 import android.util.Log;
 import com.android.systemui.R;
 import com.android.systemui.qs.QSTile;
+import com.android.systemui.qs.external.CustomTile;
+import com.android.systemui.qs.external.TileServices;
 import com.android.systemui.qs.tiles.AirplaneModeTile;
 import com.android.systemui.qs.tiles.BatteryTile;
 import com.android.systemui.qs.tiles.BluetoothTile;
 import com.android.systemui.qs.tiles.CastTile;
 import com.android.systemui.qs.tiles.CellularTile;
 import com.android.systemui.qs.tiles.ColorInversionTile;
-import com.android.systemui.qs.tiles.CustomTile;
 import com.android.systemui.qs.tiles.DndTile;
 import com.android.systemui.qs.tiles.FlashlightTile;
 import com.android.systemui.qs.tiles.HotspotTile;
@@ -76,7 +71,7 @@
 import java.util.Map;
 
 /** Platform implementation of the quick settings tile host **/
-public final class QSTileHost extends IQSService.Stub implements QSTile.Host, Tunable {
+public final class QSTileHost implements QSTile.Host, Tunable {
     private static final String TAG = "QSTileHost";
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
@@ -100,6 +95,7 @@
     private final KeyguardMonitor mKeyguard;
     private final SecurityController mSecurity;
     private final BatteryController mBattery;
+    private final TileServices mServices;
 
     private final List<Callback> mCallbacks = new ArrayList<>();
 
@@ -131,6 +127,8 @@
         ht.start();
         mLooper = ht.getLooper();
 
+        mServices = new TileServices(this, mLooper);
+
         TunerService.get(mContext).addTunable(this, TILES_SETTING);
     }
 
@@ -256,6 +254,10 @@
         return mSecurity;
     }
 
+    public TileServices getTileServices() {
+        return mServices;
+    }
+
     @Override
     public void onTuningChanged(String key, String newValue) {
         if (!TILES_SETTING.equals(key)) {
@@ -306,50 +308,6 @@
                 TextUtils.join(",", specs), ActivityManager.getCurrentUser());
     }
 
-    @Override
-    public void updateQsTile(Tile tile) throws RemoteException {
-        verifyCaller(tile.getComponentName().getPackageName());
-        CustomTile customTile = getTileForComponent(tile.getComponentName());
-        if (customTile != null) {
-            customTile.updateState(tile);
-            customTile.refreshState();
-        }
-    }
-
-    @Override
-    public void onShowDialog(Tile tile) throws RemoteException {
-        verifyCaller(tile.getComponentName().getPackageName());
-        CustomTile customTile = getTileForComponent(tile.getComponentName());
-        if (customTile != null) {
-            customTile.onDialogShown();
-            collapsePanels();
-        }
-    }
-
-    private void verifyCaller(String packageName) {
-        try {
-            int uid = mContext.getPackageManager().getPackageUid(packageName,
-                    Binder.getCallingUserHandle().getIdentifier());
-            if (Binder.getCallingUid() != uid) {
-                throw new SecurityException("Component outside caller's uid");
-            }
-        } catch (NameNotFoundException e) {
-            throw new SecurityException(e);
-        }
-    }
-
-    private CustomTile getTileForComponent(ComponentName component) {
-        // TODO: Build map for easier lookup.
-        for (QSTile<?> qsTile : mTiles.values()) {
-            if (qsTile instanceof CustomTile) {
-                if (((CustomTile) qsTile).getComponent().equals(component)) {
-                    return (CustomTile) qsTile;
-                }
-            }
-        }
-        return null;
-    }
-
     public QSTile<?> createTile(String tileSpec) {
         if (tileSpec.equals("wifi")) return WifiTile.isSupported(this)
                 ? new WifiTile(this) : null;
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java
index 1e3b0f1..9081af1 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java
@@ -153,8 +153,11 @@
     private static TunerService sInstance;
 
     public static TunerService get(Context context) {
-        SystemUIApplication sysUi = (SystemUIApplication) context.getApplicationContext();
-        TunerService service = sysUi.getComponent(TunerService.class);
+        TunerService service = null;
+        if (context.getApplicationContext() instanceof SystemUIApplication) {
+            SystemUIApplication sysUi = (SystemUIApplication) context.getApplicationContext();
+            service = sysUi.getComponent(TunerService.class);
+        }
         if (service == null) {
             // Can't get it as a component, must in the tuner, lets just create one for now.
             return getStaticService(context);
diff --git a/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java b/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java
index cd47ac1..38d8de0 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java
@@ -510,7 +510,7 @@
         GregorianCalendar weekRange = new GregorianCalendar();
         final long now = weekRange.getTimeInMillis();
         setToMidnight(weekRange);
-        weekRange.roll(Calendar.DATE, 6);
+        weekRange.add(Calendar.DATE, 6);
         final long nextAlarmMs = mController.getNextAlarm();
         if (nextAlarmMs > 0) {
             GregorianCalendar nextAlarm = new GregorianCalendar();
diff --git a/packages/SystemUI/tests/AndroidManifest.xml b/packages/SystemUI/tests/AndroidManifest.xml
index c21af24..2825601 100644
--- a/packages/SystemUI/tests/AndroidManifest.xml
+++ b/packages/SystemUI/tests/AndroidManifest.xml
@@ -19,10 +19,16 @@
 
     <uses-permission android:name="android.permission.INJECT_EVENTS" />
     <uses-permission android:name="android.permission.READ_PHONE_STATE" />
+    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
+    <uses-permission android:name="android.permission.MANAGE_USERS" />
 
     <application>
         <uses-library android:name="android.test.runner" />
         <activity android:name="com.android.systemui.screenshot.ScreenshotStubActivity" />
+
+        <service
+            android:name="com.android.systemui.qs.external.TileLifecycleManagerTests$FakeTileService"
+            android:process=":killable" />
     </application>
 
     <instrumentation android:name="android.test.InstrumentationTestRunner"
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTests.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTests.java
new file mode 100644
index 0000000..0594211
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTests.java
@@ -0,0 +1,298 @@
+/*
+ * 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.external;
+
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Process;
+import android.os.UserHandle;
+import android.service.quicksettings.TileService;
+import android.test.AndroidTestCase;
+import android.util.ArraySet;
+import android.util.Log;
+
+public class TileLifecycleManagerTests extends AndroidTestCase {
+    public static final String TILE_UPDATE_BROADCAST = "com.android.systemui.tests.TILE_UPDATE";
+    public static final String EXTRA_CALLBACK = "callback";
+
+    private HandlerThread mThread;
+    private Handler mHandler;
+    private TileLifecycleManager mStateManager;
+    private final Object mBroadcastLock = new Object();
+    private final ArraySet<String> mCallbacks = new ArraySet<>();
+    private boolean mBound;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mThread = new HandlerThread("TestThread");
+        mThread.start();
+        mHandler = new Handler(mThread.getLooper());
+        mStateManager = new TileLifecycleManager(mHandler, getContext(),
+                new Intent(mContext, FakeTileService.class), new UserHandle(UserHandle.myUserId()));
+        mCallbacks.clear();
+        getContext().registerReceiver(mReceiver, new IntentFilter(TILE_UPDATE_BROADCAST));
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+        if (mBound) {
+            unbindService();
+        }
+        mThread.quit();
+        getContext().unregisterReceiver(mReceiver);
+    }
+
+    public void testSync() {
+        syncWithHandler();
+    }
+
+    public void testBind() {
+        bindService();
+        waitForCallback("onCreate");
+    }
+
+    public void testUnbind() {
+        bindService();
+        waitForCallback("onCreate");
+        unbindService();
+        waitForCallback("onDestroy");
+    }
+
+    public void testTileServiceCallbacks() {
+        bindService();
+        waitForCallback("onCreate");
+
+        mStateManager.onTileAdded();
+        waitForCallback("onTileAdded");
+        mStateManager.onStartListening();
+        waitForCallback("onStartListening");
+        mStateManager.onClick(null);
+        waitForCallback("onClick");
+        mStateManager.onStopListening();
+        waitForCallback("onStopListening");
+        mStateManager.onTileRemoved();
+        waitForCallback("onTileRemoved");
+
+        unbindService();
+    }
+
+    public void testAddedBeforeBind() {
+        mStateManager.onTileAdded();
+
+        bindService();
+        waitForCallback("onCreate");
+        waitForCallback("onTileAdded");
+    }
+
+    public void testListeningBeforeBind() {
+        mStateManager.onTileAdded();
+        mStateManager.onStartListening();
+
+        bindService();
+        waitForCallback("onCreate");
+        waitForCallback("onTileAdded");
+        waitForCallback("onStartListening");
+    }
+
+    public void testClickBeforeBind() {
+        mStateManager.onTileAdded();
+        mStateManager.onStartListening();
+        mStateManager.onClick(null);
+
+        bindService();
+        waitForCallback("onCreate");
+        waitForCallback("onTileAdded");
+        waitForCallback("onStartListening");
+        waitForCallback("onClick");
+    }
+
+    public void testListeningNotListeningBeforeBind() {
+        mStateManager.onTileAdded();
+        mStateManager.onStartListening();
+        mStateManager.onStopListening();
+
+        bindService();
+        waitForCallback("onCreate");
+        unbindService();
+        waitForCallback("onDestroy");
+        assertFalse(mCallbacks.contains("onStartListening"));
+    }
+
+    public void testNoClickOfNotListeningAnymore() {
+        mStateManager.onTileAdded();
+        mStateManager.onStartListening();
+        mStateManager.onClick(null);
+        mStateManager.onStopListening();
+
+        bindService();
+        waitForCallback("onCreate");
+        unbindService();
+        waitForCallback("onDestroy");
+        assertFalse(mCallbacks.contains("onClick"));
+    }
+
+    public void testComponentEnabling() {
+        mStateManager.onTileAdded();
+        mStateManager.onStartListening();
+
+        PackageManager pm = getContext().getPackageManager();
+        pm.setComponentEnabledSetting(new ComponentName(getContext(), FakeTileService.class),
+                PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP);
+
+        bindService();
+        assertTrue(mStateManager.mReceiverRegistered);
+
+        pm.setComponentEnabledSetting(new ComponentName(getContext(), FakeTileService.class),
+                PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP);
+        waitForCallback("onCreate");
+    }
+
+    public void testKillProcess() {
+        mStateManager.onStartListening();
+        bindService();
+        waitForCallback("onCreate");
+        waitForCallback("onStartListening");
+
+        getContext().sendBroadcast(new Intent(FakeTileService.ACTION_KILL));
+
+        waitForCallback("onCreate");
+        waitForCallback("onStartListening");
+    }
+
+    private void bindService() {
+        mBound = true;
+        mStateManager.setBindService(true);
+    }
+
+    private void unbindService() {
+        mBound = false;
+        mStateManager.setBindService(false);
+    }
+
+    private void waitForCallback(String callback) {
+        for (int i = 0; i < 25; i++) {
+            if (mCallbacks.contains(callback)) {
+                mCallbacks.remove(callback);
+                return;
+            }
+            synchronized (mBroadcastLock) {
+                try {
+                    mBroadcastLock.wait(500);
+                } catch (InterruptedException e) {
+                }
+            }
+        }
+        if (mCallbacks.contains(callback)) {
+            mCallbacks.remove(callback);
+            return;
+        }
+        fail("Didn't receive callback: " + callback);
+    }
+
+    private void syncWithHandler() {
+        final Object lock = new Object();
+        synchronized (lock) {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    synchronized (lock) {
+                        lock.notify();
+                    }
+                }
+            });
+            try {
+                lock.wait(5000);
+            } catch (InterruptedException e) {
+            }
+        }
+    }
+
+    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            mCallbacks.add(intent.getStringExtra(EXTRA_CALLBACK));
+            synchronized (mBroadcastLock) {
+                mBroadcastLock.notify();
+            }
+        }
+    };
+
+    public static class FakeTileService extends TileService {
+        public static final String ACTION_KILL = "com.android.systemui.test.KILL";
+
+        @Override
+        public void onCreate() {
+            super.onCreate();
+            registerReceiver(mReceiver, new IntentFilter(ACTION_KILL));
+            sendCallback("onCreate");
+        }
+
+        @Override
+        public void onDestroy() {
+            super.onDestroy();
+            unregisterReceiver(mReceiver);
+            sendCallback("onDestroy");
+        }
+
+        @Override
+        public void onTileAdded() {
+            sendCallback("onTileAdded");
+        }
+
+        @Override
+        public void onTileRemoved() {
+            sendCallback("onTileRemoved");
+        }
+
+        @Override
+        public void onStartListening() {
+            sendCallback("onStartListening");
+        }
+
+        @Override
+        public void onStopListening() {
+            sendCallback("onStopListening");
+        }
+
+        @Override
+        public void onClick() {
+            sendCallback("onClick");
+        }
+
+        private void sendCallback(String callback) {
+            Log.d("TileLifecycleManager", "Relaying: " + callback);
+            sendBroadcast(new Intent(TILE_UPDATE_BROADCAST)
+                    .putExtra(EXTRA_CALLBACK, callback));
+        }
+
+        private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                if (ACTION_KILL.equals(intent.getAction())) {
+                    Process.killProcess(Process.myPid());
+                }
+            }
+        };
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTests.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTests.java
new file mode 100644
index 0000000..c4f686e
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTests.java
@@ -0,0 +1,93 @@
+/*
+ * 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.external;
+
+import android.os.Handler;
+import android.os.HandlerThread;
+import com.android.systemui.SysuiTestCase;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mockito;
+
+public class TileServiceManagerTests extends SysuiTestCase {
+
+    private TileServices mTileServices;
+    private TileLifecycleManager mTileLifecycle;
+    private HandlerThread mThread;
+    private Handler mHandler;
+    private TileServiceManager mTileServiceManager;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mThread = new HandlerThread("TestThread");
+        mThread.start();
+        mHandler = new Handler(mThread.getLooper());
+        mTileServices = Mockito.mock(TileServices.class);
+        mTileLifecycle = Mockito.mock(TileLifecycleManager.class);
+        mTileServiceManager = new TileServiceManager(mTileServices, mHandler, mTileLifecycle);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+        mThread.quit();
+    }
+
+    public void testSetBindRequested() {
+        // Request binding.
+        mTileServiceManager.setBindRequested(true);
+        mTileServiceManager.setLastUpdate(0);
+        mTileServiceManager.calculateBindPriority(5);
+        Mockito.verify(mTileServices, Mockito.times(2)).recalculateBindAllowance();
+        assertEquals(5, mTileServiceManager.getBindPriority());
+
+        // Verify same state doesn't trigger recalculating for no reason.
+        mTileServiceManager.setBindRequested(true);
+        Mockito.verify(mTileServices, Mockito.times(2)).recalculateBindAllowance();
+
+        mTileServiceManager.setBindRequested(false);
+        mTileServiceManager.calculateBindPriority(5);
+        Mockito.verify(mTileServices, Mockito.times(3)).recalculateBindAllowance();
+        assertEquals(Integer.MIN_VALUE, mTileServiceManager.getBindPriority());
+    }
+
+    public void testPendingClickPriority() {
+        Mockito.when(mTileLifecycle.hasPendingClick()).thenReturn(true);
+        mTileServiceManager.calculateBindPriority(0);
+        assertEquals(Integer.MAX_VALUE, mTileServiceManager.getBindPriority());
+    }
+
+    public void testBind() {
+        // Trigger binding requested and allowed.
+        mTileServiceManager.setBindRequested(true);
+        mTileServiceManager.setBindAllowed(true);
+
+        ArgumentCaptor<Boolean> captor = ArgumentCaptor.forClass(Boolean.class);
+        Mockito.verify(mTileLifecycle, Mockito.times(1)).setBindService(captor.capture());
+        assertTrue((boolean) captor.getValue());
+
+        mTileServiceManager.setBindRequested(false);
+        mTileServiceManager.calculateBindPriority(0);
+        // Priority shouldn't disappear after the request goes away if we just bound, instead
+        // it sticks around to avoid thrashing a bunch of processes.
+        assertEquals(Integer.MAX_VALUE - 1, mTileServiceManager.getBindPriority());
+
+        mTileServiceManager.setBindAllowed(false);
+        captor = ArgumentCaptor.forClass(Boolean.class);
+        Mockito.verify(mTileLifecycle, Mockito.times(2)).setBindService(captor.capture());
+        assertFalse((boolean) captor.getValue());
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTests.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTests.java
new file mode 100644
index 0000000..7a3ce87
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTests.java
@@ -0,0 +1,108 @@
+/*
+ * 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.external;
+
+import android.content.ComponentName;
+import android.os.Looper;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.statusbar.phone.QSTileHost;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mockito;
+
+import java.util.ArrayList;
+
+public class TileServicesTests extends SysuiTestCase {
+    private static int NUM_FAKES = TileServices.DEFAULT_MAX_BOUND * 2;
+
+    private TileServices mTileService;
+    private ArrayList<TileServiceManager> mManagers;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mManagers = new ArrayList<>();
+        QSTileHost host = new QSTileHost(mContext, null, null, null, null, null, null, null, null,
+                null, null, null, null, null, null);
+        mTileService = new TestTileServices(host, Looper.myLooper());
+    }
+
+    public void testRecalculateBindAllowance() {
+        // Add some fake tiles.
+        for (int i = 0; i < NUM_FAKES; i++) {
+            mTileService.getTileWrapper(Mockito.mock(CustomTile.class));
+        }
+        assertEquals(NUM_FAKES, mManagers.size());
+
+        for (int i = 0; i < NUM_FAKES; i++) {
+            Mockito.when(mManagers.get(i).getBindPriority()).thenReturn(i);
+        }
+        mTileService.recalculateBindAllowance();
+        for (int i = 0; i < NUM_FAKES; i++) {
+            Mockito.verify(mManagers.get(i), Mockito.times(1)).calculateBindPriority(
+                    Mockito.anyLong());
+            ArgumentCaptor<Boolean> captor = ArgumentCaptor.forClass(Boolean.class);
+            Mockito.verify(mManagers.get(i), Mockito.times(1)).setBindAllowed(captor.capture());
+
+            assertEquals("" + i + "th service", i >= (NUM_FAKES - TileServices.DEFAULT_MAX_BOUND),
+                    (boolean) captor.getValue());
+        }
+    }
+
+    public void testSetMemoryPressure() {
+        testRecalculateBindAllowance();
+        mTileService.setMemoryPressure(true);
+
+        for (int i = 0; i < NUM_FAKES; i++) {
+            ArgumentCaptor<Boolean> captor = ArgumentCaptor.forClass(Boolean.class);
+            Mockito.verify(mManagers.get(i), Mockito.times(2)).setBindAllowed(captor.capture());
+
+            assertEquals("" + i + "th service", i >= (NUM_FAKES - TileServices.REDUCED_MAX_BOUND),
+                    (boolean) captor.getValue());
+        }
+    }
+
+    public void testCalcFew() {
+        for (int i = 0; i < TileServices.DEFAULT_MAX_BOUND - 1; i++) {
+            mTileService.getTileWrapper(Mockito.mock(CustomTile.class));
+        }
+        mTileService.recalculateBindAllowance();
+
+        for (int i = 0; i < TileServices.DEFAULT_MAX_BOUND - 1; i++) {
+            // Shouldn't get bind prioirities calculated when there are less than the max services.
+            Mockito.verify(mManagers.get(i), Mockito.never()).calculateBindPriority(
+                    Mockito.anyLong());
+
+            // All should be bound since there are less than the max services.
+            ArgumentCaptor<Boolean> captor = ArgumentCaptor.forClass(Boolean.class);
+            Mockito.verify(mManagers.get(i), Mockito.times(1)).setBindAllowed(captor.capture());
+
+            assertTrue(captor.getValue());
+        }
+    }
+
+    private class TestTileServices extends TileServices {
+        public TestTileServices(QSTileHost host, Looper looper) {
+            super(host, looper);
+        }
+
+        @Override
+        protected TileServiceManager onCreateTileService(ComponentName component) {
+            TileServiceManager manager = Mockito.mock(TileServiceManager.class);
+            mManagers.add(manager);
+            return manager;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 3e8dc4e..8e64eeb 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -2743,9 +2743,9 @@
         return mAppBindArgs;
     }
 
-    final void setFocusedActivityLocked(ActivityRecord r, String reason) {
+    boolean setFocusedActivityLocked(ActivityRecord r, String reason) {
         if (r == null || mFocusedActivity == r) {
-            return;
+            return false;
         }
 
         if (DEBUG_FOCUS) Slog.d(TAG_FOCUS, "setFocusedActivityLocked: r=" + r);
@@ -2783,7 +2783,7 @@
                 finishVoiceTask(last.task.voiceSession);
             }
         }
-        if (mStackSupervisor.setFocusedStack(r, reason + " setFocusedActivity")) {
+        if (mStackSupervisor.moveActivityStackToFront(r, reason + " setFocusedActivity")) {
             mWindowManager.setFocusedApp(r.appToken, true);
         }
         applyUpdateLockStateLocked(r);
@@ -2799,23 +2799,35 @@
                 mFocusedActivity == null ? -1 : mFocusedActivity.userId,
                 mFocusedActivity == null ? "NULL" : mFocusedActivity.shortComponentName,
                 reason);
+        return true;
     }
 
-    final void clearFocusedActivity(ActivityRecord r) {
-        if (mFocusedActivity == r) {
-            ActivityStack stack = mStackSupervisor.getFocusedStack();
-            if (stack != null) {
-                ActivityRecord top = stack.topActivity();
-                if (top != null && top.userId != mLastFocusedUserId) {
-                    mHandler.removeMessages(FOREGROUND_PROFILE_CHANGED_MSG);
-                    mHandler.sendMessage(mHandler.obtainMessage(FOREGROUND_PROFILE_CHANGED_MSG,
-                                    top.userId, 0));
-                    mLastFocusedUserId = top.userId;
-                }
-            }
-            mFocusedActivity = null;
-            EventLogTags.writeAmFocusedActivity(-1, "NULL", "clearFocusedActivity");
+    final void resetFocusedActivityIfNeededLocked(ActivityRecord goingAway) {
+        if (mFocusedActivity != goingAway) {
+            return;
         }
+
+        final ActivityStack focusedStack = mStackSupervisor.getFocusedStack();
+        if (focusedStack != null) {
+            final ActivityRecord top = focusedStack.topActivity();
+            if (top != null && top.userId != mLastFocusedUserId) {
+                mHandler.removeMessages(FOREGROUND_PROFILE_CHANGED_MSG);
+                mHandler.sendMessage(
+                        mHandler.obtainMessage(FOREGROUND_PROFILE_CHANGED_MSG, top.userId, 0));
+                mLastFocusedUserId = top.userId;
+            }
+        }
+
+        // Try to move focus to another activity if possible.
+        if (setFocusedActivityLocked(
+                focusedStack.topRunningActivityLocked(), "resetFocusedActivityIfNeeded")) {
+            return;
+        }
+
+        if (DEBUG_FOCUS) Slog.d(TAG_FOCUS, "resetFocusedActivityIfNeeded: Setting focus to NULL "
+                + "prev mFocusedActivity=" + mFocusedActivity + " goingAway=" + goingAway);
+        mFocusedActivity = null;
+        EventLogTags.writeAmFocusedActivity(-1, "NULL", "resetFocusedActivityIfNeeded");
     }
 
     @Override
@@ -2827,7 +2839,7 @@
                 ActivityRecord r = stack.topRunningActivityLocked();
                 if (r != null) {
                     setFocusedActivityLocked(r, "setFocusedStack");
-                    mStackSupervisor.resumeTopActivitiesLocked(stack, null, null);
+                    mStackSupervisor.resumeFocusedStackTopActivityLocked();
                 }
             }
         }
@@ -2844,7 +2856,7 @@
                     ActivityRecord r = task.topRunningActivityLocked();
                     if (r != null) {
                         setFocusedActivityLocked(r, "setFocusedTask");
-                        mStackSupervisor.resumeTopActivitiesLocked(task.stack, null, null);
+                        mStackSupervisor.resumeFocusedStackTopActivityLocked();
                     }
                 }
             }
@@ -4473,7 +4485,7 @@
             if (config != null) {
                 r.frozenBeforeDestroy = true;
                 if (!updateConfigurationLocked(config, r, false)) {
-                    mStackSupervisor.resumeTopActivitiesLocked();
+                    mStackSupervisor.resumeFocusedStackTopActivityLocked();
                 }
             }
             Binder.restoreCallingIdentity(origId);
@@ -4811,12 +4823,11 @@
             finishInstrumentationLocked(app, Activity.RESULT_CANCELED, info);
         }
 
-        if (!restarting && hasVisibleActivities && !mStackSupervisor.resumeTopActivitiesLocked()) {
-            // If there was nothing to resume, and we are not already
-            // restarting this process, but there is a visible activity that
-            // is hosted by the process...  then make sure all visible
-            // activities are running, taking care of restarting this
-            // process.
+        if (!restarting && hasVisibleActivities
+                && !mStackSupervisor.resumeFocusedStackTopActivityLocked()) {
+            // If there was nothing to resume, and we are not already restarting this process, but
+            // there is a visible activity that is hosted by the process...  then make sure all
+            // visible activities are running, taking care of restarting this process.
             mStackSupervisor.ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS);
         }
     }
@@ -5873,7 +5884,7 @@
         // Clean-up disabled activities.
         if (mStackSupervisor.finishDisabledPackageActivitiesLocked(
                 packageName, disabledClasses, true, false, userId) && mBooted) {
-            mStackSupervisor.resumeTopActivitiesLocked();
+            mStackSupervisor.resumeFocusedStackTopActivityLocked();
             mStackSupervisor.scheduleIdleLocked();
         }
 
@@ -6061,7 +6072,7 @@
                 }
             }
             if (mBooted) {
-                mStackSupervisor.resumeTopActivitiesLocked();
+                mStackSupervisor.resumeFocusedStackTopActivityLocked();
                 mStackSupervisor.scheduleIdleLocked();
             }
         }
@@ -8994,7 +9005,7 @@
                 task.mResizeable = resizeable;
                 mWindowManager.setTaskResizeable(taskId, resizeable);
                 mStackSupervisor.ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS);
-                mStackSupervisor.resumeTopActivitiesLocked();
+                mStackSupervisor.resumeFocusedStackTopActivityLocked();
             }
         }
     }
@@ -12603,7 +12614,7 @@
             } finally {
                 Binder.restoreCallingIdentity(ident);
             }
-            mStackSupervisor.resumeTopActivitiesLocked();
+            mStackSupervisor.resumeFocusedStackTopActivityLocked();
             mUserController.sendUserSwitchBroadcastsLocked(-1, currentUserId);
         }
     }
@@ -12714,10 +12725,10 @@
                 // annoy the user repeatedly.  Unless it is persistent, since those
                 // processes run critical code.
                 removeProcessLocked(app, false, false, "crash");
-                mStackSupervisor.resumeTopActivitiesLocked();
+                mStackSupervisor.resumeFocusedStackTopActivityLocked();
                 return false;
             }
-            mStackSupervisor.resumeTopActivitiesLocked();
+            mStackSupervisor.resumeFocusedStackTopActivityLocked();
         } else {
             mStackSupervisor.finishTopRunningActivityLocked(app, reason);
         }
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index 1492e23..b9ff32c 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -572,6 +572,10 @@
         }
     }
 
+    boolean isFocusable() {
+        return StackId.canReceiveKeys(mStackId);
+    }
+
     final boolean isAttached() {
         return mStacks != null;
     }
@@ -917,7 +921,7 @@
         if (prev == null) {
             if (!resuming) {
                 Slog.wtf(TAG, "Trying to pause when nothing is resumed");
-                mStackSupervisor.resumeTopActivitiesLocked();
+                mStackSupervisor.resumeFocusedStackTopActivityLocked();
             }
             return false;
         }
@@ -1008,7 +1012,7 @@
             // pause, so just treat it as being paused now.
             if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Activity not running, resuming next.");
             if (!resuming) {
-                mStackSupervisor.getFocusedStack().resumeTopActivityLocked(null);
+                mStackSupervisor.resumeFocusedStackTopActivityLocked();
             }
             return false;
         }
@@ -1075,7 +1079,7 @@
             } else {
                 if (r.configDestroy) {
                     destroyActivityLocked(r, true, "stop-config");
-                    mStackSupervisor.resumeTopActivitiesLocked();
+                    mStackSupervisor.resumeFocusedStackTopActivityLocked();
                 } else {
                     mStackSupervisor.updatePreviousProcessLocked(r);
                 }
@@ -1131,17 +1135,16 @@
         if (resumeNext) {
             final ActivityStack topStack = mStackSupervisor.getFocusedStack();
             if (!mService.isSleepingOrShuttingDown()) {
-                mStackSupervisor.resumeTopActivitiesLocked(topStack, prev, null);
+                mStackSupervisor.resumeFocusedStackTopActivityLocked(topStack, prev, null);
             } else {
                 mStackSupervisor.checkReadyForSleepLocked();
                 ActivityRecord top = topStack.topRunningActivityLocked();
                 if (top == null || (prev != null && top != prev)) {
-                    // If there are no more activities available to run,
-                    // do resume anyway to start something.  Also if the top
-                    // activity on the stack is not the just paused activity,
-                    // we need to go ahead and resume it to ensure we complete
-                    // an in-flight app switch.
-                    mStackSupervisor.resumeTopActivitiesLocked(topStack, null, null);
+                    // If there are no more activities available to run, do resume anyway to start
+                    // something. Also if the top activity on the stack is not the just paused
+                    // activity, we need to go ahead and resume it to ensure we complete an
+                    // in-flight app switch.
+                    mStackSupervisor.resumeFocusedStackTopActivityLocked();
                 }
             }
         }
@@ -1478,7 +1481,7 @@
         boolean aboveTop = top != null;
         final boolean stackInvisible = !isStackVisibleLocked();
         boolean behindFullscreenActivity = stackInvisible;
-        boolean noStackActivityResumed = (isInStackLocked(starting) == null);
+        boolean resumeNextActivity = isFocusable() && (isInStackLocked(starting) == null);
 
         for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
             final TaskRecord task = mTaskHistory.get(taskNdx);
@@ -1509,18 +1512,18 @@
 
                     if (r.app == null || r.app.thread == null) {
                         if (makeVisibleAndRestartIfNeeded(starting, configChanges, isTop,
-                                noStackActivityResumed, r)) {
+                                resumeNextActivity, r)) {
                             if (activityNdx >= activities.size()) {
                                 // Record may be removed if its process needs to restart.
                                 activityNdx = activities.size() - 1;
                             } else {
-                                noStackActivityResumed = false;
+                                resumeNextActivity = false;
                             }
                         }
                     } else if (r.visible) {
                         // If this activity is already visible, then there is nothing to do here.
                         if (handleAlreadyVisible(r)) {
-                            noStackActivityResumed = false;
+                            resumeNextActivity = false;
                         }
                     } else {
                         makeVisible(starting, r);
@@ -1561,7 +1564,7 @@
     }
 
     private boolean makeVisibleAndRestartIfNeeded(ActivityRecord starting, int configChanges,
-            boolean isTop, boolean noStackActivityResumed, ActivityRecord r) {
+            boolean isTop, boolean andResume, ActivityRecord r) {
         // We need to make sure the app is running if it's the top, or it is just made visible from
         // invisible. If the app is already visible, it must have died while it was visible. In this
         // case, we'll show the dead window but will not restart the app. Otherwise we could end up
@@ -1578,7 +1581,7 @@
                 setVisible(r, true);
             }
             if (r != starting) {
-                mStackSupervisor.startSpecificActivityLocked(r, noStackActivityResumed, false);
+                mStackSupervisor.startSpecificActivityLocked(r, andResume, false);
                 return true;
             }
         }
@@ -1772,15 +1775,17 @@
      *
      * @param prev The previously resumed activity, for when in the process
      * of pausing; can be null to call from elsewhere.
+     * @param options Activity options.
      *
      * @return Returns true if something is being resumed, or false if
      * nothing happened.
+     *
+     * NOTE: It is not safe to call this method directly as it can cause an activity in a
+     *       non-focused stack to be resumed.
+     *       Use {@link ActivityStackSupervisor#resumeFocusedStackTopActivityLocked} to resume the
+     *       right activity for the current system state.
      */
-    final boolean resumeTopActivityLocked(ActivityRecord prev) {
-        return resumeTopActivityLocked(prev, null);
-    }
-
-    final boolean resumeTopActivityLocked(ActivityRecord prev, ActivityOptions options) {
+    boolean resumeTopActivityUncheckedLocked(ActivityRecord prev, ActivityOptions options) {
         if (mStackSupervisor.inResumeTopActivity) {
             // Don't even start recursing.
             return false;
@@ -1836,7 +1841,7 @@
                 // stack is not covering the entire screen.
                 final ActivityStack stack = getNextVisibleStackLocked();
                 if (adjustFocusToNextVisibleStackLocked(stack, reason)) {
-                    return mStackSupervisor.resumeTopActivitiesLocked(stack, prev, null);
+                    return mStackSupervisor.resumeFocusedStackTopActivityLocked(stack, prev, null);
                 }
             }
             // Let's just start up the Launcher...
@@ -2897,10 +2902,7 @@
             }
         }
 
-        final ActivityRecord top = mStackSupervisor.topRunningActivityLocked();
-        if (top != null) {
-            mService.setFocusedActivityLocked(top, myReason);
-        }
+        mService.setFocusedActivityLocked(mStackSupervisor.topRunningActivityLocked(), myReason);
     }
 
     private boolean adjustFocusToNextVisibleStackLocked(ActivityStack inStack, String reason) {
@@ -2909,12 +2911,7 @@
         if (stack == null) {
             return false;
         }
-        final ActivityRecord top = stack.topRunningActivityLocked();
-        if (top == null) {
-            return false;
-        }
-        mService.setFocusedActivityLocked(top, myReason);
-        return true;
+        return mService.setFocusedActivityLocked(stack.topRunningActivityLocked(), myReason);
     }
 
     final void stopActivityLocked(ActivityRecord r) {
@@ -3224,7 +3221,7 @@
             r.makeFinishingLocked();
             boolean activityRemoved = destroyActivityLocked(r, true, "finish-imm");
             if (activityRemoved) {
-                mStackSupervisor.resumeTopActivitiesLocked();
+                mStackSupervisor.resumeFocusedStackTopActivityLocked();
             }
             if (DEBUG_CONTAINERS) Slog.d(TAG_CONTAINERS,
                     "destroyActivityLocked: finishCurrentActivityLocked r=" + r +
@@ -3237,7 +3234,7 @@
         if (DEBUG_ALL) Slog.v(TAG, "Enqueueing pending finish: " + r);
         mStackSupervisor.mFinishingActivities.add(r);
         r.resumeKeyDispatchingLocked();
-        mStackSupervisor.getFocusedStack().resumeTopActivityLocked(null);
+        mStackSupervisor.resumeFocusedStackTopActivityLocked();
         return r;
     }
 
@@ -3395,7 +3392,7 @@
         if (mPausingActivity == r) {
             mPausingActivity = null;
         }
-        mService.clearFocusedActivity(r);
+        mService.resetFocusedActivityIfNeededLocked(r);
 
         r.configDestroy = false;
         r.frozenBeforeDestroy = false;
@@ -3526,7 +3523,7 @@
             }
         }
         if (activityRemoved) {
-            mStackSupervisor.resumeTopActivitiesLocked();
+            mStackSupervisor.resumeFocusedStackTopActivityLocked();
         }
     }
 
@@ -3700,7 +3697,7 @@
                     removeActivityFromHistoryLocked(r, reason);
                 }
             }
-            mStackSupervisor.resumeTopActivitiesLocked();
+            mStackSupervisor.resumeFocusedStackTopActivityLocked();
         } finally {
             Binder.restoreCallingIdentity(origId);
         }
@@ -3738,7 +3735,7 @@
             setVisibleBehindActivity(null);
             mStackSupervisor.scheduleIdleTimeoutLocked(null);
         }
-        mStackSupervisor.resumeTopActivitiesLocked();
+        mStackSupervisor.resumeFocusedStackTopActivityLocked();
     }
 
     boolean hasVisibleBehindActivity() {
@@ -3953,7 +3950,7 @@
             updateTransitLocked(AppTransition.TRANSIT_TASK_TO_FRONT, options);
         }
 
-        mStackSupervisor.resumeTopActivitiesLocked();
+        mStackSupervisor.resumeFocusedStackTopActivityLocked();
         EventLog.writeEvent(EventLogTags.AM_TASK_TO_FRONT, tr.userId, tr.taskId);
 
         if (VALIDATE_TOKENS) {
@@ -4016,7 +4013,7 @@
             if (fullscreenStack != null && fullscreenStack.hasVisibleBehindActivity()) {
                 final ActivityRecord visibleBehind = fullscreenStack.getVisibleBehindActivity();
                 mService.setFocusedActivityLocked(visibleBehind, "moveTaskToBack");
-                mStackSupervisor.resumeTopActivitiesLocked(fullscreenStack, null, null);
+                mStackSupervisor.resumeFocusedStackTopActivityLocked();
                 return true;
             }
         }
@@ -4071,7 +4068,7 @@
             return mStackSupervisor.resumeHomeStackTask(taskToReturnTo, null, "moveTaskToBack");
         }
 
-        mStackSupervisor.resumeTopActivitiesLocked();
+        mStackSupervisor.resumeFocusedStackTopActivityLocked();
         return true;
     }
 
@@ -4734,10 +4731,7 @@
 
     private void postAddTask(TaskRecord task, ActivityStack prevStack) {
         if (prevStack != null) {
-            if (prevStack != this
-                    && (prevStack.mStackId == PINNED_STACK_ID || mStackId == PINNED_STACK_ID)) {
-                task.reportPictureInPictureModeChange();
-            }
+            task.reportPictureInPictureModeChangeIfNeeded(prevStack);
         } else if (task.voiceSession != null) {
             try {
                 task.voiceSession.taskStarted(task.intent, task.taskId);
@@ -4758,25 +4752,21 @@
         r.taskConfigOverride = task.mOverrideConfig;
     }
 
-    void setFocusAndResumeStateIfNeeded(
-            ActivityRecord r, boolean setFocus, boolean setResume, String reason) {
-        // If the activity had focus before move focus to this stack.
-        if (setFocus) {
-            // If the activity owns the last resumed activity, transfer that together,
-            // so that we don't resume the same activity again in the new stack.
-            // Apps may depend on onResume()/onPause() being called in pairs.
-            if (setResume) {
-                mResumedActivity = r;
-                // Move the stack in which we are placing the activity to the front. We don't use
-                // ActivityManagerService.setFocusedActivityLocked, because if the activity is
-                // already focused, the call will short-circuit and do nothing.
-                moveToFront(reason);
-            } else {
-                // We need to not only move the stack to the front, but also have the activity
-                // focused. This will achieve both goals.
-                mService.setFocusedActivityLocked(r, reason);
-            }
+    void moveToFrontAndResumeStateIfNeeded(
+            ActivityRecord r, boolean moveToFront, boolean setResume, String reason) {
+        if (!moveToFront) {
+            return;
         }
+
+        // If the activity owns the last resumed activity, transfer that together,
+        // so that we don't resume the same activity again in the new stack.
+        // Apps may depend on onResume()/onPause() being called in pairs.
+        if (setResume) {
+            mResumedActivity = r;
+        }
+        // Move the stack in which we are placing the activity to the front. The call will also
+        // make sure the activity focus is set.
+        moveToFront(reason);
     }
 
     /**
@@ -4800,8 +4790,11 @@
         r.setTask(task, null);
         task.addActivityToTop(r);
         setAppTask(r, task);
-        task.reportPictureInPictureModeChange();
-        setFocusAndResumeStateIfNeeded(r, wasFocused, wasResumed, "moveActivityToStack");
+        task.reportPictureInPictureModeChangeIfNeeded(prevStack);
+        moveToFrontAndResumeStateIfNeeded(r, wasFocused, wasResumed, "moveActivityToStack");
+        if (wasResumed) {
+            prevStack.mResumedActivity = null;
+        }
     }
 
     private void setAppTask(ActivityRecord r, TaskRecord task) {
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 32671acf..df1aa52 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -370,7 +370,7 @@
      */
     private LockTaskNotify mLockTaskNotify;
 
-    /** Used to keep resumeTopActivityLocked() from being entered recursively */
+    /** Used to keep resumeTopActivityUncheckedLocked() from being entered recursively */
     boolean inResumeTopActivity;
 
     // temp. rects used during resize calculation so we don't need to create a new object each time.
@@ -537,16 +537,25 @@
         return stack == mHomeStack.mStacks.get((mHomeStack.mStacks.size() - 1));
     }
 
+    /** NOTE: Should only be called from {@link ActivityStack#moveToFront} */
     void setFocusStack(String reason, ActivityStack focusedStack) {
-        mLastFocusedStack = mFocusedStack;
-        mFocusedStack = focusedStack;
+        if (focusedStack != mFocusedStack) {
+            mLastFocusedStack = mFocusedStack;
+            mFocusedStack = focusedStack;
 
-        EventLogTags.writeAmFocusedStack(
-                mCurrentUser, mFocusedStack == null ? -1 : mFocusedStack.getStackId(),
-                mLastFocusedStack == null ? -1 : mLastFocusedStack.getStackId(), reason);
+            EventLogTags.writeAmFocusedStack(
+                    mCurrentUser, mFocusedStack == null ? -1 : mFocusedStack.getStackId(),
+                    mLastFocusedStack == null ? -1 : mLastFocusedStack.getStackId(), reason);
+        }
+
+        final ActivityRecord r = topRunningActivityLocked();
+        if (mService.mFocusedActivity != r) {
+            // The focus activity should always be the top activity in the focused stack.
+            // There will be chaos and anarchy if it isn't...
+            mService.setFocusedActivityLocked(r, reason + " setFocusStack");
+        }
 
         if (mService.mBooting || !mService.mBooted) {
-            final ActivityRecord r = topRunningActivityLocked();
             if (r != null && r.idle) {
                 checkFinishBootingLocked();
             }
@@ -591,12 +600,14 @@
 
         mHomeStack.moveHomeStackTaskToTop(homeStackTaskType);
         ActivityRecord r = getHomeActivity();
+        final String myReason = reason + " resumeHomeStackTask";
+
         // Only resume home activity if isn't finishing.
         if (r != null && !r.finishing) {
-            mService.setFocusedActivityLocked(r, reason);
-            return resumeTopActivitiesLocked(mHomeStack, prev, null);
+            mService.setFocusedActivityLocked(r, myReason);
+            return resumeFocusedStackTopActivityLocked(mHomeStack, prev, null);
         }
-        return mService.startHomeActivityLocked(mCurrentUser, reason);
+        return mService.startHomeActivityLocked(mCurrentUser, myReason);
     }
 
     TaskRecord anyTaskForIdLocked(int id) {
@@ -1148,14 +1159,12 @@
             // a resume.
             stack.minimalResumeActivityLocked(r);
         } else {
-            // This activity is not starting in the resumed state... which
-            // should look like we asked it to pause+stop (but remain visible),
-            // and it has done so and reported back the current icicle and
-            // other state.
+            // This activity is not starting in the resumed state... which should look like we asked
+            // it to pause+stop (but remain visible), and it has done so and reported back the
+            // current icicle and other state.
             if (DEBUG_STATES) Slog.v(TAG_STATES,
-                    "Moving to STOPPED: " + r + " (starting in stopped state)");
-            r.state = STOPPED;
-            r.stopped = true;
+                    "Moving to PAUSED: " + r + " (starting in paused state)");
+            r.state = PAUSED;
         }
 
         // Launch the new version setup screen if needed.  We do this -after-
@@ -1345,14 +1354,14 @@
         return ACTIVITY_RESTRICTION_NONE;
     }
 
-    boolean setFocusedStack(ActivityRecord r, String reason) {
+    boolean moveActivityStackToFront(ActivityRecord r, String reason) {
         if (r == null) {
             // Not sure what you are trying to do, but it is not going to work...
             return false;
         }
         final TaskRecord task = r.task;
         if (task == null || task.stack == null) {
-            Slog.w(TAG, "Can't set focus stack for r=" + r + " task=" + task);
+            Slog.w(TAG, "Can't move stack to front for r=" + r + " task=" + task);
             return false;
         }
         task.stack.moveToFront(reason, task);
@@ -1518,7 +1527,7 @@
         //mWindowManager.dump();
 
         if (activityRemoved) {
-            resumeTopActivitiesLocked();
+            resumeFocusedStackTopActivityLocked();
         }
 
         return r;
@@ -1611,35 +1620,17 @@
         }
     }
 
-    boolean resumeTopActivitiesLocked() {
-        return resumeTopActivitiesLocked(null, null, null);
+    boolean resumeFocusedStackTopActivityLocked() {
+        return resumeFocusedStackTopActivityLocked(null, null, null);
     }
 
-    boolean resumeTopActivitiesLocked(
+    boolean resumeFocusedStackTopActivityLocked(
             ActivityStack targetStack, ActivityRecord target, ActivityOptions targetOptions) {
-        if (targetStack == null) {
-            targetStack = mFocusedStack;
+        if (targetStack != null && isFocusedStack(targetStack)) {
+            return targetStack.resumeTopActivityUncheckedLocked(target, targetOptions);
         }
-        // Do targetStack first.
-        boolean result = false;
-        if (isFocusedStack(targetStack)) {
-            result = targetStack.resumeTopActivityLocked(target, targetOptions);
-        }
-
-        for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
-            final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
-            for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
-                final ActivityStack stack = stacks.get(stackNdx);
-                if (stack == targetStack) {
-                    // Already started above.
-                    continue;
-                }
-                if (isFocusedStack(stack)) {
-                    stack.resumeTopActivityLocked(null);
-                }
-            }
-        }
-        return result;
+        mFocusedStack.resumeTopActivityUncheckedLocked(null, null);
+        return false;
     }
 
     void finishTopRunningActivityLocked(ProcessRecord app, String reason) {
@@ -1905,7 +1896,7 @@
                 // are made visible with the correct configuration.
                 ensureActivitiesVisibleLocked(r, 0, preserveWindows);
                 if (!updated) {
-                    resumeTopActivitiesLocked(stack, null, null);
+                    resumeFocusedStackTopActivityLocked();
                 }
             }
         } finally {
@@ -1961,7 +1952,7 @@
                 // All other activities must be made visible with their correct configuration.
                 ensureActivitiesVisibleLocked(r, 0, !PRESERVE_WINDOWS);
                 if (!kept) {
-                    resumeTopActivitiesLocked(stack, null, null);
+                    resumeFocusedStackTopActivityLocked();
                 }
             }
         }
@@ -2083,8 +2074,14 @@
     ActivityStack moveTaskToStackUncheckedLocked(
             TaskRecord task, int stackId, boolean toTop, boolean forceFocus, String reason) {
         final ActivityRecord r = task.getTopActivity();
-        final boolean wasFocused = isFocusedStack(task.stack) && (topRunningActivityLocked() == r);
-        final boolean wasResumed = wasFocused && (task.stack.mResumedActivity == r);
+        final ActivityStack prevStack = task.stack;
+        final boolean wasFocused = isFocusedStack(prevStack) && (topRunningActivityLocked() == r);
+        final boolean wasResumed = wasFocused && (prevStack.mResumedActivity == r);
+        // In some cases the focused stack isn't the front stack. E.g. pinned stack.
+        // Whenever we are moving the top activity from the front stack we want to make sure to move
+        // the stack to the front.
+        final boolean wasFront = isFrontStack(prevStack)
+                && (prevStack.topRunningActivityLocked() == r);
 
         final boolean resizeable = task.mResizeable;
         // Temporarily disable resizeablility of task we are moving. We don't want it to be resized
@@ -2097,9 +2094,9 @@
         stack.addTask(task, toTop, reason);
 
         // If the task had focus before (or we're requested to move focus),
-        // move focus to the new stack.
-        stack.setFocusAndResumeStateIfNeeded(
-                r, forceFocus || wasFocused, wasResumed, reason);
+        // move focus to the new stack by moving the stack to the front.
+        stack.moveToFrontAndResumeStateIfNeeded(
+                r, forceFocus || wasFocused || wasFront, wasResumed, reason);
 
         return stack;
     }
@@ -2149,7 +2146,7 @@
         // The task might have already been running and its visibility needs to be synchronized with
         // the visibility of the stack / windows.
         ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS);
-        resumeTopActivitiesLocked();
+        resumeFocusedStackTopActivityLocked();
 
         if (!task.mResizeable && isStackDockedInEffect(stackId)) {
             showNonResizeableDockToast(taskId);
@@ -2200,7 +2197,7 @@
         // The task might have already been running and its visibility needs to be synchronized with
         // the visibility of the stack / windows.
         ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS);
-        resumeTopActivitiesLocked();
+        resumeFocusedStackTopActivityLocked();
     }
 
     void positionTaskInStackLocked(int taskId, int stackId, int position) {
@@ -2219,7 +2216,7 @@
         // The task might have already been running and its visibility needs to be synchronized with
         // the visibility of the stack / windows.
         stack.ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS);
-        resumeTopActivitiesLocked();
+        resumeFocusedStackTopActivityLocked();
     }
 
     ActivityRecord findTaskLocked(ActivityRecord r) {
@@ -2329,7 +2326,7 @@
                 final ActivityStack stack = stacks.get(stackNdx);
                 stack.awakeFromSleepingLocked();
                 if (isFocusedStack(stack)) {
-                    resumeTopActivitiesLocked();
+                    resumeFocusedStackTopActivityLocked();
                 }
             }
         }
@@ -3124,7 +3121,7 @@
                     if (DEBUG_LOCKTASK) Slog.w(TAG_LOCKTASK,
                             "setLockTaskModeLocked: Tasks remaining, can't unlock");
                     lockedTask.performClearTaskLocked();
-                    resumeTopActivitiesLocked();
+                    resumeFocusedStackTopActivityLocked();
                     return;
                 }
             }
@@ -3165,7 +3162,7 @@
 
         if (andResume) {
             findTaskToMoveToFrontLocked(task, 0, null, reason);
-            resumeTopActivitiesLocked();
+            resumeFocusedStackTopActivityLocked();
         }
     }
 
@@ -3233,7 +3230,7 @@
             didSomething = true;
         }
         if (didSomething) {
-            resumeTopActivitiesLocked();
+            resumeFocusedStackTopActivityLocked();
         }
     }
 
@@ -3280,7 +3277,7 @@
                 } break;
                 case RESUME_TOP_ACTIVITY_MSG: {
                     synchronized (mService) {
-                        resumeTopActivitiesLocked();
+                        resumeFocusedStackTopActivityLocked();
                     }
                 } break;
                 case SLEEP_TIMEOUT_MSG: {
@@ -3661,7 +3658,7 @@
 
             mSurface = surface;
             if (surface != null) {
-                mStack.resumeTopActivityLocked(null);
+                resumeFocusedStackTopActivityLocked();
             } else {
                 mContainerState = CONTAINER_STATE_NO_SURFACE;
                 ((VirtualActivityDisplay) mActivityDisplay).setSurface(null);
diff --git a/services/core/java/com/android/server/am/ActivityStarter.java b/services/core/java/com/android/server/am/ActivityStarter.java
index b16e160..bcb2215 100644
--- a/services/core/java/com/android/server/am/ActivityStarter.java
+++ b/services/core/java/com/android/server/am/ActivityStarter.java
@@ -916,7 +916,7 @@
                 // is the case, so this is it!  And for paranoia, make
                 // sure we have correctly resumed the top activity.
                 if (doResume) {
-                    mSupervisor.resumeTopActivitiesLocked(targetStack, null, options);
+                    mSupervisor.resumeFocusedStackTopActivityLocked(targetStack, null, options);
 
                     // Make sure to notify Keyguard as well if we are not running an app
                     // transition later.
@@ -1019,7 +1019,7 @@
                 // don't use that intent!)  And for paranoia, make
                 // sure we have correctly resumed the top activity.
                 if (doResume) {
-                    targetStack.resumeTopActivityLocked(null, options);
+                    mSupervisor.resumeFocusedStackTopActivityLocked(targetStack, null, options);
                     if (!movedToFront) {
                         // Make sure to notify Keyguard as well if we are not running an app
                         // transition later.
@@ -1055,7 +1055,7 @@
                 // For paranoia, make sure we have correctly resumed the top activity.
                 topStack.mLastPausedActivity = null;
                 if (doResume) {
-                    mSupervisor.resumeTopActivitiesLocked();
+                    mSupervisor.resumeFocusedStackTopActivityLocked();
                 }
                 ActivityOptions.abort(options);
                 if ((startFlags & ActivityManager.START_FLAG_ONLY_IF_NEEDED) != 0) {
@@ -1157,7 +1157,7 @@
                     // resumed the top activity.
                     targetStack.mLastPausedActivity = null;
                     if (doResume) {
-                        targetStack.resumeTopActivityLocked(null);
+                        mSupervisor.resumeFocusedStackTopActivityLocked();
                     }
                     ActivityOptions.abort(options);
                     return ActivityManager.START_DELIVERED_TO_TOP;
@@ -1176,7 +1176,7 @@
                     top.deliverNewIntentLocked(callingUid, r.intent, r.launchedFromPackage);
                     targetStack.mLastPausedActivity = null;
                     if (doResume) {
-                        targetStack.resumeTopActivityLocked(null);
+                        mSupervisor.resumeFocusedStackTopActivityLocked();
                     }
                     return ActivityManager.START_DELIVERED_TO_TOP;
                 }
@@ -1266,9 +1266,11 @@
         targetStack.startActivityLocked(r, newTask, keepCurTransition, options);
         if (doResume) {
             if (!launchTaskBehind) {
+                // TODO: Remove this code after verification that all the decision points above
+                // moved targetStack to the front which will also set the focus activity.
                 mService.setFocusedActivityLocked(r, "startedActivity");
             }
-            mSupervisor.resumeTopActivitiesLocked(targetStack, r, options);
+            mSupervisor.resumeFocusedStackTopActivityLocked(targetStack, r, options);
         } else {
             targetStack.addRecentActivityLocked(r);
         }
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index 4647d77..e77bb20 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -1321,7 +1321,12 @@
         }
     }
 
-    void reportPictureInPictureModeChange() {
+    void reportPictureInPictureModeChangeIfNeeded(ActivityStack prevStack) {
+        if (prevStack == null || prevStack == stack
+                || (prevStack.mStackId != PINNED_STACK_ID && stack.mStackId != PINNED_STACK_ID)) {
+            return;
+        }
+
         for (int i = mActivities.size() - 1; i >= 0; i--) {
             final ActivityRecord r = mActivities.get(i);
             if (r.app != null && r.app.thread != null) {
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 20b1e60..4cdf6b8 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -931,7 +931,7 @@
         if (homeInFront) {
             mService.startHomeActivityLocked(newUserId, "moveUserToForeground");
         } else {
-            mService.mStackSupervisor.resumeTopActivitiesLocked();
+            mService.mStackSupervisor.resumeFocusedStackTopActivityLocked();
         }
         EventLogTags.writeAmSwitchUser(newUserId);
         getUserManager().onUserForeground(newUserId);
diff --git a/services/core/java/com/android/server/policy/GlobalActions.java b/services/core/java/com/android/server/policy/GlobalActions.java
index 5948d3c..3eae7fc 100644
--- a/services/core/java/com/android/server/policy/GlobalActions.java
+++ b/services/core/java/com/android/server/policy/GlobalActions.java
@@ -383,13 +383,11 @@
             // Add a little delay before executing, to give the
             // dialog a chance to go away before it takes a
             // screenshot.
-            // TODO: remove once screenshots are handled by Shell (instead of dumpstate)
             mHandler.postDelayed(new Runnable() {
                 @Override
                 public void run() {
                     try {
-                        // Take a "heavy" bugreport: it's more user friendly, but causes more
-                        // interference.
+                        // Take an "interactive" bugreport.
                         ActivityManagerNative.getDefault().requestBugReport(true);
                     } catch (RemoteException e) {
                     }
@@ -405,11 +403,11 @@
                 return false;
             }
             try {
-                // Take a "light" bugreport, with less interference.
+                // Take a "full" bugreport.
                 ActivityManagerNative.getDefault().requestBugReport(false);
             } catch (RemoteException e) {
             }
-            return true;
+            return false;
         }
 
         public boolean showDuringKeyguard() {
diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java
index 92d5aa9..5d5df95 100644
--- a/telecomm/java/android/telecom/Call.java
+++ b/telecomm/java/android/telecom/Call.java
@@ -269,7 +269,7 @@
          * @return Whether the specified capability is supported.
          */
         public static boolean can(int capabilities, int capability) {
-            return (capabilities & capability) != 0;
+            return (capabilities & capability) == capability;
         }
 
         /**
@@ -351,7 +351,7 @@
          * @return Whether the specified property is supported.
          */
         public static boolean hasProperty(int properties, int property) {
-            return (properties & property) != 0;
+            return (properties & property) == property;
         }
 
         /**
diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java
index 17bd08c..deb98f4 100644
--- a/telecomm/java/android/telecom/Connection.java
+++ b/telecomm/java/android/telecom/Connection.java
@@ -294,7 +294,7 @@
      * @hide
      */
     public static boolean can(int capabilities, int capability) {
-        return (capabilities & capability) != 0;
+        return (capabilities & capability) == capability;
     }
 
     /**
diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java
index 8537f9c..c680999 100644
--- a/telephony/java/android/telephony/ServiceState.java
+++ b/telephony/java/android/telephony/ServiceState.java
@@ -153,6 +153,17 @@
      * @hide
      */
     public static final int RIL_RADIO_TECHNOLOGY_IWLAN = 18;
+
+    /** @hide */
+    public static final int RIL_RADIO_CDMA_TECHNOLOGY_BITMASK =
+            (1 << (RIL_RADIO_TECHNOLOGY_IS95A - 1))
+                    | (1 << (RIL_RADIO_TECHNOLOGY_IS95B - 1))
+                    | (1 << (RIL_RADIO_TECHNOLOGY_1xRTT - 1))
+                    | (1 << (RIL_RADIO_TECHNOLOGY_EVDO_0 - 1))
+                    | (1 << (RIL_RADIO_TECHNOLOGY_EVDO_A - 1))
+                    | (1 << (RIL_RADIO_TECHNOLOGY_EVDO_B - 1))
+                    | (1 << (RIL_RADIO_TECHNOLOGY_EHRPD - 1));
+
     /**
      * Available registration states for GSM, UMTS and CDMA.
      */
@@ -1141,16 +1152,8 @@
     }
 
     /** @hide */
-    public static boolean hasCdma(int radioTechnologyBitmask) {
-        int cdmaBitmask = (RIL_RADIO_TECHNOLOGY_IS95A
-                | RIL_RADIO_TECHNOLOGY_IS95B
-                | RIL_RADIO_TECHNOLOGY_1xRTT
-                | RIL_RADIO_TECHNOLOGY_EVDO_0
-                | RIL_RADIO_TECHNOLOGY_EVDO_A
-                | RIL_RADIO_TECHNOLOGY_EVDO_B
-                | RIL_RADIO_TECHNOLOGY_EHRPD);
-
-        return ((radioTechnologyBitmask & cdmaBitmask) != 0);
+    public static boolean bearerBitmapHasCdma(int radioTechnologyBitmap) {
+        return (RIL_RADIO_CDMA_TECHNOLOGY_BITMASK & radioTechnologyBitmap) != 0;
     }
 
     /** @hide */