Merge "Do the sorting for the ShareSheet asynchronously."
diff --git a/api/system-current.txt b/api/system-current.txt
index 4bba111..5f173cf 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -26239,10 +26239,16 @@
     ctor public ScoredNetwork(android.net.NetworkKey, android.net.RssiCurve);
     ctor public ScoredNetwork(android.net.NetworkKey, android.net.RssiCurve, boolean);
     ctor public ScoredNetwork(android.net.NetworkKey, android.net.RssiCurve, boolean, android.os.Bundle);
+    method public int calculateBadge(int);
     method public int describeContents();
     method public void writeToParcel(android.os.Parcel, int);
+    field public static final java.lang.String ATTRIBUTES_KEY_BADGING_CURVE = "android.net.attributes.key.BADGING_CURVE";
     field public static final java.lang.String ATTRIBUTES_KEY_HAS_CAPTIVE_PORTAL = "android.net.attributes.key.HAS_CAPTIVE_PORTAL";
     field public static final java.lang.String ATTRIBUTES_KEY_RANKING_SCORE_OFFSET = "android.net.attributes.key.RANKING_SCORE_OFFSET";
+    field public static final int BADGING_4K = 30; // 0x1e
+    field public static final int BADGING_HD = 20; // 0x14
+    field public static final int BADGING_NONE = 0; // 0x0
+    field public static final int BADGING_SD = 10; // 0xa
     field public static final android.os.Parcelable.Creator<android.net.ScoredNetwork> CREATOR;
     field public final android.os.Bundle attributes;
     field public final boolean meteredHint;
@@ -26250,6 +26256,9 @@
     field public final android.net.RssiCurve rssiCurve;
   }
 
+  public static abstract class ScoredNetwork.Badging implements java.lang.annotation.Annotation {
+  }
+
   public class TrafficStats {
     ctor public TrafficStats();
     method public static void clearThreadStatsTag();
diff --git a/core/java/android/net/ScoredNetwork.java b/core/java/android/net/ScoredNetwork.java
index 94e5187..7e3dd77 100644
--- a/core/java/android/net/ScoredNetwork.java
+++ b/core/java/android/net/ScoredNetwork.java
@@ -16,6 +16,7 @@
 
 package android.net;
 
+import android.annotation.IntDef;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.os.Bundle;
@@ -24,6 +25,8 @@
 
 import java.lang.Math;
 import java.lang.UnsupportedOperationException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.Objects;
 
 /**
@@ -33,6 +36,15 @@
  */
 @SystemApi
 public class ScoredNetwork implements Parcelable {
+
+  /**
+     * Key used with the {@link #attributes} bundle to define the badging curve.
+     *
+     * <p>The badging curve is a {@link RssiCurve} used to map different RSSI values to {@link
+     * Badging} enums.
+     */
+    public static final String ATTRIBUTES_KEY_BADGING_CURVE =
+            "android.net.attributes.key.BADGING_CURVE";
     /**
      * Extra used with {@link #attributes} to specify whether the
      * network is believed to have a captive portal.
@@ -58,6 +70,15 @@
     public static final String ATTRIBUTES_KEY_RANKING_SCORE_OFFSET =
             "android.net.attributes.key.RANKING_SCORE_OFFSET";
 
+    @IntDef({BADGING_NONE, BADGING_SD, BADGING_HD, BADGING_4K})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Badging {}
+
+    public static final int BADGING_NONE = 0;
+    public static final int BADGING_SD = 10;
+    public static final int BADGING_HD = 20;
+    public static final int BADGING_4K = 30;
+
     /** A {@link NetworkKey} uniquely identifying this network. */
     public final NetworkKey networkKey;
 
@@ -249,6 +270,25 @@
         }
     }
 
+    /**
+     * Return the {@link Badging} enum for this network for the given RSSI, derived from the
+     * badging curve.
+     *
+     * <p>If no badging curve is present, {@link #BADGE_NONE} will be returned.
+     *
+     * @param rssi The rssi level for which the badge should be calculated
+     */
+    @Badging
+    public int calculateBadge(int rssi) {
+        if (attributes != null && attributes.containsKey(ATTRIBUTES_KEY_BADGING_CURVE)) {
+            RssiCurve badgingCurve =
+                    attributes.getParcelable(ATTRIBUTES_KEY_BADGING_CURVE);
+            return badgingCurve.lookupScore(rssi);
+        }
+
+        return BADGING_NONE;
+    }
+
     public static final Parcelable.Creator<ScoredNetwork> CREATOR =
             new Parcelable.Creator<ScoredNetwork>() {
                 @Override
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 68d8c55..cb19ff9 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -2075,6 +2075,7 @@
          @SystemApi @hide -->
     <permission android:name="android.permission.BIND_NETWORK_RECOMMENDATION_SERVICE"
         android:protectionLevel="signature" />
+    <uses-permission android:name="android.permission.BIND_NETWORK_RECOMMENDATION_SERVICE"/>
 
     <!-- ========================================= -->
     <!-- Permissions for special development tools -->
diff --git a/core/tests/coretests/src/android/net/ScoredNetworkTest.java b/core/tests/coretests/src/android/net/ScoredNetworkTest.java
index 9c3346e..e818c56 100644
--- a/core/tests/coretests/src/android/net/ScoredNetworkTest.java
+++ b/core/tests/coretests/src/android/net/ScoredNetworkTest.java
@@ -166,4 +166,52 @@
         assertTrue(newNetwork.meteredHint);
         assertNull(newNetwork.attributes);
     }
+
+    @Test
+    public void calculateBadgeShouldReturnNoBadgeWhenNoAttributesBundle() {
+        ScoredNetwork network = new ScoredNetwork(KEY, CURVE);
+        assertEquals(ScoredNetwork.BADGING_NONE, network.calculateBadge(TEST_RSSI));
+    }
+
+    @Test
+    public void calculateBadgeShouldReturnNoBadgeWhenNoBadgingCurveInBundle() {
+        ScoredNetwork network = new ScoredNetwork(KEY, CURVE, false /* meteredHint */, ATTRIBUTES);
+        assertEquals(ScoredNetwork.BADGING_NONE, network.calculateBadge(TEST_RSSI));
+    }
+
+    @Test
+    public void calculateBadgeShouldReturn4kBadge() {
+        ScoredNetwork network =
+            buildScoredNetworkWithGivenBadgeForTestRssi(ScoredNetwork.BADGING_4K);
+        assertEquals(ScoredNetwork.BADGING_4K, network.calculateBadge(TEST_RSSI));
+    }
+
+    @Test
+    public void calculateBadgeShouldReturnHdBadge() {
+        ScoredNetwork network =
+            buildScoredNetworkWithGivenBadgeForTestRssi(ScoredNetwork.BADGING_HD);
+        assertEquals(ScoredNetwork.BADGING_HD, network.calculateBadge(TEST_RSSI));
+    }
+
+    @Test
+    public void calculateBadgeShouldReturnSdBadge() {
+        ScoredNetwork network =
+            buildScoredNetworkWithGivenBadgeForTestRssi(ScoredNetwork.BADGING_SD);
+        assertEquals(ScoredNetwork.BADGING_SD, network.calculateBadge(TEST_RSSI));
+    }
+
+    @Test
+    public void calculateBadgeShouldReturnNoBadge() {
+        ScoredNetwork network =
+            buildScoredNetworkWithGivenBadgeForTestRssi(ScoredNetwork.BADGING_NONE);
+        assertEquals(ScoredNetwork.BADGING_NONE, network.calculateBadge(TEST_RSSI));
+    }
+
+    private ScoredNetwork buildScoredNetworkWithGivenBadgeForTestRssi(int badge) {
+        RssiCurve badgingCurve =
+               new RssiCurve(RSSI_START, 10, new byte[] {0, 0, 0, 0, 0, 0, (byte) badge});
+        Bundle attr = new Bundle();
+        attr.putParcelable(ScoredNetwork.ATTRIBUTES_KEY_BADGING_CURVE, badgingCurve);
+        return new ScoredNetwork(KEY, CURVE, false /* meteredHint */, attr);
+    }
 }
diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java
index 77a0fbd..588376f 100644
--- a/media/java/android/media/MediaPlayer.java
+++ b/media/java/android/media/MediaPlayer.java
@@ -2291,7 +2291,7 @@
      * {@link #setSubtitleAnchor(SubtitleController, Anchor)} (e.g. {@link VideoView} provides one).
      */
     private synchronized void setSubtitleAnchor() {
-        if (mSubtitleController == null) {
+        if ((mSubtitleController == null) && (ActivityThread.currentApplication() != null)) {
             final HandlerThread thread = new HandlerThread("SetSubtitleAnchorThread");
             thread.start();
             Handler handler = new Handler(thread.getLooper());
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 5c2787b..5574753 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -427,6 +427,7 @@
             android:supportsPictureInPicture="true"
             android:stateNotNeeded="true"
             android:taskAffinity=""
+            android:launchMode="singleTop"
             androidprv:alwaysFocusable="true" />
 
         <!-- platform logo easter egg activity -->
diff --git a/packages/SystemUI/res/color/tint_color_selector.xml b/packages/SystemUI/res/color/tint_color_selector.xml
new file mode 100644
index 0000000..cb24425
--- /dev/null
+++ b/packages/SystemUI/res/color/tint_color_selector.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_enabled="false"
+          android:alpha="0.25"
+          android:color="?android:attr/colorForeground"/>
+    <item android:state_activated="false"
+          android:alpha="0.3"
+          android:color="?android:attr/colorForeground"/>
+    <item android:color="?android:attr/colorForeground"/>
+</selector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_hotspot_unavailable.xml b/packages/SystemUI/res/drawable/ic_hotspot_unavailable.xml
index 736cfd8..83f46e5 100644
--- a/packages/SystemUI/res/drawable/ic_hotspot_unavailable.xml
+++ b/packages/SystemUI/res/drawable/ic_hotspot_unavailable.xml
@@ -20,7 +20,8 @@
     android:width="48dp"
     android:viewportHeight="48"
     android:viewportWidth="48"
-    android:tint="@color/qs_tile_tint_unavailable" >
+    android:alpha="0.25"
+    android:tint="?android:attr/colorControlNormal" >
     <group
         android:name="ic_hotspot"
         android:translateX="23.97354"
diff --git a/packages/SystemUI/res/layout/pip_menu_activity.xml b/packages/SystemUI/res/layout/pip_menu_activity.xml
index 054bfab..cadc09b 100644
--- a/packages/SystemUI/res/layout/pip_menu_activity.xml
+++ b/packages/SystemUI/res/layout/pip_menu_activity.xml
@@ -19,7 +19,7 @@
     android:id="@+id/menu"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    android:background="#33000000">
+    android:background="#66000000">
 
     <LinearLayout
         android:id="@+id/actions"
@@ -45,7 +45,7 @@
             android:text="@string/pip_phone_dismiss"
             android:background="?android:selectableItemBackground" />
         <TextView
-            android:id="@+id/minimize"
+            android:id="@+id/expand"
             android:layout_width="0dp"
             android:layout_height="match_parent"
             android:layout_weight="1"
@@ -53,7 +53,7 @@
             android:textSize="12sp"
             android:textColor="#ffffffff"
             android:fontFamily="sans-serif"
-            android:text="@string/pip_phone_minimize"
+            android:text="@string/pip_phone_expand"
             android:background="?android:selectableItemBackground" />
     </LinearLayout>
 </FrameLayout>
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index c7149df..ba8c644 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -144,10 +144,6 @@
 
     <color name="remote_input_accent">#eeeeee</color>
 
-    <color name="qs_tile_tint_unavailable">#40ffffff</color>
-    <color name="qs_tile_tint_inactive">#4dffffff</color>
-    <color name="qs_tile_tint_active">#ffffffff</color>
-
     <!-- Keyboard shortcuts colors -->
     <color name="ksh_application_group_color">#fff44336</color>
     <color name="ksh_keyword_color">#d9000000</color>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index fc1d816..614472a 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -68,7 +68,7 @@
         <item name="android:windowIsTranslucent">true</item>
         <item name="android:windowNoTitle">true</item>
         <item name="android:windowContentOverlay">@null</item>
-        <item name="android:windowBackground">@drawable/forced_resizable_background</item>
+        <item name="android:windowBackground">@null</item>
         <item name="android:colorBackgroundCacheHint">@null</item>
         <item name="android:statusBarColor">@color/transparent</item>
         <item name="android:windowAnimationStyle">@style/Animation.PipPhoneOverlayControl</item>
diff --git a/packages/SystemUI/src/com/android/systemui/BatteryMeterDrawable.java b/packages/SystemUI/src/com/android/systemui/BatteryMeterDrawable.java
index c4698c3..07ef5e0 100755
--- a/packages/SystemUI/src/com/android/systemui/BatteryMeterDrawable.java
+++ b/packages/SystemUI/src/com/android/systemui/BatteryMeterDrawable.java
@@ -491,6 +491,12 @@
 
     @Override
     public void setColorFilter(@Nullable ColorFilter colorFilter) {
+        mFramePaint.setColorFilter(colorFilter);
+        mBatteryPaint.setColorFilter(colorFilter);
+        mWarningTextPaint.setColorFilter(colorFilter);
+        mTextPaint.setColorFilter(colorFilter);
+        mBoltPaint.setColorFilter(colorFilter);
+        mPlusPaint.setColorFilter(colorFilter);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
index fe8ee6f..e4f7816 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
@@ -16,13 +16,16 @@
 
 package com.android.systemui.pip.phone;
 
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
 import android.annotation.Nullable;
 import android.app.Activity;
 import android.app.ActivityManager;
-import android.app.PendingIntent.CanceledException;
 import android.app.RemoteAction;
 import android.content.Intent;
 import android.content.pm.ParceledListSlice;
+import android.graphics.PointF;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Message;
@@ -30,11 +33,15 @@
 import android.os.RemoteException;
 import android.util.Log;
 import android.view.LayoutInflater;
+import android.view.MotionEvent;
 import android.view.View;
+import android.view.ViewConfiguration;
 import android.view.ViewGroup;
+import android.view.WindowManager.LayoutParams;
 import android.widget.ImageView;
 import android.widget.TextView;
 
+import com.android.systemui.Interpolators;
 import com.android.systemui.R;
 
 import java.util.ArrayList;
@@ -47,28 +54,40 @@
 
     private static final String TAG = "PipMenuActivity";
 
-    public static final int MESSAGE_FINISH_SELF = 1;
-    public static final int MESSAGE_UPDATE_ACTIONS = 2;
+    public static final int MESSAGE_SHOW_MENU = 1;
+    public static final int MESSAGE_HIDE_MENU = 2;
+    public static final int MESSAGE_UPDATE_ACTIONS = 3;
 
     private static final long INITIAL_DISMISS_DELAY = 2000;
     private static final long POST_INTERACTION_DISMISS_DELAY = 1500;
+    private static final long MENU_FADE_DURATION = 125;
 
-    private List<RemoteAction> mActions = new ArrayList<>();
+    private boolean mMenuVisible;
+    private final List<RemoteAction> mActions = new ArrayList<>();
+    private View mMenuContainer;
     private View mDismissButton;
-    private View mMinimizeButton;
+    private View mExpandButton;
 
+    private ObjectAnimator mMenuContainerAnimator;
+
+    private PointF mDownPosition = new PointF();
+    private PointF mDownDelta = new PointF();
+    private ViewConfiguration mViewConfig;
     private Handler mHandler = new Handler();
     private Messenger mToControllerMessenger;
     private Messenger mMessenger = new Messenger(new Handler() {
         @Override
         public void handleMessage(Message msg) {
             switch (msg.what) {
+                case MESSAGE_SHOW_MENU:
+                    showMenu();
+                    break;
+                case MESSAGE_HIDE_MENU:
+                    hideMenu();
+                    break;
                 case MESSAGE_UPDATE_ACTIONS:
                     setActions(((ParceledListSlice) msg.obj).getList());
                     break;
-                case MESSAGE_FINISH_SELF:
-                    finish();
-                    break;
             }
         }
     });
@@ -76,12 +95,17 @@
     private final Runnable mFinishRunnable = new Runnable() {
         @Override
         public void run() {
-            finish();
+            hideMenu();
         }
     };
 
     @Override
     protected void onCreate(@Nullable Bundle savedInstanceState) {
+        // Set the flags to allow us to watch for outside touches and also hide the menu and start
+        // manipulating the PIP in the same touch gesture
+        mViewConfig = ViewConfiguration.get(this);
+        getWindow().addFlags(LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH | LayoutParams.FLAG_SLIPPERY);
+
         super.onCreate(savedInstanceState);
         setContentView(R.layout.pip_menu_activity);
 
@@ -94,44 +118,74 @@
             setActions(actions.getList());
         }
 
-        findViewById(R.id.menu).setOnClickListener((v) -> {
+        mMenuContainer = findViewById(R.id.menu);
+        mMenuContainer.setOnClickListener((v) -> {
             expandPip();
         });
         mDismissButton = findViewById(R.id.dismiss);
         mDismissButton.setOnClickListener((v) -> {
             dismissPip();
         });
-        mMinimizeButton = findViewById(R.id.minimize);
-        mMinimizeButton.setOnClickListener((v) -> {
-            minimizePip();
+        mExpandButton = findViewById(R.id.expand);
+        mExpandButton.setOnClickListener((v) -> {
+            expandPip();
         });
+
+        notifyActivityCallback(mMessenger);
+        showMenu();
+    }
+
+    @Override
+    protected void onNewIntent(Intent intent) {
+        super.onNewIntent(intent);
+        showMenu();
     }
 
     @Override
     protected void onStart() {
         super.onStart();
-        notifyActivityVisibility(true);
+        notifyMenuVisibility(true);
         repostDelayedFinish(INITIAL_DISMISS_DELAY);
     }
 
     @Override
+    public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode) {
+        if (!isInPictureInPictureMode) {
+            finish();
+        }
+    }
+
+    @Override
     public void onUserInteraction() {
         repostDelayedFinish(POST_INTERACTION_DISMISS_DELAY);
     }
 
     @Override
-    protected void onStop() {
-        super.onStop();
-        finish();
+    public boolean dispatchTouchEvent(MotionEvent ev) {
+        // On the first action outside the window, hide the menu
+        switch (ev.getAction()) {
+            case MotionEvent.ACTION_OUTSIDE:
+                hideMenu();
+                break;
+            case MotionEvent.ACTION_DOWN:
+                mDownPosition.set(ev.getX(), ev.getY());
+                break;
+            case MotionEvent.ACTION_MOVE:
+                mDownDelta.set(ev.getX() - mDownPosition.x, ev.getY() - mDownPosition.y);
+                if (mDownDelta.length() > mViewConfig.getScaledTouchSlop() && mMenuVisible) {
+                    hideMenu();
+                    mMenuVisible = false;
+                }
+        }
+        return super.dispatchTouchEvent(ev);
     }
 
     @Override
     public void finish() {
-        View v = getWindow().getDecorView();
-        v.removeCallbacks(mFinishRunnable);
-        notifyActivityVisibility(false);
+        notifyActivityCallback(null);
         super.finish();
-        overridePendingTransition(0, R.anim.forced_resizable_exit);
+        // Hide without an animation (the menu should already be invisible at this point)
+        overridePendingTransition(0, 0);
     }
 
     @Override
@@ -139,6 +193,51 @@
         // Do nothing
     }
 
+    private void showMenu() {
+        if (!mMenuVisible) {
+            if (mMenuContainerAnimator != null) {
+                mMenuContainerAnimator.cancel();
+            }
+
+            notifyMenuVisibility(true);
+            mMenuContainerAnimator = ObjectAnimator.ofFloat(mMenuContainer, View.ALPHA,
+                    mMenuContainer.getAlpha(), 1f);
+            mMenuContainerAnimator.setInterpolator(Interpolators.ALPHA_IN);
+            mMenuContainerAnimator.setDuration(MENU_FADE_DURATION);
+            mMenuContainerAnimator.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    repostDelayedFinish(INITIAL_DISMISS_DELAY);
+                }
+            });
+            mMenuContainerAnimator.start();
+        }
+    }
+
+    private void hideMenu() {
+        hideMenu(null /* animationFinishedRunnable */);
+    }
+
+    private void hideMenu(final Runnable animationFinishedRunnable) {
+        if (mMenuVisible) {
+            cancelDelayedFinish();
+            notifyMenuVisibility(false);
+            mMenuContainerAnimator = ObjectAnimator.ofFloat(mMenuContainer, View.ALPHA,
+                    mMenuContainer.getAlpha(), 0f);
+            mMenuContainerAnimator.setInterpolator(Interpolators.ALPHA_OUT);
+            mMenuContainerAnimator.setDuration(MENU_FADE_DURATION);
+            mMenuContainerAnimator.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    if (animationFinishedRunnable != null) {
+                        animationFinishedRunnable.run();
+                    }
+                }
+            });
+            mMenuContainerAnimator.start();
+        }
+    }
+
     private void setActions(List<RemoteAction> actions) {
         mActions.clear();
         mActions.addAll(actions);
@@ -173,17 +272,19 @@
         }
     }
 
-    private void notifyActivityVisibility(boolean visible) {
+    private void notifyMenuVisibility(boolean visible) {
+        mMenuVisible = visible;
         Message m = Message.obtain();
-        m.what = PipMenuActivityController.MESSAGE_ACTIVITY_VISIBILITY_CHANGED;
+        m.what = PipMenuActivityController.MESSAGE_MENU_VISIBILITY_CHANGED;
         m.arg1 = visible ? 1 : 0;
-        m.replyTo = visible ? mMessenger : null;
         sendMessage(m, "Could not notify controller of PIP menu visibility");
     }
 
     private void expandPip() {
-        sendEmptyMessage(PipMenuActivityController.MESSAGE_EXPAND_PIP,
-                "Could not notify controller to expand PIP");
+        hideMenu(() -> {
+            sendEmptyMessage(PipMenuActivityController.MESSAGE_EXPAND_PIP,
+                    "Could not notify controller to expand PIP");
+        });
     }
 
     private void minimizePip() {
@@ -192,8 +293,17 @@
     }
 
     private void dismissPip() {
-        sendEmptyMessage(PipMenuActivityController.MESSAGE_DISMISS_PIP,
-                "Could not notify controller to dismiss PIP");
+        hideMenu(() -> {
+            sendEmptyMessage(PipMenuActivityController.MESSAGE_DISMISS_PIP,
+                    "Could not notify controller to dismiss PIP");
+        });
+    }
+
+    private void notifyActivityCallback(Messenger callback) {
+        Message m = Message.obtain();
+        m.what = PipMenuActivityController.MESSAGE_UPDATE_ACTIVITY_CALLBACK;
+        m.replyTo = callback;
+        sendMessage(m, "Could not notify controller of activity finished");
     }
 
     private void sendEmptyMessage(int what, String errorMsg) {
@@ -210,6 +320,11 @@
         }
     }
 
+    private void cancelDelayedFinish() {
+        View v = getWindow().getDecorView();
+        v.removeCallbacks(mFinishRunnable);
+    }
+
     private void repostDelayedFinish(long delay) {
         View v = getWindow().getDecorView();
         v.removeCallbacks(mFinishRunnable);
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java
index 64e2d1a..0350cc6 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java
@@ -25,10 +25,11 @@
     public static final String EXTRA_CONTROLLER_MESSENGER = "messenger";
     public static final String EXTRA_ACTIONS = "actions";
 
-    public static final int MESSAGE_ACTIVITY_VISIBILITY_CHANGED = 100;
+    public static final int MESSAGE_MENU_VISIBILITY_CHANGED = 100;
     public static final int MESSAGE_EXPAND_PIP = 101;
     public static final int MESSAGE_MINIMIZE_PIP = 102;
     public static final int MESSAGE_DISMISS_PIP = 103;
+    public static final int MESSAGE_UPDATE_ACTIVITY_CALLBACK = 104;
 
     /**
      * A listener interface to receive notification on changes in PIP.
@@ -67,34 +68,25 @@
         @Override
         public void handleMessage(Message msg) {
             switch (msg.what) {
-                case MESSAGE_ACTIVITY_VISIBILITY_CHANGED: {
+                case MESSAGE_MENU_VISIBILITY_CHANGED: {
                     boolean visible = msg.arg1 > 0;
-                    int listenerCount = mListeners.size();
-                    for (int i = 0; i < listenerCount; i++) {
-                        mListeners.get(i).onPipMenuVisibilityChanged(visible);
-                    }
-                    mToActivityMessenger = msg.replyTo;
+                    mListeners.forEach(l -> l.onPipMenuVisibilityChanged(visible));
                     break;
                 }
                 case MESSAGE_EXPAND_PIP: {
-                    int listenerCount = mListeners.size();
-                    for (int i = 0; i < listenerCount; i++) {
-                        mListeners.get(i).onPipExpand();
-                    }
+                    mListeners.forEach(l -> l.onPipExpand());
                     break;
                 }
                 case MESSAGE_MINIMIZE_PIP: {
-                    int listenerCount = mListeners.size();
-                    for (int i = 0; i < listenerCount; i++) {
-                        mListeners.get(i).onPipMinimize();
-                    }
+                    mListeners.forEach(l -> l.onPipMinimize());
                     break;
                 }
                 case MESSAGE_DISMISS_PIP: {
-                    int listenerCount = mListeners.size();
-                    for (int i = 0; i < listenerCount; i++) {
-                        mListeners.get(i).onPipDismiss();
-                    }
+                    mListeners.forEach(l -> l.onPipDismiss());
+                    break;
+                }
+                case MESSAGE_UPDATE_ACTIVITY_CALLBACK: {
+                    mToActivityMessenger = msg.replyTo;
                     break;
                 }
             }
@@ -121,24 +113,34 @@
      * Shows the menu activity.
      */
     public void showMenu() {
-        // Start the menu activity on the top task of the pinned stack
-        try {
-            StackInfo pinnedStackInfo = mActivityManager.getStackInfo(PINNED_STACK_ID);
-            if (pinnedStackInfo != null && pinnedStackInfo.taskIds != null &&
-                    pinnedStackInfo.taskIds.length > 0) {
-                Intent intent = new Intent(mContext, PipMenuActivity.class);
-                intent.putExtra(EXTRA_CONTROLLER_MESSENGER, mMessenger);
-                intent.putExtra(EXTRA_ACTIONS, mActions);
-                ActivityOptions options = ActivityOptions.makeBasic();
-                options.setLaunchTaskId(
-                        pinnedStackInfo.taskIds[pinnedStackInfo.taskIds.length - 1]);
-                options.setTaskOverlay(true);
-                mContext.startActivityAsUser(intent, options.toBundle(), UserHandle.CURRENT);
-            } else {
-                Log.e(TAG, "No PIP tasks found");
+        if (mToActivityMessenger != null) {
+            Message m = Message.obtain();
+            m.what = PipMenuActivity.MESSAGE_SHOW_MENU;
+            try {
+                mToActivityMessenger.send(m);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Could not notify menu to show", e);
             }
-        } catch (RemoteException e) {
-            Log.e(TAG, "Error showing PIP menu activity", e);
+        } else {
+            // Start the menu activity on the top task of the pinned stack
+            try {
+                StackInfo pinnedStackInfo = mActivityManager.getStackInfo(PINNED_STACK_ID);
+                if (pinnedStackInfo != null && pinnedStackInfo.taskIds != null &&
+                        pinnedStackInfo.taskIds.length > 0) {
+                    Intent intent = new Intent(mContext, PipMenuActivity.class);
+                    intent.putExtra(EXTRA_CONTROLLER_MESSENGER, mMessenger);
+                    intent.putExtra(EXTRA_ACTIONS, mActions);
+                    ActivityOptions options = ActivityOptions.makeCustomAnimation(mContext, 0, 0);
+                    options.setLaunchTaskId(
+                            pinnedStackInfo.taskIds[pinnedStackInfo.taskIds.length - 1]);
+                    options.setTaskOverlay(true);
+                    mContext.startActivityAsUser(intent, options.toBundle(), UserHandle.CURRENT);
+                } else {
+                    Log.e(TAG, "No PIP tasks found");
+                }
+            } catch (RemoteException e) {
+                Log.e(TAG, "Error showing PIP menu activity", e);
+            }
         }
     }
 
@@ -148,13 +150,12 @@
     public void hideMenu() {
         if (mToActivityMessenger != null) {
             Message m = Message.obtain();
-            m.what = PipMenuActivity.MESSAGE_FINISH_SELF;
+            m.what = PipMenuActivity.MESSAGE_HIDE_MENU;
             try {
                 mToActivityMessenger.send(m);
             } catch (RemoteException e) {
-                Log.e(TAG, "Could not notify menu activity to finish", e);
+                Log.e(TAG, "Could not notify menu to hide", e);
             }
-            mToActivityMessenger = null;
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
index ff3cc79..380e4683 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
@@ -174,7 +174,7 @@
 
         @Override
         public void onPipDismiss() {
-            animateDismissPinnedStack(mPinnedStackBounds);
+            BackgroundThread.getHandler().post(PipTouchHandler.this::dismissPinnedStack);
         }
     }
 
@@ -328,26 +328,31 @@
      * Registers the input consumer.
      */
     private void registerInputConsumer() {
-        final InputChannel inputChannel = new InputChannel();
-        try {
-            mWindowManager.destroyInputConsumer(INPUT_CONSUMER_PIP);
-            mWindowManager.createInputConsumer(INPUT_CONSUMER_PIP, inputChannel);
-        } catch (RemoteException e) {
-            Log.e(TAG, "Failed to create PIP input consumer", e);
+        if (mInputEventReceiver == null) {
+            final InputChannel inputChannel = new InputChannel();
+            try {
+                mWindowManager.destroyInputConsumer(INPUT_CONSUMER_PIP);
+                mWindowManager.createInputConsumer(INPUT_CONSUMER_PIP, inputChannel);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Failed to create PIP input consumer", e);
+            }
+            mInputEventReceiver = new PipInputEventReceiver(inputChannel, Looper.myLooper());
         }
-        mInputEventReceiver = new PipInputEventReceiver(inputChannel, Looper.myLooper());
     }
 
     /**
      * Unregisters the input consumer.
      */
     private void unregisterInputConsumer() {
-        try {
-            mWindowManager.destroyInputConsumer(INPUT_CONSUMER_PIP);
-        } catch (RemoteException e) {
-            Log.e(TAG, "Failed to destroy PIP input consumer", e);
+        if (mInputEventReceiver != null) {
+            try {
+                mWindowManager.destroyInputConsumer(INPUT_CONSUMER_PIP);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Failed to destroy PIP input consumer", e);
+            }
+            mInputEventReceiver.dispose();
+            mInputEventReceiver = null;
         }
-        mInputEventReceiver.dispose();
     }
 
     /**
@@ -761,10 +766,6 @@
     private PipTouchGesture mTapThroughGesture = new PipTouchGesture() {
         @Override
         boolean onMove(PipTouchState touchState) {
-            if (mEnableTapThrough && touchState.startedDragging()) {
-                mIsTappingThrough = false;
-                mMenuController.hideMenu();
-            }
             return false;
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
index 1fcb45b..9e3889b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
@@ -15,6 +15,9 @@
  */
 package com.android.systemui.qs.external;
 
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_QS_DIALOG;
+
 import android.app.ActivityManager;
 import android.content.ComponentName;
 import android.content.Context;
@@ -24,7 +27,6 @@
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
 import android.graphics.drawable.Drawable;
-import android.graphics.drawable.Icon;
 import android.net.Uri;
 import android.os.Binder;
 import android.os.IBinder;
@@ -37,19 +39,14 @@
 import android.text.style.ForegroundColorSpan;
 import android.util.Log;
 import android.view.IWindowManager;
-import android.view.WindowManager;
 import android.view.WindowManagerGlobal;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.systemui.R;
 import com.android.systemui.qs.QSTile;
 import com.android.systemui.qs.external.TileLifecycleManager.TileChangeListener;
 import com.android.systemui.statusbar.phone.QSTileHost;
 import libcore.util.Objects;
 
-import static android.view.Display.DEFAULT_DISPLAY;
-import static android.view.WindowManager.LayoutParams.TYPE_QS_DIALOG;
-
 public class CustomTile extends QSTile<QSTile.State> implements TileChangeListener {
     public static final String PREFIX = "custom(";
 
@@ -301,7 +298,7 @@
             tileState = Tile.STATE_UNAVAILABLE;
             drawable = mDefaultIcon.loadDrawable(mAppContext);
         }
-        int color = mContext.getColor(getColor(tileState));
+        final int color = TileColorPicker.getInstance(mContext).getColor(tileState);
         drawable.setTint(color);
         state.icon = mHasRes ? new DrawableIconWithRes(drawable, icon.getResId())
                 : new DrawableIcon(drawable);
@@ -335,18 +332,6 @@
         });
     }
 
-    private static int getColor(int state) {
-        switch (state) {
-            case Tile.STATE_UNAVAILABLE:
-                return R.color.qs_tile_tint_unavailable;
-            case Tile.STATE_INACTIVE:
-                return R.color.qs_tile_tint_inactive;
-            case Tile.STATE_ACTIVE:
-                return R.color.qs_tile_tint_active;
-        }
-        return 0;
-    }
-
     public static String toSpec(ComponentName name) {
         return PREFIX + name.flattenToShortString() + ")";
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileColorPicker.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileColorPicker.java
new file mode 100644
index 0000000..0cc17ff
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileColorPicker.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.systemui.qs.external;
+
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.service.quicksettings.Tile;
+import android.support.annotation.VisibleForTesting;
+import com.android.systemui.R;
+
+public class TileColorPicker {
+    @VisibleForTesting static final int[] DISABLE_STATE_SET = {-android.R.attr.state_enabled};
+    @VisibleForTesting static final int[] ENABLE_STATE_SET = {android.R.attr.state_enabled,
+            android.R.attr.state_activated};
+    @VisibleForTesting static final int[] INACTIVE_STATE_SET = {-android.R.attr.state_activated};
+    private static TileColorPicker sInstance;
+
+    private ColorStateList mColorStateList;
+
+    private TileColorPicker(Context context) {
+        mColorStateList = context.getResources().
+                getColorStateList(R.color.tint_color_selector, context.getTheme());
+    }
+
+    public static TileColorPicker getInstance(Context context) {
+        if (sInstance == null) {
+            sInstance = new TileColorPicker(context);
+        }
+        return sInstance;
+    }
+
+    public int getColor(int state) {
+        final int defaultColor = 0;
+
+        switch (state) {
+            case Tile.STATE_UNAVAILABLE:
+                return mColorStateList.getColorForState(DISABLE_STATE_SET, defaultColor);
+            case Tile.STATE_INACTIVE:
+                return mColorStateList.getColorForState(INACTIVE_STATE_SET, defaultColor);
+            case Tile.STATE_ACTIVE:
+                return mColorStateList.getColorForState(ENABLE_STATE_SET, defaultColor);
+            default:
+                return mColorStateList.getColorForState(ENABLE_STATE_SET, defaultColor);
+        }
+    }
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BatteryTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BatteryTile.java
index fc1c1ac..87b00a7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BatteryTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BatteryTile.java
@@ -19,7 +19,10 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.graphics.PorterDuff.Mode;
+import android.graphics.PorterDuffColorFilter;
 import android.graphics.drawable.Drawable;
+import android.service.quicksettings.Tile;
 import android.text.SpannableStringBuilder;
 import android.text.Spanned;
 import android.text.style.RelativeSizeSpan;
@@ -40,6 +43,7 @@
 import com.android.systemui.R;
 import com.android.systemui.plugins.qs.QS.DetailAdapter;
 import com.android.systemui.qs.QSTile;
+import com.android.systemui.qs.external.TileColorPicker;
 import com.android.systemui.statusbar.policy.BatteryController;
 
 import java.text.NumberFormat;
@@ -120,6 +124,8 @@
                         context.getColor(R.color.batterymeter_frame_color));
                 drawable.onBatteryLevelChanged(mLevel, mPluggedIn, mCharging);
                 drawable.onPowerSaveChanged(mPowerSave);
+                final int color = TileColorPicker.getInstance(context).getColor(Tile.STATE_ACTIVE);
+                drawable.setColorFilter(new PorterDuffColorFilter(color, Mode.SRC_IN));
                 return drawable;
             }
 
@@ -205,6 +211,11 @@
             mDrawable.onBatteryLevelChanged(100, false, false);
             mDrawable.onPowerSaveChanged(true);
             mDrawable.disableShowPercent();
+
+            final int color = TileColorPicker.getInstance(mCurrentView.getContext())
+                    .getColor(Tile.STATE_ACTIVE);
+            mDrawable.setColorFilter(new PorterDuffColorFilter(color, Mode.SRC_IN));
+
             ((ImageView) mCurrentView.findViewById(android.R.id.icon)).setImageDrawable(mDrawable);
             Checkable checkbox = (Checkable) mCurrentView.findViewById(android.R.id.toggle);
             checkbox.setChecked(mPowerSave);
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 5b1638f..d86aebf 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java
@@ -20,6 +20,7 @@
 import android.content.Intent;
 import android.graphics.drawable.Drawable;
 import android.provider.MediaStore;
+import android.service.quicksettings.Tile;
 import android.text.SpannableStringBuilder;
 import android.text.style.ForegroundColorSpan;
 import android.widget.Switch;
@@ -28,6 +29,7 @@
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.systemui.R;
 import com.android.systemui.qs.QSTile;
+import com.android.systemui.qs.external.TileColorPicker;
 import com.android.systemui.statusbar.policy.FlashlightController;
 
 /** Quick settings tile: Control flashlight **/
@@ -104,7 +106,8 @@
         if (!mFlashlightController.isAvailable()) {
             Drawable icon = mHost.getContext().getDrawable(R.drawable.ic_signal_flashlight_disable)
                     .mutate();
-            final int disabledColor = mHost.getContext().getColor(R.color.qs_tile_tint_unavailable);
+            final int disabledColor = TileColorPicker.getInstance(mContext)
+                    .getColor(Tile.STATE_UNAVAILABLE);
             icon.setTint(disabledColor);
             state.icon = new DrawableIcon(icon);
             state.label = new SpannableStringBuilder().append(state.label,
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 dcee659..99485bb 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
@@ -22,6 +22,7 @@
 
 import android.provider.Settings;
 import android.provider.Settings.Global;
+import android.service.quicksettings.Tile;
 import android.text.SpannableStringBuilder;
 import android.text.style.ForegroundColorSpan;
 import android.widget.Switch;
@@ -31,6 +32,7 @@
 import com.android.systemui.R;
 import com.android.systemui.qs.GlobalSetting;
 import com.android.systemui.qs.QSTile;
+import com.android.systemui.qs.external.TileColorPicker;
 import com.android.systemui.statusbar.policy.HotspotController;
 
 /** Quick settings tile: Hotspot **/
@@ -124,7 +126,8 @@
         boolean wasAirplane = state.isAirplaneMode;
         state.isAirplaneMode = mAirplaneMode.getValue() != 0;
         if (state.isAirplaneMode) {
-            final int disabledColor = mHost.getContext().getColor(R.color.qs_tile_tint_unavailable);
+            final int disabledColor = TileColorPicker.getInstance(mContext)
+                    .getColor(Tile.STATE_UNAVAILABLE);
             state.label = new SpannableStringBuilder().append(state.label,
                     new ForegroundColorSpan(disabledColor),
                     SpannableStringBuilder.SPAN_INCLUSIVE_INCLUSIVE);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileColorPickerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileColorPickerTest.java
new file mode 100644
index 0000000..ba451e1
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileColorPickerTest.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.qs.external;
+
+import android.content.res.ColorStateList;
+import android.service.quicksettings.Tile;
+import android.support.test.runner.AndroidJUnit4;
+import android.test.suitebuilder.annotation.SmallTest;
+import com.android.systemui.SysuiTestCase;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import com.android.systemui.R;
+
+import static junit.framework.Assert.assertEquals;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class TileColorPickerTest extends SysuiTestCase {
+    private static final int DEFAULT_COLOR = 0;
+
+    private TileColorPicker mTileColorPicker;
+    private ColorStateList mTintColorStateList;
+
+    @Before
+    public void setUp() {
+        mTileColorPicker = TileColorPicker.getInstance(mContext);
+        mTintColorStateList = mContext.getResources().
+                getColorStateList(R.color.tint_color_selector, mContext.getTheme());
+    }
+
+    @Test
+    public void testGetColor_StateUnavailable_ReturnUnavailableColor() {
+        final int color = mTileColorPicker.getColor(Tile.STATE_UNAVAILABLE);
+        final int expectedColor = mTintColorStateList.getColorForState(
+                TileColorPicker.DISABLE_STATE_SET, DEFAULT_COLOR);
+
+        assertEquals(expectedColor, color);
+    }
+
+    @Test
+    public void testGetColor_StateInactive_ReturnInactiveColor() {
+        final int color = mTileColorPicker.getColor(Tile.STATE_INACTIVE);
+        final int expectedColor = mTintColorStateList.getColorForState(
+                TileColorPicker.INACTIVE_STATE_SET, DEFAULT_COLOR);
+
+        assertEquals(expectedColor, color);
+    }
+
+    @Test
+    public void testGetColor_StateActive_ReturnActiveColor() {
+        final int color = mTileColorPicker.getColor(Tile.STATE_ACTIVE);
+        final int expectedColor = mTintColorStateList.getColorForState(
+                TileColorPicker.ENABLE_STATE_SET, DEFAULT_COLOR);
+
+        assertEquals(expectedColor, color);
+    }
+}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 10a19d7..22f9f3a 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -5980,8 +5980,9 @@
             throw new IllegalArgumentException("Invalid component " + admin
                     + " for device owner");
         }
+        final boolean hasIncompatibleAccounts = hasIncompatibleAccountsNoLock(userId, admin);
         synchronized (this) {
-            enforceCanSetDeviceOwnerLocked(admin, userId);
+            enforceCanSetDeviceOwnerLocked(admin, userId, hasIncompatibleAccounts);
             final ActiveAdmin activeAdmin = getActiveAdminUncheckedLocked(admin, userId);
             if (activeAdmin == null
                     || getUserData(userId).mRemovingAdmins.contains(admin)) {
@@ -6217,8 +6218,9 @@
             throw new IllegalArgumentException("Component " + who
                     + " not installed for userId:" + userHandle);
         }
+        final boolean hasIncompatibleAccounts = hasIncompatibleAccountsNoLock(userHandle, who);
         synchronized (this) {
-            enforceCanSetProfileOwnerLocked(who, userHandle);
+            enforceCanSetProfileOwnerLocked(who, userHandle, hasIncompatibleAccounts);
 
             if (getActiveAdminUncheckedLocked(who, userHandle) == null
                     || getUserData(userHandle).mRemovingAdmins.contains(who)) {
@@ -6551,9 +6553,10 @@
      * The profile owner can only be set before the user setup phase has completed,
      * except for:
      * - SYSTEM_UID
-     * - adb if there are no accounts. (But see {@link #hasIncompatibleAccountsLocked})
+     * - adb unless hasIncompatibleAccounts is true.
      */
-    private void enforceCanSetProfileOwnerLocked(@Nullable ComponentName owner, int userHandle) {
+    private void enforceCanSetProfileOwnerLocked(@Nullable ComponentName owner, int userHandle,
+            boolean hasIncompatibleAccounts) {
         UserInfo info = getUserInfo(userHandle);
         if (info == null) {
             // User doesn't exist.
@@ -6573,7 +6576,7 @@
         }
         if (isAdb()) {
             if ((mIsWatch || hasUserSetupCompleted(userHandle))
-                    && hasIncompatibleAccountsLocked(userHandle, owner)) {
+                    && hasIncompatibleAccounts) {
                 throw new IllegalStateException("Not allowed to set the profile owner because "
                         + "there are already some accounts on the profile");
             }
@@ -6590,12 +6593,14 @@
      * The Device owner can only be set by adb or an app with the MANAGE_PROFILE_AND_DEVICE_OWNERS
      * permission.
      */
-    private void enforceCanSetDeviceOwnerLocked(@Nullable ComponentName owner, int userId) {
+    private void enforceCanSetDeviceOwnerLocked(@Nullable ComponentName owner, int userId,
+            boolean hasIncompatibleAccounts) {
         if (!isAdb()) {
             enforceCanManageProfileAndDeviceOwners();
         }
 
-        final int code = checkDeviceOwnerProvisioningPreConditionLocked(owner, userId, isAdb());
+        final int code = checkDeviceOwnerProvisioningPreConditionLocked(
+                owner, userId, isAdb(), hasIncompatibleAccounts);
         switch (code) {
             case CODE_OK:
                 return;
@@ -8904,7 +8909,7 @@
      * except for adb command if no accounts or additional users are present on the device.
      */
     private int checkDeviceOwnerProvisioningPreConditionLocked(@Nullable ComponentName owner,
-            int deviceOwnerUserId, boolean isAdb) {
+            int deviceOwnerUserId, boolean isAdb, boolean hasIncompatibleAccounts) {
         if (mOwners.hasDeviceOwner()) {
             return CODE_HAS_DEVICE_OWNER;
         }
@@ -8924,7 +8929,7 @@
                     if (mUserManager.getUserCount() > 1) {
                         return CODE_NONSYSTEM_USER_EXISTS;
                     }
-                    if (hasIncompatibleAccountsLocked(UserHandle.USER_SYSTEM, owner)) {
+                    if (hasIncompatibleAccounts) {
                         return CODE_ACCOUNTS_NOT_EMPTY;
                     }
                 } else {
@@ -8951,8 +8956,9 @@
 
     private int checkDeviceOwnerProvisioningPreCondition(int deviceOwnerUserId) {
         synchronized (this) {
+            // hasIncompatibleAccounts doesn't matter since the caller is not adb.
             return checkDeviceOwnerProvisioningPreConditionLocked(/* owner unknown */ null,
-                    deviceOwnerUserId, /* isAdb= */ false);
+                    deviceOwnerUserId, /* isAdb= */ false, /* hasIncompatibleAccounts=*/ true);
         }
     }
 
@@ -9850,8 +9856,15 @@
      * - Otherwise, if there's any account that does not have ..._ALLOWED, or does have
      *   ..._DISALLOWED, return true.
      * - Otherwise return false.
+     *
+     * DO NOT CALL IT WITH THE DPMS LOCK HELD.
      */
-    private boolean hasIncompatibleAccountsLocked(int userId, @Nullable ComponentName owner) {
+    private boolean hasIncompatibleAccountsNoLock(int userId, @Nullable ComponentName owner) {
+        if (Thread.holdsLock(this)) {
+            Slog.wtf(LOG_TAG, "hasIncompatibleAccountsNoLock() called with the DPMS lock held.");
+            return true;
+        }
+
         final long token = mInjector.binderClearCallingIdentity();
         try {
             final AccountManager am = AccountManager.get(mContext);
@@ -9859,22 +9872,30 @@
             if (accounts.length == 0) {
                 return false;
             }
+            synchronized (this) {
+                if (owner == null || !isAdminTestOnlyLocked(owner, userId)) {
+                    Log.w(LOG_TAG,
+                            "Non test-only owner can't be installed with existing accounts.");
+                    return true;
+                }
+            }
+
             final String[] feature_allow =
                     { DevicePolicyManager.ACCOUNT_FEATURE_DEVICE_OR_PROFILE_OWNER_ALLOWED };
             final String[] feature_disallow =
                     { DevicePolicyManager.ACCOUNT_FEATURE_DEVICE_OR_PROFILE_OWNER_DISALLOWED };
 
-            // Even if we find incompatible accounts along the way, we still check all accounts
-            // for logging.
             boolean compatible = true;
             for (Account account : accounts) {
                 if (hasAccountFeatures(am, account, feature_disallow)) {
                     Log.e(LOG_TAG, account + " has " + feature_disallow[0]);
                     compatible = false;
+                    break;
                 }
                 if (!hasAccountFeatures(am, account, feature_allow)) {
                     Log.e(LOG_TAG, account + " doesn't have " + feature_allow[0]);
                     compatible = false;
+                    break;
                 }
             }
             if (compatible) {
@@ -9882,28 +9903,6 @@
             } else {
                 Log.e(LOG_TAG, "Found incompatible accounts");
             }
-
-            // Then check if the owner is test-only.
-            String log;
-            if (owner == null) {
-                // Owner is unknown.  Suppose it's not test-only
-                compatible = false;
-                log = "Only test-only device/profile owner can be installed with accounts";
-            } else if (isAdminTestOnlyLocked(owner, userId)) {
-                if (compatible) {
-                    log = "Installing test-only owner " + owner;
-                } else {
-                    log = "Can't install test-only owner " + owner + " with incompatible accounts";
-                }
-            } else {
-                compatible = false;
-                log = "Can't install non test-only owner " + owner + " with accounts";
-            }
-            if (compatible) {
-                Log.w(LOG_TAG, log);
-            } else {
-                Log.e(LOG_TAG, log);
-            }
             return !compatible;
         } finally {
             mInjector.binderRestoreCallingIdentity(token);
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
index 1247b2d..65255d9 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
@@ -16,6 +16,8 @@
 
 package com.android.server.devicepolicy;
 
+import android.accounts.Account;
+import android.accounts.AccountManager;
 import android.app.IActivityManager;
 import android.app.NotificationManager;
 import android.app.backup.IBackupManager;
@@ -273,6 +275,7 @@
     public final SettingsForMock settings;
     public final MockContentResolver contentResolver;
     public final TelephonyManager telephonyManager;
+    public final AccountManager accountManager;
 
     /** Note this is a partial mock, not a real mock. */
     public final PackageManager packageManager;
@@ -315,6 +318,7 @@
         wifiManager = mock(WifiManager.class);
         settings = mock(SettingsForMock.class);
         telephonyManager = mock(TelephonyManager.class);
+        accountManager = mock(AccountManager.class);
 
         // Package manager is huge, so we use a partial mock instead.
         packageManager = spy(context.getPackageManager());
@@ -375,6 +379,7 @@
                     }
                 }
         );
+        when(accountManager.getAccountsAsUser(anyInt())).thenReturn(new Account[0]);
 
         // Create a data directory.
         final File dir = new File(dataDir, "user" + userId);
@@ -464,6 +469,8 @@
                 return powerManager;
             case Context.WIFI_SERVICE:
                 return wifiManager;
+            case Context.ACCOUNT_SERVICE:
+                return accountManager;
         }
         throw new UnsupportedOperationException();
     }