Merge "Once restored, the wallpaper needs to actually draw" into nyc-dev
diff --git a/api/system-current.txt b/api/system-current.txt
index c9176ec..0743b91 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -23829,6 +23829,7 @@
   public static class AudioMix.Builder {
     ctor public AudioMix.Builder(android.media.audiopolicy.AudioMixingRule) throws java.lang.IllegalArgumentException;
     method public android.media.audiopolicy.AudioMix build() throws java.lang.IllegalArgumentException;
+    method public android.media.audiopolicy.AudioMix.Builder setDevice(android.media.AudioDeviceInfo) throws java.lang.IllegalArgumentException;
     method public android.media.audiopolicy.AudioMix.Builder setFormat(android.media.AudioFormat) throws java.lang.IllegalArgumentException;
     method public android.media.audiopolicy.AudioMix.Builder setRouteFlags(int) throws java.lang.IllegalArgumentException;
   }
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 2f6907e..2d33a2c 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -3329,6 +3329,23 @@
         }
     }
 
+    /**
+     * Logs out current current foreground user by switching to the system user and stopping the
+     * user being switched from.
+     * @hide
+     */
+    public static void logoutCurrentUser() {
+        int currentUser = ActivityManager.getCurrentUser();
+        if (currentUser != UserHandle.USER_SYSTEM) {
+            try {
+                ActivityManagerNative.getDefault().switchUser(UserHandle.USER_SYSTEM);
+                ActivityManagerNative.getDefault().stopUser(currentUser, /* force= */ false, null);
+            } catch (RemoteException e) {
+                e.rethrowFromSystemServer();
+            }
+        }
+    }
+
     /** {@hide} */
     public static final int FLAG_OR_STOPPED = 1 << 0;
     /** {@hide} */
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 82c4c51..64586a6 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -1267,8 +1267,15 @@
 
     /** @hide */
     public void setUserRestriction(int code, boolean restricted, IBinder token) {
+        setUserRestriction(code, restricted, token, /*exceptionPackages*/null);
+    }
+
+    /** @hide */
+    public void setUserRestriction(int code, boolean restricted, IBinder token,
+            String[] exceptionPackages) {
         try {
-            mService.setUserRestriction(code, restricted, token, mContext.getUserId());
+            mService.setUserRestriction(code, restricted, token, mContext.getUserId(),
+                  exceptionPackages);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java
index d73deb6..ea8ba2f 100644
--- a/core/java/android/os/Binder.java
+++ b/core/java/android/os/Binder.java
@@ -70,6 +70,9 @@
     private static final boolean CHECK_PARCEL_SIZE = false;
     static final String TAG = "Binder";
 
+    /** @hide */
+    public static boolean LOG_RUNTIME_EXCEPTION = false; // DO NOT SUBMIT WITH TRUE
+
     /**
      * Control whether dump() calls are allowed.
      */
@@ -560,17 +563,16 @@
         // If the call was FLAG_ONEWAY then these exceptions disappear into the ether.
         try {
             res = onTransact(code, data, reply, flags);
-        } catch (RemoteException e) {
-            if ((flags & FLAG_ONEWAY) != 0) {
-                Log.w(TAG, "Binder call failed.", e);
-            } else {
-                reply.setDataPosition(0);
-                reply.writeException(e);
-            }
-            res = true;
-        } catch (RuntimeException e) {
-            if ((flags & FLAG_ONEWAY) != 0) {
+        } catch (RemoteException|RuntimeException e) {
+            if (LOG_RUNTIME_EXCEPTION) {
                 Log.w(TAG, "Caught a RuntimeException from the binder stub implementation.", e);
+            }
+            if ((flags & FLAG_ONEWAY) != 0) {
+                if (e instanceof RemoteException) {
+                    Log.w(TAG, "Binder call failed.", e);
+                } else {
+                    Log.w(TAG, "Caught a RuntimeException from the binder stub implementation.", e);
+                }
             } else {
                 reply.setDataPosition(0);
                 reply.writeException(e);
diff --git a/core/java/android/util/DisplayMetrics.java b/core/java/android/util/DisplayMetrics.java
index a747f16..d201ade 100644
--- a/core/java/android/util/DisplayMetrics.java
+++ b/core/java/android/util/DisplayMetrics.java
@@ -154,11 +154,11 @@
     public static final int DENSITY_DEVICE_STABLE = getDeviceDensity();
 
     /**
-     * The absolute width of the display in pixels.
+     * The absolute width of the available display size in pixels.
      */
     public int widthPixels;
     /**
-     * The absolute height of the display in pixels.
+     * The absolute height of the available display size in pixels.
      */
     public int heightPixels;
     /**
diff --git a/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java b/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java
index 60c7270..dcf987b 100644
--- a/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java
+++ b/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java
@@ -84,8 +84,19 @@
             if (fileSize > Integer.MAX_VALUE) {
                 return false;
             }
-            MappedByteBuffer apkContents =
-                    apk.getChannel().map(FileChannel.MapMode.READ_ONLY, 0, fileSize);
+            MappedByteBuffer apkContents;
+            try {
+                apkContents = apk.getChannel().map(FileChannel.MapMode.READ_ONLY, 0, fileSize);
+            } catch (IOException e) {
+                if (e.getCause() instanceof OutOfMemoryError) {
+                    // TODO: Remove this temporary workaround once verifying large APKs is
+                    // supported. Very large APKs cannot be memory-mapped. This verification code
+                    // needs to change to use a different approach for verifying such APKs.
+                    return false; // Pretend that this APK does not have a v2 signature.
+                } else {
+                    throw new IOException("Failed to memory-map APK", e);
+                }
+            }
             // ZipUtils and APK Signature Scheme v2 verifier expect little-endian byte order.
             apkContents.order(ByteOrder.LITTLE_ENDIAN);
 
@@ -134,11 +145,26 @@
         if (fileSize > Integer.MAX_VALUE) {
             throw new IOException("File too large: " + apk.length() + " bytes");
         }
-        MappedByteBuffer apkContents =
-                apk.getChannel().map(FileChannel.MapMode.READ_ONLY, 0, fileSize);
-        // Attempt to preload the contents into memory for faster overall verification (v2 and
-        // older) at the expense of somewhat increased latency for rejecting malformed APKs.
-        apkContents.load();
+        MappedByteBuffer apkContents;
+        try {
+            apkContents = apk.getChannel().map(FileChannel.MapMode.READ_ONLY, 0, fileSize);
+            // Attempt to preload the contents into memory for faster overall verification (v2 and
+            // older) at the expense of somewhat increased latency for rejecting malformed APKs.
+            apkContents.load();
+        } catch (IOException e) {
+            if (e.getCause() instanceof OutOfMemoryError) {
+                // TODO: Remove this temporary workaround once verifying large APKs is supported.
+                // Very large APKs cannot be memory-mapped. This verification code needs to change
+                // to use a different approach for verifying such APKs.
+                // This workaround pretends that this APK does not have a v2 signature. This works
+                // fine provided the APK is not actually v2-signed. If the APK is v2 signed, v2
+                // signature stripping protection inside v1 signature verification code will reject
+                // this APK.
+                throw new SignatureNotFoundException("Failed to memory-map APK", e);
+            } else {
+                throw new IOException("Failed to memory-map APK", e);
+            }
+        }
         return verify(apkContents);
     }
 
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index d29bc21..56ee478 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -1382,6 +1382,13 @@
             if (mIsInterestedInDrag) {
                 retval = true;
             }
+
+            if (!retval) {
+                // Neither us nor any of our children are interested in this drag, so stop tracking
+                // the current drag event.
+                mCurrentDragStartEvent.recycle();
+                mCurrentDragStartEvent = null;
+            }
         } break;
 
         case DragEvent.ACTION_DRAG_ENDED: {
diff --git a/core/java/com/android/internal/app/IAppOpsService.aidl b/core/java/com/android/internal/app/IAppOpsService.aidl
index b13be97..3a31b37 100644
--- a/core/java/com/android/internal/app/IAppOpsService.aidl
+++ b/core/java/com/android/internal/app/IAppOpsService.aidl
@@ -45,6 +45,6 @@
     void setAudioRestriction(int code, int usage, int uid, int mode, in String[] exceptionPackages);
 
     void setUserRestrictions(in Bundle restrictions, IBinder token, int userHandle);
-    void setUserRestriction(int code, boolean restricted, IBinder token, int userHandle);
+    void setUserRestriction(int code, boolean restricted, IBinder token, int userHandle, in String[] exceptionPackages);
     void removeUser(int userHandle);
 }
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index ce5d07c..51cd029 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -805,6 +805,13 @@
     -->
     <integer name="config_longPressOnPowerBehavior">1</integer>
 
+    <!-- Control the behavior when the user long presses the back button.  Non-zero values are only
+         valid for watches as part of CDD/CTS.
+            0 - Nothing
+            1 - Go to voice assist
+    -->
+    <integer name="config_longPressOnBackBehavior">0</integer>
+
     <!-- Control the behavior when the user short presses the power button.
             0 - Nothing
             1 - Go to sleep (doze)
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 70f4f54..1470741 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -382,6 +382,7 @@
   <java-symbol type="integer" name="config_extraFreeKbytesAbsolute" />
   <java-symbol type="integer" name="config_immersive_mode_confirmation_panic" />
   <java-symbol type="integer" name="config_longPressOnPowerBehavior" />
+  <java-symbol type="integer" name="config_longPressOnBackBehavior" />
   <java-symbol type="integer" name="config_lowMemoryKillerMinFreeKbytesAdjust" />
   <java-symbol type="integer" name="config_lowMemoryKillerMinFreeKbytesAbsolute" />
   <java-symbol type="integer" name="config_max_pan_devices" />
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk
index 936c7e8..f6e3b50 100644
--- a/libs/hwui/Android.mk
+++ b/libs/hwui/Android.mk
@@ -240,6 +240,7 @@
     tests/unit/DamageAccumulatorTests.cpp \
     tests/unit/DeviceInfoTests.cpp \
     tests/unit/FatVectorTests.cpp \
+    tests/unit/GlopBuilderTests.cpp \
     tests/unit/GpuMemoryTrackerTests.cpp \
     tests/unit/LayerUpdateQueueTests.cpp \
     tests/unit/LinearAllocatorTests.cpp \
diff --git a/libs/hwui/FloatColor.h b/libs/hwui/FloatColor.h
index 97dec88..9a39ec2 100644
--- a/libs/hwui/FloatColor.h
+++ b/libs/hwui/FloatColor.h
@@ -17,6 +17,7 @@
 #define FLOATCOLOR_H
 
 #include "utils/Macros.h"
+#include "utils/MathUtils.h"
 
 #include <stdint.h>
 
@@ -38,6 +39,17 @@
                 || b > 0.0f;
     }
 
+    bool operator==(const FloatColor& other) const {
+        return MathUtils::areEqual(r, other.r)
+                && MathUtils::areEqual(g, other.g)
+                && MathUtils::areEqual(b, other.b)
+                && MathUtils::areEqual(a, other.a);
+    }
+
+    bool operator!=(const FloatColor& other) const {
+        return !(*this == other);
+    }
+
     float r;
     float g;
     float b;
diff --git a/libs/hwui/Glop.h b/libs/hwui/Glop.h
index e72f396..704bd69 100644
--- a/libs/hwui/Glop.h
+++ b/libs/hwui/Glop.h
@@ -81,8 +81,10 @@
  * vertex/index/Texture/RoundRectClipState pointers prevent this from
  * being safe.
  */
-// TODO: PREVENT_COPY_AND_ASSIGN(...) or similar
 struct Glop {
+    PREVENT_COPY_AND_ASSIGN(Glop);
+public:
+    Glop() { }
     struct Mesh {
         GLuint primitiveMode; // GL_TRIANGLES and GL_TRIANGLE_STRIP supported
 
@@ -149,7 +151,7 @@
        }
     } transform;
 
-    const RoundRectClipState* roundRectClipState;
+    const RoundRectClipState* roundRectClipState = nullptr;
 
     /**
      * Blending to be used by this draw - both GL_NONE if blending is disabled.
@@ -165,7 +167,7 @@
      * Bounds of the drawing command in layer space. Only mapped into layer
      * space once GlopBuilder::build() is called.
      */
-    Rect bounds;
+    Rect bounds; // TODO: remove for HWUI_NEW_OPS
 
     /**
      * Additional render state to enumerate:
diff --git a/libs/hwui/tests/unit/GlopBuilderTests.cpp b/libs/hwui/tests/unit/GlopBuilderTests.cpp
new file mode 100644
index 0000000..949c541
--- /dev/null
+++ b/libs/hwui/tests/unit/GlopBuilderTests.cpp
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <gtest/gtest.h>
+
+#include "BakedOpRenderer.h"
+#include "Glop.h"
+#include "GlopBuilder.h"
+#include "Rect.h"
+#include "tests/common/TestUtils.h"
+#include "utils/Color.h"
+
+#include <SkPaint.h>
+
+using namespace android::uirenderer;
+
+static void expectFillEq(Glop::Fill& expectedFill, Glop::Fill& builtFill) {
+    EXPECT_EQ(expectedFill.colorEnabled, builtFill.colorEnabled);
+    if (expectedFill.colorEnabled)
+        EXPECT_EQ(expectedFill.color, builtFill.color);
+
+    EXPECT_EQ(expectedFill.filterMode, builtFill.filterMode);
+    if (expectedFill.filterMode == ProgramDescription::ColorFilterMode::Blend) {
+        EXPECT_EQ(expectedFill.filter.color, builtFill.filter.color);
+    } else if (expectedFill.filterMode == ProgramDescription::ColorFilterMode::Matrix) {
+        Glop::Fill::Filter::Matrix& expectedMatrix = expectedFill.filter.matrix;
+        Glop::Fill::Filter::Matrix& builtMatrix = expectedFill.filter.matrix;
+        EXPECT_TRUE(std::memcmp(expectedMatrix.matrix, builtMatrix.matrix,
+                sizeof(Glop::Fill::Filter::Matrix::matrix)));
+        EXPECT_TRUE(std::memcmp(expectedMatrix.vector, builtMatrix.vector,
+                sizeof(Glop::Fill::Filter::Matrix::vector)));
+    }
+    EXPECT_EQ(expectedFill.skiaShaderData.skiaShaderType, builtFill.skiaShaderData.skiaShaderType);
+    EXPECT_EQ(expectedFill.texture.clamp, builtFill.texture.clamp);
+    EXPECT_EQ(expectedFill.texture.filter, builtFill.texture.filter);
+    EXPECT_EQ(expectedFill.texture.target, builtFill.texture.target);
+    EXPECT_EQ(expectedFill.texture.textureTransform, builtFill.texture.textureTransform);
+}
+
+static void expectBlendEq(Glop::Blend& expectedBlend, Glop::Blend& builtBlend) {
+    EXPECT_EQ(expectedBlend.src, builtBlend.src);
+    EXPECT_EQ(expectedBlend.dst, builtBlend.dst);
+}
+
+static void expectMeshEq(Glop::Mesh& expectedMesh, Glop::Mesh& builtMesh) {
+    EXPECT_EQ(expectedMesh.elementCount, builtMesh.elementCount);
+    EXPECT_EQ(expectedMesh.primitiveMode, builtMesh.primitiveMode);
+    EXPECT_EQ(expectedMesh.indices.indices, builtMesh.indices.indices);
+    EXPECT_EQ(expectedMesh.indices.bufferObject, builtMesh.indices.bufferObject);
+    EXPECT_EQ(expectedMesh.vertices.attribFlags, builtMesh.vertices.attribFlags);
+    EXPECT_EQ(expectedMesh.vertices.bufferObject, builtMesh.vertices.bufferObject);
+    EXPECT_EQ(expectedMesh.vertices.color, builtMesh.vertices.color);
+    EXPECT_EQ(expectedMesh.vertices.position, builtMesh.vertices.position);
+    EXPECT_EQ(expectedMesh.vertices.stride, builtMesh.vertices.stride);
+    EXPECT_EQ(expectedMesh.vertices.texCoord, builtMesh.vertices.texCoord);
+
+    if (builtMesh.vertices.position) {
+        for (int i = 0; i < 4; i++) {
+            TextureVertex& expectedVertex = expectedMesh.mappedVertices[i];
+            TextureVertex& builtVertex = builtMesh.mappedVertices[i];
+            EXPECT_EQ(expectedVertex.u, builtVertex.u);
+            EXPECT_EQ(expectedVertex.v, builtVertex.v);
+            EXPECT_EQ(expectedVertex.x, builtVertex.x);
+            EXPECT_EQ(expectedVertex.y, builtVertex.y);
+        }
+    }
+}
+
+static void expectTransformEq(Glop::Transform& expectedTransform, Glop::Transform& builtTransform) {
+    EXPECT_EQ(expectedTransform.canvas, builtTransform.canvas);
+    EXPECT_EQ(expectedTransform.modelView, builtTransform.modelView);
+    EXPECT_EQ(expectedTransform.transformFlags, expectedTransform.transformFlags);
+}
+
+static void expectGlopEq(Glop& expectedGlop, Glop& builtGlop) {
+    EXPECT_EQ(expectedGlop.bounds, builtGlop.bounds);
+    expectBlendEq(expectedGlop.blend, builtGlop.blend);
+    expectFillEq(expectedGlop.fill, builtGlop.fill);
+    expectMeshEq(expectedGlop.mesh, builtGlop.mesh);
+    expectTransformEq(expectedGlop.transform, builtGlop.transform);
+}
+
+static std::unique_ptr<Glop> blackUnitQuadGlop(RenderState& renderState) {
+    std::unique_ptr<Glop> glop(new Glop());
+    glop->blend = { GL_ZERO, GL_ZERO };
+    glop->mesh.elementCount = 4;
+    glop->mesh.primitiveMode = GL_TRIANGLE_STRIP;
+    glop->mesh.indices.indices = nullptr;
+    glop->mesh.indices.bufferObject = GL_ZERO;
+    glop->mesh.vertices = {
+            renderState.meshState().getUnitQuadVBO(),
+            VertexAttribFlags::None,
+            nullptr, nullptr, nullptr,
+            kTextureVertexStride };
+    glop->transform.modelView.loadIdentity();
+    glop->fill.colorEnabled = true;
+    glop->fill.color.set(Color::Black);
+    glop->fill.skiaShaderData.skiaShaderType = kNone_SkiaShaderType;
+    glop->fill.filterMode = ProgramDescription::ColorFilterMode::None;
+    glop->fill.texture = { nullptr, GL_INVALID_ENUM, GL_INVALID_ENUM, GL_INVALID_ENUM, nullptr };
+    return glop;
+}
+
+RENDERTHREAD_TEST(GlopBuilder, rectSnapTest) {
+    RenderState& renderState = renderThread.renderState();
+    Caches& caches = Caches::getInstance();
+    SkPaint paint;
+    Rect dest(1, 1, 100, 100);
+    Matrix4 simpleTranslate;
+    simpleTranslate.loadTranslate(0.7, 0.7, 0);
+    Glop glop;
+    GlopBuilder(renderState, caches, &glop)
+            .setRoundRectClipState(nullptr)
+            .setMeshUnitQuad()
+            .setFillPaint(paint, 1.0f)
+            .setTransform(simpleTranslate, TransformFlags::None)
+            .setModelViewMapUnitToRectSnap(dest)
+            .build();
+
+    std::unique_ptr<Glop> goldenGlop(blackUnitQuadGlop(renderState));
+    // Rect(1,1,100,100) is the set destination,
+    // so unit quad should be translated by (1,1) and scaled by (99, 99)
+    // Tricky part: because translate (0.7, 0.7) and snapping were set in glopBuilder,
+    // unit quad also should be translate by additional (0.3, 0.3) to snap to exact pixels.
+    goldenGlop->transform.modelView.loadTranslate(1.3, 1.3, 0);
+    goldenGlop->transform.modelView.scale(99, 99, 1);
+    goldenGlop->bounds = android::uirenderer::Rect(1.70, 1.70, 100.70, 100.70);
+    goldenGlop->transform.canvas = simpleTranslate;
+    goldenGlop->fill.texture.filter = GL_NEAREST;
+    expectGlopEq(*goldenGlop, glop);
+}
diff --git a/media/java/android/media/audiopolicy/AudioMix.java b/media/java/android/media/audiopolicy/AudioMix.java
index 55fb82b..56d3c99 100644
--- a/media/java/android/media/audiopolicy/AudioMix.java
+++ b/media/java/android/media/audiopolicy/AudioMix.java
@@ -17,7 +17,9 @@
 package android.media.audiopolicy;
 
 import android.annotation.IntDef;
+import android.annotation.NonNull;
 import android.annotation.SystemApi;
+import android.media.AudioDeviceInfo;
 import android.media.AudioFormat;
 import android.media.AudioSystem;
 
@@ -36,19 +38,28 @@
     private int mRouteFlags;
     private String mRegistrationId;
     private int mMixType = MIX_TYPE_INVALID;
+
+    // written by AudioPolicy
     int mMixState = MIX_STATE_DISABLED;
     int mCallbackFlags;
 
+    // initialized in constructor, read by AudioPolicyConfig
+    final int mDeviceId;
+    final String mDeviceAddress;
+
     /**
      * All parameters are guaranteed valid through the Builder.
      */
-    private AudioMix(AudioMixingRule rule, AudioFormat format, int routeFlags, int callbackFlags) {
+    private AudioMix(AudioMixingRule rule, AudioFormat format, int routeFlags, int callbackFlags,
+            int deviceId, String deviceAddress) {
         mRule = rule;
         mFormat = format;
         mRouteFlags = routeFlags;
         mRegistrationId = null;
         mMixType = rule.getTargetMixType();
         mCallbackFlags = callbackFlags;
+        mDeviceId = deviceId;
+        mDeviceAddress = deviceAddress;
     }
 
     // CALLBACK_FLAG_* values: keep in sync with AudioMix::kCbFlag* values defined
@@ -74,6 +85,8 @@
     @SystemApi
     public static final int ROUTE_FLAG_LOOP_BACK = 0x1 << 1;
 
+    private static final int ROUTE_FLAG_SUPPORTED = ROUTE_FLAG_RENDER | ROUTE_FLAG_LOOP_BACK;
+
     // MIX_TYPE_* values to keep in sync with frameworks/av/include/media/AudioPolicy.h
     /**
      * @hide
@@ -172,6 +185,8 @@
         private AudioFormat mFormat = null;
         private int mRouteFlags = 0;
         private int mCallbackFlags = 0;
+        private int mDeviceId = -1;
+        private String mDeviceAddress = null;
 
         /**
          * @hide
@@ -200,7 +215,7 @@
          * @return the same Builder instance.
          * @throws IllegalArgumentException
          */
-        public Builder setMixingRule(AudioMixingRule rule)
+        Builder setMixingRule(AudioMixingRule rule)
                 throws IllegalArgumentException {
             if (rule == null) {
                 throw new IllegalArgumentException("Illegal null AudioMixingRule argument");
@@ -216,7 +231,7 @@
          * @return the same Builder instance.
          * @throws IllegalArgumentException
          */
-        public Builder setCallbackFlags(int flags) throws IllegalArgumentException {
+        Builder setCallbackFlags(int flags) throws IllegalArgumentException {
             if ((flags != 0) && ((flags & CALLBACK_FLAGS_ALL) == 0)) {
                 throw new IllegalArgumentException("Illegal callback flags 0x"
                         + Integer.toHexString(flags).toUpperCase());
@@ -226,6 +241,19 @@
         }
 
         /**
+         * @hide
+         * Only used by AudioPolicyConfig, not a public API.
+         * @param deviceId
+         * @param address
+         * @return the same Builder instance.
+         */
+        Builder setDevice(int deviceId, String address) {
+            mDeviceId = deviceId;
+            mDeviceAddress = address;
+            return this;
+        }
+
+        /**
          * Sets the {@link AudioFormat} for the mix.
          * @param format a non-null {@link AudioFormat} instance.
          * @return the same Builder instance.
@@ -242,7 +270,8 @@
         }
 
         /**
-         * Sets the routing behavior for the mix.
+         * Sets the routing behavior for the mix. If not set, routing behavior will default to
+         * {@link AudioMix#ROUTE_FLAG_LOOP_BACK}.
          * @param routeFlags one of {@link AudioMix#ROUTE_FLAG_LOOP_BACK},
          *     {@link AudioMix#ROUTE_FLAG_RENDER}
          * @return the same Builder instance.
@@ -254,15 +283,41 @@
             if (routeFlags == 0) {
                 throw new IllegalArgumentException("Illegal empty route flags");
             }
-            if ((routeFlags & (ROUTE_FLAG_LOOP_BACK | ROUTE_FLAG_RENDER)) == 0) {
+            if ((routeFlags & ROUTE_FLAG_SUPPORTED) == 0) {
                 throw new IllegalArgumentException("Invalid route flags 0x"
-                        + Integer.toHexString(routeFlags) + "when creating an AudioMix");
+                        + Integer.toHexString(routeFlags) + "when configuring an AudioMix");
+            }
+            if ((routeFlags & ~ROUTE_FLAG_SUPPORTED) != 0) {
+                throw new IllegalArgumentException("Unknown route flags 0x"
+                        + Integer.toHexString(routeFlags) + "when configuring an AudioMix");
             }
             mRouteFlags = routeFlags;
             return this;
         }
 
         /**
+         * Sets the audio device used for playback. Cannot be used in the context of an audio
+         * policy used to inject audio to be recorded, or in a mix whose route flags doesn't
+         * specify {@link AudioMix#ROUTE_FLAG_RENDER}.
+         * @param device a non-null AudioDeviceInfo describing the audio device to play the output
+         *     of this mix.
+         * @return the same Builder instance
+         * @throws IllegalArgumentException
+         */
+        @SystemApi
+        public Builder setDevice(@NonNull AudioDeviceInfo device) throws IllegalArgumentException {
+            if (device == null) {
+                throw new IllegalArgumentException("Illegal null AudioDeviceInfo argument");
+            }
+            if (!device.isSink()) {
+                throw new IllegalArgumentException("Unsupported device type on mix, not a sink");
+            }
+            mDeviceId = device.getId();
+            mDeviceAddress = device.getAddress();
+            return this;
+        }
+
+        /**
          * Combines all of the settings and return a new {@link AudioMix} object.
          * @return a new {@link AudioMix} object
          * @throws IllegalArgumentException if no {@link AudioMixingRule} has been set.
@@ -273,8 +328,13 @@
                 throw new IllegalArgumentException("Illegal null AudioMixingRule");
             }
             if (mRouteFlags == 0) {
-                // no route flags set, use default
-                mRouteFlags = ROUTE_FLAG_RENDER;
+                // no route flags set, use default as described in Builder.setRouteFlags(int)
+                mRouteFlags = ROUTE_FLAG_LOOP_BACK;
+            }
+            // can't do loop back AND render at same time in this implementation
+            if (mRouteFlags == (ROUTE_FLAG_RENDER | ROUTE_FLAG_LOOP_BACK)) {
+                throw new IllegalArgumentException("Unsupported route behavior combination 0x" +
+                        Integer.toHexString(mRouteFlags));
             }
             if (mFormat == null) {
                 // FIXME Can we eliminate this?  Will AudioMix work with an unspecified sample rate?
@@ -284,7 +344,22 @@
                 }
                 mFormat = new AudioFormat.Builder().setSampleRate(rate).build();
             }
-            return new AudioMix(mRule, mFormat, mRouteFlags, mCallbackFlags);
+            if (mDeviceId != -1) {
+                if ((mRouteFlags & ROUTE_FLAG_RENDER) == 0) {
+                    throw new IllegalArgumentException(
+                            "Can't have audio device without flag ROUTE_FLAG_RENDER");
+                }
+                if (mRule.getTargetMixType() != AudioMix.MIX_TYPE_PLAYERS) {
+                    throw new IllegalArgumentException("Unsupported device on non-playback mix");
+                }
+            } else {
+                if ((mRouteFlags & ROUTE_FLAG_RENDER) == ROUTE_FLAG_RENDER) {
+                    throw new IllegalArgumentException(
+                            "Can't have flag ROUTE_FLAG_RENDER without an audio device");
+                }
+            }
+            return new AudioMix(mRule, mFormat, mRouteFlags, mCallbackFlags, mDeviceId,
+                    mDeviceAddress);
         }
     }
 }
diff --git a/media/java/android/media/audiopolicy/AudioPolicyConfig.java b/media/java/android/media/audiopolicy/AudioPolicyConfig.java
index 5d2bac0..3af3ae7 100644
--- a/media/java/android/media/audiopolicy/AudioPolicyConfig.java
+++ b/media/java/android/media/audiopolicy/AudioPolicyConfig.java
@@ -17,6 +17,8 @@
 package android.media.audiopolicy;
 
 import android.media.AudioFormat;
+import android.media.AudioManager;
+import android.media.AudioPatch;
 import android.media.audiopolicy.AudioMixingRule.AudioMixMatchCriterion;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -81,6 +83,9 @@
             dest.writeInt(mix.getRouteFlags());
             // write callback flags
             dest.writeInt(mix.mCallbackFlags);
+            // write device information
+            dest.writeInt(mix.mDeviceId);
+            dest.writeString(mix.mDeviceAddress);
             // write mix format
             dest.writeInt(mix.getFormat().getSampleRate());
             dest.writeInt(mix.getFormat().getEncoding());
@@ -104,6 +109,8 @@
             mixBuilder.setRouteFlags(routeFlags);
             // read callback flags
             mixBuilder.setCallbackFlags(in.readInt());
+            // read device information
+            mixBuilder.setDevice(in.readInt(), in.readString());
             // read mix format
             int sampleRate = in.readInt();
             int encoding = in.readInt();
@@ -197,8 +204,14 @@
         int mixIndex = 0;
         for (AudioMix mix : mMixes) {
             if (!mRegistrationId.isEmpty()) {
-                mix.setRegistration(mRegistrationId + "mix" + mixTypeId(mix.getMixType()) + ":"
-                        + mixIndex++);
+                if ((mix.getRouteFlags() & AudioMix.ROUTE_FLAG_LOOP_BACK) ==
+                        AudioMix.ROUTE_FLAG_LOOP_BACK) {
+                    mix.setRegistration(mRegistrationId + "mix" + mixTypeId(mix.getMixType()) + ":"
+                            + mixIndex++);
+                } else if ((mix.getRouteFlags() & AudioMix.ROUTE_FLAG_RENDER) ==
+                        AudioMix.ROUTE_FLAG_RENDER) {
+                    mix.setRegistration(mix.mDeviceAddress);
+                }
             } else {
                 mix.setRegistration("");
             }
diff --git a/packages/DocumentsUI/res/animator-ldrtl/dir_enter.xml b/packages/DocumentsUI/res/animator-ldrtl/dir_enter.xml
deleted file mode 100644
index 6c7e224..0000000
--- a/packages/DocumentsUI/res/animator-ldrtl/dir_enter.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<!-- Copyright (C) 2013 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.
--->
-
-<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
-    android:valueFrom="-1"
-    android:valueTo="0"
-    android:propertyName="position"
-    android:valueType="floatType"
-    android:duration="@android:integer/config_mediumAnimTime"
-    android:interpolator="@android:interpolator/decelerate_quad" />
diff --git a/packages/DocumentsUI/res/animator-ldrtl/dir_leave.xml b/packages/DocumentsUI/res/animator-ldrtl/dir_leave.xml
deleted file mode 100644
index 8e2925c..0000000
--- a/packages/DocumentsUI/res/animator-ldrtl/dir_leave.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<!-- Copyright (C) 2013 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.
--->
-
-<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
-    android:valueFrom="0"
-    android:valueTo="-1"
-    android:propertyName="position"
-    android:valueType="floatType"
-    android:duration="@android:integer/config_mediumAnimTime"
-    android:interpolator="@android:interpolator/accelerate_quad" />
diff --git a/packages/DocumentsUI/res/animator/dir_frozen.xml b/packages/DocumentsUI/res/animator/dir_frozen.xml
deleted file mode 100644
index b541d13..0000000
--- a/packages/DocumentsUI/res/animator/dir_frozen.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<!-- Copyright (C) 2013 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.
--->
-
-<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
-    android:valueFrom="0"
-    android:valueTo="0"
-    android:propertyName="position"
-    android:valueType="floatType"
-    android:duration="@android:integer/config_mediumAnimTime" />
diff --git a/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java b/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java
index 0af8aa2..99f306a 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java
@@ -283,10 +283,16 @@
      */
     private void openDocument(DocumentInfo doc, Model model) {
 
-        // Provide specialized handling of downloaded APKs This sends the APK
-        // details off to get extra security information added, and finally
-        // to be handled by the package manager.
-        if (MimePredicate.isApkType(doc.mimeType)) {
+        // Anything on downloads goes through the back through downloads manager
+        // (that's the MANAGE_DOCUMENT bit).
+        // This is done for two reasons:
+        // 1) The file in question might be a failed/queued or otherwise have some
+        //    specialized download handling.
+        // 2) For APKs, the download manager will add on some important security stuff
+        //    like origin URL.
+        // All other files not on downloads, event APKs, would get no benefit from this
+        // treatment, thusly the "isDownloads" check.
+        if (getCurrentRoot().isDownloads()) {
             // First try managing the document; we expect manager to filter
             // based on authority, so we don't grant.
             final Intent manage = new Intent(DocumentsContract.ACTION_MANAGE_DOCUMENT);
diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/AnimationView.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/AnimationView.java
index 4f076f1..a666456 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/AnimationView.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/AnimationView.java
@@ -77,6 +77,8 @@
 
     public void setPosition(float position) {
         mPosition = position;
+        // Warning! If we ever decide to switch this to setX (slide left/right)
+        // please remember to add RLT variations of the animations under res/animator-ldrtl.
         setY((mSpan > 0) ? (mPosition * mSpan) : 0);
 
         if (mPosition != 0) {
diff --git a/packages/DocumentsUI/tests/AndroidManifest.xml b/packages/DocumentsUI/tests/AndroidManifest.xml
index a312427..b986285 100644
--- a/packages/DocumentsUI/tests/AndroidManifest.xml
+++ b/packages/DocumentsUI/tests/AndroidManifest.xml
@@ -2,6 +2,8 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="com.android.documentsui.tests">
 
+    <uses-permission android:name="android.permission.INTERNET" />
+
     <application>
         <uses-library android:name="android.test.runner" />
         <provider
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java
index 056e6ed..59dc232 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java
@@ -19,10 +19,17 @@
 import static com.android.documentsui.StubProvider.ROOT_0_ID;
 import static com.android.documentsui.StubProvider.ROOT_1_ID;
 
+import android.app.DownloadManager;
+import android.app.DownloadManager.Request;
+import android.content.Context;
+import android.net.Uri;
 import android.os.RemoteException;
+import android.support.test.uiautomator.Configurator;
+import android.support.test.uiautomator.UiObject;
 import android.test.suitebuilder.annotation.LargeTest;
 import android.test.suitebuilder.annotation.Suppress;
 import android.view.KeyEvent;
+import android.view.MotionEvent;
 
 import com.android.documentsui.model.RootInfo;
 
@@ -173,4 +180,37 @@
             bots.roots.assertHasFocus();
         }
     }
+
+    // We don't really need to test the entirety of download support
+    // since downloads is (almost) just another provider.
+    public void testDownload_Queued() throws Exception {
+        DownloadManager dm = (DownloadManager) context.getSystemService(
+                Context.DOWNLOAD_SERVICE);
+        // This downloads ends up being queued (because DNS can't be resolved).
+        // We'll still see an entry in the downloads UI with a "Queued" label.
+        dm.enqueue(new Request(Uri.parse("http://hammychamp.toodles")));
+
+        bots.roots.openRoot("Downloads");
+        bots.directory.assertDocumentsPresent("Queued");
+    }
+
+    public void testDownload_RetryUnsuccessful() throws Exception {
+        DownloadManager dm = (DownloadManager) context.getSystemService(
+                Context.DOWNLOAD_SERVICE);
+        // This downloads fails! But it'll still show up.
+        dm.enqueue(new Request(Uri.parse("http://www.google.com/hamfancy")));
+
+        bots.roots.openRoot("Downloads");
+        UiObject doc = bots.directory.findDocument("Unsuccessful");
+        doc.waitForExists(TIMEOUT);
+
+        int toolType = Configurator.getInstance().getToolType();
+        Configurator.getInstance().setToolType(MotionEvent.TOOL_TYPE_FINGER);
+        doc.click();
+        Configurator.getInstance().setToolType(toolType);
+
+        assertTrue(bots.main.findDownloadRetryDialog().exists());
+
+        device.pressBack(); // to clear the dialog.
+    }
 }
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/bots/UiBot.java b/packages/DocumentsUI/tests/src/com/android/documentsui/bots/UiBot.java
index 11f5194..5b53caf 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/bots/UiBot.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/bots/UiBot.java
@@ -173,6 +173,13 @@
         return findObject("android:id/content", "android:id/text1");
     }
 
+    public UiObject findDownloadRetryDialog() {
+        UiSelector selector = new UiSelector().text("Couldn't download");
+        UiObject title = mDevice.findObject(selector);
+        title.waitForExists(mTimeout);
+        return title;
+    }
+
     public UiObject findDialogOkButton() {
         UiObject object = findObject("android:id/content", "android:id/button1");
         object.waitForExists(mTimeout);
diff --git a/packages/Shell/src/com/android/shell/BugreportProgressService.java b/packages/Shell/src/com/android/shell/BugreportProgressService.java
index d212d53..10c5416 100644
--- a/packages/Shell/src/com/android/shell/BugreportProgressService.java
+++ b/packages/Shell/src/com/android/shell/BugreportProgressService.java
@@ -620,7 +620,11 @@
      * upon receiving a {@link #INTENT_BUGREPORT_STARTED}.
      */
     private void takeScreenshot(int id, boolean delayed) {
-        MetricsLogger.action(this, MetricsEvent.ACTION_BUGREPORT_NOTIFICATION_ACTION_SCREENSHOT);
+        if (delayed) {
+            // Only logs screenshots requested from the notification action.
+            MetricsLogger.action(this,
+                    MetricsEvent.ACTION_BUGREPORT_NOTIFICATION_ACTION_SCREENSHOT);
+        }
         if (getInfo(id) == null) {
             // Most likely am killed Shell before user tapped the notification. Since system might
             // be too busy anwyays, it's better to ignore the notification and switch back to the
diff --git a/packages/SystemUI/src/com/android/systemui/ExpandHelper.java b/packages/SystemUI/src/com/android/systemui/ExpandHelper.java
index 051921a..358674c 100644
--- a/packages/SystemUI/src/com/android/systemui/ExpandHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/ExpandHelper.java
@@ -78,6 +78,7 @@
     private float mOldHeight;
     private float mNaturalHeight;
     private float mInitialTouchFocusY;
+    private float mInitialTouchX;
     private float mInitialTouchY;
     private float mInitialTouchSpan;
     private float mLastFocusY;
@@ -291,7 +292,8 @@
                 }
                 if (mWatchingForPull) {
                     final float yDiff = ev.getRawY() - mInitialTouchY;
-                    if (yDiff > mTouchSlop) {
+                    final float xDiff = ev.getRawX() - mInitialTouchX;
+                    if (yDiff > mTouchSlop && yDiff > Math.abs(xDiff)) {
                         if (DEBUG) Log.v(TAG, "got venetian gesture (dy=" + yDiff + "px)");
                         mWatchingForPull = false;
                         if (mResizedView != null && !isFullyExpanded(mResizedView)) {
@@ -316,6 +318,7 @@
                     mWatchingForPull = false;
                 }
                 mInitialTouchY = ev.getY();
+                mInitialTouchX = ev.getX();
                 break;
 
             case MotionEvent.ACTION_CANCEL:
@@ -409,12 +412,14 @@
                 mWatchingForPull = mScrollAdapter != null &&
                         isInside(mScrollAdapter.getHostView(), x, y);
                 mResizedView = findView(x, y);
+                mInitialTouchX = ev.getX();
                 mInitialTouchY = ev.getY();
                 break;
             case MotionEvent.ACTION_MOVE: {
                 if (mWatchingForPull) {
                     final float yDiff = ev.getRawY() - mInitialTouchY;
-                    if (yDiff > mTouchSlop) {
+                    final float xDiff = ev.getRawX() - mInitialTouchX;
+                    if (yDiff > mTouchSlop && yDiff > Math.abs(xDiff)) {
                         if (DEBUG) Log.v(TAG, "got venetian gesture (dy=" + yDiff + "px)");
                         mWatchingForPull = false;
                         if (mResizedView != null && !isFullyExpanded(mResizedView)) {
diff --git a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
index 81ba23f..1fe218a 100644
--- a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
@@ -517,16 +517,35 @@
                 break;
             case MotionEvent.ACTION_UP:
             case MotionEvent.ACTION_CANCEL:
-                if (mCurrView == null) {
-                    break;
-                }
-                mVelocityTracker.computeCurrentVelocity(1000 /* px/sec */, getMaxVelocity());
-                float velocity = getVelocity(mVelocityTracker);
+                if (mCurrView != null) {
+                    float maxVelocity = MAX_DISMISS_VELOCITY * mDensityScale;
+                    mVelocityTracker.computeCurrentVelocity(1000 /* px/sec */, maxVelocity);
+                    float escapeVelocity = SWIPE_ESCAPE_VELOCITY * mDensityScale;
+                    float velocity = getVelocity(mVelocityTracker);
+                    float perpendicularVelocity = getPerpendicularVelocity(mVelocityTracker);
 
-                if (!handleUpEvent(ev, mCurrView, velocity, getTranslation(mCurrView))) {
-                    if (isDismissGesture(ev)) {
+                    float translation = getTranslation(mCurrView);
+                    // Decide whether to dismiss the current view
+                    boolean childSwipedFarEnough = DISMISS_IF_SWIPED_FAR_ENOUGH &&
+                            Math.abs(translation) > 0.4 * getSize(mCurrView);
+                    boolean childSwipedFastEnough = (Math.abs(velocity) > escapeVelocity) &&
+                            (Math.abs(velocity) > Math.abs(perpendicularVelocity)) &&
+                            (velocity > 0) == (translation > 0);
+                    boolean falsingDetected = mCallback.isAntiFalsingNeeded();
+
+                    if (mFalsingManager.isClassiferEnabled()) {
+                        falsingDetected = falsingDetected && mFalsingManager.isFalseTouch();
+                    } else {
+                        falsingDetected = falsingDetected && !mTouchAboveFalsingThreshold;
+                    }
+
+                    boolean dismissChild = mCallback.canChildBeDismissed(mCurrView)
+                            && !falsingDetected && (childSwipedFastEnough || childSwipedFarEnough)
+                            && ev.getActionMasked() == MotionEvent.ACTION_UP;
+
+                    if (dismissChild) {
                         // flingadingy
-                        dismissChild(mCurrView, swipedFastEnough() ? velocity : 0f);
+                        dismissChild(mCurrView, childSwipedFastEnough ? velocity : 0f);
                     } else {
                         // snappity
                         mCallback.onDragCancelled(mCurrView);
@@ -543,46 +562,6 @@
         return (int) (mFalsingThreshold * factor);
     }
 
-    private float getMaxVelocity() {
-        return MAX_DISMISS_VELOCITY * mDensityScale;
-    }
-
-    protected float getEscapeVelocity() {
-        return SWIPE_ESCAPE_VELOCITY * mDensityScale;
-    }
-
-    protected boolean swipedFarEnough() {
-        float translation = getTranslation(mCurrView);
-        return DISMISS_IF_SWIPED_FAR_ENOUGH && Math.abs(translation) > 0.4 * getSize(mCurrView);
-    }
-
-    protected boolean isDismissGesture(MotionEvent ev) {
-        boolean falsingDetected = mCallback.isAntiFalsingNeeded();
-        if (mFalsingManager.isClassiferEnabled()) {
-            falsingDetected = falsingDetected && mFalsingManager.isFalseTouch();
-        } else {
-            falsingDetected = falsingDetected && !mTouchAboveFalsingThreshold;
-        }
-        return !falsingDetected && (swipedFastEnough() || swipedFarEnough())
-                && ev.getActionMasked() == MotionEvent.ACTION_UP
-                && mCallback.canChildBeDismissed(mCurrView);
-    }
-
-    protected boolean swipedFastEnough() {
-        float velocity = getVelocity(mVelocityTracker);
-        float perpendicularVelocity = getPerpendicularVelocity(mVelocityTracker);
-        float translation = getTranslation(mCurrView);
-        boolean ret = (Math.abs(velocity) > getEscapeVelocity()) &&
-                (Math.abs(velocity) > Math.abs(perpendicularVelocity)) &&
-                (velocity > 0) == (translation > 0);
-        return ret;
-    }
-
-    protected boolean handleUpEvent(MotionEvent ev, View animView, float velocity,
-            float translation) {
-        return false;
-    }
-
     public interface Callback {
         View getChildAtPosition(MotionEvent ev);
 
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Recents.java b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
index 73ce26f..2b6ed44 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Recents.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
@@ -45,6 +45,7 @@
 import com.android.systemui.RecentsComponent;
 import com.android.systemui.SystemUI;
 import com.android.systemui.recents.events.EventBus;
+import com.android.systemui.recents.events.activity.ConfigurationChangedEvent;
 import com.android.systemui.recents.events.activity.DockedTopTaskEvent;
 import com.android.systemui.recents.events.activity.RecentsActivityStartingEvent;
 import com.android.systemui.recents.events.component.RecentsVisibilityChangedEvent;
@@ -619,6 +620,12 @@
         }
     }
 
+    public final void onBusEvent(ConfigurationChangedEvent event) {
+        // Update the configuration for the Recents component when the activity configuration
+        // changes as well
+        mImpl.onConfigurationChanged();
+    }
+
     /**
      * Attempts to register with the system user.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
index 4e11bca..d864df8 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
@@ -759,6 +759,7 @@
             TaskStackLayoutAlgorithm stackLayout = stackView.getStackAlgorithm();
             TaskStackViewScroller stackScroller = stackView.getScroller();
 
+            stackView.updateLayoutAlgorithm(true /* boundScroll */);
             stackView.updateToInitialState();
 
             for (int i = tasks.size() - 1; i >= 0; i--) {
@@ -825,6 +826,7 @@
         }
 
         // Get the transform for the running task
+        stackView.updateLayoutAlgorithm(true /* boundScroll */);
         stackView.updateToInitialState();
         mTmpTransform = stackView.getStackAlgorithm().getStackTransformScreenCoordinates(launchTask,
                 stackView.getScroller().getStackScroll(), mTmpTransform, null);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
index e2830a1..6c410c3 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -813,7 +813,7 @@
      *
      * @see #updateLayoutAlgorithm(boolean, ArraySet<Task.TaskKey>)
      */
-    void updateLayoutAlgorithm(boolean boundScrollToNewMinMax) {
+   public void updateLayoutAlgorithm(boolean boundScrollToNewMinMax) {
         updateLayoutAlgorithm(boundScrollToNewMinMax, mIgnoreTasks);
     }
 
@@ -822,7 +822,7 @@
      *
      * @param ignoreTasksSet the set of tasks to ignore in the relayout
      */
-    void updateLayoutAlgorithm(boolean boundScrollToNewMinMax,
+    public void updateLayoutAlgorithm(boolean boundScrollToNewMinMax,
             ArraySet<Task.TaskKey> ignoreTasksSet) {
         // Compute the min and max scroll values
         mLayoutAlgorithm.update(mStack, ignoreTasksSet);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
index 51553be..93cb952 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
@@ -241,18 +241,22 @@
     }
 
     private void updateLimits() {
-        boolean customView = getPrivateLayout().getContractedChild().getId()
+        updateLimitsForView(mPrivateLayout);
+        updateLimitsForView(mPublicLayout);
+    }
+
+    private void updateLimitsForView(NotificationContentView layout) {
+        boolean customView = layout.getContractedChild().getId()
                 != com.android.internal.R.id.status_bar_latest_event_content;
         boolean beforeN = mEntry.targetSdk < Build.VERSION_CODES.N;
         int minHeight = customView && beforeN && !mIsSummaryWithChildren ?
                 mNotificationMinHeightLegacy : mNotificationMinHeight;
-        boolean headsUpCustom = getPrivateLayout().getHeadsUpChild() != null &&
-                getPrivateLayout().getHeadsUpChild().getId()
-                != com.android.internal.R.id.status_bar_latest_event_content;
+        boolean headsUpCustom = layout.getHeadsUpChild() != null &&
+                layout.getHeadsUpChild().getId()
+                        != com.android.internal.R.id.status_bar_latest_event_content;
         int headsUpheight = headsUpCustom && beforeN ? mMaxHeadsUpHeightLegacy
                 : mMaxHeadsUpHeight;
-        mPrivateLayout.setHeights(minHeight, headsUpheight, mNotificationMaxHeight);
-        mPublicLayout.setHeights(minHeight, headsUpheight, mNotificationMaxHeight);
+        layout.setHeights(minHeight, headsUpheight, mNotificationMaxHeight);
     }
 
     public StatusBarNotification getStatusBarNotification() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationSettingsIconRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationSettingsIconRow.java
index fcc48bf..375459f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationSettingsIconRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationSettingsIconRow.java
@@ -29,18 +29,11 @@
 
 public class NotificationSettingsIconRow extends FrameLayout implements View.OnClickListener {
 
-    private static final int GEAR_ALPHA_ANIM_DURATION = 200;
-
     public interface SettingsIconRowListener {
         /**
          * Called when the gear behind a notification is touched.
          */
         public void onGearTouched(ExpandableNotificationRow row, int x, int y);
-
-        /**
-         * Called when a notification is slid back over the gear.
-         */
-        public void onSettingsIconRowReset(NotificationSettingsIconRow row);
     }
 
     private ExpandableNotificationRow mParent;
@@ -52,8 +45,6 @@
     private boolean mSettingsFadedIn = false;
     private boolean mAnimating = false;
     private boolean mOnLeft = true;
-    private boolean mDismissing = false;
-    private boolean mSnapping = false;
     private int[] mGearLocation = new int[2];
     private int[] mParentLocation = new int[2];
 
@@ -87,14 +78,8 @@
 
     public void resetState() {
         setGearAlpha(0f);
-        mSettingsFadedIn = false;
         mAnimating = false;
-        mSnapping = false;
-        mDismissing = false;
         setIconLocation(true /* on left */);
-        if (mListener != null) {
-            mListener.onSettingsIconRowReset(this);
-        }
     }
 
     public void setGearListener(SettingsIconRowListener listener) {
@@ -109,24 +94,20 @@
         return mParent;
     }
 
-    public void setGearAlpha(float alpha) {
+    private void setGearAlpha(float alpha) {
         if (alpha == 0) {
             mSettingsFadedIn = false; // Can fade in again once it's gone.
             setVisibility(View.INVISIBLE);
         } else {
+            if (alpha == 1) {
+                mSettingsFadedIn = true;
+            }
             setVisibility(View.VISIBLE);
         }
         mGearIcon.setAlpha(alpha);
     }
 
     /**
-     * Returns whether the icon is on the left side of the view or not.
-     */
-    public boolean isIconOnLeft() {
-        return mOnLeft;
-    }
-
-    /**
      * Returns the horizontal space in pixels required to display the gear behind a notification.
      */
     public float getSpaceForGear() {
@@ -138,7 +119,7 @@
      * if entire view is visible.
      */
     public boolean isVisible() {
-        return mGearIcon.getAlpha() > 0;
+        return mSettingsFadedIn;
     }
 
     public void cancelFadeAnimator() {
@@ -148,18 +129,16 @@
     }
 
     public void updateSettingsIcons(final float transX, final float size) {
-        if (mAnimating || !mSettingsFadedIn) {
-            // Don't adjust when animating, or if the gear hasn't been shown yet.
+        if (mAnimating || (mGearIcon.getAlpha() == 0)) {
+            // Don't adjust when animating or settings aren't visible
             return;
         }
-
+        setIconLocation(transX > 0 /* fromLeft */);
         final float fadeThreshold = size * 0.3f;
         final float absTrans = Math.abs(transX);
         float desiredAlpha = 0;
 
-        if (absTrans == 0) {
-            desiredAlpha = 0;
-        } else if (absTrans <= fadeThreshold) {
+        if (absTrans <= fadeThreshold) {
             desiredAlpha = 1;
         } else {
             desiredAlpha = 1 - ((absTrans - fadeThreshold) / (size - fadeThreshold));
@@ -169,12 +148,6 @@
 
     public void fadeInSettings(final boolean fromLeft, final float transX,
             final float notiThreshold) {
-        if (mDismissing || mAnimating) {
-            return;
-        }
-        if (isIconLocationChange(transX)) {
-            setGearAlpha(0f);
-        }
         setIconLocation(transX > 0 /* fromLeft */);
         mFadeAnimator = ValueAnimator.ofFloat(mGearIcon.getAlpha(), 1);
         mFadeAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@@ -191,53 +164,40 @@
         });
         mFadeAnimator.addListener(new AnimatorListenerAdapter() {
             @Override
+            public void onAnimationCancel(Animator animation) {
+                super.onAnimationCancel(animation);
+                mAnimating = false;
+                mSettingsFadedIn = false;
+            }
+
+            @Override
             public void onAnimationStart(Animator animation) {
+                super.onAnimationStart(animation);
                 mAnimating = true;
             }
 
             @Override
-            public void onAnimationCancel(Animator animation) {
-                // TODO should animate back to 0f from current alpha
-                mGearIcon.setAlpha(0f);
-            }
-
-            @Override
             public void onAnimationEnd(Animator animation) {
+                super.onAnimationEnd(animation);
                 mAnimating = false;
-                mSettingsFadedIn = mGearIcon.getAlpha() == 1;
+                mSettingsFadedIn = true;
             }
         });
         mFadeAnimator.setInterpolator(Interpolators.ALPHA_IN);
-        mFadeAnimator.setDuration(GEAR_ALPHA_ANIM_DURATION);
+        mFadeAnimator.setDuration(200);
         mFadeAnimator.start();
     }
 
-    public void setIconLocation(boolean onLeft) {
-        if (onLeft == mOnLeft || mSnapping) {
+    private void setIconLocation(boolean onLeft) {
+        if (onLeft == mOnLeft) {
             // Same side? Do nothing.
             return;
         }
+
         setTranslationX(onLeft ? 0 : (mParent.getWidth() - mHorizSpaceForGear));
         mOnLeft = onLeft;
     }
 
-    public boolean isIconLocationChange(float translation) {
-        boolean onLeft = translation > mGearIcon.getPaddingStart();
-        boolean onRight = translation < -mGearIcon.getPaddingStart();
-        if ((mOnLeft && onRight) || (!mOnLeft && onLeft)) {
-            return true;
-        }
-        return false;
-    }
-
-    public void setDismissing() {
-        mDismissing = true;
-    }
-
-    public void setSnapping(boolean snapping) {
-        mSnapping = snapping;
-    }
-
     @Override
     public void onClick(View v) {
         if (v.getId() == R.id.gear_icon) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ViewTransformationHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/ViewTransformationHelper.java
index 66f945e..f75f3574 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ViewTransformationHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ViewTransformationHelper.java
@@ -188,6 +188,9 @@
 
     @Override
     public void setVisible(boolean visible) {
+        if (mViewTransformationAnimation != null) {
+            mViewTransformationAnimation.cancel();
+        }
         for (Integer viewType : mTransformedViews.keySet()) {
             TransformState ownState = getCurrentState(viewType);
             if (ownState != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
index cedc3c7..ab44b6a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
@@ -315,8 +315,8 @@
     public void logoutCurrentUser() {
         int currentUser = ActivityManager.getCurrentUser();
         if (currentUser != UserHandle.USER_SYSTEM) {
-            switchToUserId(UserHandle.USER_SYSTEM);
-            stopUserId(currentUser);
+            pauseRefreshUsers();
+            ActivityManager.logoutCurrentUser();
         }
     }
 
@@ -384,14 +384,6 @@
         }
     }
 
-    private void stopUserId(int id) {
-        try {
-            ActivityManagerNative.getDefault().stopUser(id, /* force= */ false, null);
-        } catch (RemoteException e) {
-            Log.e(TAG, "Couldn't stop user.", e);
-        }
-    }
-
     private void showExitGuestDialog(int id) {
         if (mExitGuestDialog != null && mExitGuestDialog.isShowing()) {
             mExitGuestDialog.cancel();
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 4cb0dea..eaa0c98 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -372,11 +372,6 @@
     }
 
     @Override
-    public void onSettingsIconRowReset(NotificationSettingsIconRow row) {
-        mSwipeHelper.setSnappedToGear(false);
-    }
-
-    @Override
     protected void onDraw(Canvas canvas) {
         canvas.drawRect(0, mCurrentBounds.top, getWidth(), mCurrentBounds.bottom, mBackgroundPaint);
         if (DEBUG) {
@@ -722,15 +717,11 @@
             mDragAnimPendingChildren.remove(animView);
         }
 
-        if (mCurrIconRow != null) {
-            if (targetLeft == 0) {
-                mCurrIconRow.resetState();
-                mCurrIconRow = null;
-                if (mGearExposedView != null && mGearExposedView == mTranslatingParentView) {
-                    mGearExposedView = null;
-                }
-            } else {
-                mSwipeHelper.setSnappedToGear(true);
+        if (targetLeft == 0 && mCurrIconRow != null) {
+            mCurrIconRow.resetState();
+            mCurrIconRow = null;
+            if (mGearExposedView != null && mGearExposedView == mTranslatingParentView) {
+                mGearExposedView = null;
             }
         }
     }
@@ -2739,13 +2730,13 @@
         if (view instanceof ExpandableNotificationRow) {
             ExpandableNotificationRow row = (ExpandableNotificationRow) view;
             if (row.isUserLocked() && row != getFirstChildNotGone()) {
+                if (row.isSummaryWithChildren()) {
+                    return;
+                }
                 // We are actually expanding this view
-                float endPosition;
+                float endPosition = row.getTranslationY() + row.getActualHeight();
                 if (row.isChildInGroup()) {
-                    ExpandableNotificationRow parent = row.getNotificationParent();
-                    endPosition = parent.getTranslationY() + parent.getActualHeight();
-                } else {
-                    endPosition = row.getTranslationY() + row.getActualHeight();
+                    endPosition += row.getNotificationParent().getTranslationY();
                 }
                 int stackEnd = mMaxLayoutHeight - mBottomStackPeekSize -
                         mBottomStackSlowDownHeight + (int) mStackTranslation;
@@ -3388,11 +3379,15 @@
     }
 
     private class NotificationSwipeHelper extends SwipeHelper {
+        private static final int MOVE_STATE_LEFT = -1;
+        private static final int MOVE_STATE_UNDEFINED = 0;
+        private static final int MOVE_STATE_RIGHT = 1;
+
         private static final long GEAR_SHOW_DELAY = 60;
+
         private CheckForDrag mCheckForDrag;
         private Handler mHandler;
-        private boolean mGearSnappedTo;
-        private boolean mGearSnappedOnLeft;
+        private int mMoveState = MOVE_STATE_UNDEFINED;
 
         public NotificationSwipeHelper(int swipeDirection, Callback callback, Context context) {
             super(swipeDirection, callback, context);
@@ -3405,10 +3400,6 @@
             mTranslatingParentView = currView;
 
             // Reset check for drag gesture
-            cancelCheckForDrag();
-            if (mCurrIconRow != null) {
-                mCurrIconRow.setSnapping(false);
-            }
             mCheckForDrag = null;
             mCurrIconRow = null;
 
@@ -3420,32 +3411,17 @@
                 mCurrIconRow = ((ExpandableNotificationRow) currView).getSettingsRow();
                 mCurrIconRow.setGearListener(NotificationStackScrollLayout.this);
             }
+            mMoveState = MOVE_STATE_UNDEFINED;
         }
 
         @Override
         public void onMoveUpdate(View view, float translation, float delta) {
-            if (mCurrIconRow != null) {
-                mCurrIconRow.setSnapping(false); // If we're moving, we're not snapping.
-
-                // If the gear is visible and the movement is towards it it's not a location change.
-                boolean onLeft = mGearSnappedTo ? mGearSnappedOnLeft : mCurrIconRow.isIconOnLeft();
-                boolean locationChange = isTowardsGear(translation, onLeft)
-                        ? false : mCurrIconRow.isIconLocationChange(translation);
-                if (locationChange) {
-                    // Don't consider it "snapped" if location has changed.
-                    setSnappedToGear(false);
-
-                    // Changed directions, make sure we check to fade in icon again.
-                    if (!mHandler.hasCallbacks(mCheckForDrag)) {
-                        // No check scheduled, set null to schedule a new one.
-                        mCheckForDrag = null;
-                    } else {
-                        // Check scheduled, reset alpha and update location; check will fade it in
-                        mCurrIconRow.setGearAlpha(0f);
-                        mCurrIconRow.setIconLocation(translation > 0 /* onLeft */);
-                    }
-                }
+            final int newMoveState = (delta < 0) ? MOVE_STATE_RIGHT : MOVE_STATE_LEFT;
+            if (mMoveState != MOVE_STATE_UNDEFINED && mMoveState != newMoveState) {
+                // Changed directions, make sure we check for drag again.
+                mCheckForDrag = null;
             }
+            mMoveState = newMoveState;
 
             final boolean gutsExposed = (view instanceof ExpandableNotificationRow)
                     && ((ExpandableNotificationRow) view).areGutsExposed();
@@ -3458,99 +3434,35 @@
 
         @Override
         public void dismissChild(final View view, float velocity) {
-            super.dismissChild(view, velocity);
             cancelCheckForDrag();
-            setSnappedToGear(false);
+            super.dismissChild(view, velocity);
         }
 
         @Override
         public void snapChild(final View animView, final float targetLeft, float velocity) {
-            super.snapChild(animView, targetLeft, velocity);
-            if (targetLeft == 0) {
-                cancelCheckForDrag();
-                setSnappedToGear(false);
-            }
-        }
-
-
-        @Override
-        public boolean handleUpEvent(MotionEvent ev, View animView, float velocity,
-                float translation) {
-            if (mCurrIconRow == null) {
-                cancelCheckForDrag();
-                return false; // Let SwipeHelper handle it.
-            }
-
-            boolean gestureTowardsGear = isTowardsGear(velocity, mCurrIconRow.isIconOnLeft());
-            boolean gestureFastEnough = Math.abs(velocity) > getEscapeVelocity();
-
-            if (mGearSnappedTo && mCurrIconRow.isVisible()) {
-                if (mGearSnappedOnLeft == mCurrIconRow.isIconOnLeft()) {
-                    boolean coveringGear =
-                            Math.abs(getTranslation(animView)) <= getSpaceForGear(animView) * 0.6f;
-                    if (gestureTowardsGear || coveringGear) {
-                        // Gesture is towards or covering the gear
-                        snapChild(animView, 0 /* leftTarget */, velocity);
-                    } else if (isDismissGesture(ev)) {
-                        // Gesture is a dismiss that's not towards the gear
-                        dismissChild(animView, swipedFastEnough() ? velocity : 0f);
-                    } else {
-                        // Didn't move enough to dismiss or cover, snap to the gear
-                        snapToGear(animView, velocity);
-                    }
-                } else if ((!gestureFastEnough && swipedEnoughToShowGear(animView))
-                        || (gestureTowardsGear && !swipedFarEnough())) {
-                    // The gear has been snapped to previously, however, the gear is now on the
-                    // other side. If gesture is towards gear and not too far snap to the gear.
-                    snapToGear(animView, velocity);
-                } else {
-                    dismissOrSnapBack(animView, velocity, ev);
-                }
-            } else if ((!gestureFastEnough && swipedEnoughToShowGear(animView))
-                    || gestureTowardsGear) {
-                // Gear has not been snapped to previously and this is gear revealing gesture
-                snapToGear(animView, velocity);
-            } else {
-                dismissOrSnapBack(animView, velocity, ev);
-            }
-            return true;
-        }
-
-        private void dismissOrSnapBack(View animView, float velocity, MotionEvent ev) {
-            if (isDismissGesture(ev)) {
-                dismissChild(animView, swipedFastEnough() ? velocity : 0f);
-            } else {
-                snapChild(animView, 0 /* leftTarget */, velocity);
-            }
-        }
-
-        private void snapToGear(View animView, float velocity) {
-            final float snapBackThreshold = getSpaceForGear(animView);
-            final float target = mCurrIconRow.isIconOnLeft() ? snapBackThreshold
-                    : -snapBackThreshold;
-            mGearExposedView = mTranslatingParentView;
-            if (mGearDisplayedListener != null
-                    && (animView instanceof ExpandableNotificationRow)) {
-                mGearDisplayedListener.onGearDisplayed((ExpandableNotificationRow) animView);
-            }
-            if (mCurrIconRow != null) {
-                mCurrIconRow.setSnapping(true);
-                setSnappedToGear(true);
-            }
-            super.snapChild(animView, target, velocity);
-        }
-
-        private boolean swipedEnoughToShowGear(View animView) {
             final float snapBackThreshold = getSpaceForGear(animView);
             final float translation = getTranslation(animView);
             final boolean fromLeft = translation > 0;
             final float absTrans = Math.abs(translation);
             final float notiThreshold = getSize(mTranslatingParentView) * 0.4f;
 
-            // If the notification can't be dismissed then how far it can move is
-            // restricted -- reduce the distance it needs to move in this case.
-            final float multiplier = canChildBeDismissed(animView) ? 0.4f : 0.2f;
-            return absTrans >= snapBackThreshold * 0.4f && absTrans <= notiThreshold;
+            boolean pastGear = (fromLeft && translation >= snapBackThreshold * 0.4f
+                    && translation <= notiThreshold) ||
+                    (!fromLeft && absTrans >= snapBackThreshold * 0.4f
+                            && absTrans <= notiThreshold);
+
+            if (pastGear && !isPinnedHeadsUp(animView)
+                    && (animView instanceof ExpandableNotificationRow)) {
+                // bouncity
+                final float target = fromLeft ? snapBackThreshold : -snapBackThreshold;
+                mGearExposedView = mTranslatingParentView;
+                if (mGearDisplayedListener != null) {
+                    mGearDisplayedListener.onGearDisplayed((ExpandableNotificationRow) animView);
+                }
+                super.snapChild(animView, target, velocity);
+            } else {
+                super.snapChild(animView, 0, velocity);
+            }
         }
 
         @Override
@@ -3587,25 +3499,6 @@
         }
 
         /**
-         * Returns whether the gesture is towards the gear location or not.
-         */
-        private boolean isTowardsGear(float velocity, boolean onLeft) {
-            if (mCurrIconRow == null) {
-                return false;
-            }
-            return mCurrIconRow.isVisible()
-                    && ((onLeft && velocity <= 0) || (!onLeft && velocity >= 0));
-        }
-
-        /**
-         * Indicates the the gear has been snapped to.
-         */
-        private void setSnappedToGear(boolean snapped) {
-            mGearSnappedOnLeft = (mCurrIconRow != null) ? mCurrIconRow.isIconOnLeft() : false;
-            mGearSnappedTo = snapped && mCurrIconRow != null;
-        }
-
-        /**
          * Returns the horizontal space in pixels required to display the gear behind a
          * notification.
          */
@@ -3617,7 +3510,7 @@
         }
 
         private void checkForDrag() {
-            if (mCheckForDrag == null || !mHandler.hasCallbacks(mCheckForDrag)) {
+            if (mCheckForDrag == null) {
                 mCheckForDrag = new CheckForDrag();
                 mHandler.postDelayed(mCheckForDrag, GEAR_SHOW_DELAY);
             }
@@ -3628,6 +3521,7 @@
                 mCurrIconRow.cancelFadeAnimator();
             }
             mHandler.removeCallbacks(mCheckForDrag);
+            mCheckForDrag = null;
         }
 
         private final class CheckForDrag implements Runnable {
@@ -3637,13 +3531,14 @@
                 final float absTransX = Math.abs(translation);
                 final float bounceBackToGearWidth = getSpaceForGear(mTranslatingParentView);
                 final float notiThreshold = getSize(mTranslatingParentView) * 0.4f;
-                if ((mCurrIconRow != null && (!mCurrIconRow.isVisible()
-                        || mCurrIconRow.isIconLocationChange(translation)))
-                        && absTransX >= bounceBackToGearWidth * 0.4
+                if (mCurrIconRow != null && absTransX >= bounceBackToGearWidth * 0.4
                         && absTransX < notiThreshold) {
-                    // Fade in the gear
+                    // Show icon
                     mCurrIconRow.fadeInSettings(translation > 0 /* fromLeft */, translation,
                             notiThreshold);
+                } else {
+                    // Allow more to be posted if this wasn't a drag.
+                    mCheckForDrag = null;
                 }
             }
         }
@@ -3656,7 +3551,7 @@
 
             final View prevGearExposedView = mGearExposedView;
             mGearExposedView = null;
-            mGearSnappedTo = false;
+
             Animator anim = getViewTranslationAnimator(prevGearExposedView,
                     0 /* leftTarget */, null /* updateListener */);
             if (anim != null) {
diff --git a/services/core/java/com/android/server/AppOpsService.java b/services/core/java/com/android/server/AppOpsService.java
index 32f2d59..bfe9e8e 100644
--- a/services/core/java/com/android/server/AppOpsService.java
+++ b/services/core/java/com/android/server/AppOpsService.java
@@ -25,6 +25,7 @@
 import java.io.PrintWriter;
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Iterator;
@@ -57,6 +58,7 @@
 import android.util.ArraySet;
 import android.util.AtomicFile;
 import android.util.Log;
+import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseIntArray;
@@ -107,8 +109,21 @@
 
     private final SparseArray<UidState> mUidStates = new SparseArray<>();
 
-    /** These are app op restrictions imposed per user from various parties */
-    private final ArrayMap<IBinder, SparseArray<boolean[]>> mOpUserRestrictions = new ArrayMap<>();
+    /*
+     * These are app op restrictions imposed per user from various parties.
+     *
+     * This is organized as follows:
+     *
+     * ArrayMap w/ mapping:
+     *  IBinder (for client imposing restriction) --> SparseArray w/ mapping:
+     *    User handle --> Pair containing:
+     *       - Array w/ index = AppOp code, value = restricted status boolean
+     *       - SparseArray w/ mapping:
+     *          AppOp code --> Set of packages that are not restricted for this code
+     *
+     */
+    private final ArrayMap<IBinder, SparseArray<Pair<boolean[], SparseArray<ArraySet<String>>>>>
+            mOpUserRestrictions = new ArrayMap<>();
 
     private static final class UidState {
         public final int uid;
@@ -1267,11 +1282,35 @@
     private boolean isOpRestricted(int uid, int code, String packageName) {
         int userHandle = UserHandle.getUserId(uid);
         final int restrictionSetCount = mOpUserRestrictions.size();
+
         for (int i = 0; i < restrictionSetCount; i++) {
-            SparseArray<boolean[]> perUserRestrictions = mOpUserRestrictions.valueAt(i);
-            boolean[] opRestrictions = perUserRestrictions.get(userHandle);
-            if (opRestrictions != null && opRestrictions[code]) {
+            // For each client, check that the given op is not restricted, or that the given
+            // package is exempt from the restriction.
+
+            SparseArray<Pair<boolean[],SparseArray<ArraySet<String>>>> perUserRestrictions =
+                    mOpUserRestrictions.valueAt(i);
+
+            Pair<boolean[],SparseArray<ArraySet<String>>> restrictions =
+                    perUserRestrictions.get(userHandle);
+            if (restrictions == null) {
+                continue; // No restrictions set by this client
+            }
+
+            boolean[] opRestrictions = restrictions.first;
+            SparseArray<ArraySet<String>> opExceptions = restrictions.second;
+
+            if (opRestrictions == null) {
+                continue; // No restrictions set by this client
+            }
+
+            if (opRestrictions[code]) {
+                if (opExceptions != null && opExceptions.get(code) != null &&
+                        opExceptions.get(code).contains(packageName)) {
+                    continue; // AppOps code is restricted, but this package is exempt
+                }
+
                 if (AppOpsManager.opAllowSystemBypassRestriction(code)) {
+                    // If we are the system, bypass user restrictions for certain codes
                     synchronized (this) {
                         Ops ops = getOpsLocked(uid, packageName, true);
                         if ((ops != null) && ops.isPrivileged) {
@@ -1279,6 +1318,7 @@
                         }
                     }
                 }
+
                 return true;
             }
         }
@@ -2069,7 +2109,8 @@
     }
 
     @Override
-    public void setUserRestriction(int code, boolean restricted, IBinder token, int userHandle) {
+    public void setUserRestriction(int code, boolean restricted, IBinder token, int userHandle,
+            String[] exceptionPackages) {
         if (Binder.getCallingPid() != Process.myPid()) {
             mContext.enforcePermission(Manifest.permission.MANAGE_APP_OPS_RESTRICTIONS,
                     Binder.getCallingPid(), Binder.getCallingUid(), null);
@@ -2085,12 +2126,37 @@
         }
         verifyIncomingOp(code);
         Preconditions.checkNotNull(token);
-        setUserRestrictionNoCheck(code, restricted, token, userHandle);
+        setUserRestrictionNoCheck(code, restricted, token, userHandle, exceptionPackages);
     }
 
     private void setUserRestrictionNoCheck(int code, boolean restricted, IBinder token,
             int userHandle) {
+        setUserRestrictionNoCheck(code, restricted, token, userHandle, /*exceptionPackages*/null);
+    }
+
+    private void setUserRestrictionNoCheck(int code, boolean restricted, IBinder token,
+            int userHandle, String[] exceptionPackages) {
+
         final boolean[] opRestrictions = getOrCreateUserRestrictionsForToken(token, userHandle);
+
+        if (restricted) {
+            final SparseArray<ArraySet<String>> opExceptions =
+                    getUserPackageExemptionsForToken(token, userHandle);
+
+            // If exceptionPackages is not null, update the exception packages for this AppOps code
+            ArraySet<String> exceptions = opExceptions.get(code);
+            if (exceptionPackages != null) {
+                if (exceptions == null) {
+                    exceptions = new ArraySet<>(exceptionPackages.length);
+                    opExceptions.put(code, exceptions);
+                } else {
+                    exceptions.clear();
+                }
+
+                exceptions.addAll(Arrays.asList(exceptionPackages));
+            }
+        }
+
         if (opRestrictions[code] == restricted) {
             return;
         }
@@ -2132,7 +2198,8 @@
         checkSystemUid("removeUser");
         final int tokenCount = mOpUserRestrictions.size();
         for (int i = tokenCount - 1; i >= 0; i--) {
-            SparseArray<boolean[]> opRestrictions = mOpUserRestrictions.valueAt(i);
+            SparseArray<Pair<boolean[], SparseArray<ArraySet<String>>>> opRestrictions =
+                    mOpUserRestrictions.valueAt(i);
             if (opRestrictions != null) {
                 opRestrictions.remove(userHandle);
                 if (opRestrictions.size() <= 0) {
@@ -2144,15 +2211,23 @@
 
 
     private void pruneUserRestrictionsForToken(IBinder token, int userHandle) {
-        SparseArray<boolean[]> perTokenRestrictions = mOpUserRestrictions.get(token);
+        SparseArray<Pair<boolean[], SparseArray<ArraySet<String>>>> perTokenRestrictions =
+                mOpUserRestrictions.get(token);
         if (perTokenRestrictions != null) {
-            final boolean[] opRestrictions = perTokenRestrictions.get(userHandle);
-            if (opRestrictions != null) {
-                for (boolean restriction : opRestrictions) {
-                    if (restriction) {
-                        return;
+            final Pair<boolean[], SparseArray<ArraySet<String>>> restrictions =
+                    perTokenRestrictions.get(userHandle);
+
+            if (restrictions != null) {
+                final boolean[] opRestrictions = restrictions.first;
+                if (opRestrictions != null) {
+                    for (boolean restriction : opRestrictions) {
+                        if (restriction) {
+                            return;
+                        }
                     }
                 }
+
+                // No restrictions set for this client
                 perTokenRestrictions.remove(userHandle);
                 if (perTokenRestrictions.size() <= 0) {
                     mOpUserRestrictions.remove(token);
@@ -2161,18 +2236,61 @@
         }
     }
 
+    /**
+     * Get or create the user restrictions array for a given client if it doesn't already exist.
+     *
+     * @param token the binder client creating the restriction.
+     * @param userHandle the user handle to create a restriction for.
+     *
+     * @return the array of restriction states for each AppOps code.
+     */
     private boolean[] getOrCreateUserRestrictionsForToken(IBinder token, int userHandle) {
-        SparseArray<boolean[]> perTokenRestrictions = mOpUserRestrictions.get(token);
+        SparseArray<Pair<boolean[], SparseArray<ArraySet<String>>>> perTokenRestrictions =
+                mOpUserRestrictions.get(token);
+
         if (perTokenRestrictions == null) {
-            perTokenRestrictions = new SparseArray<>();
+            perTokenRestrictions =
+                    new SparseArray<Pair<boolean[], SparseArray<ArraySet<String>>>>();
             mOpUserRestrictions.put(token, perTokenRestrictions);
         }
-        boolean[] opRestrictions = perTokenRestrictions.get(userHandle);
-        if (opRestrictions == null) {
-            opRestrictions = new boolean[AppOpsManager._NUM_OP];
-            perTokenRestrictions.put(userHandle, opRestrictions);
+
+        Pair<boolean[], SparseArray<ArraySet<String>>> restrictions =
+                perTokenRestrictions.get(userHandle);
+
+        if (restrictions == null) {
+            restrictions = new Pair<boolean[], SparseArray<ArraySet<String>>>(
+                    new boolean[AppOpsManager._NUM_OP], new SparseArray<ArraySet<String>>());
+            perTokenRestrictions.put(userHandle, restrictions);
         }
-        return opRestrictions;
+
+        return restrictions.first;
+    }
+
+    /**
+     * Get the per-package exemptions for each AppOps code for a given client and userHandle.
+     *
+     * @param token the binder client to get the exemptions for.
+     * @param userHandle the user handle to get the exemptions for.
+     *
+     * @return a mapping from the AppOps code to a set of packages exempt for that code.
+     */
+    private SparseArray<ArraySet<String>> getUserPackageExemptionsForToken(IBinder token,
+            int userHandle) {
+        SparseArray<Pair<boolean[], SparseArray<ArraySet<String>>>> perTokenRestrictions =
+                mOpUserRestrictions.get(token);
+
+        if (perTokenRestrictions == null) {
+            return null; // Don't create user restrictions accidentally
+        }
+
+        Pair<boolean[], SparseArray<ArraySet<String>>> restrictions =
+                perTokenRestrictions.get(userHandle);
+
+        if (restrictions == null) {
+            return null; // Don't create user restrictions accidentally
+        }
+
+        return restrictions.second;
     }
 
     private void checkSystemUid(String function) {
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 76a93d4..9af1304 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -191,6 +191,9 @@
     static final int LONG_PRESS_POWER_SHUT_OFF = 2;
     static final int LONG_PRESS_POWER_SHUT_OFF_NO_CONFIRM = 3;
 
+    static final int LONG_PRESS_BACK_NOTHING = 0;
+    static final int LONG_PRESS_BACK_GO_TO_VOICE_ASSIST = 1;
+
     static final int MULTI_PRESS_POWER_NOTHING = 0;
     static final int MULTI_PRESS_POWER_THEATER_MODE = 1;
     static final int MULTI_PRESS_POWER_BRIGHTNESS_BOOST = 2;
@@ -391,6 +394,7 @@
     // handler thread.  We'll need to resolve this someday by teaching the input dispatcher
     // to hold wakelocks during dispatch and eliminating the critical path.
     volatile boolean mPowerKeyHandled;
+    volatile boolean mBackKeyHandled;
     volatile boolean mBeganFromNonInteractive;
     volatile int mPowerKeyPressCounter;
     volatile boolean mEndCallKeyHandled;
@@ -443,6 +447,7 @@
     int mLongPressOnPowerBehavior;
     int mDoublePressOnPowerBehavior;
     int mTriplePressOnPowerBehavior;
+    int mLongPressOnBackBehavior;
     int mShortPressOnSleepBehavior;
     int mShortPressWindowBehavior;
     boolean mAwake;
@@ -699,6 +704,7 @@
     private static final int MSG_UPDATE_DREAMING_SLEEP_TOKEN = 15;
     private static final int MSG_REQUEST_TRANSIENT_BARS = 16;
     private static final int MSG_REQUEST_TV_PICTURE_IN_PICTURE = 17;
+    private static final int MSG_BACK_LONG_PRESS = 18;
 
     private static final int MSG_REQUEST_TRANSIENT_BARS_ARG_STATUS = 0;
     private static final int MSG_REQUEST_TRANSIENT_BARS_ARG_NAVIGATION = 1;
@@ -763,6 +769,9 @@
                 case MSG_REQUEST_TV_PICTURE_IN_PICTURE:
                     requestTvPictureInPictureInternal();
                     break;
+                case MSG_BACK_LONG_PRESS:
+                    backLongPress();
+                    break;
             }
         }
     }
@@ -1109,6 +1118,13 @@
         }
     }
 
+    private void cancelPendingBackKeyAction() {
+        if (!mBackKeyHandled) {
+            mBackKeyHandled = true;
+            mHandler.removeMessages(MSG_BACK_LONG_PRESS);
+        }
+    }
+
     private void powerPress(long eventTime, boolean interactive, int count) {
         if (mScreenOnEarly && !mScreenOnFully) {
             Slog.i(TAG, "Suppressed redundant power key press while "
@@ -1216,6 +1232,19 @@
         }
     }
 
+    private void backLongPress() {
+        mBackKeyHandled = true;
+
+        switch (mLongPressOnBackBehavior) {
+            case LONG_PRESS_BACK_NOTHING:
+                break;
+            case LONG_PRESS_BACK_GO_TO_VOICE_ASSIST:
+                Intent intent = new Intent(Intent.ACTION_VOICE_ASSIST);
+                startActivityAsUser(intent, UserHandle.CURRENT_OR_SELF);
+                break;
+        }
+    }
+
     private void sleepPress(long eventTime) {
         if (mShortPressOnSleepBehavior == SHORT_PRESS_SLEEP_GO_TO_SLEEP_AND_GO_HOME) {
             launchHomeFromHotKey(false /* awakenDreams */, true /*respectKeyguard*/);
@@ -1244,6 +1273,10 @@
         return getResolvedLongPressOnPowerBehavior() != LONG_PRESS_POWER_NOTHING;
     }
 
+    private boolean hasLongPressOnBackBehavior() {
+        return mLongPressOnBackBehavior != LONG_PRESS_BACK_NOTHING;
+    }
+
     private void interceptScreenshotChord() {
         if (mScreenshotChordEnabled
                 && mScreenshotChordVolumeDownKeyTriggered && mScreenshotChordPowerKeyTriggered
@@ -1573,6 +1606,9 @@
         mSupportLongPressPowerWhenNonInteractive = mContext.getResources().getBoolean(
                 com.android.internal.R.bool.config_supportLongPressPowerWhenNonInteractive);
 
+        mLongPressOnBackBehavior = mContext.getResources().getInteger(
+                com.android.internal.R.integer.config_longPressOnBackBehavior);
+
         mShortPressOnPowerBehavior = mContext.getResources().getInteger(
                 com.android.internal.R.integer.config_shortPressOnPowerBehavior);
         mLongPressOnPowerBehavior = mContext.getResources().getInteger(
@@ -5341,6 +5377,29 @@
 
         // Handle special keys.
         switch (keyCode) {
+            case KeyEvent.KEYCODE_BACK: {
+                if (down) {
+                    mBackKeyHandled = false;
+                    if (hasLongPressOnBackBehavior()) {
+                        Message msg = mHandler.obtainMessage(MSG_BACK_LONG_PRESS);
+                        msg.setAsynchronous(true);
+                        mHandler.sendMessageDelayed(msg,
+                                ViewConfiguration.get(mContext).getDeviceGlobalActionKeyTimeout());
+                    }
+                } else {
+                    boolean handled = mBackKeyHandled;
+
+                    // Reset back key state
+                    cancelPendingBackKeyAction();
+
+                    // Don't pass back press to app if we've already handled it
+                    if (handled) {
+                        result &= ~ACTION_PASS_TO_USER;
+                    }
+                }
+                break;
+            }
+
             case KeyEvent.KEYCODE_VOLUME_DOWN:
             case KeyEvent.KEYCODE_VOLUME_UP:
             case KeyEvent.KEYCODE_VOLUME_MUTE: {
@@ -7390,6 +7449,8 @@
                 pw.print(" mLidControlsScreenLock="); pw.println(mLidControlsScreenLock);
                 pw.print(" mLidControlsSleep="); pw.println(mLidControlsSleep);
         pw.print(prefix);
+                pw.print(" mLongPressOnBackBehavior="); pw.println(mLongPressOnBackBehavior);
+        pw.print(prefix);
                 pw.print("mShortPressOnPowerBehavior="); pw.print(mShortPressOnPowerBehavior);
                 pw.print(" mLongPressOnPowerBehavior="); pw.println(mLongPressOnPowerBehavior);
         pw.print(prefix);
diff --git a/services/core/java/com/android/server/vr/VrManagerService.java b/services/core/java/com/android/server/vr/VrManagerService.java
index f5914faf..d0ee6e0 100644
--- a/services/core/java/com/android/server/vr/VrManagerService.java
+++ b/services/core/java/com/android/server/vr/VrManagerService.java
@@ -201,13 +201,16 @@
         }
     }
 
-    private void updateOverlayStateLocked() {
+    private void updateOverlayStateLocked(ComponentName exemptedComponent) {
         final long identity = Binder.clearCallingIdentity();
         try {
             AppOpsManager appOpsManager = getContext().getSystemService(AppOpsManager.class);
             if (appOpsManager != null) {
+                String[] exemptions = (exemptedComponent == null) ? new String[0] :
+                        new String[] { exemptedComponent.getPackageName() };
+
                 appOpsManager.setUserRestriction(AppOpsManager.OP_SYSTEM_ALERT_WINDOW,
-                        mVrModeEnabled, mOverlayToken);
+                        mVrModeEnabled, mOverlayToken, exemptions);
             }
         } finally {
             Binder.restoreCallingIdentity(identity);
@@ -230,12 +233,12 @@
     private boolean updateCurrentVrServiceLocked(boolean enabled,
             @NonNull ComponentName component, int userId) {
 
-        // Always send mode change events.
-        changeVrModeLocked(enabled);
-
         boolean validUserComponent = (mComponentObserver.isValid(component, userId) ==
                 EnabledComponentsObserver.NO_ERROR);
 
+        // Always send mode change events.
+        changeVrModeLocked(enabled, (enabled && validUserComponent) ? component : null);
+
         if (!enabled || !validUserComponent) {
             // Unbind whatever is running
             if (mCurrentVrService != null) {
@@ -275,8 +278,9 @@
      * Note: Must be called while holding {@code mLock}.
      *
      * @param enabled new state of the VR mode.
+     * @param exemptedComponent a component to exempt from AppOps restrictions for overlays.
      */
-    private void changeVrModeLocked(boolean enabled) {
+    private void changeVrModeLocked(boolean enabled, ComponentName exemptedComponent) {
         if (mVrModeEnabled != enabled) {
             mVrModeEnabled = enabled;
 
@@ -284,7 +288,7 @@
             Slog.i(TAG, "VR mode " + ((mVrModeEnabled) ? "enabled" : "disabled"));
             setVrModeNative(mVrModeEnabled);
 
-            updateOverlayStateLocked();
+            updateOverlayStateLocked(exemptedComponent);
             onVrModeChangedLocked();
         }
     }
diff --git a/tests/CanvasCompare/src/com/android/test/hwuicompare/errorCalculator.rs b/tests/CanvasCompare/src/com/android/test/hwuicompare/errorCalculator.rs
index caa947d..0a1742e 100644
--- a/tests/CanvasCompare/src/com/android/test/hwuicompare/errorCalculator.rs
+++ b/tests/CanvasCompare/src/com/android/test/hwuicompare/errorCalculator.rs
@@ -14,10 +14,14 @@
 
     for (int x = 0; x < HEIGHT; x += REGION_SIZE) {
         bool interestingRegion = false;
-        int regionColor = (int) rsGetElementAt_uchar4(ideal, x, y);
+        uchar4 regionColor = rsGetElementAt_uchar4(ideal, x, y);
         for (int i = 0; i < REGION_SIZE && !interestingRegion; i++) {
             for (int j = 0; j < REGION_SIZE && !interestingRegion; j++) {
-                interestingRegion |= ((int) rsGetElementAt_uchar4(ideal, x + j, y + i)) != regionColor;
+                uchar4 testVal = rsGetElementAt_uchar4(ideal, x + j, y + i);
+                interestingRegion |= (testVal.r != regionColor.r);
+                interestingRegion |= (testVal.g != regionColor.g);
+                interestingRegion |= (testVal.b != regionColor.b);
+                interestingRegion |= (testVal.a != regionColor.a);
             }
         }
         if (interestingRegion) {