Merge "AdaptiveIconDrawable should not update layer bounds when bound is empty" into oc-dr1-dev
diff --git a/Android.mk b/Android.mk
index 933ac62..55ea69a 100644
--- a/Android.mk
+++ b/Android.mk
@@ -292,6 +292,7 @@
 	core/java/android/service/euicc/IGetEidCallback.aidl \
 	core/java/android/service/euicc/IGetEuiccInfoCallback.aidl \
 	core/java/android/service/euicc/IGetEuiccProfileInfoListCallback.aidl \
+	core/java/android/service/euicc/IRetainSubscriptionsForFactoryResetCallback.aidl \
 	core/java/android/service/euicc/ISwitchToSubscriptionCallback.aidl \
 	core/java/android/service/euicc/IUpdateSubscriptionNicknameCallback.aidl \
 	core/java/android/service/gatekeeper/IGateKeeperService.aidl \
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index bc6e9cd..0ff3215 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -6416,17 +6416,7 @@
      */
     @Deprecated
     public boolean requestVisibleBehind(boolean visible) {
-        if (!mResumed) {
-            // Do not permit paused or stopped activities to do this.
-            visible = false;
-        }
-        try {
-            mVisibleBehind = ActivityManager.getService()
-                    .requestVisibleBehind(mToken, visible) && visible;
-        } catch (RemoteException e) {
-            mVisibleBehind = false;
-        }
-        return mVisibleBehind;
+        return false;
     }
 
     /**
diff --git a/core/java/android/content/pm/permission/IRuntimePermissionPresenter.aidl b/core/java/android/content/pm/permission/IRuntimePermissionPresenter.aidl
index 3c3b84d..9490e27 100644
--- a/core/java/android/content/pm/permission/IRuntimePermissionPresenter.aidl
+++ b/core/java/android/content/pm/permission/IRuntimePermissionPresenter.aidl
@@ -25,4 +25,5 @@
  */
 oneway interface IRuntimePermissionPresenter {
     void getAppPermissions(String packageName, in RemoteCallback callback);
+    void revokeRuntimePermission(String packageName, String permissionName);
 }
diff --git a/core/java/android/content/pm/permission/RuntimePermissionPresenter.java b/core/java/android/content/pm/permission/RuntimePermissionPresenter.java
index 6d55d2f..02d0a6d 100644
--- a/core/java/android/content/pm/permission/RuntimePermissionPresenter.java
+++ b/core/java/android/content/pm/permission/RuntimePermissionPresenter.java
@@ -22,7 +22,6 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.ServiceConnection;
-import android.content.pm.ApplicationInfo;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
@@ -31,6 +30,7 @@
 import android.os.RemoteException;
 import android.permissionpresenterservice.RuntimePermissionPresenterService;
 import android.util.Log;
+
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.os.SomeArgs;
 
@@ -118,6 +118,22 @@
         mRemoteService.processMessage(message);
     }
 
+    /**
+     * Revoke the permission {@code permissionName} for app {@code packageName}
+     *
+     * @param packageName The package for which to revoke
+     * @param permissionName The permission to revoke
+     */
+    public void revokeRuntimePermission(String packageName, String permissionName) {
+        SomeArgs args = SomeArgs.obtain();
+        args.arg1 = packageName;
+        args.arg2 = permissionName;
+
+        Message message = mRemoteService.obtainMessage(
+                RemoteService.MSG_REVOKE_APP_PERMISSIONS, args);
+        mRemoteService.processMessage(message);
+    }
+
     private static final class RemoteService
             extends Handler implements ServiceConnection {
         private static final long UNBIND_TIMEOUT_MILLIS = 10000;
@@ -125,6 +141,7 @@
         public static final int MSG_GET_APP_PERMISSIONS = 1;
         public static final int MSG_GET_APPS_USING_PERMISSIONS = 2;
         public static final int MSG_UNBIND = 3;
+        public static final int MSG_REVOKE_APP_PERMISSIONS = 4;
 
         private final Object mLock = new Object();
 
@@ -231,6 +248,25 @@
                         mRemoteInstance = null;
                     }
                 } break;
+
+                case MSG_REVOKE_APP_PERMISSIONS: {
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    final String packageName = (String) args.arg1;
+                    final String permissionName = (String) args.arg2;
+                    args.recycle();
+                    final IRuntimePermissionPresenter remoteInstance;
+                    synchronized (mLock) {
+                        remoteInstance = mRemoteInstance;
+                    }
+                    if (remoteInstance == null) {
+                        return;
+                    }
+                    try {
+                        remoteInstance.revokeRuntimePermission(packageName, permissionName);
+                    } catch (RemoteException re) {
+                        Log.e(TAG, "Error getting app permissions", re);
+                    }
+                } break;
             }
 
             synchronized (mLock) {
diff --git a/core/java/android/permissionpresenterservice/RuntimePermissionPresenterService.java b/core/java/android/permissionpresenterservice/RuntimePermissionPresenterService.java
index 344d947..2931627f 100644
--- a/core/java/android/permissionpresenterservice/RuntimePermissionPresenterService.java
+++ b/core/java/android/permissionpresenterservice/RuntimePermissionPresenterService.java
@@ -20,7 +20,6 @@
 import android.app.Service;
 import android.content.Context;
 import android.content.Intent;
-import android.content.pm.ApplicationInfo;
 import android.content.pm.permission.IRuntimePermissionPresenter;
 import android.content.pm.permission.RuntimePermissionPresentationInfo;
 import android.content.pm.permission.RuntimePermissionPresenter;
@@ -30,6 +29,7 @@
 import android.os.Looper;
 import android.os.Message;
 import android.os.RemoteCallback;
+
 import com.android.internal.os.SomeArgs;
 
 import java.util.List;
@@ -72,6 +72,16 @@
      */
     public abstract List<RuntimePermissionPresentationInfo> onGetAppPermissions(String packageName);
 
+    /**
+     * Revoke the permission {@code permissionName} for app {@code packageName}
+     *
+     * @param packageName The package for which to revoke
+     * @param permissionName The permission to revoke
+     *
+     * @hide
+     */
+    public abstract void onRevokeRuntimePermission(String packageName, String permissionName);
+
     @Override
     public final IBinder onBind(Intent intent) {
         return new IRuntimePermissionPresenter.Stub() {
@@ -83,12 +93,22 @@
                 mHandler.obtainMessage(MyHandler.MSG_GET_APP_PERMISSIONS,
                         args).sendToTarget();
             }
+
+            @Override
+            public void revokeRuntimePermission(String packageName, String permissionName) {
+                SomeArgs args = SomeArgs.obtain();
+                args.arg1 = packageName;
+                args.arg2 = permissionName;
+                mHandler.obtainMessage(MyHandler.MSG_REVOKE_APP_PERMISSION,
+                        args).sendToTarget();
+            }
         };
     }
 
     private final class MyHandler extends Handler {
         public static final int MSG_GET_APP_PERMISSIONS = 1;
         public static final int MSG_GET_APPS_USING_PERMISSIONS = 2;
+        public static final int MSG_REVOKE_APP_PERMISSION = 3;
 
         public MyHandler(Looper looper) {
             super(looper, null, false);
@@ -113,6 +133,14 @@
                         callback.sendResult(null);
                     }
                 } break;
+                case MSG_REVOKE_APP_PERMISSION: {
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    String packageName = (String) args.arg1;
+                    String permissionName = (String) args.arg2;
+                    args.recycle();
+
+                    onRevokeRuntimePermission(packageName, permissionName);
+                } break;
             }
         }
     }
diff --git a/core/java/android/service/euicc/EuiccService.java b/core/java/android/service/euicc/EuiccService.java
index 26f8528..0c2e4b7 100644
--- a/core/java/android/service/euicc/EuiccService.java
+++ b/core/java/android/service/euicc/EuiccService.java
@@ -317,6 +317,21 @@
     public abstract int onEraseSubscriptions(int slotId);
 
     /**
+     * Ensure that subscriptions will be retained on the next factory reset.
+     *
+     * <p>Called directly before a factory reset. Assumes that a normal factory reset will lead to
+     * profiles being erased on first boot (to cover fastboot/recovery wipes), so the implementation
+     * should persist some bit that will remain accessible after the factory reset to bypass this
+     * flow when this method is called.
+     *
+     * @param slotId ID of the SIM slot to use for the operation. This is currently not populated
+     *     but is here to future-proof the APIs.
+     * @return the result of the operation. May be one of the predefined {@code RESULT_} constants
+     *     or any implementation-specific code starting with {@link #RESULT_FIRST_USER}.
+     */
+    public abstract int onRetainSubscriptionsForFactoryReset(int slotId);
+
+    /**
      * Wrapper around IEuiccService that forwards calls to implementations of {@link EuiccService}.
      */
     private class IEuiccServiceWrapper extends IEuiccService.Stub {
@@ -488,5 +503,21 @@
                 }
             });
         }
+
+        @Override
+        public void retainSubscriptionsForFactoryReset(int slotId,
+                IRetainSubscriptionsForFactoryResetCallback callback) {
+            mExecutor.execute(new Runnable() {
+                @Override
+                public void run() {
+                    int result = EuiccService.this.onRetainSubscriptionsForFactoryReset(slotId);
+                    try {
+                        callback.onComplete(result);
+                    } catch (RemoteException e) {
+                        // Can't communicate with the phone process; ignore.
+                    }
+                }
+            });
+        }
     }
 }
diff --git a/core/java/android/service/euicc/IEuiccService.aidl b/core/java/android/service/euicc/IEuiccService.aidl
index 18ea915..e10dd8c 100644
--- a/core/java/android/service/euicc/IEuiccService.aidl
+++ b/core/java/android/service/euicc/IEuiccService.aidl
@@ -24,6 +24,7 @@
 import android.service.euicc.IGetEidCallback;
 import android.service.euicc.IGetEuiccInfoCallback;
 import android.service.euicc.IGetEuiccProfileInfoListCallback;
+import android.service.euicc.IRetainSubscriptionsForFactoryResetCallback;
 import android.service.euicc.ISwitchToSubscriptionCallback;
 import android.service.euicc.IUpdateSubscriptionNicknameCallback;
 import android.telephony.euicc.DownloadableSubscription;
@@ -46,4 +47,6 @@
     void updateSubscriptionNickname(int slotId, String iccid, String nickname,
             in IUpdateSubscriptionNicknameCallback callback);
     void eraseSubscriptions(int slotId, in IEraseSubscriptionsCallback callback);
+    void retainSubscriptionsForFactoryReset(
+            int slotId, in IRetainSubscriptionsForFactoryResetCallback callback);
 }
\ No newline at end of file
diff --git a/core/java/android/service/euicc/IRetainSubscriptionsForFactoryResetCallback.aidl b/core/java/android/service/euicc/IRetainSubscriptionsForFactoryResetCallback.aidl
new file mode 100644
index 0000000..1276830
--- /dev/null
+++ b/core/java/android/service/euicc/IRetainSubscriptionsForFactoryResetCallback.aidl
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.euicc;
+
+/** @hide */
+oneway interface IRetainSubscriptionsForFactoryResetCallback {
+    void onComplete(int result);
+}
\ No newline at end of file
diff --git a/core/java/android/util/Log.java b/core/java/android/util/Log.java
index 951aa8d..8691136 100644
--- a/core/java/android/util/Log.java
+++ b/core/java/android/util/Log.java
@@ -30,8 +30,9 @@
 /**
  * API for sending log output.
  *
- * <p>Generally, use the Log.v() Log.d() Log.i() Log.w() and Log.e()
- * methods.
+ * <p>Generally, you should use the {@link #v Log.v()}, {@link #d Log.d()},
+ * {@link #i Log.i()}, {@link #w Log.w()}, and {@link #e Log.e()} methods to write logs.
+ * You can then <a href="{@docRoot}studio/debug/am-logcat.html">view the logs in logcat</a>.
  *
  * <p>The order in terms of verbosity, from least to most is
  * ERROR, WARN, INFO, DEBUG, VERBOSE.  Verbose should never be compiled
diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java
index 8fe9100..544afd9 100644
--- a/core/java/com/android/internal/policy/PhoneWindow.java
+++ b/core/java/com/android/internal/policy/PhoneWindow.java
@@ -3103,12 +3103,29 @@
     }
 
     /**
+     * Check if Setup or Post-Setup update is completed on TV
+     * @return true if completed
+     */
+    private boolean isTvUserSetupComplete() {
+        boolean isTvSetupComplete = Settings.Secure.getInt(getContext().getContentResolver(),
+                Settings.Secure.USER_SETUP_COMPLETE, 0) != 0;
+        isTvSetupComplete &= Settings.Secure.getInt(getContext().getContentResolver(),
+                Settings.Secure.TV_USER_SETUP_COMPLETE, 0) != 0;
+        return isTvSetupComplete;
+    }
+
+    /**
      * Helper method for adding launch-search to most applications. Opens the
      * search window using default settings.
      *
      * @return true if search window opened
      */
     private boolean launchDefaultSearch(KeyEvent event) {
+        if (getContext().getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK)
+                && !isTvUserSetupComplete()) {
+            // If we are in Setup or Post-Setup update mode on TV, consume the search key
+            return false;
+        }
         boolean result;
         final Callback cb = getCallback();
         if (cb == null || isDestroyed()) {
diff --git a/core/java/com/android/internal/policy/PipSnapAlgorithm.java b/core/java/com/android/internal/policy/PipSnapAlgorithm.java
index 95d714f..749d00c1 100644
--- a/core/java/com/android/internal/policy/PipSnapAlgorithm.java
+++ b/core/java/com/android/internal/policy/PipSnapAlgorithm.java
@@ -49,9 +49,6 @@
     // Allows snapping on the long edge in each orientation and magnets towards corners
     private static final int SNAP_MODE_LONG_EDGE_MAGNET_CORNERS = 4;
 
-    // The friction multiplier to control how slippery the PIP is when flung
-    private static final float SCROLL_FRICTION_MULTIPLIER = 8f;
-
     // Threshold to magnet to a corner
     private static final float CORNER_MAGNET_THRESHOLD = 0.3f;
 
@@ -64,8 +61,8 @@
     private final float mDefaultSizePercent;
     private final float mMinAspectRatioForMinSize;
     private final float mMaxAspectRatioForMinSize;
+    private final int mFlingDeceleration;
 
-    private Scroller mScroller;
     private int mOrientation = Configuration.ORIENTATION_UNDEFINED;
 
     private final int mMinimizedVisibleSize;
@@ -81,6 +78,8 @@
         mMaxAspectRatioForMinSize = res.getFloat(
                 com.android.internal.R.dimen.config_pictureInPictureAspectRatioLimitForMinSize);
         mMinAspectRatioForMinSize = 1f / mMaxAspectRatioForMinSize;
+        mFlingDeceleration = mContext.getResources().getDimensionPixelSize(
+                com.android.internal.R.dimen.pip_fling_deceleration);
         onConfigurationChanged();
     }
 
@@ -107,20 +106,97 @@
      * those for the given {@param stackBounds}.
      */
     public Rect findClosestSnapBounds(Rect movementBounds, Rect stackBounds, float velocityX,
-            float velocityY) {
-        final Rect finalStackBounds = new Rect(stackBounds);
-        if (mScroller == null) {
-            final ViewConfiguration viewConfig = ViewConfiguration.get(mContext);
-            mScroller = new Scroller(mContext);
-            mScroller.setFriction(viewConfig.getScrollFriction() * SCROLL_FRICTION_MULTIPLIER);
+            float velocityY, Point dragStartPosition) {
+        final Rect intersectStackBounds = new Rect(stackBounds);
+        final Point intersect = getEdgeIntersect(stackBounds, movementBounds, velocityX, velocityY,
+                dragStartPosition);
+        intersectStackBounds.offsetTo(intersect.x, intersect.y);
+        return findClosestSnapBounds(movementBounds, intersectStackBounds);
+    }
+
+    /**
+     * @return The point along the {@param movementBounds} that the PIP would intersect with based
+     *         on the provided {@param velX}, {@param velY} along with the position of the PIP when
+     *         the gesture started, {@param dragStartPosition}.
+     */
+    public Point getEdgeIntersect(Rect stackBounds, Rect movementBounds, float velX, float velY,
+            Point dragStartPosition) {
+        final boolean isLandscape = mOrientation == Configuration.ORIENTATION_LANDSCAPE;
+        final int x = stackBounds.left;
+        final int y = stackBounds.top;
+
+        // Find the line of movement the PIP is on. Line defined by: y = slope * x + yIntercept
+        final float slope = velY / velX; // slope = rise / run
+        final float yIntercept = y - slope * x; // rearrange line equation for yIntercept
+        // The PIP can have two intercept points:
+        // 1) Where the line intersects with one of the edges of the screen (vertical line)
+        Point vertPoint = new Point();
+        // 2) Where the line intersects with the top or bottom of the screen (horizontal line)
+        Point horizPoint = new Point();
+
+        // Find the vertical line intersection, x will be one of the edges
+        vertPoint.x = velX > 0 ? movementBounds.right : movementBounds.left;
+        // Sub in x in our line equation to determine y position
+        vertPoint.y = findY(slope, yIntercept, vertPoint.x);
+
+        // Find the horizontal line intersection, y will be the top or bottom of the screen
+        horizPoint.y = velY > 0 ? movementBounds.bottom : movementBounds.top;
+        // Sub in y in our line equation to determine x position
+        horizPoint.x = findX(slope, yIntercept, horizPoint.y);
+
+        // Now pick one of these points -- first determine if we're flinging along the current edge.
+        // Only fling along current edge if it's a direction with space for the PIP to move to
+        int maxDistance;
+        if (isLandscape) {
+            maxDistance = velX > 0
+                    ? movementBounds.right - stackBounds.left
+                    : stackBounds.left - movementBounds.left;
+        } else {
+            maxDistance = velY > 0
+                    ? movementBounds.bottom - stackBounds.top
+                    : stackBounds.top - movementBounds.top;
         }
-        mScroller.fling(stackBounds.left, stackBounds.top,
-                (int) velocityX, (int) velocityY,
-                movementBounds.left, movementBounds.right,
-                movementBounds.top, movementBounds.bottom);
-        finalStackBounds.offsetTo(mScroller.getFinalX(), mScroller.getFinalY());
-        mScroller.abortAnimation();
-        return findClosestSnapBounds(movementBounds, finalStackBounds);
+        if (maxDistance > 0) {
+            // Only fling along the current edge if the start and end point are on the same side
+            final int startPoint = isLandscape ? dragStartPosition.y : dragStartPosition.x;
+            final int endPoint = isLandscape ? horizPoint.y : horizPoint.x;
+            final int center = movementBounds.centerX();
+            if ((startPoint < center && endPoint < center)
+                    || (startPoint > center && endPoint > center)) {
+                // We are flinging along the current edge, figure out how far it should travel
+                // based on velocity and assumed deceleration.
+                int distance = (int) (0 - Math.pow(isLandscape ? velX : velY, 2))
+                        / (2 * mFlingDeceleration);
+                distance = Math.min(distance, maxDistance);
+                // Adjust the point for the distance
+                if (isLandscape) {
+                    horizPoint.x = stackBounds.left + (velX > 0 ? distance : -distance);
+                } else {
+                    horizPoint.y = stackBounds.top + (velY > 0 ? distance : -distance);
+                }
+                return horizPoint;
+            }
+        }
+        // If we're not flinging along the current edge, find the closest point instead.
+        final double distanceVert = Math.hypot(vertPoint.x - x, vertPoint.y - y);
+        final double distanceHoriz = Math.hypot(horizPoint.x - x, horizPoint.y - y);
+        // Ensure that we're actually going somewhere
+        if (distanceVert == 0) {
+            return horizPoint;
+        }
+        if (distanceHoriz == 0) {
+            return vertPoint;
+        }
+        // Otherwise use the closest point
+        return Math.abs(distanceVert) > Math.abs(distanceHoriz) ? horizPoint : vertPoint;
+    }
+
+    private int findY(float slope, float yIntercept, float x) {
+        return (int) ((slope * x) + yIntercept);
+    }
+
+    private int findX(float slope, float yIntercept, float y) {
+        return (int) ((y - yIntercept) / slope);
     }
 
     /**
diff --git a/core/jni/android_os_HwBinder.cpp b/core/jni/android_os_HwBinder.cpp
index cdd3c09..6c6fa66 100644
--- a/core/jni/android_os_HwBinder.cpp
+++ b/core/jni/android_os_HwBinder.cpp
@@ -370,7 +370,7 @@
     if (transport != IServiceManager::Transport::HWBINDER && !vintfLegacy) {
         LOG(ERROR) << "service " << ifaceName << " declares transport method "
                    << toString(transport) << " but framework expects hwbinder.";
-        signalExceptionForError(env, UNKNOWN_ERROR, true /* canThrowRemoteException */);
+        signalExceptionForError(env, NAME_NOT_FOUND, true /* canThrowRemoteException */);
         return NULL;
     }
 
diff --git a/core/res/res/anim/task_close_enter.xml b/core/res/res/anim/task_close_enter.xml
index b07f470..bea0ee5 100644
--- a/core/res/res/anim/task_close_enter.xml
+++ b/core/res/res/anim/task_close_enter.xml
@@ -18,7 +18,7 @@
 -->
 
 <set xmlns:android="http://schemas.android.com/apk/res/android"
-        android:background="#ff000000" android:shareInterpolator="false" android:zAdjustment="normal">
+        android:shareInterpolator="false" android:zAdjustment="normal">
 
     <alpha android:fromAlpha="0.6" android:toAlpha="1.0"
             android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true"
diff --git a/core/res/res/anim/task_close_exit.xml b/core/res/res/anim/task_close_exit.xml
index d23c74f..b6a0807 100644
--- a/core/res/res/anim/task_close_exit.xml
+++ b/core/res/res/anim/task_close_exit.xml
@@ -18,7 +18,7 @@
 -->
 
 <set xmlns:android="http://schemas.android.com/apk/res/android"
-        android:background="#ff000000" android:shareInterpolator="false" android:zAdjustment="top">
+        android:shareInterpolator="false" android:zAdjustment="top">
 
     <alpha android:fromAlpha="1.0" android:toAlpha="0"
             android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true"
diff --git a/core/res/res/layout/notification_template_right_icon.xml b/core/res/res/layout/notification_template_right_icon.xml
index aa4e05b..fbf7538 100644
--- a/core/res/res/layout/notification_template_right_icon.xml
+++ b/core/res/res/layout/notification_template_right_icon.xml
@@ -26,8 +26,9 @@
                android:layout_gravity="top|end"
                android:layout_marginTop="36dp"
                android:layout_marginEnd="@dimen/notification_content_margin_end"
-               android:scaleType="centerCrop"/>
-    <ImageView android:id="@+id/reply_icon_action"
+               android:scaleType="centerCrop"
+               android:importantForAccessibility="no" />
+    <ImageButton android:id="@+id/reply_icon_action"
                android:layout_width="16dp"
                android:layout_height="16dp"
                android:layout_gravity="top|end"
@@ -36,6 +37,7 @@
                android:background="@drawable/notification_reply_background"
                android:src="@drawable/ic_reply_notification"
                android:scaleType="center"
+               android:contentDescription="@string/notification_reply_button_accessibility"
                visiblity="gone"/>
 </FrameLayout>
 
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index d47bde7..69bb0b9 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2983,4 +2983,7 @@
 
     <!-- Show area update info settings in CellBroadcastReceiver and information in SIM status in Settings app -->
     <bool name="config_showAreaUpdateInfoSettings">false</bool>
+
+    <!-- Enable the RingtonePickerActivity in 'com.android.providers.media'. -->
+    <bool name="config_defaultRingtonePickerEnabled">true</bool>
 </resources>
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index 5e2334d..fa33d56 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -67,6 +67,9 @@
     <!-- The amount to leave on-screen when the PIP is minimized. -->
     <dimen name="pip_minimized_visible_size">48dp</dimen>
 
+    <!-- The the PIP decelerates at while moving from a fling. -->
+    <dimen name="pip_fling_deceleration">-3000dp</dimen>
+
     <!-- Min width for a tablet device -->
     <dimen name="min_xlarge_screen_width">800dp</dimen>
 
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index ce701a7..bd8e14ea 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -4713,6 +4713,9 @@
     <!-- Primary ETWS (Earthquake and Tsunami Warning System) default message for test -->
     <string name="etws_primary_default_message_test">Emergency messages test</string>
 
+    <!-- Content description for the reply button in the notification area [CHAR LIMIT=NONE]-->
+    <string name="notification_reply_button_accessibility">Reply</string>
+
     <!-- Primary ETWS (Earthquake and Tsunami Warning System) default message for others -->
     <string name="etws_primary_default_message_others"></string>
 
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 1614efb..99807f9 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1583,6 +1583,7 @@
   <java-symbol type="dimen" name="docked_stack_divider_insets" />
   <java-symbol type="dimen" name="docked_stack_minimize_thickness" />
   <java-symbol type="dimen" name="pip_minimized_visible_size" />
+  <java-symbol type="dimen" name="pip_fling_deceleration" />
   <java-symbol type="integer" name="config_dockedStackDividerSnapMode" />
   <java-symbol type="integer" name="config_pictureInPictureSnapMode" />
   <java-symbol type="fraction" name="docked_stack_divider_fixed_ratio" />
diff --git a/libs/hwui/GlopBuilder.cpp b/libs/hwui/GlopBuilder.cpp
index 2e9a6e8..c19c1a1 100644
--- a/libs/hwui/GlopBuilder.cpp
+++ b/libs/hwui/GlopBuilder.cpp
@@ -457,11 +457,13 @@
     return *this;
 }
 
-GlopBuilder& GlopBuilder::setFillExternalTexture(Texture& texture, Matrix4& textureTransform) {
+GlopBuilder& GlopBuilder::setFillExternalTexture(Texture& texture, Matrix4& textureTransform,
+        bool requiresFilter) {
     TRIGGER_STAGE(kFillStage);
     REQUIRE_STAGES(kMeshStage | kRoundRectClipStage);
 
-    mOutGlop->fill.texture = { &texture, GL_LINEAR, GL_CLAMP_TO_EDGE, &textureTransform };
+    GLenum filter = requiresFilter ? GL_LINEAR : GL_NEAREST;
+    mOutGlop->fill.texture = { &texture, filter, GL_CLAMP_TO_EDGE, &textureTransform };
 
     setFill(SK_ColorWHITE, 1.0f, SkBlendMode::kSrc, Blend::ModeOrderSwap::NoSwap,
             nullptr, nullptr);
diff --git a/libs/hwui/GlopBuilder.h b/libs/hwui/GlopBuilder.h
index 87b1568..6d11da1 100644
--- a/libs/hwui/GlopBuilder.h
+++ b/libs/hwui/GlopBuilder.h
@@ -75,7 +75,8 @@
     GlopBuilder& setFillTextureLayer(GlLayer& layer, float alpha);
     // TODO: setFillLayer normally forces its own wrap & filter mode,
     // which isn't always correct.
-    GlopBuilder& setFillExternalTexture(Texture& texture, Matrix4& textureTransform);
+    GlopBuilder& setFillExternalTexture(Texture& texture, Matrix4& textureTransform,
+            bool requiresFilter);
 
     GlopBuilder& setTransform(const Matrix4& canvas, const int transformFlags);
 
diff --git a/libs/hwui/OpenGLReadback.cpp b/libs/hwui/OpenGLReadback.cpp
index b073070b..19d5d9d 100644
--- a/libs/hwui/OpenGLReadback.cpp
+++ b/libs/hwui/OpenGLReadback.cpp
@@ -199,6 +199,7 @@
             GL_TEXTURE_2D, texture, 0);
 
     {
+        bool requiresFilter;
         // Draw & readback
         renderState.setViewport(destWidth, destHeight);
         renderState.scissor().setEnabled(false);
@@ -216,12 +217,17 @@
             croppedTexTransform.scale(srcRect.getWidth() / sourceTexture.width(),
                     srcRect.getHeight() / sourceTexture.height(), 1);
             croppedTexTransform.multiply(sFlipV);
+            requiresFilter = srcRect.getWidth() != (float) destWidth
+                    || srcRect.getHeight() != (float) destHeight;
+        } else {
+            requiresFilter = sourceTexture.width() != (uint32_t) destWidth
+                    || sourceTexture.height() != (uint32_t) destHeight;
         }
         Glop glop;
         GlopBuilder(renderState, caches, &glop)
                 .setRoundRectClipState(nullptr)
                 .setMeshTexturedUnitQuad(nullptr)
-                .setFillExternalTexture(sourceTexture, croppedTexTransform)
+                .setFillExternalTexture(sourceTexture, croppedTexTransform, requiresFilter)
                 .setTransform(Matrix4::identity(), TransformFlags::None)
                 .setModelViewMapUnitToRect(Rect(destWidth, destHeight))
                 .build();
diff --git a/location/java/android/location/GnssStatus.java b/location/java/android/location/GnssStatus.java
index 912551f..b2903c4 100644
--- a/location/java/android/location/GnssStatus.java
+++ b/location/java/android/location/GnssStatus.java
@@ -97,13 +97,12 @@
             CONSTELLATION_QZSS, CONSTELLATION_BEIDOU, CONSTELLATION_GALILEO})
     public @interface ConstellationType {}
 
-    /* These package private values are modified by the LocationManager class */
-    /* package */ int[] mSvidWithFlags;
-    /* package */ float[] mCn0DbHz;
-    /* package */ float[] mElevations;
-    /* package */ float[] mAzimuths;
-    /* package */ int mSvCount;
-    /* package */ float[] mCarrierFrequencies;
+    final int[] mSvidWithFlags;
+    final float[] mCn0DbHz;
+    final float[] mElevations;
+    final float[] mAzimuths;
+    final int mSvCount;
+    final float[] mCarrierFrequencies;
 
     GnssStatus(int svCount, int[] svidWithFlags, float[] cn0s, float[] elevations,
             float[] azimuths, float[] carrierFrequencies) {
diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java
index 26ac2a2..968f596 100644
--- a/location/java/android/location/LocationManager.java
+++ b/location/java/android/location/LocationManager.java
@@ -74,7 +74,8 @@
             new HashMap<>();
     private final HashMap<OnNmeaMessageListener, GnssStatusListenerTransport> mGnssNmeaListeners =
             new HashMap<>();
-    private GnssStatus mGnssStatus;
+    // volatile + GnssStatus final-fields pattern to avoid a partially published object
+    private volatile GnssStatus mGnssStatus;
     private int mTimeToFirstFix;
 
     /**
@@ -1563,7 +1564,7 @@
                 float[] cn0s, float[] elevations, float[] azimuths, float[] carrierFreqs) {
             if (mGnssCallback != null) {
                 mGnssStatus = new GnssStatus(svCount, prnWithFlags, cn0s, elevations, azimuths,
-                    carrierFreqs);
+                        carrierFreqs);
 
                 Message msg = Message.obtain();
                 msg.what = GpsStatus.GPS_EVENT_SATELLITE_STATUS;
@@ -1949,7 +1950,7 @@
             status = new GpsStatus();
         }
         // When mGnssStatus is null, that means that this method is called outside
-        // onGpsStatusChanged().  Return an empty status  to maintain backwards compatibility.
+        // onGpsStatusChanged().  Return an empty status to maintain backwards compatibility.
         if (mGnssStatus != null) {
             status.setStatus(mGnssStatus, mTimeToFirstFix);
         }
diff --git a/media/java/android/media/AudioManagerInternal.java b/media/java/android/media/AudioManagerInternal.java
index 0a1de33..2b5ac5e 100644
--- a/media/java/android/media/AudioManagerInternal.java
+++ b/media/java/android/media/AudioManagerInternal.java
@@ -59,4 +59,15 @@
 
         int getRingerModeAffectedStreams(int streams);
     }
+
+    /**
+     * Disable or restore the ability to play audio for a given UID.
+     * When a UID isn't meant to be tracked anymore (e.g. client died), re-enable audio for this UID
+     * to prevent disabling audio for future UIDs that would reuse the same value.
+     * This operation is asynchronous.
+     * @param disable when true, prevents playback of audio streams from the given uid. If false,
+     *         restores the ability to play, or no-op if playback hadn't been disabled before.
+     * @param uid the client UID whose ability to play will be affected.
+     */
+    public abstract void disableAudioForUid(boolean disable, int uid);
 }
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java
index 3b29a6c..1e262314 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java
@@ -77,7 +77,7 @@
 
     private BluetoothAdapter mBluetoothAdapter;
     private WifiManager mWifiManager;
-    private BluetoothLeScanner mBLEScanner;
+    @Nullable private BluetoothLeScanner mBLEScanner;
     private ScanSettings mDefaultScanSettings = new ScanSettings.Builder().build();
 
     private List<DeviceFilter<?>> mFilters;
@@ -185,7 +185,7 @@
             mBluetoothAdapter.startDiscovery();
         }
 
-        if (shouldScan(mBLEFilters)) {
+        if (shouldScan(mBLEFilters) && mBLEScanner != null) {
             mBLEScanCallback = new BLEScanCallback();
             mBLEScanner.startScan(mBLEScanFilters, mDefaultScanSettings, mBLEScanCallback);
         }
@@ -224,7 +224,7 @@
             unregisterReceiver(mBluetoothBroadcastReceiver);
             mBluetoothBroadcastReceiver = null;
         }
-        mBLEScanner.stopScan(mBLEScanCallback);
+        if (mBLEScanner != null) mBLEScanner.stopScan(mBLEScanCallback);
         if (mWifiBroadcastReceiver != null) {
             unregisterReceiver(mWifiBroadcastReceiver);
             mWifiBroadcastReceiver = null;
diff --git a/packages/SettingsLib/src/com/android/settingslib/TronUtils.java b/packages/SettingsLib/src/com/android/settingslib/TronUtils.java
index bea6e8f..945cb57 100644
--- a/packages/SettingsLib/src/com/android/settingslib/TronUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/TronUtils.java
@@ -16,48 +16,18 @@
 package com.android.settingslib;
 
 import android.content.Context;
-import android.net.NetworkBadging;
 
 import com.android.internal.logging.MetricsLogger;
+import com.android.settingslib.wifi.AccessPoint.Speed;
 
 /** Utilites for Tron Logging. */
 public final class TronUtils {
 
+    private static final String TAG = "TronUtils";
+
     private TronUtils() {};
 
-    public static void logWifiSettingsBadge(Context context, int badgeEnum) {
-        logNetworkBadgeMetric(context, "settings_wifibadging", badgeEnum);
-    }
-
-    /**
-     * Logs an occurrence of the given network badge to a Histogram.
-     *
-     * @param context Context
-     * @param histogram the Tron histogram name to write to
-     * @param badgeEnum the {@link NetworkBadging.Badging} badge value
-     * @throws IllegalArgumentException if the given badge enum is not supported
-     */
-    private static void logNetworkBadgeMetric(
-            Context context, String histogram, int badgeEnum)
-            throws IllegalArgumentException {
-        int bucket;
-        switch (badgeEnum) {
-            case NetworkBadging.BADGING_NONE:
-                bucket = 0;
-                break;
-            case NetworkBadging.BADGING_SD:
-                bucket = 1;
-                break;
-            case NetworkBadging.BADGING_HD:
-                bucket = 2;
-                break;
-            case NetworkBadging.BADGING_4K:
-                bucket = 3;
-                break;
-            default:
-                throw new IllegalArgumentException("Unsupported badge enum: " + badgeEnum);
-        }
-
-        MetricsLogger.histogram(context, histogram, bucket);
+    public static void logWifiSettingsSpeed(Context context, @Speed int speedEnum) {
+        MetricsLogger.histogram(context, "settings_wifi_speed_labels", speedEnum);
     }
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
index d45ed19..4a54451 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
@@ -16,6 +16,7 @@
 
 package com.android.settingslib.wifi;
 
+import android.annotation.IntDef;
 import android.annotation.Nullable;
 import android.app.AppGlobals;
 import android.content.Context;
@@ -53,6 +54,8 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.settingslib.R;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.Iterator;
 import java.util.concurrent.ConcurrentHashMap;
@@ -82,35 +85,30 @@
      */
     public static final int HIGHER_FREQ_5GHZ = 5900;
 
-    /**
-     * Constant value representing an unlabeled / unscored network.
-     */
-    @VisibleForTesting
-    static final int SPEED_NONE = 0;
-
-    /**
-     * Constant value representing a slow speed network connection.
-     */
-    @VisibleForTesting
-    static final int SPEED_SLOW = 5;
-
-    /**
-     * Constant value representing a medium speed network connection.
-     */
-    @VisibleForTesting
-    static final int SPEED_MEDIUM = 10;
-
-    /**
-     * Constant value representing a fast speed network connection.
-     */
-    @VisibleForTesting
-    static final int SPEED_FAST = 20;
-
-    /**
-     * Constant value representing a very fast speed network connection.
-     */
-    @VisibleForTesting
-    static final int SPEED_VERY_FAST = 30;
+    @IntDef({Speed.NONE, Speed.SLOW, Speed.MODERATE, Speed.FAST, Speed.VERY_FAST})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Speed {
+        /**
+         * Constant value representing an unlabeled / unscored network.
+         */
+        int NONE = 0;
+        /**
+         * Constant value representing a slow speed network connection.
+         */
+        int SLOW = 5;
+        /**
+         * Constant value representing a medium speed network connection.
+         */
+        int MODERATE = 10;
+        /**
+         * Constant value representing a fast speed network connection.
+         */
+        int FAST = 20;
+        /**
+         * Constant value representing a very fast speed network connection.
+         */
+        int VERY_FAST = 30;
+    }
 
     /**
      * Experimental: we should be able to show the user the list of BSSIDs and bands
@@ -177,7 +175,7 @@
     private Object mTag;
 
     private int mRankingScore = Integer.MIN_VALUE;
-    private int mSpeed = AccessPoint.SPEED_NONE;
+    private int mSpeed = Speed.NONE;
     private boolean mIsScoredNetworkMetered = false;
 
     // used to co-relate internal vs returned accesspoint.
@@ -322,8 +320,15 @@
         if (difference != 0) {
             return difference;
         }
+
         // Sort by ssid.
-        return getSsidStr().compareToIgnoreCase(other.getSsidStr());
+        difference = getSsidStr().compareToIgnoreCase(other.getSsidStr());
+        if (difference != 0) {
+            return difference;
+        }
+
+        // Do a case sensitive comparison to distinguish SSIDs that differ in case only
+        return getSsidStr().compareTo(other.getSsidStr());
     }
 
     @Override
@@ -368,7 +373,7 @@
         if (mRankingScore != Integer.MIN_VALUE) {
             builder.append(",rankingScore=").append(mRankingScore);
         }
-        if (mSpeed != SPEED_NONE) {
+        if (mSpeed != Speed.NONE) {
             builder.append(",speed=").append(mSpeed);
         }
         builder.append(",metered=").append(isMetered());
@@ -399,7 +404,7 @@
     private boolean updateScores(WifiNetworkScoreCache scoreCache) {
         int oldSpeed = mSpeed;
         int oldRankingScore = mRankingScore;
-        mSpeed = SPEED_NONE;
+        mSpeed = Speed.NONE;
         mRankingScore = Integer.MIN_VALUE;
 
         for (ScanResult result : mScanResultCache.values()) {
@@ -675,7 +680,7 @@
         // TODO(b/62354743): Standardize and international delimiter usage
         final String concatenator = " / ";
 
-        if (mSpeed != SPEED_NONE) {
+        if (mSpeed != Speed.NONE) {
             summary.append(getSpeedLabel() + concatenator);
         }
 
@@ -799,7 +804,7 @@
             if (mRankingScore != Integer.MIN_VALUE) {
                 visibility.append(" rankingScore=").append(getRankingScore());
             }
-            if (mSpeed != SPEED_NONE) {
+            if (mSpeed != Speed.NONE) {
                 visibility.append(" speed=").append(getSpeedLabel());
             }
             visibility.append(String.format(" tx=%.1f,", mInfo.txSuccessRate));
@@ -1053,6 +1058,12 @@
         final int oldLevel = getLevel();
         if (info != null && isInfoForThisAccessPoint(config, info)) {
             updated = (mInfo == null);
+            if (mConfig != config) {
+                // We do not set updated = true as we do not want to increase the amount of sorting
+                // and copying performed in WifiTracker at this time. If issues involving refresh
+                // are still seen, we will investigate further.
+                update(config); // Notifies the AccessPointListener of the change
+            }
             if (mRssi != info.getRssi()) {
                 mRssi = info.getRssi();
                 updated = true;
@@ -1104,15 +1115,15 @@
     @Nullable
     String getSpeedLabel() {
         switch (mSpeed) {
-            case SPEED_VERY_FAST:
+            case Speed.VERY_FAST:
                 return mContext.getString(R.string.speed_label_very_fast);
-            case SPEED_FAST:
+            case Speed.FAST:
                 return mContext.getString(R.string.speed_label_fast);
-            case SPEED_MEDIUM:
+            case Speed.MODERATE:
                 return mContext.getString(R.string.speed_label_okay);
-            case SPEED_SLOW:
+            case Speed.SLOW:
                 return mContext.getString(R.string.speed_label_slow);
-            case SPEED_NONE:
+            case Speed.NONE:
             default:
                 return null;
         }
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java
index e7525f3..e5977f3 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java
@@ -161,7 +161,7 @@
             safeSetDefaultIcon();
             return;
         }
-        TronUtils.logWifiSettingsBadge(context, mWifiSpeed);
+        TronUtils.logWifiSettingsSpeed(context, mWifiSpeed);
 
         // TODO(b/62355275): Revert this to N code after deleting NetworkBadging API
         Drawable drawable = NetworkBadging.getWifiIcon(
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
index 9083d90..85a453d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
@@ -91,7 +91,7 @@
     private final boolean mIncludeScans;
     private final boolean mIncludePasspoints;
     @VisibleForTesting final MainHandler mMainHandler;
-    private final WorkHandler mWorkHandler;
+    @VisibleForTesting final WorkHandler mWorkHandler;
 
     private WifiTrackerNetworkCallback mNetworkCallback;
 
@@ -653,6 +653,7 @@
         /* sticky broadcasts can call this when wifi is disabled */
         if (!mWifiManager.isWifiEnabled()) {
             mMainHandler.sendEmptyMessage(MainHandler.MSG_PAUSE_SCANNING);
+            clearAccessPointsAndConditionallyUpdate();
             return;
         }
 
@@ -696,6 +697,17 @@
         }
     }
 
+    private void clearAccessPointsAndConditionallyUpdate() {
+        synchronized (mLock) {
+            if (!mInternalAccessPoints.isEmpty()) {
+                mInternalAccessPoints.clear();
+                if (!mMainHandler.hasMessages(MainHandler.MSG_ACCESS_POINT_CHANGED)) {
+                    mMainHandler.sendEmptyMessage(MainHandler.MSG_ACCESS_POINT_CHANGED);
+                }
+            }
+        }
+    }
+
     /**
      * Update all the internal access points rankingScores, badge and metering.
      *
@@ -720,6 +732,9 @@
 
     private void updateWifiState(int state) {
         mWorkHandler.obtainMessage(WorkHandler.MSG_UPDATE_WIFI_STATE, state, 0).sendToTarget();
+        if (!mWifiManager.isWifiEnabled()) {
+            clearAccessPointsAndConditionallyUpdate();
+        }
     }
 
     public static List<AccessPoint> getCurrentAccessPoints(Context context, boolean includeSaved,
@@ -803,8 +818,15 @@
                     mListener.onWifiStateChanged(msg.arg1);
                     break;
                 case MSG_ACCESS_POINT_CHANGED:
-                    copyAndNotifyListeners(true /*notifyListeners*/);
-                    mListener.onAccessPointsChanged();
+                    // Only notify listeners of changes if we have fresh scan results, otherwise the
+                    // UI will be updated with stale results. We want to copy the APs regardless,
+                    // for instances where forceUpdate was invoked by the caller.
+                    if (mStaleScanResults) {
+                        copyAndNotifyListeners(false /*notifyListeners*/);
+                    } else {
+                        copyAndNotifyListeners(true /*notifyListeners*/);
+                        mListener.onAccessPointsChanged();
+                    }
                     break;
                 case MSG_RESUME_SCANNING:
                     if (mScanner != null) {
@@ -828,7 +850,8 @@
         }
     }
 
-    private final class WorkHandler extends Handler {
+    @VisibleForTesting
+    final class WorkHandler extends Handler {
         private static final int MSG_UPDATE_ACCESS_POINTS = 0;
         private static final int MSG_UPDATE_NETWORK_INFO = 1;
         private static final int MSG_RESUME = 2;
diff --git a/packages/SettingsLib/tests/integ/AndroidManifest.xml b/packages/SettingsLib/tests/integ/AndroidManifest.xml
index 0d5ff2c..e8e0b41 100644
--- a/packages/SettingsLib/tests/integ/AndroidManifest.xml
+++ b/packages/SettingsLib/tests/integ/AndroidManifest.xml
@@ -21,6 +21,7 @@
     <uses-permission android:name="android.permission.MANAGE_USERS" />
     <uses-permission android:name="android.permission.MANAGE_NETWORK_POLICY"/>
     <uses-permission android:name="android.permission.SET_TIME_ZONE" />
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
     <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
     <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
 
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java
index 89328ee..5c64cff 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java
@@ -20,6 +20,8 @@
 
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
@@ -169,6 +171,21 @@
     }
 
     @Test
+    public void testCompareTo_GivesSsidCasePrecendenceAfterAlphabetical() {
+
+        final String firstName = "aaAaaa";
+        final String secondName = "aaaaaa";
+        final String thirdName = "BBBBBB";
+
+        AccessPoint firstAp = new TestAccessPointBuilder(mContext).setSsid(firstName).build();
+        AccessPoint secondAp = new TestAccessPointBuilder(mContext).setSsid(secondName).build();
+        AccessPoint thirdAp = new TestAccessPointBuilder(mContext).setSsid(thirdName).build();
+
+        assertSortingWorks(firstAp, secondAp);
+        assertSortingWorks(secondAp, thirdAp);
+    }
+
+    @Test
     public void testCompareTo_AllSortingRulesCombined() {
 
         AccessPoint active = new TestAccessPointBuilder(mContext).setActive(true).build();
@@ -334,11 +351,11 @@
 
         when(mockWifiNetworkScoreCache.getScoredNetwork(any(ScanResult.class)))
                 .thenReturn(buildScoredNetworkWithMockBadgeCurve());
-        when(mockBadgeCurve.lookupScore(anyInt())).thenReturn((byte) AccessPoint.SPEED_VERY_FAST);
+        when(mockBadgeCurve.lookupScore(anyInt())).thenReturn((byte) AccessPoint.Speed.VERY_FAST);
 
         ap.update(mockWifiNetworkScoreCache, true /* scoringUiEnabled */);
 
-        assertThat(ap.getSpeed()).isEqualTo(AccessPoint.SPEED_VERY_FAST);
+        assertThat(ap.getSpeed()).isEqualTo(AccessPoint.Speed.VERY_FAST);
         assertThat(ap.getSpeedLabel())
                 .isEqualTo(mContext.getString(R.string.speed_label_very_fast));
     }
@@ -349,11 +366,11 @@
 
         when(mockWifiNetworkScoreCache.getScoredNetwork(any(ScanResult.class)))
                 .thenReturn(buildScoredNetworkWithMockBadgeCurve());
-        when(mockBadgeCurve.lookupScore(anyInt())).thenReturn((byte) AccessPoint.SPEED_FAST);
+        when(mockBadgeCurve.lookupScore(anyInt())).thenReturn((byte) AccessPoint.Speed.FAST);
 
         ap.update(mockWifiNetworkScoreCache, true /* scoringUiEnabled */);
 
-        assertThat(ap.getSpeed()).isEqualTo(AccessPoint.SPEED_FAST);
+        assertThat(ap.getSpeed()).isEqualTo(AccessPoint.Speed.FAST);
         assertThat(ap.getSpeedLabel())
                 .isEqualTo(mContext.getString(R.string.speed_label_fast));
     }
@@ -364,11 +381,11 @@
 
         when(mockWifiNetworkScoreCache.getScoredNetwork(any(ScanResult.class)))
                 .thenReturn(buildScoredNetworkWithMockBadgeCurve());
-        when(mockBadgeCurve.lookupScore(anyInt())).thenReturn((byte) AccessPoint.SPEED_MEDIUM);
+        when(mockBadgeCurve.lookupScore(anyInt())).thenReturn((byte) AccessPoint.Speed.MODERATE);
 
         ap.update(mockWifiNetworkScoreCache, true /* scoringUiEnabled */);
 
-        assertThat(ap.getSpeed()).isEqualTo(AccessPoint.SPEED_MEDIUM);
+        assertThat(ap.getSpeed()).isEqualTo(AccessPoint.Speed.MODERATE);
         assertThat(ap.getSpeedLabel())
                 .isEqualTo(mContext.getString(R.string.speed_label_okay));
     }
@@ -379,11 +396,11 @@
 
         when(mockWifiNetworkScoreCache.getScoredNetwork(any(ScanResult.class)))
                 .thenReturn(buildScoredNetworkWithMockBadgeCurve());
-        when(mockBadgeCurve.lookupScore(anyInt())).thenReturn((byte) AccessPoint.SPEED_SLOW);
+        when(mockBadgeCurve.lookupScore(anyInt())).thenReturn((byte) AccessPoint.Speed.SLOW);
 
         ap.update(mockWifiNetworkScoreCache, true /* scoringUiEnabled */);
 
-        assertThat(ap.getSpeed()).isEqualTo(AccessPoint.SPEED_SLOW);
+        assertThat(ap.getSpeed()).isEqualTo(AccessPoint.Speed.SLOW);
         assertThat(ap.getSpeedLabel())
                 .isEqualTo(mContext.getString(R.string.speed_label_slow));
     }
@@ -394,7 +411,7 @@
 
         when(mockWifiNetworkScoreCache.getScoredNetwork(any(ScanResult.class)))
                 .thenReturn(buildScoredNetworkWithMockBadgeCurve());
-        when(mockBadgeCurve.lookupScore(anyInt())).thenReturn((byte) AccessPoint.SPEED_VERY_FAST);
+        when(mockBadgeCurve.lookupScore(anyInt())).thenReturn((byte) AccessPoint.Speed.VERY_FAST);
 
         ap.update(mockWifiNetworkScoreCache, true /* scoringUiEnabled */);
 
@@ -408,7 +425,7 @@
 
         when(mockWifiNetworkScoreCache.getScoredNetwork(any(ScanResult.class)))
                 .thenReturn(buildScoredNetworkWithMockBadgeCurve());
-        when(mockBadgeCurve.lookupScore(anyInt())).thenReturn((byte) AccessPoint.SPEED_VERY_FAST);
+        when(mockBadgeCurve.lookupScore(anyInt())).thenReturn((byte) AccessPoint.Speed.VERY_FAST);
 
         ap.update(mockWifiNetworkScoreCache, true /* scoringUiEnabled */);
 
@@ -607,4 +624,36 @@
         NetworkInfo newInfo = new NetworkInfo(networkInfo); // same values
         assertThat(ap.update(config, wifiInfo, newInfo)).isFalse();
     }
+
+    @Test
+    public void testUpdateWithConfigChangeOnly_returnsFalseButInvokesListener() {
+        int networkId = 123;
+        int rssi = -55;
+        WifiConfiguration config = new WifiConfiguration();
+        config.networkId = networkId;
+        config.numNoInternetAccessReports = 1;
+
+        WifiInfo wifiInfo = new WifiInfo();
+        wifiInfo.setNetworkId(networkId);
+        wifiInfo.setRssi(rssi);
+
+        NetworkInfo networkInfo =
+                new NetworkInfo(ConnectivityManager.TYPE_WIFI, 0 /* subtype */, "WIFI", "");
+        networkInfo.setDetailedState(NetworkInfo.DetailedState.CONNECTED, "", "");
+
+        AccessPoint ap = new TestAccessPointBuilder(mContext)
+                .setNetworkInfo(networkInfo)
+                .setNetworkId(networkId)
+                .setRssi(rssi)
+                .setWifiInfo(wifiInfo)
+                .build();
+
+        AccessPoint.AccessPointListener mockListener = mock(AccessPoint.AccessPointListener.class);
+        ap.setListener(mockListener);
+        WifiConfiguration newConfig = new WifiConfiguration(config);
+        config.validatedInternetAccess = true;
+
+        assertThat(ap.update(newConfig, wifiInfo, networkInfo)).isFalse();
+        verify(mockListener).onAccessPointChanged(ap);
+    }
 }
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java
index 340ef01..073da7e 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java
@@ -94,7 +94,7 @@
             new NetworkKey(new WifiKey('"' + SSID_1 + '"', BSSID_1));
     private static final int RSSI_1 = -30;
     private static final byte SCORE_1 = 10;
-    private static final int BADGE_1 = AccessPoint.SPEED_MEDIUM;
+    private static final int BADGE_1 = AccessPoint.Speed.MODERATE;
 
     private static final String SSID_2 = "ssid2";
     private static final String BSSID_2 = "AA:AA:AA:AA:AA:AA";
@@ -102,7 +102,7 @@
             new NetworkKey(new WifiKey('"' + SSID_2 + '"', BSSID_2));
     private static final int RSSI_2 = -30;
     private static final byte SCORE_2 = 15;
-    private static final int BADGE_2 = AccessPoint.SPEED_FAST;
+    private static final int BADGE_2 = AccessPoint.Speed.FAST;
 
     private static final int CONNECTED_NETWORK_ID = 123;
     private static final int CONNECTED_RSSI = -50;
@@ -256,6 +256,7 @@
         }
 
         sendScanResultsAndProcess(tracker);
+        waitForHandlersToProcessCurrentlyEnqueuedMessages(tracker);
 
         return tracker;
     }
@@ -341,6 +342,23 @@
         return createTrackerWithImmediateBroadcastsAndInjectInitialScanResults(intent);
     }
 
+    private void waitForHandlersToProcessCurrentlyEnqueuedMessages(WifiTracker tracker)
+            throws InterruptedException {
+        CountDownLatch workerLatch = new CountDownLatch(1);
+        tracker.mWorkHandler.post(() -> {
+            workerLatch.countDown();
+        });
+        assertTrue("Latch timed out while waiting for WorkerHandler",
+                workerLatch.await(LATCH_TIMEOUT, TimeUnit.MILLISECONDS));
+
+        CountDownLatch mainLatch = new CountDownLatch(1);
+        tracker.mMainHandler.post(() -> {
+            mainLatch.countDown();
+        });
+        assertTrue("Latch timed out while waiting for MainHandler",
+                mainLatch.await(LATCH_TIMEOUT, TimeUnit.MILLISECONDS));
+    }
+
     @Test
     public void testAccessPointListenerSetWhenLookingUpUsingScanResults() {
         ScanResult scanResult = new ScanResult();
@@ -426,7 +444,7 @@
 
     @Test
     public void startTrackingShouldSetConnectedAccessPointAsActive() throws InterruptedException {
-        WifiTracker tracker =  createTrackerWithScanResultsAndAccessPoint1Connected();
+        WifiTracker tracker = createTrackerWithScanResultsAndAccessPoint1Connected();
 
         List<AccessPoint> aps = tracker.getAccessPoints();
 
@@ -460,16 +478,18 @@
         startTracking(tracker);
         sendScanResultsAndProcess(tracker);
 
-        updateScoresAndWaitForAccessPointsChangedCallback();
+        updateScoresAndWaitForAccessPointsChangedCallback(tracker);
     }
 
-    private void updateScoresAndWaitForAccessPointsChangedCallback() throws InterruptedException {
+    private void updateScoresAndWaitForAccessPointsChangedCallback(WifiTracker tracker)
+            throws InterruptedException {
         // Updating scores can happen together or one after the other, so the latch countdown is set
         // to 2.
-        mAccessPointsChangedLatch = new CountDownLatch(2);
+        mAccessPointsChangedLatch = new CountDownLatch(1);
         updateScores();
-        assertTrue("onAccessPointChanged was not called twice",
+        assertTrue("onAccessPointChanged was not called after updating scores",
             mAccessPointsChangedLatch.await(LATCH_TIMEOUT, TimeUnit.MILLISECONDS));
+        waitForHandlersToProcessCurrentlyEnqueuedMessages(tracker);
     }
 
     @Test
@@ -480,7 +500,7 @@
         assertEquals(aps.get(0).getSsidStr(), SSID_1);
         assertEquals(aps.get(1).getSsidStr(), SSID_2);
 
-        updateScoresAndWaitForAccessPointsChangedCallback();
+        updateScoresAndWaitForAccessPointsChangedCallback(tracker);
 
         aps = tracker.getAccessPoints();
         assertTrue(aps.size() == 2);
@@ -502,7 +522,7 @@
         assertEquals(aps.get(0).getSsidStr(), SSID_1);
         assertEquals(aps.get(1).getSsidStr(), SSID_2);
 
-        updateScoresAndWaitForAccessPointsChangedCallback();
+        updateScoresAndWaitForAccessPointsChangedCallback(tracker);
 
         aps = tracker.getAccessPoints();
         assertTrue(aps.size() == 2);
@@ -514,7 +534,7 @@
     public void scoreCacheUpdateScoresShouldInsertSpeedIntoAccessPoint()
             throws InterruptedException {
         WifiTracker tracker = createTrackerWithImmediateBroadcastsAndInjectInitialScanResults();
-        updateScoresAndWaitForAccessPointsChangedCallback();
+        updateScoresAndWaitForAccessPointsChangedCallback(tracker);
 
         List<AccessPoint> aps = tracker.getAccessPoints();
 
@@ -531,7 +551,7 @@
     public void scoreCacheUpdateMeteredShouldUpdateAccessPointMetering()
             throws InterruptedException {
         WifiTracker tracker = createTrackerWithImmediateBroadcastsAndInjectInitialScanResults();
-        updateScoresAndWaitForAccessPointsChangedCallback();
+        updateScoresAndWaitForAccessPointsChangedCallback(tracker);
 
         List<AccessPoint> aps = tracker.getAccessPoints();
 
@@ -553,15 +573,15 @@
                 0 /* disabled */);
 
         WifiTracker tracker = createTrackerWithImmediateBroadcastsAndInjectInitialScanResults();
-        updateScoresAndWaitForAccessPointsChangedCallback();
+        updateScoresAndWaitForAccessPointsChangedCallback(tracker);
 
         List<AccessPoint> aps = tracker.getAccessPoints();
 
         for (AccessPoint ap : aps) {
             if (ap.getSsidStr().equals(SSID_1)) {
-                assertEquals(AccessPoint.SPEED_NONE, ap.getSpeed());
+                assertEquals(AccessPoint.Speed.NONE, ap.getSpeed());
             } else if (ap.getSsidStr().equals(SSID_2)) {
-                assertEquals(AccessPoint.SPEED_NONE, ap.getSpeed());
+                assertEquals(AccessPoint.Speed.NONE, ap.getSpeed());
             }
         }
     }
@@ -754,13 +774,10 @@
             throws Exception {
         WifiTracker tracker = createMockedWifiTracker();
         startTracking(tracker);
-        tracker.stopTracking();
+        waitForHandlersToProcessCurrentlyEnqueuedMessages(tracker);
 
-        CountDownLatch latch1 = new CountDownLatch(1);
-        tracker.mMainHandler.post(() -> {
-                latch1.countDown();
-        });
-        assertTrue("Latch 1 timed out", latch1.await(LATCH_TIMEOUT, TimeUnit.MILLISECONDS));
+        tracker.stopTracking();
+        waitForHandlersToProcessCurrentlyEnqueuedMessages(tracker);
 
         startTracking(tracker);
 
@@ -770,14 +787,24 @@
         tracker.mReceiver.onReceive(
                 mContext, new Intent(WifiManager.LINK_CONFIGURATION_CHANGED_ACTION));
 
-        CountDownLatch latch2 = new CountDownLatch(1);
-        tracker.mMainHandler.post(() -> {
-            latch2.countDown();
-        });
-        assertTrue("Latch 2 timed out", latch2.await(LATCH_TIMEOUT, TimeUnit.MILLISECONDS));
+        waitForHandlersToProcessCurrentlyEnqueuedMessages(tracker);
 
         verify(mockWifiListener, never()).onAccessPointsChanged();
 
         sendScanResultsAndProcess(tracker); // verifies onAccessPointsChanged is invoked
     }
+
+    @Test
+    public void disablingWifiShouldClearExistingAccessPoints() throws Exception {
+        WifiTracker tracker = createTrackerWithScanResultsAndAccessPoint1Connected();
+
+        when(mockWifiManager.isWifiEnabled()).thenReturn(false);
+        mAccessPointsChangedLatch = new CountDownLatch(1);
+        tracker.mReceiver.onReceive(mContext, new Intent(WifiManager.WIFI_STATE_CHANGED_ACTION));
+
+        mAccessPointsChangedLatch.await(LATCH_TIMEOUT, TimeUnit.MILLISECONDS);
+        waitForHandlersToProcessCurrentlyEnqueuedMessages(tracker);
+
+        assertThat(tracker.getAccessPoints()).isEmpty();
+    }
 }
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginActivity.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginActivity.java
new file mode 100644
index 0000000..925214e
--- /dev/null
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginActivity.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui.plugins;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.res.Resources;
+import android.os.Bundle;
+
+import com.android.systemui.plugins.annotations.ProvidesInterface;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * A PluginActivity is an activity that replaces another full activity (e.g. RecentsActivity)
+ * at runtime within the sysui process.
+ */
+@ProvidesInterface(version = PluginActivity.VERSION)
+public abstract class PluginActivity extends Activity implements Plugin {
+
+    public static final int VERSION = 1;
+
+    public static final String ACTION_RECENTS = "com.android.systemui.action.PLUGIN_RECENTS";
+
+    private Context mSysuiContext;
+    private boolean mSettingActionBar;
+
+    @Override
+    public final void onCreate(Context sysuiContext, Context pluginContext) {
+        mSysuiContext = sysuiContext;
+        super.attachBaseContext(pluginContext);
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        Theme theme = getClass().getDeclaredAnnotation(Theme.class);
+        if (theme != null && theme.value() != 0) {
+            setTheme(theme.value());
+        }
+        mSettingActionBar = true;
+        getActionBar();
+        mSettingActionBar = false;
+    }
+
+    @Override
+    public Resources getResources() {
+        return mSettingActionBar ? mSysuiContext.getResources() : super.getResources();
+    }
+
+    @Override
+    protected void attachBaseContext(Context newBase) {
+        mSysuiContext = newBase;
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+    }
+
+    public Context getSysuiContext() {
+        return mSysuiContext;
+    }
+
+    public Context getPluginContext() {
+        return getBaseContext();
+    }
+
+    /**
+     * Since PluginActivities are declared as services instead of activities (since they
+     * are plugins), they can't have a theme attached to them. Instead a PluginActivity
+     * can annotate itself with @Theme to specify the resource of the style it wants
+     * to be themed with.
+     */
+    @Retention(RetentionPolicy.RUNTIME)
+    public @interface Theme {
+        int value();
+    }
+}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginDependency.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginDependency.java
index 25ce3dd..db2e376 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginDependency.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginDependency.java
@@ -21,6 +21,11 @@
     public static final int VERSION = 1;
     static DependencyProvider sProvider;
 
+    /**
+     * Allows a plugin to get a hold of static dependencies if they have declared dependence
+     * on their interface. For one-shot plugins this will only work during onCreate and will
+     * not work afterwards.
+     */
     public static <T> T get(Plugin p, Class<T> cls) {
         return sProvider.get(p, cls);
     }
diff --git a/packages/SystemUI/res/drawable/recents_dismiss_dark.xml b/packages/SystemUI/res/drawable/recents_dismiss_dark.xml
index 951269b..b837ebe 100644
--- a/packages/SystemUI/res/drawable/recents_dismiss_dark.xml
+++ b/packages/SystemUI/res/drawable/recents_dismiss_dark.xml
@@ -14,11 +14,16 @@
     limitations under the License.
 -->
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="48.0dp"
-        android:height="48.0dp"
-        android:viewportWidth="48.0"
-        android:viewportHeight="48.0">
-    <path
-        android:fillColor="@color/recents_task_bar_dark_icon_color"
-        android:pathData="M38.000000,12.800000l-2.799999,-2.800000 -11.200001,11.200001 -11.200000,-11.200001 -2.800000,2.800000 11.200001,11.200000 -11.200001,11.200001 2.800000,2.799999 11.200000,-11.200001 11.200001,11.200001 2.799999,-2.799999 -11.200001,-11.200001z"/>
+android:width="24dp"
+android:height="24dp"
+android:viewportWidth="24"
+android:viewportHeight="24">
+
+<path
+    android:fillColor="@color/recents_task_bar_dark_icon_color"
+    android:pathData="M18.3 5.71a.996 .996 0 0 0-1.41 0L12 10.59 7.11 5.7A.996 .996 0 1 0 5.7
+7.11L10.59 12 5.7 16.89a.996 .996 0 1 0 1.41 1.41L12 13.41l4.89 4.89a.996 .996 0
+1 0 1.41-1.41L13.41 12l4.89-4.89c.38-.38 .38 -1.02 0-1.4z" />
+<path
+    android:pathData="M0 0h24v24H0z" />
 </vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/recents_dismiss_light.xml b/packages/SystemUI/res/drawable/recents_dismiss_light.xml
index 1f44c1c..2b20814 100644
--- a/packages/SystemUI/res/drawable/recents_dismiss_light.xml
+++ b/packages/SystemUI/res/drawable/recents_dismiss_light.xml
@@ -14,11 +14,16 @@
     limitations under the License.
 -->
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="48.0dp"
-        android:height="48.0dp"
-        android:viewportWidth="48.0"
-        android:viewportHeight="48.0">
-    <path
-        android:fillColor="@color/recents_task_bar_light_icon_color"
-        android:pathData="M38.000000,12.800000l-2.799999,-2.800000 -11.200001,11.200001 -11.200000,-11.200001 -2.800000,2.800000 11.200001,11.200000 -11.200001,11.200001 2.800000,2.799999 11.200000,-11.200001 11.200001,11.200001 2.799999,-2.799999 -11.200001,-11.200001z"/>
+android:width="24dp"
+android:height="24dp"
+android:viewportWidth="24"
+android:viewportHeight="24">
+
+<path
+    android:fillColor="@color/recents_task_bar_light_icon_color"
+    android:pathData="M18.3 5.71a.996 .996 0 0 0-1.41 0L12 10.59 7.11 5.7A.996 .996 0 1 0 5.7
+7.11L10.59 12 5.7 16.89a.996 .996 0 1 0 1.41 1.41L12 13.41l4.89 4.89a.996 .996 0
+1 0 1.41-1.41L13.41 12l4.89-4.89c.38-.38 .38 -1.02 0-1.4z" />
+<path
+    android:pathData="M0 0h24v24H0z" />
 </vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/back.xml b/packages/SystemUI/res/layout/back.xml
index 4e8726b..43bec91 100644
--- a/packages/SystemUI/res/layout/back.xml
+++ b/packages/SystemUI/res/layout/back.xml
@@ -22,8 +22,10 @@
     android:layout_height="match_parent"
     android:layout_weight="0"
     systemui:keyCode="4"
-    android:scaleType="center"
+    android:scaleType="fitCenter"
     android:contentDescription="@string/accessibility_back"
+    android:paddingTop="15dp"
+    android:paddingBottom="15dp"
     android:paddingStart="@dimen/navigation_key_padding"
     android:paddingEnd="@dimen/navigation_key_padding"
     />
diff --git a/packages/SystemUI/res/layout/home.xml b/packages/SystemUI/res/layout/home.xml
index 9586327..53ef2ab 100644
--- a/packages/SystemUI/res/layout/home.xml
+++ b/packages/SystemUI/res/layout/home.xml
@@ -21,8 +21,10 @@
     android:layout_height="match_parent"
     android:layout_weight="0"
     systemui:keyCode="3"
-    android:scaleType="center"
+    android:scaleType="fitCenter"
     android:contentDescription="@string/accessibility_home"
+    android:paddingTop="13dp"
+    android:paddingBottom="13dp"
     android:paddingStart="@dimen/navigation_key_padding"
     android:paddingEnd="@dimen/navigation_key_padding"
     />
diff --git a/packages/SystemUI/res/layout/recent_apps.xml b/packages/SystemUI/res/layout/recent_apps.xml
index 870bcf7..c84d280 100644
--- a/packages/SystemUI/res/layout/recent_apps.xml
+++ b/packages/SystemUI/res/layout/recent_apps.xml
@@ -21,8 +21,10 @@
     android:layout_width="@dimen/navigation_key_width"
     android:layout_height="match_parent"
     android:layout_weight="0"
-    android:scaleType="center"
+    android:scaleType="fitCenter"
     android:contentDescription="@string/accessibility_recent"
+    android:paddingTop="15dp"
+    android:paddingBottom="15dp"
     android:paddingStart="@dimen/navigation_key_padding"
     android:paddingEnd="@dimen/navigation_key_padding"
     />
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 93d2072..4a1d953 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -827,7 +827,4 @@
     <!-- How far to inset the rounded edges -->
     <dimen name="stat_sys_mobile_signal_circle_inset">0.9dp</dimen>
 
-    <!-- Width of the hollow triangle for empty signal state -->
-    <dimen name="mobile_signal_empty_strokewidth">2dp</dimen>
-
 </resources>
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index 1b694b3..bb44123 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -31,6 +31,7 @@
 import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.fragments.FragmentService;
 import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.plugins.PluginActivityManager;
 import com.android.systemui.plugins.PluginDependencyProvider;
 import com.android.systemui.plugins.PluginManager;
 import com.android.systemui.plugins.PluginManagerImpl;
@@ -276,6 +277,9 @@
 
         mProviders.put(UiOffloadThread.class, UiOffloadThread::new);
 
+        mProviders.put(PluginActivityManager.class,
+                () -> new PluginActivityManager(mContext, getDependency(PluginManager.class)));
+
         // Put all dependencies above here so the factory can override them if it wants.
         SystemUIFactory.getInstance().injectDependencies(mProviders, mContext);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
index 4a45997..fe6eb4b 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui;
 
+import android.app.Activity;
 import android.app.ActivityThread;
 import android.app.Application;
 import android.content.BroadcastReceiver;
@@ -39,6 +40,7 @@
 import com.android.systemui.plugins.GlobalActions;
 import com.android.systemui.plugins.OverlayPlugin;
 import com.android.systemui.plugins.Plugin;
+import com.android.systemui.plugins.PluginActivityManager;
 import com.android.systemui.plugins.PluginListener;
 import com.android.systemui.plugins.PluginManager;
 import com.android.systemui.power.PowerUI;
@@ -266,4 +268,10 @@
     public SystemUI[] getServices() {
         return mServices;
     }
+
+    @Override
+    public Activity instantiateActivity(ClassLoader cl, String className, Intent intent) {
+        if (!mServicesStarted) return null;
+        return Dependency.get(PluginActivityManager.class).instantiate(cl, className, intent);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
index ae936db..8d1d6e0 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
@@ -39,6 +39,7 @@
 import com.android.systemui.util.wakelock.WakeLock;
 
 import java.io.PrintWriter;
+import java.util.function.IntConsumer;
 
 /**
  * Handles triggers for ambient state changes.
@@ -98,18 +99,44 @@
         requestPulse(DozeLog.PULSE_REASON_NOTIFICATION, false /* performedProxCheck */);
     }
 
+    private void proximityCheckThenCall(IntConsumer callback,
+            boolean alreadyPerformedProxCheck,
+            int pulseReason) {
+        if (alreadyPerformedProxCheck) {
+            callback.accept(ProximityCheck.RESULT_NOT_CHECKED);
+        } else {
+            final long start = SystemClock.uptimeMillis();
+            new ProximityCheck() {
+                @Override
+                public void onProximityResult(int result) {
+                    final long end = SystemClock.uptimeMillis();
+                    DozeLog.traceProximityResult(mContext, result == RESULT_NEAR,
+                            end - start, pulseReason);
+                    callback.accept(result);
+                }
+            }.check();
+        }
+    }
+
     private void onSensor(int pulseReason, boolean sensorPerformedProxCheck,
             float screenX, float screenY) {
         boolean isDoubleTap = pulseReason == DozeLog.PULSE_REASON_SENSOR_DOUBLE_TAP;
         boolean isPickup = pulseReason == DozeLog.PULSE_REASON_SENSOR_PICKUP;
 
         if (mConfig.alwaysOnEnabled(UserHandle.USER_CURRENT)) {
-            if (isDoubleTap) {
-                mDozeHost.onDoubleTap(screenX, screenY);
-                mMachine.wakeUp();
-            } else {
-                mDozeHost.extendPulse();
-            }
+            proximityCheckThenCall((result) -> {
+                if (result == ProximityCheck.RESULT_NEAR) {
+                    // In pocket, drop event.
+                    return;
+                }
+                if (isDoubleTap) {
+                    mDozeHost.onDoubleTap(screenX, screenY);
+                    mMachine.wakeUp();
+                } else {
+                    mDozeHost.extendPulse();
+                }
+            }, sensorPerformedProxCheck, pulseReason);
+            return;
         } else {
             requestPulse(pulseReason, sensorPerformedProxCheck);
         }
@@ -158,13 +185,16 @@
                 break;
             case DOZE:
             case DOZE_AOD:
-            case DOZE_AOD_PAUSED:
                 mDozeSensors.setProxListening(newState != DozeMachine.State.DOZE);
                 mDozeSensors.setListening(true);
                 if (oldState != DozeMachine.State.INITIALIZED) {
                     mDozeSensors.reregisterAllSensors();
                 }
                 break;
+            case DOZE_AOD_PAUSED:
+                mDozeSensors.setProxListening(true);
+                mDozeSensors.setListening(false);
+                break;
             case DOZE_PULSING:
                 mDozeSensors.setProxListening(true);
                 break;
@@ -199,33 +229,15 @@
         }
 
         mPulsePending = true;
-        if (!mDozeParameters.getProxCheckBeforePulse() || performedProxCheck) {
-            // skip proximity check
-            continuePulseRequest(reason);
-            return;
-        }
-
-        final long start = SystemClock.uptimeMillis();
-        new ProximityCheck() {
-            @Override
-            public void onProximityResult(int result) {
-                final long end = SystemClock.uptimeMillis();
-                DozeLog.traceProximityResult(mContext, result == RESULT_NEAR,
-                        end - start, reason);
-                if (performedProxCheck) {
-                    // we already continued
-                    return;
-                }
-                // avoid pulsing in pockets
-                if (result == RESULT_NEAR) {
-                    mPulsePending = false;
-                    return;
-                }
-
-                // not in-pocket, continue pulsing
+        proximityCheckThenCall((result) -> {
+            if (result == ProximityCheck.RESULT_NEAR) {
+                // in pocket, abort pulse
+                mPulsePending = false;
+            } else {
+                // not in pocket, continue pulsing
                 continuePulseRequest(reason);
             }
-        }.check();
+        }, !mDozeParameters.getProxCheckBeforePulse() || performedProxCheck, reason);
     }
 
     private boolean canPulse() {
@@ -259,6 +271,7 @@
         protected static final int RESULT_UNKNOWN = 0;
         protected static final int RESULT_NEAR = 1;
         protected static final int RESULT_FAR = 2;
+        protected static final int RESULT_NOT_CHECKED = 3;
 
         private boolean mRegistered;
         private boolean mFinished;
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
index 80a6418..1a8a474 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
@@ -339,6 +339,7 @@
                     long id) {
                 final Action action = mAdapter.getItem(position);
                 if (action instanceof LongPressAction) {
+                    mDialog.dismiss();
                     return ((LongPressAction) action).onLongPress();
                 }
                 return false;
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java
index b8771d7..cebb22f 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java
@@ -241,14 +241,14 @@
     /**
      * Flings the minimized PiP to the closest minimized snap target.
      */
-    Rect flingToMinimizedState(float velocityY, Rect movementBounds) {
+    Rect flingToMinimizedState(float velocityY, Rect movementBounds, Point dragStartPosition) {
         cancelAnimations();
         // We currently only allow flinging the minimized stack up and down, so just lock the
         // movement bounds to the current stack bounds horizontally
         movementBounds = new Rect(mBounds.left, movementBounds.top, mBounds.left,
                 movementBounds.bottom);
         Rect toBounds = mSnapAlgorithm.findClosestSnapBounds(movementBounds, mBounds,
-                0 /* velocityX */, velocityY);
+                0 /* velocityX */, velocityY, dragStartPosition);
         if (!mBounds.equals(toBounds)) {
             mBoundsAnimator = createAnimationToBounds(mBounds, toBounds, 0, FAST_OUT_SLOW_IN);
             mFlingAnimationUtils.apply(mBoundsAnimator, 0,
@@ -281,10 +281,11 @@
      * Flings the PiP to the closest snap target.
      */
     Rect flingToSnapTarget(float velocity, float velocityX, float velocityY, Rect movementBounds,
-            AnimatorUpdateListener updateListener, AnimatorListener listener) {
+            AnimatorUpdateListener updateListener, AnimatorListener listener,
+            Point startPosition) {
         cancelAnimations();
         Rect toBounds = mSnapAlgorithm.findClosestSnapBounds(movementBounds, mBounds,
-                velocityX, velocityY);
+                velocityX, velocityY, startPosition);
         if (!mBounds.equals(toBounds)) {
             mBoundsAnimator = createAnimationToBounds(mBounds, toBounds, 0, FAST_OUT_SLOW_IN);
             mFlingAnimationUtils.apply(mBoundsAnimator, 0,
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 3682ae6..9588b03 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
@@ -185,7 +185,7 @@
         mDismissViewController = new PipDismissViewController(context);
         mSnapAlgorithm = new PipSnapAlgorithm(mContext);
         mTouchState = new PipTouchState(mViewConfig);
-        mFlingAnimationUtils = new FlingAnimationUtils(context, 2f);
+        mFlingAnimationUtils = new FlingAnimationUtils(context, 2.5f);
         mGestures = new PipTouchGesture[] {
                 mDefaultMovementGesture
         };
@@ -534,6 +534,7 @@
     private PipTouchGesture mDefaultMovementGesture = new PipTouchGesture() {
         // Whether the PiP was on the left side of the screen at the start of the gesture
         private boolean mStartedOnLeft;
+        private Point mStartPosition;
 
         @Override
         public void onDown(PipTouchState touchState) {
@@ -541,7 +542,9 @@
                 return;
             }
 
-            mStartedOnLeft = mMotionHelper.getBounds().left < mMovementBounds.centerX();
+            Rect bounds = mMotionHelper.getBounds();
+            mStartPosition = new Point(bounds.left, bounds.top);
+            mStartedOnLeft = bounds.left < mMovementBounds.centerX();
             mMovementWithinMinimize = true;
             mMovementWithinDismiss = touchState.getDownTouchPosition().y >= mMovementBounds.bottom;
 
@@ -687,7 +690,8 @@
 
                 if (isFling) {
                     mMotionHelper.flingToSnapTarget(velocity, vel.x, vel.y, mMovementBounds,
-                            mUpdateScrimListener, postAnimationCallback);
+                            mUpdateScrimListener, postAnimationCallback,
+                            mStartPosition);
                 } else {
                     mMotionHelper.animateToClosestSnapTarget(mMovementBounds, mUpdateScrimListener,
                             postAnimationCallback);
diff --git a/packages/SystemUI/src/com/android/systemui/plugins/PluginActivityManager.java b/packages/SystemUI/src/com/android/systemui/plugins/PluginActivityManager.java
new file mode 100644
index 0000000..9becc38
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/plugins/PluginActivityManager.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui.plugins;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+
+public class PluginActivityManager {
+
+    private final Context mContext;
+    private final PluginManager mPluginManager;
+    private final ArrayMap<String, String> mActionLookup = new ArrayMap<>();
+
+    public PluginActivityManager(Context context, PluginManager pluginManager) {
+        mContext = context;
+        mPluginManager = pluginManager;
+    }
+
+    public void addActivityPlugin(String className, String action) {
+        mActionLookup.put(className, action);
+    }
+
+    public Activity instantiate(ClassLoader cl, String className, Intent intent) {
+        String action = mActionLookup.get(className);
+        if (TextUtils.isEmpty(action)) return null;
+        return mPluginManager.getOneShotPlugin(action, PluginActivity.class);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/plugins/PluginManagerImpl.java b/packages/SystemUI/src/com/android/systemui/plugins/PluginManagerImpl.java
index 493d244..a968399 100644
--- a/packages/SystemUI/src/com/android/systemui/plugins/PluginManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/plugins/PluginManagerImpl.java
@@ -42,7 +42,6 @@
 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
 import com.android.systemui.Dependency;
 import com.android.systemui.plugins.PluginInstanceManager.PluginContextWrapper;
-import com.android.systemui.plugins.PluginInstanceManager.PluginInfo;
 import com.android.systemui.plugins.annotations.ProvidesInterface;
 
 import dalvik.system.PathClassLoader;
@@ -120,14 +119,21 @@
         }
         PluginInstanceManager<T> p = mFactory.createPluginInstanceManager(mContext, action, null,
                 false, mLooper, cls, this);
+        PluginListener<Plugin> listener = new PluginListener<Plugin>() {
+            @Override
+            public void onPluginConnected(Plugin plugin, Context pluginContext) { }
+        };
+        mPluginMap.put(listener, p);
         mPluginPrefs.addAction(action);
-        PluginInfo<T> info = p.getPlugin();
+        PluginInstanceManager.PluginInfo<T> info = p.getPlugin();
         if (info != null) {
             mOneShotPackages.add(info.mPackage);
             mHasOneShot = true;
             startListening();
+            mPluginMap.remove(listener);
             return info.mPlugin;
         }
+        mPluginMap.remove(listener);
         return null;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Recents.java b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
index 9ba32b3..de2ace4 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Recents.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
@@ -43,11 +43,14 @@
 
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.systemui.Dependency;
 import com.android.systemui.EventLogConstants;
 import com.android.systemui.EventLogTags;
 import com.android.systemui.R;
 import com.android.systemui.RecentsComponent;
 import com.android.systemui.SystemUI;
+import com.android.systemui.plugins.PluginActivity;
+import com.android.systemui.plugins.PluginActivityManager;
 import com.android.systemui.recents.events.EventBus;
 import com.android.systemui.recents.events.activity.ConfigurationChangedEvent;
 import com.android.systemui.recents.events.activity.DockedTopTaskEvent;
@@ -234,6 +237,8 @@
             registerWithSystemUser();
         }
         putComponent(Recents.class, this);
+        Dependency.get(PluginActivityManager.class).addActivityPlugin(RecentsImpl.RECENTS_ACTIVITY,
+                PluginActivity.ACTION_RECENTS);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
index eec818b..29b720c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
@@ -943,6 +943,10 @@
         }
     }
 
+    public boolean isDrawingAppearAnimation() {
+        return mDrawingAppearAnimation;
+    }
+
     @Override
     protected void dispatchDraw(Canvas canvas) {
         if (mDrawingAppearAnimation) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
index 6abf35f..ed4f685 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
@@ -756,6 +756,19 @@
         return getShowingLayout().getVisibleNotificationHeader();
     }
 
+
+    /**
+     * @return the contracted notification header. This can be different from
+     * {@link #getNotificationHeader()} and also {@link #getVisibleNotificationHeader()} and only
+     * returns the contracted version.
+     */
+    public NotificationHeaderView getContractedNotificationHeader() {
+        if (mIsSummaryWithChildren) {
+            return mChildrenContainer.getHeaderView();
+        }
+        return mPrivateLayout.getContractedNotificationHeader();
+    }
+
     public void setOnExpandClickListener(OnExpandClickListener onExpandClickListener) {
         mOnExpandClickListener = onExpandClickListener;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
index 75704b16..9e059c89 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
@@ -1308,6 +1308,14 @@
         return header;
     }
 
+
+    public NotificationHeaderView getContractedNotificationHeader() {
+        if (mContractedChild != null) {
+            return mContractedWrapper.getNotificationHeader();
+        }
+        return null;
+    }
+
     public NotificationHeaderView getVisibleNotificationHeader() {
         NotificationViewWrapper wrapper = getVisibleWrapper(mVisibleType);
         return wrapper == null ? null : wrapper.getNotificationHeader();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationHeaderUtil.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationHeaderUtil.java
index 7f95d48..4301817 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationHeaderUtil.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationHeaderUtil.java
@@ -267,9 +267,10 @@
             if (!mApply) {
                 return;
             }
-            NotificationHeaderView header = row.getNotificationHeader();
+            NotificationHeaderView header = row.getContractedNotificationHeader();
             if (header == null) {
-                mApply = false;
+                // No header found. We still consider this to be the same to avoid weird flickering
+                // when for example showing an undo notification
                 return;
             }
             Object childData = mExtractor == null ? null : mExtractor.extractData(row);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index 97e2f6d..a601028 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -16,15 +16,18 @@
 
 package com.android.systemui.statusbar;
 
+import static com.android.systemui.statusbar.phone.NotificationIconContainer.IconState.NO_VALUE;
+import static com.android.systemui.statusbar.phone.NotificationIconContainer.OVERFLOW_EARLY_AMOUNT;
+
 import android.content.Context;
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.os.SystemProperties;
 import android.util.AttributeSet;
-import android.util.Log;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.accessibility.AccessibilityNodeInfo;
+
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
 import com.android.systemui.ViewInvertHelper;
@@ -315,26 +318,65 @@
     private float updateIconAppearance(ExpandableNotificationRow row, float expandAmount,
             boolean scrolling, boolean scrollingFast, boolean expandingAnimated,
             boolean isLastChild) {
+        StatusBarIconView icon = row.getEntry().expandedIcon;
+        NotificationIconContainer.IconState iconState = getIconState(icon);
+        if (iconState == null) {
+            return 0.0f;
+        }
+
         // Let calculate how much the view is in the shelf
         float viewStart = row.getTranslationY();
         int fullHeight = row.getActualHeight() + mPaddingBetweenElements;
         float iconTransformDistance = getIntrinsicHeight() * 1.5f;
         iconTransformDistance *= NotificationUtils.interpolate(1.f, 1.5f, expandAmount);
+        iconTransformDistance = Math.min(iconTransformDistance, fullHeight);
         if (isLastChild) {
             fullHeight = Math.min(fullHeight, row.getMinHeight() - getIntrinsicHeight());
             iconTransformDistance = Math.min(iconTransformDistance, row.getMinHeight()
                     - getIntrinsicHeight());
         }
         float viewEnd = viewStart + fullHeight;
+        if (expandingAnimated && mAmbientState.getScrollY() == 0
+                && !mAmbientState.isOnKeyguard() && !iconState.isLastExpandIcon) {
+            // We are expanding animated. Because we switch to a linear interpolation in this case,
+            // the last icon may be stuck in between the shelf position and the notification
+            // position, which looks pretty bad. We therefore optimize this case by applying a
+            // shorter transition such that the icon is either fully in the notification or we clamp
+            // it into the shelf if it's close enough.
+            // We need to persist this, since after the expansion, the behavior should still be the
+            // same.
+            float position = mAmbientState.getIntrinsicPadding()
+                    + mHostLayout.getPositionInLinearLayout(row);
+            int maxShelfStart = mMaxLayoutHeight - getIntrinsicHeight();
+            if (position < maxShelfStart && position + row.getIntrinsicHeight() >= maxShelfStart
+                    && row.getTranslationY() < position) {
+                iconState.isLastExpandIcon = true;
+                iconState.customTransformHeight = NO_VALUE;
+                // Let's check if we're close enough to snap into the shelf
+                boolean forceInShelf = mMaxLayoutHeight - getIntrinsicHeight() - position
+                        < getIntrinsicHeight();
+                if (!forceInShelf) {
+                    // We are overlapping the shelf but not enough, so the icon needs to be
+                    // repositioned
+                    iconState.customTransformHeight = (int) (mMaxLayoutHeight
+                            - getIntrinsicHeight() - position);
+                }
+            }
+        }
         float fullTransitionAmount;
         float iconTransitionAmount;
         float shelfStart = getTranslationY();
+        if (iconState.hasCustomTransformHeight()) {
+            fullHeight = iconState.customTransformHeight;
+            iconTransformDistance = iconState.customTransformHeight;
+        }
+        boolean fullyInOrOut = true;
         if (viewEnd >= shelfStart && (!mAmbientState.isUnlockHintRunning() || row.isInShelf())
                 && (mAmbientState.isShadeExpanded()
                         || (!row.isPinned() && !row.isHeadsUpAnimatingAway()))) {
             if (viewStart < shelfStart) {
-
                 float fullAmount = (shelfStart - viewStart) / fullHeight;
+                fullAmount = Math.min(1.0f, fullAmount);
                 float interpolatedAmount =  Interpolators.ACCELERATE_DECELERATE.getInterpolation(
                         fullAmount);
                 interpolatedAmount = NotificationUtils.interpolate(
@@ -344,7 +386,7 @@
                 iconTransitionAmount = (shelfStart - viewStart) / iconTransformDistance;
                 iconTransitionAmount = Math.min(1.0f, iconTransitionAmount);
                 iconTransitionAmount = 1.0f - iconTransitionAmount;
-
+                fullyInOrOut = false;
             } else {
                 fullTransitionAmount = 1.0f;
                 iconTransitionAmount = 1.0f;
@@ -353,6 +395,10 @@
             fullTransitionAmount = 0.0f;
             iconTransitionAmount = 0.0f;
         }
+        if (fullyInOrOut && !expandingAnimated && iconState.isLastExpandIcon) {
+            iconState.isLastExpandIcon = false;
+            iconState.customTransformHeight = NO_VALUE;
+        }
         updateIconPositioning(row, iconTransitionAmount, fullTransitionAmount,
                 iconTransformDistance, scrolling, scrollingFast, expandingAnimated, isLastChild);
         return fullTransitionAmount;
@@ -366,9 +412,10 @@
         if (iconState == null) {
             return;
         }
+        boolean forceInShelf = iconState.isLastExpandIcon && !iconState.hasCustomTransformHeight();
         float clampedAmount = iconTransitionAmount > 0.5f ? 1.0f : 0.0f;
         if (clampedAmount == fullTransitionAmount) {
-            iconState.noAnimations = scrollingFast || expandingAnimated;
+            iconState.noAnimations = (scrollingFast || expandingAnimated) && !forceInShelf;
             iconState.useFullTransitionAmount = iconState.noAnimations
                 || (!ICON_ANMATIONS_WHILE_SCROLLING && fullTransitionAmount == 0.0f && scrolling);
             iconState.useLinearTransitionAmount = !ICON_ANMATIONS_WHILE_SCROLLING
@@ -376,12 +423,18 @@
             iconState.translateContent = mMaxLayoutHeight - getTranslationY()
                     - getIntrinsicHeight() > 0;
         }
-        if (scrollingFast || (expandingAnimated && iconState.useFullTransitionAmount
-                && !ViewState.isAnimatingY(icon))) {
+        if (!forceInShelf && (scrollingFast || (expandingAnimated
+                && iconState.useFullTransitionAmount && !ViewState.isAnimatingY(icon)))) {
             iconState.cancelAnimations(icon);
             iconState.useFullTransitionAmount = true;
             iconState.noAnimations = true;
         }
+        if (iconState.hasCustomTransformHeight()) {
+            iconState.useFullTransitionAmount = true;
+        }
+        if (iconState.isLastExpandIcon) {
+            iconState.translateContent = false;
+        }
         float transitionAmount;
         if (isLastChild || !USE_ANIMATIONS_WHEN_OPENING || iconState.useFullTransitionAmount
                 || iconState.useLinearTransitionAmount) {
@@ -452,6 +505,11 @@
             iconState.scaleX = newSize / icon.getHeight() / icon.getIconScale();
             iconState.scaleY = iconState.scaleX;
             iconState.hidden = transitionAmount == 0.0f && !iconState.isAnimating(icon);
+            boolean isAppearing = row.isDrawingAppearAnimation() && !row.isInShelf();
+            if (isAppearing) {
+                iconState.hidden = true;
+                iconState.iconAppearAmount = 0.0f;
+            }
             iconState.alpha = alpha;
             iconState.yTranslation = iconYTranslation;
             if (stayingInShelf) {
@@ -548,8 +606,7 @@
         if (!hasOverflow) {
             // we have to ensure that adding the low priority notification won't lead to an
             // overflow
-            collapsedPadding -= (1.0f + NotificationIconContainer.OVERFLOW_EARLY_AMOUNT)
-                    * mCollapsedIcons.getIconSize();
+            collapsedPadding -= (1.0f + OVERFLOW_EARLY_AMOUNT) * mCollapsedIcons.getIconSize();
         }
         float padding = NotificationUtils.interpolate(collapsedPadding,
                 mShelfIcons.getPaddingEnd(),
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java
index a14d1bc..09ae521 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java
@@ -37,6 +37,7 @@
 import com.android.systemui.plugins.PluginListener;
 import com.android.systemui.plugins.PluginManager;
 import com.android.systemui.plugins.statusbar.phone.NavBarButtonProvider;
+import com.android.systemui.statusbar.phone.ReverseLinearLayout.ReverseFrameLayout;
 import com.android.systemui.statusbar.policy.KeyButtonView;
 import com.android.systemui.tuner.TunerService;
 import com.android.systemui.tuner.TunerService.Tunable;
@@ -285,7 +286,7 @@
 
         if (sizeStr.contains(WEIGHT_SUFFIX)) {
             float weight = Float.parseFloat(sizeStr.substring(0, sizeStr.indexOf(WEIGHT_SUFFIX)));
-            FrameLayout frame = new FrameLayout(mContext);
+            FrameLayout frame = new ReverseFrameLayout(mContext);
             LayoutParams childParams = new LayoutParams(v.getLayoutParams());
             if (sizeStr.endsWith(WEIGHT_CENTERED_SUFFIX)) {
                 childParams.gravity = Gravity.CENTER;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
index a6cd472..4264441 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -575,15 +575,17 @@
         mRotatedViews[Surface.ROTATION_270] =
                 mRotatedViews[Surface.ROTATION_90] = findViewById(R.id.rot90);
 
-        updateCurrentView();
+        mCurrentRotation = -1;
+        reorient();
     }
 
     public boolean needsReorient(int rotation) {
         return mCurrentRotation != rotation;
     }
 
-    private void updateCurrentView() {
+    private boolean updateCurrentView() {
         final int rot = mDisplay.getRotation();
+        if (rot == mCurrentRotation) return false;
         for (int i=0; i<4; i++) {
             mRotatedViews[i].setVisibility(View.GONE);
         }
@@ -595,6 +597,7 @@
         }
         updateLayoutTransitionsEnabled();
         mCurrentRotation = rot;
+        return true;
     }
 
     private void updateRecentsIcon() {
@@ -607,10 +610,14 @@
     }
 
     public void reorient() {
-        updateCurrentView();
+        if (!updateCurrentView()) {
+            return;
+        }
 
         mDeadZone = (DeadZone) mCurrentView.findViewById(R.id.deadzone);
-        ((NavigationBarFrame) getRootView()).setDeadZone(mDeadZone);
+        if (getRootView() instanceof NavigationBarFrame) {
+            ((NavigationBarFrame) getRootView()).setDeadZone(mDeadZone);
+        }
         mDeadZone.setDisplayRotation(mCurrentRotation);
 
         // force the low profile & disabled states into compliance
@@ -644,6 +651,7 @@
             mVertical = newVertical;
             //Log.v(TAG, String.format("onSizeChanged: h=%d, w=%d, vert=%s", h, w, mVertical?"y":"n"));
             reorient();
+            getHomeButton().setVertical(mVertical);
             notifyVerticalChangedListener(newVertical);
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
index 021e11a..6f5ad96 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
@@ -526,6 +526,7 @@
     }
 
     public class IconState extends ViewState {
+        public static final int NO_VALUE = NotificationIconContainer.NO_VALUE;
         public float iconAppearAmount = 1.0f;
         public float clampedAppearAmount = 1.0f;
         public int visibleState;
@@ -538,6 +539,8 @@
         public boolean justUndarkened;
         public int iconColor = StatusBarIconView.NO_COLOR;
         public boolean noAnimations;
+        public boolean isLastExpandIcon;
+        public int customTransformHeight = NO_VALUE;
 
         @Override
         public void applyToView(View view) {
@@ -615,6 +618,10 @@
             justUndarkened = false;
         }
 
+        public boolean hasCustomTransformHeight() {
+            return isLastExpandIcon && customTransformHeight != NO_VALUE;
+        }
+
         @Override
         public void initFrom(View view) {
             super.initFrom(view);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ReverseLinearLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ReverseLinearLayout.java
index f45967a..bcbc345 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ReverseLinearLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ReverseLinearLayout.java
@@ -16,10 +16,10 @@
 
 import android.annotation.Nullable;
 import android.content.Context;
-import android.content.res.Configuration;
 import android.util.AttributeSet;
 import android.view.View;
 import android.view.ViewGroup;
+import android.widget.FrameLayout;
 import android.widget.LinearLayout;
 
 import java.util.ArrayList;
@@ -48,7 +48,7 @@
 
     @Override
     public void addView(View child) {
-        reversParams(child.getLayoutParams());
+        reverseParams(child.getLayoutParams(), child);
         if (mIsLayoutReverse) {
             super.addView(child, 0);
         } else {
@@ -58,7 +58,7 @@
 
     @Override
     public void addView(View child, ViewGroup.LayoutParams params) {
-        reversParams(params);
+        reverseParams(params, child);
         if (mIsLayoutReverse) {
             super.addView(child, 0, params);
         } else {
@@ -100,7 +100,15 @@
         }
     }
 
-    private void reversParams(ViewGroup.LayoutParams params) {
+    private static void reverseParams(ViewGroup.LayoutParams params, View child) {
+        if (child instanceof Reversable) {
+            ((Reversable) child).reverse();
+        }
+        if (child.getPaddingLeft() == child.getPaddingRight()
+                && child.getPaddingTop() == child.getPaddingBottom()) {
+            child.setPadding(child.getPaddingTop(), child.getPaddingLeft(),
+                    child.getPaddingTop(), child.getPaddingLeft());
+        }
         if (params == null) {
             return;
         }
@@ -109,4 +117,23 @@
         params.height = width;
     }
 
+    public interface Reversable {
+        void reverse();
+    }
+
+    public static class ReverseFrameLayout extends FrameLayout implements Reversable {
+
+        public ReverseFrameLayout(Context context) {
+            super(context);
+        }
+
+        @Override
+        public void reverse() {
+            for (int i = 0; i < getChildCount(); i++) {
+                View child = getChildAt(i);
+                reverseParams(child.getLayoutParams(), child);
+            }
+        }
+    }
+
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SignalDrawable.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SignalDrawable.java
index deea521..7a7efbd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SignalDrawable.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SignalDrawable.java
@@ -100,12 +100,8 @@
     // How far the circle defining the corners is inset from the edges
     private final float mAppliedCornerInset;
 
-    // The easiest way to understand this is as if we set Style.STROKE and draw the triangle,
-    // but that is only theoretically right. Instead, draw the triangle and clip out a smaller
-    // one inset by this amount.
-    private final float mEmptyStrokeWidth;
     private static final float INV_TAN = 1f / (float) Math.tan(Math.PI / 8f);
-    private final float mEmptyDiagInset;  // == mEmptyStrokeWidth * INV_TAN
+    private static final float CUT_WIDTH_DP = 1f / 12f;
 
     // Where the top and left points of the triangle would be if not for rounding
     private final PointF mVirtualTop  = new PointF();
@@ -145,11 +141,6 @@
                 Utils.getDefaultColor(context, R.color.light_mode_icon_color_dual_tone_fill);
         mIntrinsicSize = context.getResources().getDimensionPixelSize(R.dimen.signal_icon_size);
 
-        // mCutPath parameters
-        mEmptyStrokeWidth = context.getResources()
-                .getDimensionPixelSize(R.dimen.mobile_signal_empty_strokewidth);
-        mEmptyDiagInset = mEmptyStrokeWidth * INV_TAN;
-
         mHandler = new Handler();
         setDarkIntensity(0);
 
@@ -326,22 +317,20 @@
                     (padding + cornerRadius + mAppliedCornerInset) - (INV_TAN * cornerRadius),
                     height - padding);
 
+            final float cutWidth = CUT_WIDTH_DP * height;
+            final float cutDiagInset = cutWidth * INV_TAN;
+
             // Cut out a smaller triangle from the center of mFullPath
             mCutPath.reset();
             mCutPath.setFillType(FillType.WINDING);
-            mCutPath.moveTo(width - padding - mEmptyStrokeWidth,
-                    height - padding - mEmptyStrokeWidth);
-            mCutPath.lineTo(width - padding - mEmptyStrokeWidth,
-                    mVirtualTop.y + mEmptyDiagInset);
-            mCutPath.lineTo(mVirtualLeft.x + mEmptyDiagInset,
-                    height - padding - mEmptyStrokeWidth);
-            mCutPath.lineTo(width - padding - mEmptyStrokeWidth,
-                    height - padding - mEmptyStrokeWidth);
+            mCutPath.moveTo(width - padding - cutWidth, height - padding - cutWidth);
+            mCutPath.lineTo(width - padding - cutWidth, mVirtualTop.y + cutDiagInset);
+            mCutPath.lineTo(mVirtualLeft.x + cutDiagInset, height - padding - cutWidth);
+            mCutPath.lineTo(width - padding - cutWidth, height - padding - cutWidth);
 
-            // In empty state, draw the full path as the foreground paint
-            mForegroundPath.set(mFullPath);
-            mFullPath.reset();
-            mForegroundPath.op(mCutPath, Path.Op.DIFFERENCE);
+            // Draw empty state as only background
+            mForegroundPath.reset();
+            mFullPath.op(mCutPath, Path.Op.DIFFERENCE);
         } else if (mState == STATE_AIRPLANE) {
             // Airplane mode is slashed, full-signal
             mForegroundPath.set(mFullPath);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java
index b7b991e..ba1e7c2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java
@@ -62,6 +62,7 @@
     private boolean mHasPulsingNotifications;
     private boolean mUnlockHintRunning;
     private boolean mQsCustomizerShowing;
+    private int mIntrinsicPadding;
 
     public AmbientState(Context context) {
         reload(context);
@@ -323,4 +324,12 @@
     public void setQsCustomizerShowing(boolean qsCustomizerShowing) {
         mQsCustomizerShowing = qsCustomizerShowing;
     }
+
+    public void setIntrinsicPadding(int intrinsicPadding) {
+        mIntrinsicPadding = intrinsicPadding;
+    }
+
+    public int getIntrinsicPadding() {
+        return mIntrinsicPadding;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
index cbd315b..41cde9c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -2735,7 +2735,7 @@
         return view.getHeight();
     }
 
-    private int getPositionInLinearLayout(View requestedView) {
+    public int getPositionInLinearLayout(View requestedView) {
         ExpandableNotificationRow childInGroup = null;
         ExpandableNotificationRow requestedRow = null;
         if (isChildInGroup(requestedView)) {
@@ -3650,6 +3650,7 @@
 
     public void setIntrinsicPadding(int intrinsicPadding) {
         mIntrinsicPadding = intrinsicPadding;
+        mAmbientState.setIntrinsicPadding(intrinsicPadding);
     }
 
     public int getIntrinsicPadding() {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
index 5d51a33..eaad2f9 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
@@ -43,6 +43,7 @@
 import android.service.notification.Condition;
 import android.util.ArrayMap;
 import android.util.Log;
+import android.view.accessibility.AccessibilityManager;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.systemui.Dumpable;
@@ -122,6 +123,12 @@
         mReceiver.init();
         mVibrator = (Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE);
         mHasVibrator = mVibrator != null && mVibrator.hasVibrator();
+
+        boolean accessibilityVolumeStreamActive = context.getSystemService(
+                AccessibilityManager.class).isAccessibilityVolumeStreamActive();
+        mVolumeController.setA11yMode(accessibilityVolumeStreamActive ?
+                    VolumePolicy.A11Y_MODE_INDEPENDENT_A11Y_VOLUME :
+                        VolumePolicy.A11Y_MODE_MEDIA_A11Y_VOLUME);
     }
 
     public AudioManager getAudioManager() {
@@ -210,6 +217,7 @@
 
     public void addCallback(Callbacks callback, Handler handler) {
         mCallbacks.add(callback, handler);
+        callback.onAccessibilityModeChanged(mShowA11yStream);
     }
 
     public void setUserActivityListener(UserActivityListener listener) {
diff --git a/packages/SystemUI/tests/AndroidManifest.xml b/packages/SystemUI/tests/AndroidManifest.xml
index b12fd1c..9bb2180 100644
--- a/packages/SystemUI/tests/AndroidManifest.xml
+++ b/packages/SystemUI/tests/AndroidManifest.xml
@@ -48,7 +48,7 @@
             android:process=":killable" />
     </application>
 
-    <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+    <instrumentation android:name="android.testing.TestableInstrumentation"
         android:targetPackage="com.android.systemui.tests"
         android:label="Tests for SystemUI">
     </instrumentation>
diff --git a/packages/SystemUI/tests/src/com/android/systemui/RoundedCornersTest.java b/packages/SystemUI/tests/src/com/android/systemui/RoundedCornersTest.java
index 945af34..13ed2a2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/RoundedCornersTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/RoundedCornersTest.java
@@ -21,7 +21,6 @@
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
@@ -30,7 +29,6 @@
 import static org.mockito.Mockito.when;
 
 import android.app.Fragment;
-import android.support.test.filters.FlakyTest;
 import android.support.test.filters.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.view.Display;
@@ -53,7 +51,6 @@
 import org.junit.runner.RunWith;
 
 @RunWith(AndroidTestingRunner.class)
-@FlakyTest
 @SmallTest
 public class RoundedCornersTest extends SysuiTestCase {
 
@@ -72,6 +69,7 @@
         mWindowManager = mock(WindowManager.class);
         mView = spy(new StatusBarWindowView(mContext, null));
         when(mStatusBar.getStatusBarWindow()).thenReturn(mView);
+        when(mStatusBar.getNavigationBarWindow()).thenReturn(mView);
         mContext.putComponent(StatusBar.class, mStatusBar);
 
         Display display = mContext.getSystemService(WindowManager.class).getDefaultDisplay();
@@ -94,6 +92,8 @@
     @Test
     public void testNoRounding() {
         mContext.getOrCreateTestableResources().addOverride(dimen.rounded_corner_radius, 0);
+        mContext.getOrCreateTestableResources()
+                .addOverride(dimen.rounded_corner_content_padding, 0);
 
         mRoundedCorners.start();
         // No views added.
@@ -105,8 +105,11 @@
     }
 
     @Test
+    @Ignore
     public void testRounding() {
         mContext.getOrCreateTestableResources().addOverride(dimen.rounded_corner_radius, 20);
+        mContext.getOrCreateTestableResources()
+                .addOverride(dimen.rounded_corner_content_padding, 20);
 
         mRoundedCorners.start();
         // Add 2 windows for rounded corners (top and bottom).
diff --git a/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java b/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java
index 361a20f..66d00dd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java
@@ -31,6 +31,8 @@
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
 
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Future;
@@ -56,10 +58,14 @@
 
         mRealInstrumentation = InstrumentationRegistry.getInstrumentation();
         Instrumentation inst = spy(mRealInstrumentation);
-        when(inst.getContext()).thenThrow(new RuntimeException(
-                "SysUI Tests should use SysuiTestCase#getContext or SysuiTestCase#mContext"));
-        when(inst.getTargetContext()).thenThrow(new RuntimeException(
-                "SysUI Tests should use SysuiTestCase#getContext or SysuiTestCase#mContext"));
+        when(inst.getContext()).thenAnswer(invocation -> {
+            throw new RuntimeException(
+                    "SysUI Tests should use SysuiTestCase#getContext or SysuiTestCase#mContext");
+        });
+        when(inst.getTargetContext()).thenAnswer(invocation -> {
+            throw new RuntimeException(
+                    "SysUI Tests should use SysuiTestCase#getContext or SysuiTestCase#mContext");
+        });
         InstrumentationRegistry.registerInstance(inst, InstrumentationRegistry.getArguments());
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
index 2363b2a..8641fac 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
@@ -27,7 +27,6 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.FlakyTest;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginManagerTest.java
index b8e9fcd..bba982c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginManagerTest.java
@@ -26,8 +26,6 @@
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.net.Uri;
-import android.support.test.annotation.UiThreadTest;
-import android.support.test.runner.AndroidJUnit4;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
@@ -36,11 +34,10 @@
 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
 import com.android.systemui.Dependency;
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.plugins.annotations.ProvidesInterface;
 import com.android.systemui.plugins.PluginInstanceManager.PluginInfo;
+import com.android.systemui.plugins.annotations.ProvidesInterface;
 import com.android.systemui.plugins.PluginManagerImpl.PluginInstanceManagerFactory;
 
-import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSDetailTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSDetailTest.java
index 6a85511..ed47fbb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSDetailTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSDetailTest.java
@@ -23,10 +23,7 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import org.junit.After;
-import org.junit.Ignore;
 import android.support.test.filters.SmallTest;
-import android.support.test.filters.FlakyTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.testing.TestableLooper.RunWithLooper;
@@ -41,6 +38,7 @@
 import com.android.systemui.plugins.qs.DetailAdapter;
 
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterTest.java
index f4fda06..d25bbe1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterTest.java
@@ -21,7 +21,6 @@
 import static org.mockito.Mockito.when;
 
 import android.support.test.filters.SmallTest;
-import android.support.test.filters.FlakyTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.testing.TestableLooper.RunWithLooper;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java
index 637b244..85cdfcc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java
@@ -20,7 +20,6 @@
 import static org.mockito.Mockito.when;
 
 import android.support.test.filters.SmallTest;
-import android.support.test.filters.FlakyTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.testing.TestableLooper.RunWithLooper;
@@ -31,7 +30,6 @@
 import com.android.systemui.qs.customize.QSCustomizer;
 
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -40,7 +38,6 @@
 @RunWith(AndroidTestingRunner.class)
 @RunWithLooper
 @SmallTest
-@FlakyTest
 public class QSPanelTest extends SysuiTestCase {
 
     private MetricsLogger mMetricsLogger;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ExpandableNotificationRowTest.java
index 664ea71..2f6511c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ExpandableNotificationRowTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ExpandableNotificationRowTest.java
@@ -21,27 +21,21 @@
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 
-import android.content.Context;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.annotation.UiThreadTest;
-import android.support.test.filters.FlakyTest;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
 import android.view.View;
 
+import com.android.systemui.SysuiTestCase;
 import com.android.systemui.statusbar.notification.AboveShelfChangedListener;
 import com.android.systemui.statusbar.stack.NotificationChildrenContainer;
-import com.android.systemui.SysuiTestCase;
 
 import org.junit.Assert;
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
-@FlakyTest
 public class ExpandableNotificationRowTest extends SysuiTestCase {
 
     private ExpandableNotificationRow mGroup;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationContentViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationContentViewTest.java
index 4dce2f5..436849c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationContentViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationContentViewTest.java
@@ -16,30 +16,25 @@
 
 package com.android.systemui.statusbar;
 
-import android.content.Context;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.annotation.UiThreadTest;
-import android.support.test.filters.FlakyTest;
-import android.support.test.filters.SmallTest;
-import android.support.test.runner.AndroidJUnit4;
-import android.view.View;
-
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Ignore;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
 import static org.mockito.ArgumentMatchers.anyFloat;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.spy;
 
+import android.support.test.annotation.UiThreadTest;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.View;
+
 import com.android.systemui.SysuiTestCase;
 
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
 @SmallTest
 @RunWith(AndroidJUnit4.class)
-@FlakyTest
 public class NotificationContentViewTest extends SysuiTestCase {
 
     NotificationContentView mView;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationCustomViewWrapperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationCustomViewWrapperTest.java
index d18e63b..6e59d10 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationCustomViewWrapperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationCustomViewWrapperTest.java
@@ -16,10 +16,7 @@
 
 package com.android.systemui.statusbar;
 
-import android.content.Context;
-import android.support.test.InstrumentationRegistry;
 import android.support.test.annotation.UiThreadTest;
-import android.support.test.filters.FlakyTest;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
 import android.view.View;
@@ -32,13 +29,11 @@
 
 import org.junit.Assert;
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
-@FlakyTest
 public class NotificationCustomViewWrapperTest extends SysuiTestCase {
 
     private ExpandableNotificationRow mRow;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/stack/NotificationChildrenContainerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/stack/NotificationChildrenContainerTest.java
index bc6833d..5ac965c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/stack/NotificationChildrenContainerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/stack/NotificationChildrenContainerTest.java
@@ -16,10 +16,6 @@
 
 package com.android.systemui.statusbar.stack;
 
-import android.content.Context;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.annotation.UiThreadTest;
-import android.support.test.filters.FlakyTest;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
 import android.view.NotificationHeaderView;
@@ -31,13 +27,11 @@
 
 import org.junit.Assert;
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
-@FlakyTest
 public class NotificationChildrenContainerTest extends SysuiTestCase {
 
     private ExpandableNotificationRow mGroup;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakePluginManager.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakePluginManager.java
index 0a83a89..d1b1c5b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakePluginManager.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakePluginManager.java
@@ -14,7 +14,6 @@
 
 package com.android.systemui.utils.leaks;
 
-import android.content.Context;
 import android.testing.LeakCheck;
 
 import com.android.systemui.plugins.Plugin;
diff --git a/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto
index 2edcd71..9aa58f5 100644
--- a/proto/src/metrics_constants.proto
+++ b/proto/src/metrics_constants.proto
@@ -4153,11 +4153,44 @@
     // OS: O DR
     FIELD_PLUG_BATTERY_PERCENTAGE = 1024;
 
+    // Device Headset battery level on Plug
+    // CATEGORY: OTHER
+    // FIELD - The battery percentage when the user decided to plug in
+    // Type: integer
+    // OS: O DR
+    FIELD_UNPLUG_BATTERY_PERCENTAGE = 1025;
+
     // Device Headset Pose status
     // CATEGORY: OTHER
     //  SUBTYPE: 1 is 6DOF, 2 is 3DOF
     // OS: O DR
-    ACTION_HEADSET_POSE_STATUS = 1025;
+    ACTION_HEADSET_POSE_STATUS = 1026;
+
+    // Device Headset Usage session time
+    // CATEGORY: OTHER
+    // FIELD - The time the headset was used in a session
+    // OS: O DR
+    FIELD_SESSION_TIME_MS = 1027;
+
+    // Device Headset Idle time
+    // CATEGORY: OTHER
+    // FIELD - The time in between each session
+    // OS: O DR
+    FIELD_TIME_ELAPSED_BETWEEN_SESSION_MS = 1028;
+
+    // Device Headset charge session time
+    // CATEGORY: OTHER
+    // FIELD - The time taken for each charge
+    // OS: O DR
+    FIELD_TIME_OF_CHARGE_MS = 1029;
+
+    // Device Headset time between charge
+    // CATEGORY: OTHER
+    // FIELD - The time in between each charge
+    // OS: O DR
+    FIELD_TIME_ELAPSED_BETWEEN_CHARGE_MS = 1030;
+
+    // ---- End O-DR1 Constants, all O-DR1 constants go above this line ----
 
     // Add new aosp constants above this line.
     // END OF AOSP CONSTANTS
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
index 9e4d89c..0e42e6d 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
@@ -271,6 +271,7 @@
 
     private void processKeyEvent(EventStreamState state, KeyEvent event, int policyFlags) {
         if (!state.shouldProcessKeyEvent(event)) {
+            super.onInputEvent(event, policyFlags);
             return;
         }
         mEventHandler.onKeyEvent(event, policyFlags);
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 4810f4f..f47b0d3 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -383,7 +383,7 @@
                                     findDeviceCallback,
                                     getServiceCallback());
                 } catch (RemoteException e) {
-                    throw new RuntimeException(e);
+                    Log.e(LOG_TAG, "Error while initiating device discovery", e);
                 }
             }
 
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index fd4f672..f106ca0 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -20,6 +20,7 @@
 import static android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST;
 import static android.Manifest.permission.INTERACT_ACROSS_USERS;
 import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
+import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW;
 import static android.Manifest.permission.MANAGE_ACTIVITY_STACKS;
 import static android.Manifest.permission.READ_FRAME_BUFFER;
 import static android.Manifest.permission.START_TASKS_FROM_RECENTS;
@@ -10297,7 +10298,7 @@
 
     @Override
     public void moveStackToDisplay(int stackId, int displayId) {
-        enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "moveStackToDisplay()");
+        enforceCallingPermission(INTERNAL_SYSTEM_WINDOW, "moveStackToDisplay()");
 
         synchronized (this) {
             final long ident = Binder.clearCallingIdentity();
@@ -12573,7 +12574,6 @@
                 Binder.restoreCallingIdentity(ident);
             }
         }
-        closeSystemDialogs("setLockScreenShown");
     }
 
     @Override
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 1ccac1b..e0f2a75 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -16,6 +16,7 @@
 
 package com.android.server.am;
 
+import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW;
 import static android.Manifest.permission.MANAGE_ACTIVITY_STACKS;
 import static android.Manifest.permission.START_ANY_ACTIVITY;
 import static android.Manifest.permission.START_TASKS_FROM_RECENTS;
@@ -38,11 +39,13 @@
 import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.os.Process.SYSTEM_UID;
 import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.Display.FLAG_PRIVATE;
 import static android.view.Display.INVALID_DISPLAY;
 import static android.view.Display.REMOVE_MODE_DESTROY_CONTENT;
+import static android.view.Display.TYPE_VIRTUAL;
 
 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_PICTURE_IN_PICTURE_EXPANDED_TO_FULLSCREEN;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ALL;
@@ -1694,6 +1697,24 @@
             return false;
         }
 
+        // Check if the caller can manage activity stacks.
+        final int startAnyPerm = mService.checkPermission(INTERNAL_SYSTEM_WINDOW, callingPid,
+                callingUid);
+        if (startAnyPerm == PERMISSION_GRANTED) {
+            if (DEBUG_TASKS) Slog.d(TAG, "Launch on display check:"
+                    + " allow launch any on display");
+            return true;
+        }
+
+        if (activityDisplay.mDisplay.getType() == TYPE_VIRTUAL
+                && activityDisplay.mDisplay.getOwnerUid() != SYSTEM_UID) {
+            // Limit launching on virtual displays, because their contents can be read from Surface
+            // by apps that created them.
+            if (DEBUG_TASKS) Slog.d(TAG, "Launch on display check:"
+                    + " disallow launch on virtual display for not-embedded activity");
+            return false;
+        }
+
         if (!activityDisplay.isPrivate()) {
             // Anyone can launch on a public display.
             if (DEBUG_TASKS) Slog.d(TAG, "Launch on display check:"
@@ -1715,15 +1736,6 @@
             return true;
         }
 
-        // Check if the caller can manage activity stacks.
-        final int startAnyPerm = mService.checkPermission(MANAGE_ACTIVITY_STACKS, callingPid,
-                callingUid);
-        if (startAnyPerm == PERMISSION_GRANTED) {
-            if (DEBUG_TASKS) Slog.d(TAG, "Launch on display check:"
-                    + " allow launch any on display");
-            return true;
-        }
-
         Slog.w(TAG, "Launch on display check: denied");
         return false;
     }
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index e5ab784..2199bba 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -239,6 +239,7 @@
     private static final int MSG_SET_A2DP_SRC_CONNECTION_STATE = 101;
     private static final int MSG_SET_A2DP_SINK_CONNECTION_STATE = 102;
     private static final int MSG_A2DP_DEVICE_CONFIG_CHANGE = 103;
+    private static final int MSG_DISABLE_AUDIO_FOR_UID = 104;
     // end of messages handled under wakelock
 
     private static final int BTA2DP_DOCK_TIMEOUT_MILLIS = 8000;
@@ -4871,6 +4872,12 @@
                     mAudioEventWakeLock.release();
                     break;
 
+                case MSG_DISABLE_AUDIO_FOR_UID:
+                    mPlaybackMonitor.disableAudioForUid( msg.arg1 == 1 /* disable */,
+                            msg.arg2 /* uid */);
+                    mAudioEventWakeLock.release();
+                    break;
+
                 case MSG_REPORT_NEW_ROUTES: {
                     int N = mRoutesObservers.beginBroadcast();
                     if (N > 0) {
@@ -6591,6 +6598,13 @@
                 }
             }
         }
+
+        @Override
+        public void disableAudioForUid(boolean disable, int uid) {
+            queueMsgUnderWakeLock(mAudioHandler, MSG_DISABLE_AUDIO_FOR_UID,
+                    disable ? 1 : 0 /* arg1 */,  uid /* arg2 */,
+                    null /* obj */,  0 /* delay */);
+        }
     }
 
     //==========================================================================================
diff --git a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
index f9e4d94..663559f 100644
--- a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
+++ b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
@@ -95,6 +95,43 @@
     }
 
     //=================================================================
+    private final ArrayList<Integer> mBannedUids = new ArrayList<Integer>();
+
+    // see AudioManagerInternal.disableAudioForUid(boolean disable, int uid)
+    public void disableAudioForUid(boolean disable, int uid) {
+        synchronized(mPlayerLock) {
+            final int index = mBannedUids.indexOf(new Integer(uid));
+            if (index >= 0) {
+                if (!disable) {
+                    mBannedUids.remove(index);
+                    // nothing else to do, future playback requests from this uid are ok
+                } // no else to handle, uid already present, so disabling again is no-op
+            } else {
+                if (disable) {
+                    for (AudioPlaybackConfiguration apc : mPlayers.values()) {
+                        checkBanPlayer(apc, uid);
+                    }
+                    mBannedUids.add(new Integer(uid));
+                } // no else to handle, uid already not in list, so enabling again is no-op
+            }
+        }
+    }
+
+    private boolean checkBanPlayer(@NonNull AudioPlaybackConfiguration apc, int uid) {
+        final boolean toBan = (apc.getClientUid() == uid);
+        if (toBan) {
+            final int piid = apc.getPlayerInterfaceId();
+            try {
+                Log.v(TAG, "banning player " + piid + " uid:" + uid);
+                apc.getPlayerProxy().pause();
+            } catch (Exception e) {
+                Log.e(TAG, "error banning player " + piid + " uid:" + uid, e);
+            }
+        }
+        return toBan;
+    }
+
+    //=================================================================
     // Track players and their states
     // methods playerAttributes, playerEvent, releasePlayer are all oneway calls
     //  into AudioService. They trigger synchronous dispatchPlaybackChange() which updates
@@ -137,6 +174,14 @@
             if (apc == null) {
                 return;
             }
+            if (event == AudioPlaybackConfiguration.PLAYER_STATE_STARTED) {
+                for (Integer uidInteger: mBannedUids) {
+                    if (checkBanPlayer(apc, uidInteger.intValue())) {
+                        // player was banned, do not update its state
+                        return;
+                    }
+                }
+            }
             if (apc.getPlayerType() == AudioPlaybackConfiguration.PLAYER_TYPE_JAM_SOUNDPOOL) {
                 // FIXME SoundPool not ready for state reporting
                 return;
@@ -186,10 +231,17 @@
             pw.println("\n  ducked players:");
             mDuckingManager.dump(pw);
             // players muted due to the device ringing or being in a call
-            pw.println("\n  muted player piids:");
+            pw.print("\n  muted player piids:");
             for (int piid : mMutedPlayers) {
-                pw.println(" " + piid);
+                pw.print(" " + piid);
             }
+            pw.println();
+            // banned players:
+            pw.print("\n  banned uids:");
+            for (int uid : mBannedUids) {
+                pw.print(" " + uid);
+            }
+            pw.println();
         }
     }
 
diff --git a/services/core/java/com/android/server/fingerprint/AuthenticationClient.java b/services/core/java/com/android/server/fingerprint/AuthenticationClient.java
index 5339bac..370e569 100644
--- a/services/core/java/com/android/server/fingerprint/AuthenticationClient.java
+++ b/services/core/java/com/android/server/fingerprint/AuthenticationClient.java
@@ -79,7 +79,7 @@
         }
         if (!authenticated) {
             if (receiver != null) {
-                FingerprintUtils.vibrateFingerprintError(getContext());
+                vibrateError();
             }
             // allow system-defined limit of number of attempts before giving up
             int lockoutMode =  handleFailedAttempt();
@@ -99,7 +99,7 @@
             result |= lockoutMode != LOCKOUT_NONE; // in a lockout mode
         } else {
             if (receiver != null) {
-                FingerprintUtils.vibrateFingerprintSuccess(getContext());
+                vibrateSuccess();
             }
             result |= true; // we have a valid fingerprint, done
             resetFailedAttempts();
diff --git a/services/core/java/com/android/server/fingerprint/ClientMonitor.java b/services/core/java/com/android/server/fingerprint/ClientMonitor.java
index 1a2e144..3eae157 100644
--- a/services/core/java/com/android/server/fingerprint/ClientMonitor.java
+++ b/services/core/java/com/android/server/fingerprint/ClientMonitor.java
@@ -23,6 +23,8 @@
 import android.hardware.fingerprint.IFingerprintServiceReceiver;
 import android.os.IBinder;
 import android.os.RemoteException;
+import android.os.VibrationEffect;
+import android.os.Vibrator;
 import android.util.Slog;
 
 import java.util.NoSuchElementException;
@@ -36,14 +38,18 @@
     protected static final String TAG = FingerprintService.TAG; // TODO: get specific name
     protected static final int ERROR_ESRCH = 3; // Likely fingerprint HAL is dead. See errno.h.
     protected static final boolean DEBUG = FingerprintService.DEBUG;
+    private static final long[] DEFAULT_SUCCESS_VIBRATION_PATTERN = new long[] {0, 30};
+    private final Context mContext;
+    private final long mHalDeviceId;
+    private final int mTargetUserId;
+    private final int mGroupId;
+    // True if client does not have MANAGE_FINGERPRINT permission
+    private final boolean mIsRestricted;
+    private final String mOwner;
+    private final VibrationEffect mSuccessVibrationEffect;
+    private final VibrationEffect mErrorVibrationEffect;
     private IBinder mToken;
     private IFingerprintServiceReceiver mReceiver;
-    private int mTargetUserId;
-    private int mGroupId;
-    private boolean mIsRestricted; // True if client does not have MANAGE_FINGERPRINT permission
-    private String mOwner;
-    private Context mContext;
-    private long mHalDeviceId;
     protected boolean mAlreadyCancelled;
 
     /**
@@ -68,6 +74,8 @@
         mGroupId = groupId;
         mIsRestricted = restricted;
         mOwner = owner;
+        mSuccessVibrationEffect = getSuccessVibrationEffect(context);
+        mErrorVibrationEffect = VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK);
         try {
             if (token != null) {
                 token.linkToDeath(this, 0);
@@ -79,7 +87,7 @@
 
     /**
      * Contacts fingerprint HAL to start the client.
-     * @return 0 on succes, errno from driver on failure
+     * @return 0 on success, errno from driver on failure
      */
     public abstract int start();
 
@@ -211,4 +219,39 @@
     public final IBinder getToken() {
         return mToken;
     }
+
+    public final void vibrateSuccess() {
+        Vibrator vibrator = mContext.getSystemService(Vibrator.class);
+        if (vibrator != null) {
+            vibrator.vibrate(mSuccessVibrationEffect);
+        }
+    }
+
+    public final void vibrateError() {
+        Vibrator vibrator = mContext.getSystemService(Vibrator.class);
+        if (vibrator != null) {
+            vibrator.vibrate(mErrorVibrationEffect);
+        }
+    }
+
+    private static VibrationEffect getSuccessVibrationEffect(Context ctx) {
+        int[] arr = ctx.getResources().getIntArray(
+                com.android.internal.R.array.config_longPressVibePattern);
+        final long[] vibePattern;
+        if (arr == null || arr.length == 0) {
+            vibePattern = DEFAULT_SUCCESS_VIBRATION_PATTERN;
+        } else {
+            vibePattern = new long[arr.length];
+            for (int i = 0; i < arr.length; i++) {
+                vibePattern[i] = arr[i];
+            }
+        }
+        if (vibePattern.length == 1) {
+            return VibrationEffect.createOneShot(
+                    vibePattern[0], VibrationEffect.DEFAULT_AMPLITUDE);
+        } else {
+            return VibrationEffect.createWaveform(vibePattern, -1);
+        }
+    }
+
 }
diff --git a/services/core/java/com/android/server/fingerprint/EnrollClient.java b/services/core/java/com/android/server/fingerprint/EnrollClient.java
index 6170894..c9efcf2 100644
--- a/services/core/java/com/android/server/fingerprint/EnrollClient.java
+++ b/services/core/java/com/android/server/fingerprint/EnrollClient.java
@@ -65,7 +65,7 @@
         if (receiver == null)
             return true; // client not listening
 
-        FingerprintUtils.vibrateFingerprintSuccess(getContext());
+        vibrateSuccess();
         MetricsLogger.action(getContext(), MetricsEvent.ACTION_FINGERPRINT_ENROLL);
         try {
             receiver.onEnrollResult(getHalDeviceId(), fpId, groupId, remaining);
diff --git a/services/core/java/com/android/server/fingerprint/FingerprintUtils.java b/services/core/java/com/android/server/fingerprint/FingerprintUtils.java
index 49dc8e4..5fbd735 100644
--- a/services/core/java/com/android/server/fingerprint/FingerprintUtils.java
+++ b/services/core/java/com/android/server/fingerprint/FingerprintUtils.java
@@ -18,7 +18,6 @@
 
 import android.content.Context;
 import android.hardware.fingerprint.Fingerprint;
-import android.os.Vibrator;
 import android.text.TextUtils;
 import android.util.SparseArray;
 
@@ -31,9 +30,6 @@
  */
 public class FingerprintUtils {
 
-    private static final long[] FP_ERROR_VIBRATE_PATTERN = new long[] {0, 30, 100, 30};
-    private static final long[] FP_SUCCESS_VIBRATE_PATTERN = new long[] {0, 30};
-
     private static final Object sInstanceLock = new Object();
     private static FingerprintUtils sInstance;
 
@@ -72,20 +68,6 @@
         getStateForUser(ctx, userId).renameFingerprint(fingerId, name);
     }
 
-    public static void vibrateFingerprintError(Context context) {
-        Vibrator vibrator = context.getSystemService(Vibrator.class);
-        if (vibrator != null) {
-            vibrator.vibrate(FP_ERROR_VIBRATE_PATTERN, -1);
-        }
-    }
-
-    public static void vibrateFingerprintSuccess(Context context) {
-        Vibrator vibrator = context.getSystemService(Vibrator.class);
-        if (vibrator != null) {
-            vibrator.vibrate(FP_SUCCESS_VIBRATE_PATTERN, -1);
-        }
-    }
-
     private FingerprintsUserState getStateForUser(Context ctx, int userId) {
         synchronized (this) {
             FingerprintsUserState state = mUsers.get(userId);
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 5ee7ac4..038e47e 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -4004,16 +4004,19 @@
             }
             int indexBefore = findNotificationRecordIndexLocked(record);
             boolean interceptBefore = record.isIntercepted();
+            float contactAffinityBefore = record.getContactAffinity();
             int visibilityBefore = record.getPackageVisibilityOverride();
             recon.applyChangesLocked(record);
             applyZenModeLocked(record);
             mRankingHelper.sort(mNotificationList);
             int indexAfter = findNotificationRecordIndexLocked(record);
             boolean interceptAfter = record.isIntercepted();
+            float contactAffinityAfter = record.getContactAffinity();
             int visibilityAfter = record.getPackageVisibilityOverride();
             changed = indexBefore != indexAfter || interceptBefore != interceptAfter
                     || visibilityBefore != visibilityAfter;
-            if (interceptBefore && !interceptAfter) {
+            if (interceptBefore && !interceptAfter
+                    && Float.compare(contactAffinityBefore, contactAffinityAfter) != 0) {
                 buzzBeepBlinkLocked(record);
             }
         }
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 89dbc2a..5d2d4b6 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -1677,8 +1677,17 @@
     }
 
     boolean isUserSetupComplete() {
-        return Settings.Secure.getIntForUser(mContext.getContentResolver(),
+        boolean isSetupComplete = Settings.Secure.getIntForUser(mContext.getContentResolver(),
                 Settings.Secure.USER_SETUP_COMPLETE, 0, UserHandle.USER_CURRENT) != 0;
+        if (mHasFeatureLeanback) {
+            isSetupComplete &= isTvUserSetupComplete();
+        }
+        return isSetupComplete;
+    }
+
+    private boolean isTvUserSetupComplete() {
+        return Settings.Secure.getIntForUser(mContext.getContentResolver(),
+                Settings.Secure.TV_USER_SETUP_COMPLETE, 0, UserHandle.USER_CURRENT) != 0;
     }
 
     private void handleShortPressOnHome() {
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index 32871bb..984b40f 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -783,7 +783,7 @@
             mHandler.post(() -> {
                 // ShutdownThread displays UI, so give it a UI context.
                 if (safeMode) {
-                    ShutdownThread.rebootSafeMode(getUiContext(), false);
+                    ShutdownThread.rebootSafeMode(getUiContext(), true);
                 } else {
                     ShutdownThread.reboot(getUiContext(),
                             PowerManager.SHUTDOWN_USER_REQUESTED, false);
diff --git a/services/core/java/com/android/server/wm/AppWindowContainerController.java b/services/core/java/com/android/server/wm/AppWindowContainerController.java
index 5f34c60..4e4398e 100644
--- a/services/core/java/com/android/server/wm/AppWindowContainerController.java
+++ b/services/core/java/com/android/server/wm/AppWindowContainerController.java
@@ -614,7 +614,7 @@
             return STARTING_WINDOW_TYPE_SPLASH_SCREEN;
         } else if (taskSwitch && allowTaskSnapshot) {
             return snapshot == null ? STARTING_WINDOW_TYPE_NONE
-                    : snapshotOrientationSameAsDisplay(snapshot) || fromRecents
+                    : snapshotOrientationSameAsTask(snapshot) || fromRecents
                             ? STARTING_WINDOW_TYPE_SNAPSHOT : STARTING_WINDOW_TYPE_SPLASH_SCREEN;
         } else {
             return STARTING_WINDOW_TYPE_NONE;
@@ -640,24 +640,11 @@
         return true;
     }
 
-    private boolean snapshotOrientationSameAsDisplay(TaskSnapshot snapshot) {
+    private boolean snapshotOrientationSameAsTask(TaskSnapshot snapshot) {
         if (snapshot == null) {
             return false;
         }
-        final Rect rect = new Rect(0, 0, snapshot.getSnapshot().getWidth(),
-                snapshot.getSnapshot().getHeight());
-        rect.inset(snapshot.getContentInsets());
-        final Rect taskBoundsWithoutInsets = new Rect();
-        mContainer.getTask().getBounds(taskBoundsWithoutInsets);
-        final DisplayInfo di = mContainer.getDisplayContent().getDisplayInfo();
-        final Rect displayBounds = new Rect(0, 0, di.logicalWidth, di.logicalHeight);
-        final Rect stableInsets = new Rect();
-        mService.mPolicy.getStableInsetsLw(di.rotation, di.logicalWidth, di.logicalHeight,
-                stableInsets);
-        displayBounds.inset(stableInsets);
-        final boolean snapshotInLandscape = rect.width() >= rect.height();
-        final boolean displayInLandscape = displayBounds.width() >= displayBounds.height();
-        return snapshotInLandscape == displayInLandscape;
+        return mContainer.getTask().getConfiguration().orientation == snapshot.getOrientation();
     }
 
     public void removeStartingWindow() {
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index bd37934..8afc4fd 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -937,8 +937,6 @@
             // Update keyguard flags upon finishing relaunch.
             checkKeyguardFlagsChanged();
         }
-
-        updateAllDrawn();
     }
 
     void clearRelaunching() {
@@ -1344,6 +1342,10 @@
         }
     }
 
+    /**
+     *  Determines if the token has finished drawing. This should only be called from
+     *  {@link DisplayContent#applySurfaceChangesTransaction}
+     */
     void updateAllDrawn() {
         if (!allDrawn) {
             // Number of drawn windows can be less when a window is being relaunched, wait for
diff --git a/services/core/jni/com_android_server_VibratorService.cpp b/services/core/jni/com_android_server_VibratorService.cpp
index 804cd17..cb8416b 100644
--- a/services/core/jni/com_android_server_VibratorService.cpp
+++ b/services/core/jni/com_android_server_VibratorService.cpp
@@ -42,107 +42,121 @@
 namespace android
 {
 
-static sp<IVibrator> mHal;
+static constexpr int NUM_TRIES = 2;
+
+// Creates a Return<R> with STATUS::EX_NULL_POINTER.
+template<class R>
+inline Return<R> NullptrStatus() {
+    using ::android::hardware::Status;
+    return Return<R>{Status::fromExceptionCode(Status::EX_NULL_POINTER)};
+}
+
+// Helper used to transparently deal with the vibrator HAL becoming unavailable.
+template<class R, class I, class... Args0, class... Args1>
+Return<R> halCall(Return<R> (I::* fn)(Args0...), Args1&&... args1) {
+    // Assume that if getService returns a nullptr, HAL is not available on the
+    // device.
+    static sp<I> sHal = I::getService();
+    static bool sAvailable = sHal != nullptr;
+
+    if (!sAvailable) {
+        return NullptrStatus<R>();
+    }
+
+    // Return<R> doesn't have a default constructor, so make a Return<R> with
+    // STATUS::EX_NONE.
+    using ::android::hardware::Status;
+    Return<R> ret{Status::fromExceptionCode(Status::EX_NONE)};
+
+    // Note that ret is guaranteed to be changed after this loop.
+    for (int i = 0; i < NUM_TRIES; ++i) {
+        ret = (sHal == nullptr) ? NullptrStatus<R>()
+                : (*sHal.*fn)(std::forward<Args1>(args1)...);
+
+        if (!ret.isOk()) {
+            ALOGE("Failed to issue command to vibrator HAL. Retrying.");
+            // Restoring connection to the HAL.
+            sHal = I::tryGetService();
+        }
+    }
+    return ret;
+}
 
 static void vibratorInit(JNIEnv /* env */, jobject /* clazz */)
 {
-    /* TODO(b/31632518) */
-    if (mHal != nullptr) {
-        return;
-    }
-    mHal = IVibrator::getService();
+    halCall(&IVibrator::ping).isOk();
 }
 
 static jboolean vibratorExists(JNIEnv* /* env */, jobject /* clazz */)
 {
-    if (mHal != nullptr) {
-        return JNI_TRUE;
-    } else {
-        return JNI_FALSE;
-    }
+    return halCall(&IVibrator::ping).isOk() ? JNI_TRUE : JNI_FALSE;
 }
 
 static void vibratorOn(JNIEnv* /* env */, jobject /* clazz */, jlong timeout_ms)
 {
-    if (mHal != nullptr) {
-        Status retStatus = mHal->on(timeout_ms);
-        if (retStatus != Status::OK) {
-            ALOGE("vibratorOn command failed (%" PRIu32 ").", static_cast<uint32_t>(retStatus));
-        }
-    } else {
-        ALOGW("Tried to vibrate but there is no vibrator device.");
+    Status retStatus = halCall(&IVibrator::on, timeout_ms).withDefault(Status::UNKNOWN_ERROR);
+    if (retStatus != Status::OK) {
+        ALOGE("vibratorOn command failed (%" PRIu32 ").", static_cast<uint32_t>(retStatus));
     }
 }
 
 static void vibratorOff(JNIEnv* /* env */, jobject /* clazz */)
 {
-    if (mHal != nullptr) {
-        Status retStatus = mHal->off();
-        if (retStatus != Status::OK) {
-            ALOGE("vibratorOff command failed (%" PRIu32 ").", static_cast<uint32_t>(retStatus));
-        }
-    } else {
-        ALOGW("Tried to stop vibrating but there is no vibrator device.");
+    Status retStatus = halCall(&IVibrator::off).withDefault(Status::UNKNOWN_ERROR);
+    if (retStatus != Status::OK) {
+        ALOGE("vibratorOff command failed (%" PRIu32 ").", static_cast<uint32_t>(retStatus));
     }
 }
 
 static jlong vibratorSupportsAmplitudeControl(JNIEnv*, jobject) {
-    if (mHal != nullptr) {
-        return mHal->supportsAmplitudeControl();
-    } else {
-        ALOGW("Unable to get max vibration amplitude, there is no vibrator device.");
-    }
-    return false;
+    return halCall(&IVibrator::supportsAmplitudeControl).withDefault(false);
 }
 
 static void vibratorSetAmplitude(JNIEnv*, jobject, jint amplitude) {
-    if (mHal != nullptr) {
-        Status status = mHal->setAmplitude(static_cast<uint32_t>(amplitude));
-        if (status != Status::OK) {
-            ALOGE("Failed to set vibrator amplitude (%" PRIu32 ").",
-                    static_cast<uint32_t>(status));
-        }
-    } else {
-        ALOGW("Unable to set vibration amplitude, there is no vibrator device.");
+    Status status = halCall(&IVibrator::setAmplitude, static_cast<uint32_t>(amplitude))
+        .withDefault(Status::UNKNOWN_ERROR);
+    if (status != Status::OK) {
+      ALOGE("Failed to set vibrator amplitude (%" PRIu32 ").",
+            static_cast<uint32_t>(status));
     }
 }
 
 static jlong vibratorPerformEffect(JNIEnv*, jobject, jlong effect, jint strength) {
-    if (mHal != nullptr) {
-        Status status;
-        uint32_t lengthMs;
-        auto callback = [&status, &lengthMs](Status retStatus, uint32_t retLengthMs) {
-            status = retStatus;
-            lengthMs = retLengthMs;
-        };
-        EffectStrength effectStrength(static_cast<EffectStrength>(strength));
+    Status status;
+    uint32_t lengthMs;
+    auto callback = [&status, &lengthMs](Status retStatus, uint32_t retLengthMs) {
+        status = retStatus;
+        lengthMs = retLengthMs;
+    };
+    EffectStrength effectStrength(static_cast<EffectStrength>(strength));
 
-        if (effect < 0  || effect > static_cast<uint32_t>(Effect_1_1::TICK)) {
-            ALOGW("Unable to perform haptic effect, invalid effect ID (%" PRId32 ")",
+    if (effect < 0  || effect > static_cast<uint32_t>(Effect_1_1::TICK)) {
+        ALOGW("Unable to perform haptic effect, invalid effect ID (%" PRId32 ")",
+                static_cast<int32_t>(effect));
+    } else if (effect == static_cast<uint32_t>(Effect_1_1::TICK)) {
+        auto ret = halCall(&IVibrator_1_1::perform_1_1, static_cast<Effect_1_1>(effect),
+                           effectStrength, callback);
+        if (!ret.isOk()) {
+            ALOGW("Failed to perform effect (%" PRId32 "), insufficient HAL version",
                     static_cast<int32_t>(effect));
-        } else if (effect == static_cast<uint32_t>(Effect_1_1::TICK)) {
-            sp<IVibrator_1_1> hal_1_1 = IVibrator_1_1::castFrom(mHal);
-            if (hal_1_1 != nullptr) {
-                hal_1_1->perform_1_1(static_cast<Effect_1_1>(effect), effectStrength, callback);
-            } else {
-                ALOGW("Failed to perform effect (%" PRId32 "), insufficient HAL version",
-                        static_cast<int32_t>(effect));
-            }
-        } else {
-            mHal->perform(static_cast<Effect>(effect), effectStrength, callback);
-        }
-        if (status == Status::OK) {
-            return lengthMs;
-        } else if (status != Status::UNSUPPORTED_OPERATION) {
-            // Don't warn on UNSUPPORTED_OPERATION, that's a normal even and just means the motor
-            // doesn't have a pre-defined waveform to perform for it, so we should just fall back
-            // to the framework waveforms.
-            ALOGE("Failed to perform haptic effect: effect=%" PRId64 ", strength=%" PRId32
-                    ", error=%" PRIu32 ").", static_cast<int64_t>(effect),
-                    static_cast<int32_t>(strength), static_cast<uint32_t>(status));
         }
     } else {
-        ALOGW("Unable to perform haptic effect, there is no vibrator device.");
+        auto ret = halCall(&IVibrator::perform, static_cast<Effect>(effect), effectStrength,
+                           callback);
+        if (!ret.isOk()) {
+            ALOGW("Failed to perform effect (%" PRId32 ")", static_cast<int32_t>(effect));
+        }
+    }
+
+    if (status == Status::OK) {
+        return lengthMs;
+    } else if (status != Status::UNSUPPORTED_OPERATION) {
+        // Don't warn on UNSUPPORTED_OPERATION, that's a normal even and just means the motor
+        // doesn't have a pre-defined waveform to perform for it, so we should just fall back
+        // to the framework waveforms.
+        ALOGE("Failed to perform haptic effect: effect=%" PRId64 ", strength=%" PRId32
+                ", error=%" PRIu32 ").", static_cast<int64_t>(effect),
+                static_cast<int32_t>(strength), static_cast<uint32_t>(status));
     }
     return -1;
 }
diff --git a/services/tests/notification/AndroidManifest.xml b/services/tests/notification/AndroidManifest.xml
index 99d9c7b..c20020a 100644
--- a/services/tests/notification/AndroidManifest.xml
+++ b/services/tests/notification/AndroidManifest.xml
@@ -31,7 +31,7 @@
     </application>
 
     <instrumentation
-        android:name="android.support.test.runner.AndroidJUnitRunner"
+        android:name="android.testing.TestableInstrumentation"
         android:targetPackage="com.android.frameworks.tests.notification"
         android:label="Notification Tests" />
 </manifest>
diff --git a/telephony/java/android/telephony/ImsiEncryptionInfo.java b/telephony/java/android/telephony/ImsiEncryptionInfo.java
index ecb9d25..d2680ad 100644
--- a/telephony/java/android/telephony/ImsiEncryptionInfo.java
+++ b/telephony/java/android/telephony/ImsiEncryptionInfo.java
@@ -17,6 +17,7 @@
 
 import android.os.Parcel;
 import android.os.Parcelable;
+import java.util.Date;
 import android.util.Log;
 
 import java.security.KeyFactory;
@@ -34,7 +35,6 @@
 public final class ImsiEncryptionInfo implements Parcelable {
 
     private static final String LOG_TAG = "ImsiEncryptionInfo";
-    private static final boolean DBG = false;
 
 
     private final String mcc;
@@ -42,14 +42,25 @@
     private final PublicKey publicKey;
     private final String keyIdentifier;
     private final int keyType;
+    //Date-Time in UTC when the key will expire.
+    private final Date expirationTime;
 
     public ImsiEncryptionInfo(String mcc, String mnc, int keyType, String keyIdentifier,
-                              PublicKey publicKey) {
+                              byte[] key, Date expirationTime) {
+        this(mcc, mnc, keyType, keyIdentifier, makeKeyObject(key), expirationTime);
+    }
+
+    public ImsiEncryptionInfo(String mcc, String mnc, int keyType, String keyIdentifier,
+                              PublicKey publicKey, Date expirationTime) {
+        // todo need to validate that ImsiEncryptionInfo is being created with the correct params.
+        //      Including validating that the public key is in "X.509" format. This will be done in
+        //      a subsequent CL.
         this.mcc = mcc;
         this.mnc = mnc;
         this.keyType = keyType;
         this.publicKey = publicKey;
         this.keyIdentifier = keyIdentifier;
+        this.expirationTime = expirationTime;
     }
 
     public ImsiEncryptionInfo(Parcel in) {
@@ -61,7 +72,7 @@
         mnc = in.readString();
         keyIdentifier = in.readString();
         keyType = in.readInt();
-
+        expirationTime = new Date(in.readLong());
     }
 
     public String getMnc() {
@@ -84,6 +95,10 @@
         return this.publicKey;
     }
 
+    public Date getExpirationTime() {
+        return this.expirationTime;
+    }
+
     private static PublicKey makeKeyObject(byte[] publicKeyBytes) {
         try {
             X509EncodedKeySpec pubKeySpec = new X509EncodedKeySpec(publicKeyBytes);
@@ -91,7 +106,7 @@
         } catch (InvalidKeySpecException | NoSuchAlgorithmException ex) {
             Log.e(LOG_TAG, "Error makeKeyObject: unable to convert into PublicKey", ex);
         }
-     return null;
+        throw new IllegalArgumentException();
     }
 
     /** Implement the Parcelable interface */
@@ -122,6 +137,7 @@
         dest.writeString(mnc);
         dest.writeString(keyIdentifier);
         dest.writeInt(keyType);
+        dest.writeLong(expirationTime.getTime());
     }
 
     @Override
@@ -132,6 +148,7 @@
                 + "publicKey=" + publicKey
                 + ", keyIdentifier=" + keyIdentifier
                 + ", keyType=" + keyType
+                + ", expirationTime=" + expirationTime
                 + "]";
     }
 }
diff --git a/telephony/java/android/telephony/TelephonyScanManager.java b/telephony/java/android/telephony/TelephonyScanManager.java
index c905d3a..92a21b6 100644
--- a/telephony/java/android/telephony/TelephonyScanManager.java
+++ b/telephony/java/android/telephony/TelephonyScanManager.java
@@ -20,15 +20,18 @@
 
 import android.content.Context;
 import android.os.Binder;
+import android.os.Bundle;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.Looper;
 import android.os.Message;
 import android.os.Messenger;
+import android.os.Parcelable;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.util.Log;
 import android.util.SparseArray;
+import java.util.Arrays;
 import java.util.List;
 
 import com.android.internal.telephony.ITelephony;
@@ -42,6 +45,9 @@
     private static final String TAG = "TelephonyScanManager";
 
     /** @hide */
+    public static final String SCAN_RESULT_KEY = "scanResult";
+
+    /** @hide */
     public static final int CALLBACK_SCAN_RESULTS = 1;
     /** @hide */
     public static final int CALLBACK_SCAN_ERROR = 2;
@@ -112,7 +118,13 @@
                 switch (message.what) {
                     case CALLBACK_SCAN_RESULTS:
                         try {
-                            callback.onResults((List<CellInfo>) message.obj);
+                            final Bundle b = message.getData();
+                            final Parcelable[] parcelables = b.getParcelableArray(SCAN_RESULT_KEY);
+                            CellInfo[] ci = new CellInfo[parcelables.length];
+                            for (int i = 0; i < parcelables.length; i++) {
+                                ci[i] = (CellInfo) parcelables[i];
+                            }
+                            callback.onResults((List<CellInfo>) Arrays.asList(ci));
                         } catch (Exception e) {
                             Rlog.e(TAG, "Exception in networkscan callback onResults", e);
                         }
diff --git a/telephony/java/android/telephony/euicc/EuiccManager.java b/telephony/java/android/telephony/euicc/EuiccManager.java
index f22a632..8304d84 100644
--- a/telephony/java/android/telephony/euicc/EuiccManager.java
+++ b/telephony/java/android/telephony/euicc/EuiccManager.java
@@ -475,6 +475,36 @@
         }
     }
 
+    /**
+     * Ensure that subscriptions will be retained on the next factory reset.
+     *
+     * <p>By default, all subscriptions on the eUICC are erased the first time a device boots (ever
+     * and after factory resets). This ensures that the data is wiped after a factory reset is
+     * performed via fastboot or recovery mode, as these modes do not support the necessary radio
+     * communication needed to wipe the eSIM.
+     *
+     * <p>However, this method may be called right before a factory reset issued via settings when
+     * the user elects to retain subscriptions. Doing so will mark them for retention so that they
+     * are not cleared after the ensuing reset.
+     *
+     * <p>Requires that the calling app has the {@link android.Manifest.permission#MASTER_CLEAR}
+     * permission. This is for internal system use only.
+     *
+     * @param callbackIntent a PendingIntent to launch when the operation completes.
+     * @hide
+     */
+    public void retainSubscriptionsForFactoryReset(PendingIntent callbackIntent) {
+        if (!isEnabled()) {
+            sendUnavailableError(callbackIntent);
+            return;
+        }
+        try {
+            mController.retainSubscriptionsForFactoryReset(callbackIntent);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
     private static void sendUnavailableError(PendingIntent callbackIntent) {
         try {
             callbackIntent.send(EMBEDDED_SUBSCRIPTION_RESULT_ERROR);
diff --git a/telephony/java/android/telephony/ims/ImsServiceProxy.java b/telephony/java/android/telephony/ims/ImsServiceProxy.java
index a75cd86..038e295 100644
--- a/telephony/java/android/telephony/ims/ImsServiceProxy.java
+++ b/telephony/java/android/telephony/ims/ImsServiceProxy.java
@@ -90,11 +90,11 @@
                         " status: " + status);
                 if (mSlotId == slotId && feature == mSupportedFeature) {
                     mFeatureStatusCached = status;
+                    if (mStatusCallback != null) {
+                        mStatusCallback.notifyStatusChanged();
+                    }
                 }
             }
-            if (mStatusCallback != null) {
-                mStatusCallback.notifyStatusChanged();
-            }
         }
     };
 
@@ -129,7 +129,9 @@
     @Override
     public void endSession(int sessionId) throws RemoteException {
         synchronized (mLock) {
-            checkServiceIsReady();
+            // Only check to make sure the binder connection still exists. This method should
+            // still be able to be called when the state is STATE_NOT_AVAILABLE.
+            checkBinderConnection();
             getServiceInterface(mBinder).endSession(mSlotId, mSupportedFeature, sessionId);
         }
     }
diff --git a/telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl b/telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl
index fa43631..b3fc90d 100644
--- a/telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl
+++ b/telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl
@@ -40,4 +40,5 @@
     oneway void updateSubscriptionNickname(int subscriptionId, String nickname,
         in PendingIntent callbackIntent);
     oneway void eraseSubscriptions(in PendingIntent callbackIntent);
+    oneway void retainSubscriptionsForFactoryReset(in PendingIntent callbackIntent);
 }
\ No newline at end of file
diff --git a/tests/StatusBar/AndroidManifest.xml b/tests/StatusBar/AndroidManifest.xml
index 81442bf..6a082e9 100644
--- a/tests/StatusBar/AndroidManifest.xml
+++ b/tests/StatusBar/AndroidManifest.xml
@@ -6,6 +6,7 @@
     <uses-permission android:name="android.permission.EXPAND_STATUS_BAR" />
     <uses-permission android:name="android.permission.VIBRATE" />
     <uses-permission android:name="android.permission.CHANGE_COMPONENT_ENABLED_STATE" />
+    <uses-permission android:name="android.permission.MANAGE_NOTIFICATIONS" />
 
     <application>
         <activity android:name="StatusBarTest" android:label="_StatusBar">
diff --git a/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java b/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java
index 5dd42dd..fc68183 100644
--- a/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java
+++ b/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java
@@ -129,6 +129,42 @@
                     mNM.notify(7001, n);
                 }
             },
+            new Test("with zen") {
+                public void run()
+                {
+                    mNM.setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALARMS);
+                    Notification n = new Notification.Builder(NotificationTestList.this,
+                            "default")
+                            .setSmallIcon(R.drawable.icon2)
+                            .setContentTitle("Default priority")
+                            .build();
+                    mNM.notify("default", 7004, n);
+                    try {
+                        Thread.sleep(8000);
+                    } catch (InterruptedException e) {
+                        e.printStackTrace();
+                    }
+                    mNM.setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL);
+                }
+            },
+            new Test("repeated") {
+                public void run()
+                {
+                    for (int i = 0; i < 50; i++) {
+                        Notification n = new Notification.Builder(NotificationTestList.this,
+                                "default")
+                                .setSmallIcon(R.drawable.icon2)
+                                .setContentTitle("Default priority")
+                                .build();
+                        mNM.notify("default", 7004, n);
+                        try {
+                            Thread.sleep(100);
+                        } catch (InterruptedException e) {
+                            e.printStackTrace();
+                        }
+                    }
+                }
+            },
             new Test("Post a group") {
                 public void run()
                 {
diff --git a/tests/testables/src/android/testing/TestableInstrumentation.java b/tests/testables/src/android/testing/TestableInstrumentation.java
new file mode 100644
index 0000000..93fed85
--- /dev/null
+++ b/tests/testables/src/android/testing/TestableInstrumentation.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package android.testing;
+
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.TestLooperManager;
+import android.support.test.runner.AndroidJUnitRunner;
+import android.util.Log;
+
+import java.util.ArrayList;
+
+/**
+ * Wrapper around instrumentation that spins up a TestLooperManager around
+ * the main looper whenever a test is not using it to attempt to stop crashes
+ * from stopping other tests from running.
+ */
+public class TestableInstrumentation extends AndroidJUnitRunner {
+
+    private static final String TAG = "TestableInstrumentation";
+
+    private static final int MAX_CRASHES = 5;
+    private static MainLooperManager sManager;
+
+    @Override
+    public void onCreate(Bundle arguments) {
+        sManager = new MainLooperManager();
+        Log.setWtfHandler((tag, what, system) -> {
+            if (system) {
+                Log.e(TAG, "WTF!!", what);
+            } else {
+                // These normally kill the app, but we don't want that in a test, instead we want
+                // it to throw.
+                throw new RuntimeException(what);
+            }
+        });
+        super.onCreate(arguments);
+    }
+
+    @Override
+    public void finish(int resultCode, Bundle results) {
+        sManager.destroy();
+        super.finish(resultCode, results);
+    }
+
+    public static void acquireMain() {
+        if (sManager != null) {
+            sManager.acquireMain();
+        }
+    }
+
+    public static void releaseMain() {
+        if (sManager != null) {
+            sManager.releaseMain();
+        }
+    }
+
+    public class MainLooperManager implements Runnable {
+
+        private final ArrayList<Throwable> mExceptions = new ArrayList<>();
+        private Message mStopMessage;
+        private final Handler mMainHandler;
+        private TestLooperManager mManager;
+
+        public MainLooperManager() {
+            mMainHandler = new Handler(Looper.getMainLooper());
+            startManaging();
+        }
+
+        @Override
+        public void run() {
+            try {
+                synchronized (this) {
+                    // Let the thing starting us know we are up and ready to run.
+                    notify();
+                }
+                while (true) {
+                    Message m = mManager.next();
+                    if (m == mStopMessage) {
+                        mManager.recycle(m);
+                        return;
+                    }
+                    try {
+                        mManager.execute(m);
+                    } catch (Throwable t) {
+                        if (!checkStack(t) || (mExceptions.size() == MAX_CRASHES)) {
+                            throw t;
+                        }
+                        mExceptions.add(t);
+                        Log.d(TAG, "Ignoring exception to run more tests", t);
+                    }
+                    mManager.recycle(m);
+                }
+            } finally {
+                mManager.release();
+                synchronized (this) {
+                    // Let the caller know we are done managing the main thread.
+                    notify();
+                }
+            }
+        }
+
+        private boolean checkStack(Throwable t) {
+            StackTraceElement topStack = t.getStackTrace()[0];
+            String className = topStack.getClassName();
+            if (className.equals(TestLooperManager.class.getName())) {
+                topStack = t.getCause().getStackTrace()[0];
+                className = topStack.getClassName();
+            }
+            // Only interested in blocking exceptions from the app itself, not from android
+            // framework.
+            return !className.startsWith("android.")
+                    && !className.startsWith("com.android.internal");
+        }
+
+        public void destroy() {
+            mStopMessage.sendToTarget();
+            if (mExceptions.size() != 0) {
+                throw new RuntimeException("Exception caught during tests", mExceptions.get(0));
+            }
+        }
+
+        public void acquireMain() {
+            synchronized (this) {
+                mStopMessage.sendToTarget();
+                try {
+                    wait();
+                } catch (InterruptedException e) {
+                }
+            }
+        }
+
+        public void releaseMain() {
+            startManaging();
+        }
+
+        private void startManaging() {
+            mStopMessage = mMainHandler.obtainMessage();
+            synchronized (this) {
+                mManager = acquireLooperManager(Looper.getMainLooper());
+                // This bit needs to happen on a background thread or it will hang if called
+                // from the same thread we are looking to block.
+                new Thread(() -> {
+                    // Post a message to the main handler that will manage executing all future
+                    // messages.
+                    mMainHandler.post(this);
+                    while (!mManager.hasMessages(mMainHandler, null, this));
+                    // Lastly run the message that executes this so it can manage the main thread.
+                    Message next = mManager.next();
+                    // Run through messages until we reach ours.
+                    while (next.getCallback() != this) {
+                        mManager.execute(next);
+                        mManager.recycle(next);
+                        next = mManager.next();
+                    }
+                    mManager.execute(next);
+                }).start();
+                if (Looper.myLooper() != Looper.getMainLooper()) {
+                    try {
+                        wait();
+                    } catch (InterruptedException e) {
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/tests/testables/src/android/testing/TestableLooper.java b/tests/testables/src/android/testing/TestableLooper.java
index f6c3cb3..f1a7092 100644
--- a/tests/testables/src/android/testing/TestableLooper.java
+++ b/tests/testables/src/android/testing/TestableLooper.java
@@ -29,7 +29,6 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
-import java.lang.reflect.Field;
 import java.util.Map;
 
 /**
@@ -49,7 +48,7 @@
     private TestLooperManager mQueueWrapper;
 
     public TestableLooper(Looper l) throws Exception {
-        this(InstrumentationRegistry.getInstrumentation().acquireLooperManager(l), l);
+        this(acquireLooperManager(l), l);
     }
 
     private TestableLooper(TestLooperManager wrapper, Looper l) throws Exception {
@@ -78,6 +77,9 @@
      */
     public void destroy() throws NoSuchFieldException, IllegalAccessException {
         mQueueWrapper.release();
+        if (mLooper == Looper.getMainLooper()) {
+            TestableInstrumentation.releaseMain();
+        }
     }
 
     /**
@@ -196,6 +198,13 @@
         }
     }
 
+    private static TestLooperManager acquireLooperManager(Looper l) {
+        if (l == Looper.getMainLooper()) {
+            TestableInstrumentation.acquireMain();
+        }
+        return InstrumentationRegistry.getInstrumentation().acquireLooperManager(l);
+    }
+
     private static final Map<Object, TestableLooper> sLoopers = new ArrayMap<>();
 
     /**
@@ -247,8 +256,7 @@
             }
             boolean set = mTestableLooper.mQueueWrapper == null;
             if (set) {
-                mTestableLooper.mQueueWrapper = InstrumentationRegistry.getInstrumentation()
-                        .acquireLooperManager(mLooper);
+                mTestableLooper.mQueueWrapper = acquireLooperManager(mLooper);
             }
             try {
                 Object[] ret = new Object[1];
@@ -283,6 +291,9 @@
                 if (set) {
                     mTestableLooper.mQueueWrapper.release();
                     mTestableLooper.mQueueWrapper = null;
+                    if (mLooper == Looper.getMainLooper()) {
+                        TestableInstrumentation.releaseMain();
+                    }
                 }
             }
         }