Merge "Wire up lifecycle, send unlocked broadcast."
diff --git a/Android.mk b/Android.mk
index f5d5a11..d22273c 100644
--- a/Android.mk
+++ b/Android.mk
@@ -261,6 +261,7 @@
 	core/java/android/view/IApplicationToken.aidl \
 	core/java/android/view/IAppTransitionAnimationSpecsFuture.aidl \
 	core/java/android/view/IAssetAtlas.aidl \
+	core/java/android/view/IDockDividerVisibilityListener.aidl \
 	core/java/android/view/IGraphicsStats.aidl \
 	core/java/android/view/IInputFilter.aidl \
 	core/java/android/view/IInputFilterHost.aidl \
diff --git a/api/current.txt b/api/current.txt
index c0a6bde..fc95ca3 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -31404,7 +31404,10 @@
   public static final class Telephony.Sms.Intents {
     method public static android.telephony.SmsMessage[] getMessagesFromIntent(android.content.Intent);
     field public static final java.lang.String ACTION_CHANGE_DEFAULT = "android.provider.Telephony.ACTION_CHANGE_DEFAULT";
+    field public static final java.lang.String ACTION_DEFAULT_SMS_PACKAGE_CHANGED = "android.provider.action.DEFAULT_SMS_PACKAGE_CHANGED";
+    field public static final java.lang.String ACTION_EXTERNAL_PROVIDER_CHANGE = "android.provider.action.EXTERNAL_PROVIDER_CHANGE";
     field public static final java.lang.String DATA_SMS_RECEIVED_ACTION = "android.intent.action.DATA_SMS_RECEIVED";
+    field public static final java.lang.String EXTRA_IS_DEFAULT_SMS_APP = "android.provider.extra.IS_DEFAULT_SMS_APP";
     field public static final java.lang.String EXTRA_PACKAGE_NAME = "package";
     field public static final int RESULT_SMS_DUPLICATED = 5; // 0x5
     field public static final int RESULT_SMS_GENERIC_ERROR = 2; // 0x2
diff --git a/api/system-current.txt b/api/system-current.txt
index 00e99e3..409a7d5 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -33520,7 +33520,10 @@
   public static final class Telephony.Sms.Intents {
     method public static android.telephony.SmsMessage[] getMessagesFromIntent(android.content.Intent);
     field public static final java.lang.String ACTION_CHANGE_DEFAULT = "android.provider.Telephony.ACTION_CHANGE_DEFAULT";
+    field public static final java.lang.String ACTION_DEFAULT_SMS_PACKAGE_CHANGED = "android.provider.action.DEFAULT_SMS_PACKAGE_CHANGED";
+    field public static final java.lang.String ACTION_EXTERNAL_PROVIDER_CHANGE = "android.provider.action.EXTERNAL_PROVIDER_CHANGE";
     field public static final java.lang.String DATA_SMS_RECEIVED_ACTION = "android.intent.action.DATA_SMS_RECEIVED";
+    field public static final java.lang.String EXTRA_IS_DEFAULT_SMS_APP = "android.provider.extra.IS_DEFAULT_SMS_APP";
     field public static final java.lang.String EXTRA_PACKAGE_NAME = "package";
     field public static final int RESULT_SMS_DUPLICATED = 5; // 0x5
     field public static final int RESULT_SMS_GENERIC_ERROR = 2; // 0x2
diff --git a/api/test-current.txt b/api/test-current.txt
index c0a6bde..fc95ca3 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -31404,7 +31404,10 @@
   public static final class Telephony.Sms.Intents {
     method public static android.telephony.SmsMessage[] getMessagesFromIntent(android.content.Intent);
     field public static final java.lang.String ACTION_CHANGE_DEFAULT = "android.provider.Telephony.ACTION_CHANGE_DEFAULT";
+    field public static final java.lang.String ACTION_DEFAULT_SMS_PACKAGE_CHANGED = "android.provider.action.DEFAULT_SMS_PACKAGE_CHANGED";
+    field public static final java.lang.String ACTION_EXTERNAL_PROVIDER_CHANGE = "android.provider.action.EXTERNAL_PROVIDER_CHANGE";
     field public static final java.lang.String DATA_SMS_RECEIVED_ACTION = "android.intent.action.DATA_SMS_RECEIVED";
+    field public static final java.lang.String EXTRA_IS_DEFAULT_SMS_APP = "android.provider.extra.IS_DEFAULT_SMS_APP";
     field public static final java.lang.String EXTRA_PACKAGE_NAME = "package";
     field public static final int RESULT_SMS_DUPLICATED = 5; // 0x5
     field public static final int RESULT_SMS_GENERIC_ERROR = 2; // 0x2
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index 273483a..468c145 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -178,15 +178,8 @@
  * </p>
  * <h3>Notification strategy</h3>
  * <p>
- * For each feedback type only one accessibility service is notified. Services are notified
- * in the order of registration. Hence, if two services are registered for the same
- * feedback type in the same package the first one wins. It is possible however, to
- * register a service as the default one for a given feedback type. In such a case this
- * service is invoked if no other service was interested in the event. In other words, default
- * services do not compete with other services and are notified last regardless of the
- * registration order. This enables "generic" accessibility services that work reasonably
- * well with most applications to coexist with "polished" ones that are targeted for
- * specific applications.
+ * All accessibility services are notified of all events they have requested, regardless of their
+ * feedback type.
  * </p>
  * <p class="note">
  * <strong>Note:</strong> The event notification timeout is useful to avoid propagating
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index f560f8a..c92382a 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -4420,8 +4420,19 @@
          * to receive changes in this value.
          */
         public static final String LOCATION_MODE = "location_mode";
+        /**
+         * Stores the previous location mode when {@link #LOCATION_MODE} is set to
+         * {@link #LOCATION_MODE_OFF}
+         * @hide
+         */
+        public static final String LOCATION_PREVIOUS_MODE = "location_previous_mode";
 
         /**
+         * Sets all location providers to the previous states before location was turned off.
+         * @hide
+         */
+        public static final int LOCATION_MODE_PREVIOUS = -1;
+        /**
          * Location access disabled.
          */
         public static final int LOCATION_MODE_OFF = 0;
@@ -5795,6 +5806,7 @@
             CLONE_TO_MANAGED_PROFILE.add(ENABLED_ACCESSIBILITY_SERVICES);
             CLONE_TO_MANAGED_PROFILE.add(ENABLED_INPUT_METHODS);
             CLONE_TO_MANAGED_PROFILE.add(LOCATION_MODE);
+            CLONE_TO_MANAGED_PROFILE.add(LOCATION_PREVIOUS_MODE);
             CLONE_TO_MANAGED_PROFILE.add(LOCATION_PROVIDERS_ALLOWED);
             CLONE_TO_MANAGED_PROFILE.add(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
             CLONE_TO_MANAGED_PROFILE.add(SELECTED_INPUT_METHOD_SUBTYPE);
@@ -5882,6 +5894,28 @@
         }
 
         /**
+         * Saves the current location mode into {@link #LOCATION_PREVIOUS_MODE}.
+         */
+        private static final boolean saveLocationModeForUser(ContentResolver cr, int userId) {
+            final int mode = getLocationModeForUser(cr, userId);
+            return putIntForUser(cr, Settings.Secure.LOCATION_PREVIOUS_MODE, mode, userId);
+        }
+
+        /**
+         * Restores the current location mode from {@link #LOCATION_PREVIOUS_MODE}.
+         */
+        private static final boolean restoreLocationModeForUser(ContentResolver cr, int userId) {
+            int mode = getIntForUser(cr, Settings.Secure.LOCATION_PREVIOUS_MODE,
+                    LOCATION_MODE_HIGH_ACCURACY, userId);
+            // Make sure that the previous mode is never "off". Otherwise the user won't be able to
+            // turn on location any longer.
+            if (mode == LOCATION_MODE_OFF) {
+                mode = LOCATION_MODE_HIGH_ACCURACY;
+            }
+            return setLocationModeForUser(cr, mode, userId);
+        }
+
+        /**
          * Thread-safe method for setting the location mode to one of
          * {@link #LOCATION_MODE_HIGH_ACCURACY}, {@link #LOCATION_MODE_SENSORS_ONLY},
          * {@link #LOCATION_MODE_BATTERY_SAVING}, or {@link #LOCATION_MODE_OFF}.
@@ -5899,7 +5933,11 @@
                 boolean gps = false;
                 boolean network = false;
                 switch (mode) {
+                    case LOCATION_MODE_PREVIOUS:
+                        // Retrieve the actual mode and set to that mode.
+                        return restoreLocationModeForUser(cr, userId);
                     case LOCATION_MODE_OFF:
+                        saveLocationModeForUser(cr, userId);
                         break;
                     case LOCATION_MODE_SENSORS_ONLY:
                         gps = true;
diff --git a/core/java/android/view/IDockDividerVisibilityListener.aidl b/core/java/android/view/IDockDividerVisibilityListener.aidl
new file mode 100644
index 0000000..a7d5cda
--- /dev/null
+++ b/core/java/android/view/IDockDividerVisibilityListener.aidl
@@ -0,0 +1,27 @@
+/**
+ * Copyright (c) 2015, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+/**
+  * Listener for showing/hiding of the dock divider. Will fire when an app is shown in side by side
+  * mode and a divider should be shown.
+  *
+  * @hide
+  */
+oneway interface IDockDividerVisibilityListener {
+    void onDockDividerVisibilityChanged(boolean visible);
+}
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 64a046e..bd65532 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -29,6 +29,7 @@
 import android.os.IRemoteCallback;
 import android.view.IApplicationToken;
 import android.view.IAppTransitionAnimationSpecsFuture;
+import android.view.IDockDividerVisibilityListener;
 import android.view.IOnKeyguardExitResult;
 import android.view.IRotationWatcher;
 import android.view.IWindowSession;
@@ -290,10 +291,10 @@
     /**
      * Create a screenshot of the applications currently displayed.
      *
-     * @param frameScale the scale to apply to the frame, only used when width = -1 and 
+     * @param frameScale the scale to apply to the frame, only used when width = -1 and
      *                   height = -1
      */
-    Bitmap screenshotApplications(IBinder appToken, int displayId, int maxWidth, int maxHeight, 
+    Bitmap screenshotApplications(IBinder appToken, int displayId, int maxWidth, int maxHeight,
             float frameScale);
 
     /**
@@ -348,4 +349,9 @@
      * stack size.
      */
     void setDockedStackResizing(boolean resizing);
+
+    /**
+     * Registers a listener that will be called when the dock divider changes its visibility.
+     */
+    void registerDockDividerVisibilityListener(IDockDividerVisibilityListener listener);
 }
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 476c6a2..2f385c1 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -7225,6 +7225,7 @@
         // of the intended optimizations as part of requestLayoutForChild.
         nullLayouts();
         requestLayout();
+        invalidate();
     }
 
     @Override
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 57338be..568b601 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1877,6 +1877,11 @@
     <permission android:name="android.permission.MANAGE_APP_TOKENS"
         android:protectionLevel="signature" />
 
+    <!-- Allows System UI to register listeners for events from Window Manager.
+         @hide -->
+    <permission android:name="android.permission.REGISTER_WINDOW_MANAGER_LISTENERS"
+        android:protectionLevel="signature" />
+
     <!-- @hide Allows the application to temporarily freeze the screen for a
          full-screen transition. -->
     <permission android:name="android.permission.FREEZE_SCREEN"
diff --git a/data/sounds/AllAudio.mk b/data/sounds/AllAudio.mk
index f6d8ee9..edfd380 100644
--- a/data/sounds/AllAudio.mk
+++ b/data/sounds/AllAudio.mk
@@ -228,6 +228,7 @@
     $(LOCAL_PATH)/effects/ogg/Unlock.ogg:system/media/audio/ui/Unlock.ogg \
     $(LOCAL_PATH)/effects/ogg/Trusted_48k.ogg:system/media/audio/ui/Trusted.ogg \
     $(LOCAL_PATH)/effects/ogg/VideoRecord_48k.ogg:system/media/audio/ui/VideoRecord.ogg \
+    $(LOCAL_PATH)/effects/ogg/VideoStop_48k.ogg:system/media/audio/ui/VideoStop.ogg \
     $(LOCAL_PATH)/effects/ogg/WirelessChargingStarted.ogg:system/media/audio/ui/WirelessChargingStarted.ogg \
     $(LOCAL_PATH)/effects/ogg/camera_click_48k.ogg:system/media/audio/ui/camera_click.ogg \
     $(LOCAL_PATH)/effects/ogg/camera_focus.ogg:system/media/audio/ui/camera_focus.ogg \
diff --git a/data/sounds/AudioPackage10.mk b/data/sounds/AudioPackage10.mk
index 5a5eea6..c5222af 100644
--- a/data/sounds/AudioPackage10.mk
+++ b/data/sounds/AudioPackage10.mk
@@ -1,9 +1,9 @@
 #
 # Audio Package 10 - Mako
-# 
+#
 # Include this file in a product makefile to include these audio files
 #
-# 
+#
 
 LOCAL_PATH:= frameworks/base/data/sounds
 
@@ -23,6 +23,7 @@
 	$(LOCAL_PATH)/effects/ogg/KeypressInvalid_48k.ogg:system/media/audio/ui/KeypressInvalid.ogg \
 	$(LOCAL_PATH)/effects/ogg/KeypressReturn_48k.ogg:system/media/audio/ui/KeypressReturn.ogg \
 	$(LOCAL_PATH)/effects/material/ogg/VideoRecord_48k.ogg:system/media/audio/ui/VideoRecord.ogg \
+	$(LOCAL_PATH)/effects/material/ogg/VideoStop_48k.ogg:system/media/audio/ui/VideoStop.ogg \
 	$(LOCAL_PATH)/effects/material/ogg/camera_click_48k.ogg:system/media/audio/ui/camera_click.ogg \
 	$(LOCAL_PATH)/effects/ogg/camera_focus.ogg:system/media/audio/ui/camera_focus.ogg \
 	$(LOCAL_PATH)/effects/material/ogg/LowBattery_48k.ogg:system/media/audio/ui/LowBattery.ogg \
diff --git a/data/sounds/AudioPackage11.mk b/data/sounds/AudioPackage11.mk
index 0f85b33..43c83b9 100644
--- a/data/sounds/AudioPackage11.mk
+++ b/data/sounds/AudioPackage11.mk
@@ -1,9 +1,9 @@
 #
 # Audio Package 11 - Razor
-# 
+#
 # Include this file in a product makefile to include these audio files
 #
-# 
+#
 
 LOCAL_PATH:= frameworks/base/data/sounds
 
@@ -23,6 +23,7 @@
 	$(LOCAL_PATH)/effects/ogg/KeypressInvalid_48k.ogg:system/media/audio/ui/KeypressInvalid.ogg \
 	$(LOCAL_PATH)/effects/ogg/KeypressReturn_48k.ogg:system/media/audio/ui/KeypressReturn.ogg \
 	$(LOCAL_PATH)/effects/material/ogg/VideoRecord_48k.ogg:system/media/audio/ui/VideoRecord.ogg \
+	$(LOCAL_PATH)/effects/material/ogg/VideoStop_48k.ogg:system/media/audio/ui/VideoStop.ogg \
 	$(LOCAL_PATH)/effects/material/ogg/camera_click_48k.ogg:system/media/audio/ui/camera_click.ogg \
 	$(LOCAL_PATH)/effects/ogg/camera_focus.ogg:system/media/audio/ui/camera_focus.ogg \
 	$(LOCAL_PATH)/effects/material/ogg/LowBattery_48k.ogg:system/media/audio/ui/LowBattery.ogg \
diff --git a/data/sounds/AudioPackage12.mk b/data/sounds/AudioPackage12.mk
index 4251332..cd4d35b 100644
--- a/data/sounds/AudioPackage12.mk
+++ b/data/sounds/AudioPackage12.mk
@@ -13,7 +13,7 @@
 RINGTONE_FILES := Callisto Dione Ganymede Luna Oberon Phobos Sedna Titania Triton Umbriel
 EFFECT_FILES := Effect_Tick KeypressReturn KeypressInvalid KeypressDelete KeypressSpacebar KeypressStandard \
 	camera_focus Dock Undock Lock Unlock Trusted
-MATERIAL_EFFECT_FILES := camera_click VideoRecord LowBattery WirelessChargingStarted
+MATERIAL_EFFECT_FILES := camera_click VideoRecord LowBattery WirelessChargingStarted VideoStop
 
 PRODUCT_COPY_FILES += $(foreach fn,$(ALARM_FILES),\
 	$(LOCAL_PATH)/alarms/ogg/$(fn).ogg:system/media/audio/alarms/$(fn).ogg)
diff --git a/data/sounds/AudioPackage12_48.mk b/data/sounds/AudioPackage12_48.mk
index 70e68d3..80758f4 100644
--- a/data/sounds/AudioPackage12_48.mk
+++ b/data/sounds/AudioPackage12_48.mk
@@ -13,7 +13,7 @@
 RINGTONE_FILES := Callisto Dione Ganymede Luna Oberon Phobos Sedna Titania Triton Umbriel
 EFFECT_FILES := Effect_Tick KeypressReturn KeypressInvalid KeypressDelete KeypressSpacebar KeypressStandard \
 	Lock Unlock Trusted
-MATERIAL_EFFECT_FILES := camera_click VideoRecord LowBattery WirelessChargingStarted
+MATERIAL_EFFECT_FILES := camera_click VideoRecord LowBattery WirelessChargingStarted VideoStop
 
 # Alarms not yet available in 48 kHz
 PRODUCT_COPY_FILES += $(foreach fn,$(ALARM_FILES),\
diff --git a/data/sounds/AudioPackage13.mk b/data/sounds/AudioPackage13.mk
index cec7280..d33a4af 100644
--- a/data/sounds/AudioPackage13.mk
+++ b/data/sounds/AudioPackage13.mk
@@ -14,7 +14,7 @@
 	Umbriel
 EFFECT_FILES := Effect_Tick KeypressReturn KeypressInvalid KeypressDelete KeypressSpacebar KeypressStandard \
 	camera_focus Dock Undock Lock Unlock Trusted
-MATERIAL_EFFECT_FILES := camera_click VideoRecord WirelessChargingStarted LowBattery
+MATERIAL_EFFECT_FILES := camera_click VideoRecord WirelessChargingStarted LowBattery VideoStop
 
 PRODUCT_COPY_FILES += $(foreach fn,$(ALARM_FILES),\
 	$(LOCAL_PATH)/alarms/material/ogg/$(fn).ogg:system/media/audio/alarms/$(fn).ogg)
diff --git a/data/sounds/AudioPackage13_48.mk b/data/sounds/AudioPackage13_48.mk
index d1b17c8..9c320ae 100644
--- a/data/sounds/AudioPackage13_48.mk
+++ b/data/sounds/AudioPackage13_48.mk
@@ -14,7 +14,7 @@
 	Umbriel
 EFFECT_FILES := Effect_Tick KeypressReturn KeypressInvalid KeypressDelete KeypressSpacebar KeypressStandard \
 	Lock Unlock Trusted
-MATERIAL_EFFECT_FILES := camera_click VideoRecord WirelessChargingStarted LowBattery
+MATERIAL_EFFECT_FILES := camera_click VideoRecord WirelessChargingStarted LowBattery VideoStop
 
 PRODUCT_COPY_FILES += $(foreach fn,$(ALARM_FILES),\
 	$(LOCAL_PATH)/alarms/material/ogg/$(fn)_48k.ogg:system/media/audio/alarms/$(fn).ogg)
diff --git a/data/sounds/AudioPackage2.mk b/data/sounds/AudioPackage2.mk
index ba9d7e2..40319c4 100644
--- a/data/sounds/AudioPackage2.mk
+++ b/data/sounds/AudioPackage2.mk
@@ -1,11 +1,11 @@
 #
 # Audio Package 2
-# 
+#
 # Include this file in a product makefile to include these audio files
 #
 # This is a larger package of sounds than the 1.0 release for devices
 # that have larger internal flash.
-# 
+#
 
 LOCAL_PATH:= frameworks/base/data/sounds
 
@@ -34,6 +34,7 @@
 	$(LOCAL_PATH)/effects/ogg/KeypressInvalid.ogg:system/media/audio/ui/KeypressInvalid.ogg \
 	$(LOCAL_PATH)/effects/KeypressReturn.ogg:system/media/audio/ui/KeypressReturn.ogg \
 	$(LOCAL_PATH)/effects/VideoRecord.ogg:system/media/audio/ui/VideoRecord.ogg \
+	$(LOCAL_PATH)/effects/VideoStop.ogg:system/media/audio/ui/VideoStop.ogg \
 	$(LOCAL_PATH)/effects/camera_click.ogg:system/media/audio/ui/camera_click.ogg \
 	$(LOCAL_PATH)/effects/LowBattery.ogg:system/media/audio/ui/LowBattery.ogg \
 	$(LOCAL_PATH)/effects/Dock.ogg:system/media/audio/ui/Dock.ogg \
diff --git a/data/sounds/AudioPackage3.mk b/data/sounds/AudioPackage3.mk
index 5bfeb42..a05de72 100644
--- a/data/sounds/AudioPackage3.mk
+++ b/data/sounds/AudioPackage3.mk
@@ -1,11 +1,11 @@
 #
 # Audio Package 3
-# 
+#
 # Include this file in a product makefile to include these audio files
 #
 # This is a larger package of sounds than the 1.0 release for devices
 # that have larger internal flash.
-# 
+#
 
 LOCAL_PATH:= frameworks/base/data/sounds
 
@@ -34,6 +34,7 @@
 	$(LOCAL_PATH)/effects/KeypressInvalid.ogg:system/media/audio/ui/KeypressInvalid.ogg \
 	$(LOCAL_PATH)/effects/KeypressReturn.ogg:system/media/audio/ui/KeypressReturn.ogg \
 	$(LOCAL_PATH)/effects/VideoRecord.ogg:system/media/audio/ui/VideoRecord.ogg \
+	$(LOCAL_PATH)/effects/VideoStop.ogg:system/media/audio/ui/VideoStop.ogg \
 	$(LOCAL_PATH)/effects/camera_click.ogg:system/media/audio/ui/camera_click.ogg \
 	$(LOCAL_PATH)/effects/LowBattery.ogg:system/media/audio/ui/LowBattery.ogg \
 	$(LOCAL_PATH)/effects/Dock.ogg:system/media/audio/ui/Dock.ogg \
diff --git a/data/sounds/AudioPackage4.mk b/data/sounds/AudioPackage4.mk
index 43dbe20..d376a2d 100644
--- a/data/sounds/AudioPackage4.mk
+++ b/data/sounds/AudioPackage4.mk
@@ -1,11 +1,11 @@
 #
 # Audio Package 4
-# 
+#
 # Include this file in a product makefile to include these audio files
 #
 # This is a larger package of sounds than the 1.0 release for devices
 # that have larger internal flash.
-# 
+#
 
 LOCAL_PATH:= frameworks/base/data/sounds
 
@@ -39,6 +39,7 @@
 	$(LOCAL_PATH)/effects/KeypressInvalid.ogg:system/media/audio/ui/KeypressInvalid.ogg \
 	$(LOCAL_PATH)/effects/KeypressReturn.ogg:system/media/audio/ui/KeypressReturn.ogg \
 	$(LOCAL_PATH)/effects/VideoRecord.ogg:system/media/audio/ui/VideoRecord.ogg \
+	$(LOCAL_PATH)/effects/VideoStop.ogg:system/media/audio/ui/VideoStop.ogg \
 	$(LOCAL_PATH)/effects/camera_click.ogg:system/media/audio/ui/camera_click.ogg \
 	$(LOCAL_PATH)/effects/LowBattery.ogg:system/media/audio/ui/LowBattery.ogg \
 	$(LOCAL_PATH)/effects/Dock.ogg:system/media/audio/ui/Dock.ogg \
diff --git a/data/sounds/AudioPackage5.mk b/data/sounds/AudioPackage5.mk
index fbbb16a..72384c8 100644
--- a/data/sounds/AudioPackage5.mk
+++ b/data/sounds/AudioPackage5.mk
@@ -1,9 +1,9 @@
 #
 # Audio Package 5 - Crespo/Soju
-# 
+#
 # Include this file in a product makefile to include these audio files
 #
-# 
+#
 
 LOCAL_PATH:= frameworks/base/data/sounds
 
@@ -20,6 +20,7 @@
 	$(LOCAL_PATH)/effects/KeypressInvalid.ogg:system/media/audio/ui/KeypressInvalid.ogg \
 	$(LOCAL_PATH)/effects/KeypressReturn.ogg:system/media/audio/ui/KeypressReturn.ogg \
 	$(LOCAL_PATH)/effects/VideoRecord.ogg:system/media/audio/ui/VideoRecord.ogg \
+	$(LOCAL_PATH)/effects/VideoStop.ogg:system/media/audio/ui/VideoStop.ogg \
 	$(LOCAL_PATH)/effects/camera_click.ogg:system/media/audio/ui/camera_click.ogg \
 	$(LOCAL_PATH)/effects/ogg/camera_focus.ogg:system/media/audio/ui/camera_focus.ogg \
 	$(LOCAL_PATH)/effects/LowBattery.ogg:system/media/audio/ui/LowBattery.ogg \
diff --git a/data/sounds/AudioPackage6.mk b/data/sounds/AudioPackage6.mk
index c843fdc..5413704 100644
--- a/data/sounds/AudioPackage6.mk
+++ b/data/sounds/AudioPackage6.mk
@@ -1,9 +1,9 @@
 #
 # Audio Package 6 - Trygon/Stingray
-# 
+#
 # Include this file in a product makefile to include these audio files
 #
-# 
+#
 
 LOCAL_PATH:= frameworks/base/data/sounds
 
@@ -19,6 +19,7 @@
 	$(LOCAL_PATH)/effects/ogg/KeypressInvalid.ogg:system/media/audio/ui/KeypressInvalid.ogg \
 	$(LOCAL_PATH)/effects/ogg/KeypressReturn.ogg:system/media/audio/ui/KeypressReturn.ogg \
 	$(LOCAL_PATH)/effects/material/ogg/VideoRecord.ogg:system/media/audio/ui/VideoRecord.ogg \
+	$(LOCAL_PATH)/effects/material/ogg/VideoStop.ogg:system/media/audio/ui/VideoStop.ogg \
 	$(LOCAL_PATH)/effects/material/ogg/camera_click.ogg:system/media/audio/ui/camera_click.ogg \
 	$(LOCAL_PATH)/effects/ogg/camera_focus.ogg:system/media/audio/ui/camera_focus.ogg \
 	$(LOCAL_PATH)/effects/material/ogg/LowBattery.ogg:system/media/audio/ui/LowBattery.ogg \
diff --git a/data/sounds/AudioPackage7.mk b/data/sounds/AudioPackage7.mk
index ce82651..e4763be 100644
--- a/data/sounds/AudioPackage7.mk
+++ b/data/sounds/AudioPackage7.mk
@@ -1,9 +1,9 @@
 #
 # Audio Package 7 - Tuna
-# 
+#
 # Include this file in a product makefile to include these audio files
 #
-# 
+#
 
 LOCAL_PATH:= frameworks/base/data/sounds
 
@@ -21,6 +21,7 @@
 	$(LOCAL_PATH)/effects/ogg/KeypressInvalid_120.ogg:system/media/audio/ui/KeypressInvalid.ogg \
 	$(LOCAL_PATH)/effects/ogg/KeypressReturn_120.ogg:system/media/audio/ui/KeypressReturn.ogg \
 	$(LOCAL_PATH)/effects/material/ogg/VideoRecord.ogg:system/media/audio/ui/VideoRecord.ogg \
+	$(LOCAL_PATH)/effects/material/ogg/VideoStop.ogg:system/media/audio/ui/VideoStop.ogg \
 	$(LOCAL_PATH)/effects/material/ogg/camera_click.ogg:system/media/audio/ui/camera_click.ogg \
 	$(LOCAL_PATH)/effects/ogg/camera_focus.ogg:system/media/audio/ui/camera_focus.ogg \
 	$(LOCAL_PATH)/effects/material/ogg/LowBattery.ogg:system/media/audio/ui/LowBattery.ogg \
diff --git a/data/sounds/AudioPackage7alt.mk b/data/sounds/AudioPackage7alt.mk
index db468f3..30e6173 100644
--- a/data/sounds/AudioPackage7alt.mk
+++ b/data/sounds/AudioPackage7alt.mk
@@ -21,6 +21,7 @@
 	$(LOCAL_PATH)/effects/ogg/KeypressInvalid_120.ogg:system/media/audio/ui/KeypressInvalid.ogg \
 	$(LOCAL_PATH)/effects/ogg/KeypressReturn_120.ogg:system/media/audio/ui/KeypressReturn.ogg \
 	$(LOCAL_PATH)/effects/ogg/VideoRecord.ogg:system/media/audio/ui/VideoRecord.ogg \
+	$(LOCAL_PATH)/effects/ogg/VideoStop.ogg:system/media/audio/ui/VideoStop.ogg \
 	$(LOCAL_PATH)/effects/material/ogg/camera_click.ogg:system/media/audio/ui/camera_click.ogg \
 	$(LOCAL_PATH)/effects/ogg/LowBattery.ogg:system/media/audio/ui/LowBattery.ogg \
 	$(LOCAL_PATH)/effects/ogg/Dock.ogg:system/media/audio/ui/Dock.ogg \
diff --git a/data/sounds/AudioPackage8.mk b/data/sounds/AudioPackage8.mk
index 4112c18..b38e62d 100644
--- a/data/sounds/AudioPackage8.mk
+++ b/data/sounds/AudioPackage8.mk
@@ -1,9 +1,9 @@
 #
 # Audio Package 7 - Tuna
-# 
+#
 # Include this file in a product makefile to include these audio files
 #
-# 
+#
 
 LOCAL_PATH:= frameworks/base/data/sounds
 
@@ -23,6 +23,7 @@
 	$(LOCAL_PATH)/effects/ogg/KeypressInvalid.ogg:system/media/audio/ui/KeypressInvalid.ogg \
 	$(LOCAL_PATH)/effects/ogg/KeypressReturn.ogg:system/media/audio/ui/KeypressReturn.ogg \
 	$(LOCAL_PATH)/effects/material/ogg/VideoRecord.ogg:system/media/audio/ui/VideoRecord.ogg \
+	$(LOCAL_PATH)/effects/material/ogg/VideoStop.ogg:system/media/audio/ui/VideoStop.ogg \
 	$(LOCAL_PATH)/effects/material/ogg/camera_click.ogg:system/media/audio/ui/camera_click.ogg \
 	$(LOCAL_PATH)/effects/ogg/camera_focus.ogg:system/media/audio/ui/camera_focus.ogg \
 	$(LOCAL_PATH)/effects/material/ogg/LowBattery.ogg:system/media/audio/ui/LowBattery.ogg \
diff --git a/data/sounds/AudioPackage9.mk b/data/sounds/AudioPackage9.mk
index 1b430c0..dbe1350 100644
--- a/data/sounds/AudioPackage9.mk
+++ b/data/sounds/AudioPackage9.mk
@@ -23,6 +23,7 @@
 	$(LOCAL_PATH)/effects/ogg/KeypressInvalid.ogg:system/media/audio/ui/KeypressInvalid.ogg \
 	$(LOCAL_PATH)/effects/ogg/KeypressReturn.ogg:system/media/audio/ui/KeypressReturn.ogg \
 	$(LOCAL_PATH)/effects/material/ogg/VideoRecord.ogg:system/media/audio/ui/VideoRecord.ogg \
+	$(LOCAL_PATH)/effects/material/ogg/VideoStop.ogg:system/media/audio/ui/VideoStop.ogg \
 	$(LOCAL_PATH)/effects/material/ogg/camera_click.ogg:system/media/audio/ui/camera_click.ogg \
 	$(LOCAL_PATH)/effects/ogg/camera_focus.ogg:system/media/audio/ui/camera_focus.ogg \
 	$(LOCAL_PATH)/effects/material/ogg/LowBattery.ogg:system/media/audio/ui/LowBattery.ogg \
diff --git a/data/sounds/OriginalAudio.mk b/data/sounds/OriginalAudio.mk
index e1ca24b..f683752 100644
--- a/data/sounds/OriginalAudio.mk
+++ b/data/sounds/OriginalAudio.mk
@@ -1,8 +1,8 @@
 #
 # Original audio package that shipped on G1
-# 
+#
 # This file is included from core.mk so that all devices will have these sounds
-# 
+#
 # TODO: Clean up for future releases
 #
 
@@ -32,6 +32,7 @@
 	$(LOCAL_PATH)/effects/KeypressDelete.ogg:system/media/audio/ui/KeypressDelete.ogg \
 	$(LOCAL_PATH)/effects/KeypressReturn.ogg:system/media/audio/ui/KeypressReturn.ogg \
 	$(LOCAL_PATH)/effects/VideoRecord.ogg:system/media/audio/ui/VideoRecord.ogg \
+	$(LOCAL_PATH)/effects/VideoStop.ogg:system/media/audio/ui/VideoStop.ogg \
 	$(LOCAL_PATH)/effects/camera_click.ogg:system/media/audio/ui/camera_click.ogg \
 	$(LOCAL_PATH)/newwavelabs/BeatPlucker.ogg:system/media/audio/ringtones/BeatPlucker.ogg \
 	$(LOCAL_PATH)/newwavelabs/CaffeineSnake.ogg:system/media/audio/notifications/CaffeineSnake.ogg
diff --git a/data/sounds/effects/VideoStop.ogg b/data/sounds/effects/VideoStop.ogg
new file mode 100644
index 0000000..1450522
--- /dev/null
+++ b/data/sounds/effects/VideoStop.ogg
Binary files differ
diff --git a/data/sounds/effects/VideoStop.wav b/data/sounds/effects/VideoStop.wav
new file mode 100644
index 0000000..5809d93
--- /dev/null
+++ b/data/sounds/effects/VideoStop.wav
Binary files differ
diff --git a/data/sounds/effects/material/ogg/VideoStop.ogg b/data/sounds/effects/material/ogg/VideoStop.ogg
new file mode 100644
index 0000000..e98fabc0
--- /dev/null
+++ b/data/sounds/effects/material/ogg/VideoStop.ogg
Binary files differ
diff --git a/data/sounds/effects/material/ogg/VideoStop_48k.ogg b/data/sounds/effects/material/ogg/VideoStop_48k.ogg
new file mode 100644
index 0000000..b1eb780
--- /dev/null
+++ b/data/sounds/effects/material/ogg/VideoStop_48k.ogg
Binary files differ
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index bf3982d..25346ac 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -144,6 +144,7 @@
             android:name=".BugreportReceiver"
             android:permission="android.permission.DUMP">
             <intent-filter>
+                <action android:name="android.intent.action.BUGREPORT_STARTED" />
                 <action android:name="android.intent.action.BUGREPORT_FINISHED" />
             </intent-filter>
         </receiver>
diff --git a/packages/Shell/res/values/strings.xml b/packages/Shell/res/values/strings.xml
index 4469d38..cff36f7 100644
--- a/packages/Shell/res/values/strings.xml
+++ b/packages/Shell/res/values/strings.xml
@@ -17,6 +17,8 @@
 <resources>
     <string name="app_label">Shell</string>
 
+    <!-- Title of notification indicating a bugreport process is in progress. [CHAR LIMIT=50] -->
+    <string name="bugreport_in_progress_title">Bug report in progress</string>
     <!-- Title of notification indicating a bugreport has been successfully captured. [CHAR LIMIT=50] -->
     <string name="bugreport_finished_title">Bug report captured</string>
 
diff --git a/packages/Shell/src/com/android/shell/BugreportProgressService.java b/packages/Shell/src/com/android/shell/BugreportProgressService.java
index a2030ef..d0e91d2 100644
--- a/packages/Shell/src/com/android/shell/BugreportProgressService.java
+++ b/packages/Shell/src/com/android/shell/BugreportProgressService.java
@@ -21,11 +21,16 @@
 
 import java.io.BufferedOutputStream;
 import java.io.File;
+import java.io.FileDescriptor;
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.PrintWriter;
+import java.text.NumberFormat;
+import java.text.SimpleDateFormat;
 import java.util.ArrayList;
+import java.util.Date;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipOutputStream;
 
@@ -45,26 +50,102 @@
 import android.content.res.Configuration;
 import android.net.Uri;
 import android.os.AsyncTask;
+import android.os.Handler;
+import android.os.HandlerThread;
 import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Parcelable;
+import android.os.Process;
 import android.os.SystemProperties;
 import android.support.v4.content.FileProvider;
+import android.text.format.DateUtils;
 import android.util.Log;
 import android.util.Patterns;
+import android.util.SparseArray;
 import android.widget.Toast;
 
+/**
+ * Service used to keep progress of bug reports processes ({@code dumpstate}).
+ * <p>
+ * The workflow is:
+ * <ol>
+ * <li>When {@code dumpstate} starts, it sends a {@code BUGREPORT_STARTED} with its pid and the
+ * estimated total effort.
+ * <li>{@link BugreportReceiver} receives the intent and delegates it to this service.
+ * <li>Upon start, this service:
+ * <ol>
+ * <li>Issues a system notification so user can watch the progresss (which is 0% initially).
+ * <li>Polls the {@link SystemProperties} for updates on the {@code dumpstate} progress.
+ * <li>If the progress changed, it updates the system notification.
+ * </ol>
+ * <li>As {@code dumpstate} progresses, it updates the system property.
+ * <li>When {@code dumpstate} finishes, it sends a {@code BUGREPORT_FINISHED} intent.
+ * <li>{@link BugreportReceiver} receives the intent and delegates it to this service, which in
+ * turn:
+ * <ol>
+ * <li>Updates the system notification so user can share the bug report.
+ * <li>Stops monitoring that {@code dumpstate} process.
+ * <li>Stops itself if it doesn't have any process left to monitor.
+ * </ol>
+ * </ol>
+ */
 public class BugreportProgressService extends Service {
     private static final String TAG = "Shell";
+    private static final boolean DEBUG = false;
 
     private static final String AUTHORITY = "com.android.shell";
 
+    static final String INTENT_BUGREPORT_STARTED = "android.intent.action.BUGREPORT_STARTED";
+    static final String INTENT_BUGREPORT_FINISHED = "android.intent.action.BUGREPORT_FINISHED";
+
     static final String EXTRA_BUGREPORT = "android.intent.extra.BUGREPORT";
     static final String EXTRA_SCREENSHOT = "android.intent.extra.SCREENSHOT";
+    static final String EXTRA_PID = "android.intent.extra.PID";
+    static final String EXTRA_MAX = "android.intent.extra.MAX";
+    static final String EXTRA_NAME = "android.intent.extra.NAME";
+    static final String EXTRA_ORIGINAL_INTENT = "android.intent.extra.ORIGINAL_INTENT";
+
+    private static final int MSG_SERVICE_COMMAND = 1;
+    private static final int MSG_POLL = 2;
+
+    /** Polling frequency, in milliseconds. */
+    private static final long POLLING_FREQUENCY = 500;
+
+    /** How long (in ms) a dumpstate process will be monitored if it didn't show progress. */
+    private static final long INACTIVITY_TIMEOUT = 3 * DateUtils.MINUTE_IN_MILLIS;
+
+    /** System property used for monitoring progress. */
+    private static final String PROGRESS_PROPERTY_TEMPLATE = "dumpstate.%d.progress";
+
+    /** Managed dumpstate processes (keyed by pid) */
+    private final SparseArray<BugreportInfo> mProcesses = new SparseArray<>();
+
+    private Looper mServiceLooper;
+    private ServiceHandler mServiceHandler;
+
+    @Override
+    public void onCreate() {
+        HandlerThread thread = new HandlerThread("BugreportProgressServiceThread",
+                Process.THREAD_PRIORITY_BACKGROUND);
+        thread.start();
+
+        mServiceLooper = thread.getLooper();
+        mServiceHandler = new ServiceHandler(mServiceLooper);
+    }
 
     @Override
     public int onStartCommand(Intent intent, int flags, int startId) {
         if (intent != null) {
-            onBugreportFinished(intent);
+            // Handle it in a separate thread.
+            Message msg = mServiceHandler.obtainMessage();
+            msg.what = MSG_SERVICE_COMMAND;
+            msg.obj = intent;
+            mServiceHandler.sendMessage(msg);
         }
+
+        // If service is killed it cannot be recreated because it would not know which
+        // dumpstate PIDs it would have to watch.
         return START_NOT_STICKY;
     }
 
@@ -73,26 +154,249 @@
         return null;
     }
 
-    private void onBugreportFinished(Intent intent) {
-        final Context context = getApplicationContext();
-        final Configuration conf = context.getResources().getConfiguration();
-        final File bugreportFile = getFileExtra(intent, EXTRA_BUGREPORT);
-        final File screenshotFile = getFileExtra(intent, EXTRA_SCREENSHOT);
+    @Override
+    public void onDestroy() {
+        mServiceLooper.quit();
+        super.onDestroy();
+    }
 
-        if ((conf.uiMode & Configuration.UI_MODE_TYPE_MASK) != Configuration.UI_MODE_TYPE_WATCH) {
-            triggerLocalNotification(context, bugreportFile, screenshotFile);
+    @Override
+    protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
+        writer.printf("Monitored dumpstate processes: \n");
+        synchronized (mProcesses) {
+            for (int i = 0; i < mProcesses.size(); i++) {
+              writer.printf("\t%s\n", mProcesses.valueAt(i));
+            }
         }
-        stopSelf();
+    }
+
+    private final class ServiceHandler extends Handler {
+        public ServiceHandler(Looper looper) {
+            super(looper);
+            pollProgress();
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            if (msg.what == MSG_POLL) {
+                pollProgress();
+                return;
+            }
+
+            if (msg.what != MSG_SERVICE_COMMAND) {
+                // Sanity check.
+                Log.e(TAG, "Invalid message type: " + msg.what);
+                return;
+            }
+
+            // At this point it's handling onStartCommand(), whose intent contains the extras
+            // originally received by BugreportReceiver.
+            if (!(msg.obj instanceof Intent)) {
+                // Sanity check.
+                Log.e(TAG, "Internal error: invalid msg.obj: " + msg.obj);
+                return;
+            }
+            final Parcelable parcel = ((Intent) msg.obj).getParcelableExtra(EXTRA_ORIGINAL_INTENT);
+            if (!(parcel instanceof Intent)) {
+                // Sanity check.
+                Log.e(TAG, "Internal error: msg.obj is missing extra " + EXTRA_ORIGINAL_INTENT);
+                return;
+            }
+
+            final Intent intent = (Intent) parcel;
+            final String action = intent.getAction();
+            int pid = intent.getIntExtra(EXTRA_PID, 0);
+            int max = intent.getIntExtra(EXTRA_MAX, -1);
+            String name = intent.getStringExtra(EXTRA_NAME);
+
+            if (DEBUG) Log.v(TAG, "action: " + action + ", name: " + name + ", pid: " + pid
+                    + ", max: "+ max);
+            switch (action) {
+                case INTENT_BUGREPORT_STARTED:
+                    if (!startProgress(name, pid, max)) {
+                        stopSelfWhenDone();
+                        return;
+                    }
+                    break;
+                case INTENT_BUGREPORT_FINISHED:
+                    if (pid == -1) {
+                        // Shouldn't happen, unless BUGREPORT_FINISHED is received from a legacy,
+                        // out-of-sync dumpstate process.
+                        Log.w(TAG, "Missing " + EXTRA_PID + " on intent " + intent);
+                    }
+                    stopProgress(pid, intent);
+                    break;
+                default:
+                    Log.w(TAG, "Unsupported intent: " + action);
+            }
+            return;
+
+        }
+
+        /**
+         * Creates the {@link BugreportInfo} for a process and issue a system notification to
+         * indicate its progress.
+         *
+         * @return whether it succeeded or not.
+         */
+        private boolean startProgress(String name, int pid, int max) {
+            if (name == null) {
+                Log.w(TAG, "Missing " + EXTRA_NAME + " on start intent");
+                name = "N/A";
+            }
+            if (pid == -1) {
+                Log.e(TAG, "Missing " + EXTRA_PID + " on start intent");
+                return false;
+            }
+            if (max <= 0) {
+                Log.e(TAG, "Invalid value for extra " + EXTRA_MAX + ": " + max);
+                return false;
+            }
+
+            final BugreportInfo info = new BugreportInfo(pid, name, max);
+            synchronized (mProcesses) {
+                if (mProcesses.indexOfKey(pid) >= 0) {
+                    Log.w(TAG, "PID " + pid + " already watched");
+                } else {
+                    mProcesses.put(info.pid, info);
+                }
+            }
+            updateProgress(info);
+            return true;
+        }
+
+        /**
+         * Updates the system notification for a given bug report.
+         */
+        private void updateProgress(BugreportInfo info) {
+            if (info.max <= 0 || info.progress < 0 || info.name == null) {
+                Log.e(TAG, "Invalid progress values for " + info);
+                return;
+            }
+
+            final Context context = getApplicationContext();
+            final NumberFormat nf = NumberFormat.getPercentInstance();
+            nf.setMinimumFractionDigits(2);
+            nf.setMaximumFractionDigits(2);
+            final String percentText = nf.format((double) info.progress / info.max);
+
+            final String title = context.getString(R.string.bugreport_in_progress_title);
+            final Notification notification = new Notification.Builder(context)
+                    .setSmallIcon(com.android.internal.R.drawable.stat_sys_adb)
+                    .setContentTitle(title)
+                    .setTicker(title)
+                    .setContentText(info.name)
+                    .setContentInfo(percentText)
+                    .setProgress(info.max, info.progress, false)
+                    // TODO: .setOngoing(true) once it has a CANCEL action
+                    .setLocalOnly(true)
+                    .setColor(context.getColor(
+                            com.android.internal.R.color.system_notification_accent_color))
+                    .build();
+
+            NotificationManager.from(context).notify(TAG, info.pid, notification);
+        }
+
+        /**
+         * Finalizes the progress on a given process and sends the finished intent.
+         */
+        private void stopProgress(int pid, Intent intent) {
+            synchronized (mProcesses) {
+                if (mProcesses.indexOfKey(pid) < 0) {
+                    Log.w(TAG, "PID not watched: " + pid);
+                } else {
+                    mProcesses.remove(pid);
+                }
+                stopSelfWhenDone();
+            }
+            if (DEBUG) Log.v(TAG, "stopProgress(" + pid + "): cancel notification");
+            NotificationManager.from(getApplicationContext()).cancel(TAG, pid);
+            if (intent != null) {
+                // Bug report finished fine: send a new, different notification.
+                if (DEBUG) Log.v(TAG, "stopProgress(" + pid + "): finish bug report");
+                onBugreportFinished(pid, intent);
+            }
+        }
+
+        /**
+         * Poll {@link SystemProperties} to get the progress on each monitored process.
+         */
+        private void pollProgress() {
+            synchronized (mProcesses) {
+                if (mProcesses.size() == 0) {
+                    Log.d(TAG, "No process to poll progress.");
+                }
+                for (int i = 0; i < mProcesses.size(); i++) {
+                    int pid = mProcesses.keyAt(i);
+                    String property = String.format(PROGRESS_PROPERTY_TEMPLATE, pid);
+                    int progress;
+                    try {
+                        progress = SystemProperties.getInt(property, 0);
+                    } catch (IllegalArgumentException e) {
+                        Log.v(TAG, "Could not read system property " + property, e);
+                        continue;
+                    }
+                    if (progress == 0) {
+                        Log.v(TAG, "System property " + property + " is not set yet");
+                        continue;
+                    }
+
+                    BugreportInfo info = mProcesses.valueAt(i);
+
+                    if (progress != info.progress) {
+                        if (DEBUG) Log.v(TAG, "Updating progress for PID " + pid + " from "
+                                + info.progress + " to " + progress);
+                        info.progress = progress;
+                        info.lastUpdate = System.currentTimeMillis();
+                        updateProgress(info);
+                    } else {
+                        long inactiveTime = System.currentTimeMillis() - info.lastUpdate;
+                        if (inactiveTime >= INACTIVITY_TIMEOUT) {
+                            Log.w(TAG, "No progress update for process " + pid + " since "
+                                    + info.getFormattedLastUpdate());
+                            stopProgress(info.pid, null);
+                        }
+                    }
+                }
+                // Keep polling...
+                sendEmptyMessageDelayed(MSG_POLL, POLLING_FREQUENCY);
+            }
+        }
+
+        /**
+         * Finishes the service when it's not monitoring any more processes.
+         */
+        private void stopSelfWhenDone() {
+            synchronized (mProcesses) {
+                if (mProcesses.size() > 0) {
+                    if (DEBUG) Log.v(TAG, "Staying alive, waiting for pids " + mProcesses);
+                    return;
+                }
+                Log.v(TAG, "No more pids to handle, shutting down");
+                stopSelf();
+            }
+        }
+
+        private void onBugreportFinished(int pid, Intent intent) {
+            final Context context = getApplicationContext();
+            final Configuration conf = context.getResources().getConfiguration();
+            final File bugreportFile = getFileExtra(intent, EXTRA_BUGREPORT);
+            final File screenshotFile = getFileExtra(intent, EXTRA_SCREENSHOT);
+
+            if ((conf.uiMode & Configuration.UI_MODE_TYPE_MASK) != Configuration.UI_MODE_TYPE_WATCH) {
+                triggerLocalNotification(context, pid, bugreportFile, screenshotFile);
+            }
+        }
     }
 
     /**
-     * Responsible for triggering a notification that allows the user to start a
-     * "share" intent with the bug report. On watches we have other methods to allow the user to
-     * start this intent (usually by triggering it on another connected device); we don't need to
-     * display the notification in this case.
+     * Responsible for triggering a notification that allows the user to start a "share" intent with
+     * the bug report. On watches we have other methods to allow the user to start this intent
+     * (usually by triggering it on another connected device); we don't need to display the
+     * notification in this case.
      */
-    private static void triggerLocalNotification(final Context context, final File bugreportFile,
-            final File screenshotFile) {
+    private static void triggerLocalNotification(final Context context, final int pid,
+            final File bugreportFile, final File screenshotFile) {
         if (!bugreportFile.exists() || !bugreportFile.canRead()) {
             Log.e(TAG, "Could not read bugreport file " + bugreportFile);
             Toast.makeText(context, context.getString(R.string.bugreport_unreadable_text),
@@ -103,10 +407,10 @@
         boolean isPlainText = bugreportFile.getName().toLowerCase().endsWith(".txt");
         if (!isPlainText) {
             // Already zipped, send it right away.
-            sendBugreportNotification(context, bugreportFile, screenshotFile);
+            sendBugreportNotification(context, pid, bugreportFile, screenshotFile);
         } else {
             // Asynchronously zip the file first, then send it.
-            sendZippedBugreportNotification(context, bugreportFile, screenshotFile);
+            sendZippedBugreportNotification(context, pid, bugreportFile, screenshotFile);
         }
     }
 
@@ -155,7 +459,7 @@
     /**
      * Sends a bugreport notitication.
      */
-    private static void sendBugreportNotification(Context context, File bugreportFile,
+    private static void sendBugreportNotification(Context context, int pid, File bugreportFile,
             File screenshotFile) {
         // Files are kept on private storage, so turn into Uris that we can
         // grant temporary permissions for.
@@ -173,10 +477,11 @@
         }
         notifIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
 
+        final String title = context.getString(R.string.bugreport_finished_title);
         final Notification.Builder builder = new Notification.Builder(context)
                 .setSmallIcon(com.android.internal.R.drawable.stat_sys_adb)
-                .setContentTitle(context.getString(R.string.bugreport_finished_title))
-                .setTicker(context.getString(R.string.bugreport_finished_title))
+                .setContentTitle(title)
+                .setTicker(title)
                 .setContentText(context.getString(R.string.bugreport_finished_text))
                 .setContentIntent(PendingIntent.getActivity(
                         context, 0, notifIntent, PendingIntent.FLAG_CANCEL_CURRENT))
@@ -185,19 +490,19 @@
                 .setColor(context.getColor(
                         com.android.internal.R.color.system_notification_accent_color));
 
-        NotificationManager.from(context).notify(TAG, 0, builder.build());
+        NotificationManager.from(context).notify(TAG, pid, builder.build());
     }
 
     /**
      * Sends a zipped bugreport notification.
      */
     private static void sendZippedBugreportNotification(final Context context,
-            final File bugreportFile, final File screenshotFile) {
+            final int pid, final File bugreportFile, final File screenshotFile) {
         new AsyncTask<Void, Void, Void>() {
             @Override
             protected Void doInBackground(Void... params) {
                 File zippedFile = zipBugreport(bugreportFile);
-                sendBugreportNotification(context, zippedFile, screenshotFile);
+                sendBugreportNotification(context, pid, zippedFile, screenshotFile);
                 return null;
             }
         }.execute();
@@ -213,8 +518,8 @@
         Log.v(TAG, "zipping " + bugreportPath + " as " + zippedPath);
         File bugreportZippedFile = new File(zippedPath);
         try (InputStream is = new FileInputStream(bugreportFile);
-            ZipOutputStream zos = new ZipOutputStream(
-                new BufferedOutputStream(new FileOutputStream(bugreportZippedFile)))) {
+                ZipOutputStream zos = new ZipOutputStream(
+                        new BufferedOutputStream(new FileOutputStream(bugreportZippedFile)))) {
             ZipEntry entry = new ZipEntry(bugreportFile.getName());
             entry.setTime(bugreportFile.lastModified());
             zos.putNextEntry(entry);
@@ -230,8 +535,8 @@
             }
             return bugreportZippedFile;
         } catch (IOException e) {
-          Log.e(TAG, "exception zipping file " + zippedPath, e);
-          return bugreportFile;  // Return original.
+            Log.e(TAG, "exception zipping file " + zippedPath, e);
+            return bugreportFile; // Return original.
         }
     }
 
@@ -281,4 +586,55 @@
             return null;
         }
     }
+
+    /**
+     * Information about a bug report process while its in progress.
+     */
+    private static final class BugreportInfo {
+        /**
+         * {@code pid} of the {@code dumpstate} process generating the bug report.
+         */
+        final int pid;
+
+        /**
+         * Name of the bug report, will be used to rename the final files.
+         * <p>
+         * Initial value is the bug report filename reported by {@code dumpstate}, but user can
+         * change it later to a more meaningful name.
+         */
+        final String name;
+
+        /**
+         * Maximum progress of the bug report generation.
+         */
+        final int max;
+
+        /**
+         * Current progress of the bug report generation.
+         */
+        int progress;
+
+        /**
+         * Time of the last progress update.
+         */
+        long lastUpdate = System.currentTimeMillis();
+
+        BugreportInfo(int pid, String name, int max) {
+            this.pid = pid;
+            this.name = name;
+            this.max = max;
+        }
+
+        String getFormattedLastUpdate() {
+            return SimpleDateFormat.getDateTimeInstance().format(new Date(lastUpdate));
+        }
+
+        @Override
+        public String toString() {
+            final float percent = ((float) progress * 100 / max);
+            return String.format("Progress for %s (pid=%d): %d/%d (%2.2f%%) Last update: %s", name,
+                    pid, progress, max, percent,
+                    getFormattedLastUpdate());
+        }
+    }
 }
diff --git a/packages/Shell/src/com/android/shell/BugreportReceiver.java b/packages/Shell/src/com/android/shell/BugreportReceiver.java
index f1da14d..5133162 100644
--- a/packages/Shell/src/com/android/shell/BugreportReceiver.java
+++ b/packages/Shell/src/com/android/shell/BugreportReceiver.java
@@ -17,6 +17,8 @@
 package com.android.shell;
 
 import static com.android.shell.BugreportProgressService.EXTRA_BUGREPORT;
+import static com.android.shell.BugreportProgressService.EXTRA_ORIGINAL_INTENT;
+import static com.android.shell.BugreportProgressService.INTENT_BUGREPORT_FINISHED;
 import static com.android.shell.BugreportProgressService.getFileExtra;
 
 import java.io.File;
@@ -50,13 +52,16 @@
         // Clean up older bugreports in background
         cleanupOldFiles(intent);
 
-        // Delegate to service.
+        // Delegate intent handling to service.
         Intent serviceIntent = new Intent(context, BugreportProgressService.class);
-        serviceIntent.putExtras(intent.getExtras());
+        serviceIntent.putExtra(EXTRA_ORIGINAL_INTENT, intent);
         context.startService(serviceIntent);
     }
 
     private void cleanupOldFiles(Intent intent) {
+        if (!INTENT_BUGREPORT_FINISHED.equals(intent.getAction())) {
+            return;
+        }
         final File bugreportFile = getFileExtra(intent, EXTRA_BUGREPORT);
         final PendingResult result = goAsync();
         new AsyncTask<Void, Void, Void>() {
diff --git a/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java b/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java
index 1bdd9dd..33c4ef1 100644
--- a/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java
+++ b/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java
@@ -19,7 +19,12 @@
 import static android.test.MoreAsserts.assertContainsRegex;
 import static com.android.shell.ActionSendMultipleConsumerActivity.UI_NAME;
 import static com.android.shell.BugreportProgressService.EXTRA_BUGREPORT;
+import static com.android.shell.BugreportProgressService.EXTRA_MAX;
+import static com.android.shell.BugreportProgressService.EXTRA_NAME;
+import static com.android.shell.BugreportProgressService.EXTRA_PID;
 import static com.android.shell.BugreportProgressService.EXTRA_SCREENSHOT;
+import static com.android.shell.BugreportProgressService.INTENT_BUGREPORT_FINISHED;
+import static com.android.shell.BugreportProgressService.INTENT_BUGREPORT_STARTED;
 
 import java.io.BufferedOutputStream;
 import java.io.BufferedWriter;
@@ -96,6 +101,33 @@
         cancelExistingNotifications();
     }
 
+    public void testFullWorkflow() throws Exception {
+        final String name = "BUG, Y U NO REPORT?";
+        // TODO: call method to remove property instead
+        SystemProperties.set("dumpstate.42.progress", "-1");
+
+        Intent intent = new Intent(INTENT_BUGREPORT_STARTED);
+        intent.putExtra(EXTRA_PID, 42);
+        intent.putExtra(EXTRA_NAME, name);
+        intent.putExtra(EXTRA_MAX, 1000);
+        mContext.sendBroadcast(intent);
+
+        assertProgressNotification(name, "0.00%");
+
+        SystemProperties.set("dumpstate.42.progress", "108");
+        assertProgressNotification(name, "10.80%");
+
+        SystemProperties.set("dumpstate.42.progress", "500");
+        assertProgressNotification(name, "50.00%");
+
+        createTextFile(PLAIN_TEXT_PATH, BUGREPORT_CONTENT);
+        createTextFile(SCREENSHOT_PATH, SCREENSHOT_CONTENT);
+        Bundle extras = sendBugreportFinishedIntent(42, PLAIN_TEXT_PATH, SCREENSHOT_PATH);
+        assertActionSendMultiple(extras, BUGREPORT_CONTENT, SCREENSHOT_CONTENT);
+
+        // TODO: assert service is down
+    }
+
     public void testBugreportFinished_plainBugreportAndScreenshot() throws Exception {
         createTextFile(PLAIN_TEXT_PATH, BUGREPORT_CONTENT);
         createTextFile(SCREENSHOT_PATH, SCREENSHOT_CONTENT);
@@ -131,13 +163,32 @@
         }
     }
 
+    private void assertProgressNotification(String name, String percent) {
+        // TODO: it current looks for 3 distinct objects, without taking advantage of their
+        // relationship.
+        String title = mContext.getString(R.string.bugreport_in_progress_title);
+        Log.v(TAG, "Looking for progress notification title: '" + title+ "'");
+        mUiBot.getNotification(title);
+        Log.v(TAG, "Looking for progress notification details: '" + name + "-" + percent + "'");
+        mUiBot.getObject(name);
+        mUiBot.getObject(percent);
+    }
+
     /**
      * Sends a "bugreport finished" intent and waits for the result.
      *
      * @return extras sent to the bugreport finished consumer.
      */
     private Bundle sendBugreportFinishedIntent(String bugreportPath, String screenshotPath) {
-        Intent intent = new Intent("android.intent.action.BUGREPORT_FINISHED");
+        return sendBugreportFinishedIntent(null, bugreportPath, screenshotPath);
+    }
+
+    private Bundle sendBugreportFinishedIntent(Integer pid, String bugreportPath,
+            String screenshotPath) {
+        Intent intent = new Intent(INTENT_BUGREPORT_FINISHED);
+        if (pid != null) {
+            intent.putExtra(EXTRA_PID, pid);
+        }
         if (bugreportPath != null) {
             intent.putExtra(EXTRA_BUGREPORT, bugreportPath);
         }
diff --git a/packages/Shell/tests/src/com/android/shell/UiBot.java b/packages/Shell/tests/src/com/android/shell/UiBot.java
index f5dd31c..fa1714e 100644
--- a/packages/Shell/tests/src/com/android/shell/UiBot.java
+++ b/packages/Shell/tests/src/com/android/shell/UiBot.java
@@ -42,32 +42,49 @@
     }
 
     /**
-     * Opens the system notification and clicks a given notification.
+     * Opens the system notification and gets a given notification.
      *
      * @param text Notificaton's text as displayed by the UI.
+     * @return notification object.
      */
-    public void clickOnNotification(String text) {
+    public UiObject getNotification(String text) {
         boolean opened = mDevice.openNotification();
         Log.v(TAG, "openNotification(): " + opened);
         boolean gotIt = mDevice.wait(Until.hasObject(By.pkg(SYSTEMUI_PACKAGED)), mTimeout);
         assertTrue("could not get system ui (" + SYSTEMUI_PACKAGED + ")", gotIt);
 
-        gotIt = mDevice.wait(Until.hasObject(By.text(text)), mTimeout);
-        assertTrue("object with text '(" + text + "') not visible yet", gotIt);
+        return getObject(text);
+    }
 
-        UiObject notification = getVisibleObject(text);
-
+    /**
+     * Opens the system notification and clicks a given notification.
+     *
+     * @param text Notificaton's text as displayed by the UI.
+     */
+    public void clickOnNotification(String text) {
+        UiObject notification = getNotification(text);
         click(notification, "bug report notification");
     }
 
     /**
-     * Gets an object which is guaranteed to be present in the current UI.\
+     * Gets an object that might not yet be available in current UI.
+     *
+     * @param text Object's text as displayed by the UI.
+     */
+    public UiObject getObject(String text) {
+        boolean gotIt = mDevice.wait(Until.hasObject(By.text(text)), mTimeout);
+        assertTrue("object with text '(" + text + "') not visible yet", gotIt);
+        return getVisibleObject(text);
+    }
+
+    /**
+     * Gets an object which is guaranteed to be present in the current UI.
      *
      * @param text Object's text as displayed by the UI.
      */
     public UiObject getVisibleObject(String text) {
         UiObject uiObject = mDevice.findObject(new UiSelector().text(text));
-        assertTrue("could not find object with text '(" + text + "')", uiObject.exists());
+        assertTrue("could not find object with text '" + text + "'", uiObject.exists());
         return uiObject;
     }
 
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 6fda2c6..2f79adf 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -87,6 +87,7 @@
     <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
     <uses-permission android:name="android.permission.READ_FRAME_BUFFER" />
     <uses-permission android:name="android.permission.MANAGE_APP_TOKENS" />
+    <uses-permission android:name="android.permission.REGISTER_WINDOW_MANAGER_LISTENERS" />
     <uses-permission android:name="android.permission.SET_ORIENTATION" />
     <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
 
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index c85ada8..82192fe 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -214,9 +214,6 @@
     <!-- The amount to offset when animating into an affiliate group. -->
     <dimen name="recents_task_view_affiliate_group_enter_offset">64dp</dimen>
 
-    <!-- The alpha to apply to a task thumbnail. -->
-    <item name="recents_task_view_thumbnail_alpha" format="float" type="dimen">0.9</item>
-
     <!-- The height of a task view bar. -->
     <dimen name="recents_task_bar_height">56dp</dimen>
 
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
index 94c45a4..a0c481a 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
@@ -724,18 +724,6 @@
         getResizeTaskDebugDialog().showResizeTaskDialog(event.task, mRecentsView);
     }
 
-    public final void onBusEvent(DragStartEvent event) {
-        // Lock the orientation while dragging
-        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LOCKED);
-
-        // TODO: docking requires custom accessibility actions
-    }
-
-    public final void onBusEvent(DragEndEvent event) {
-        // Unlock the orientation when dragging completes
-        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_BEHIND);
-    }
-
     public final void onBusEvent(LaunchTaskSucceededEvent event) {
         MetricsLogger.histogram(this, "overview_task_launch_index", event.taskIndexFromStackFront);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragEndEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragEndEvent.java
index 957da94..3deeb47 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragEndEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragEndEvent.java
@@ -19,7 +19,6 @@
 import com.android.systemui.recents.events.EventBus;
 import com.android.systemui.recents.misc.ReferenceCountedTrigger;
 import com.android.systemui.recents.model.Task;
-import com.android.systemui.recents.views.DragView;
 import com.android.systemui.recents.views.DropTarget;
 import com.android.systemui.recents.views.TaskView;
 
@@ -30,15 +29,13 @@
 
     public final Task task;
     public final TaskView taskView;
-    public final DragView dragView;
     public final DropTarget dropTarget;
     public final ReferenceCountedTrigger postAnimationTrigger;
 
-    public DragEndEvent(Task task, TaskView taskView, DragView dragView, DropTarget dropTarget,
+    public DragEndEvent(Task task, TaskView taskView, DropTarget dropTarget,
             ReferenceCountedTrigger postAnimationTrigger) {
         this.task = task;
         this.taskView = taskView;
-        this.dragView = dragView;
         this.dropTarget = dropTarget;
         this.postAnimationTrigger = postAnimationTrigger;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragStartEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragStartEvent.java
index 2d42a0e..b81c10c 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragStartEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragStartEvent.java
@@ -16,9 +16,9 @@
 
 package com.android.systemui.recents.events.ui.dragndrop;
 
+import android.graphics.Point;
 import com.android.systemui.recents.events.EventBus;
 import com.android.systemui.recents.model.Task;
-import com.android.systemui.recents.views.DragView;
 import com.android.systemui.recents.views.TaskView;
 
 /**
@@ -28,11 +28,11 @@
 
     public final Task task;
     public final TaskView taskView;
-    public final DragView dragView;
+    public final Point tlOffset;
 
-    public DragStartEvent(Task task, TaskView taskView, DragView dragView) {
+    public DragStartEvent(Task task, TaskView taskView, Point tlOffset) {
         this.task = task;
         this.taskView = taskView;
-        this.dragView = dragView;
+        this.tlOffset = tlOffset;
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
index de40a37..2b20c07 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
@@ -56,6 +56,7 @@
 import android.util.MutableBoolean;
 import android.util.Pair;
 import android.view.Display;
+import android.view.IDockDividerVisibilityListener;
 import android.view.WindowManager;
 import android.view.WindowManagerGlobal;
 import android.view.accessibility.AccessibilityManager;
@@ -409,7 +410,7 @@
             return thumbnail;
         }
 
-        Bitmap thumbnail = SystemServicesProxy.getThumbnail(mAm, taskId);
+        Bitmap thumbnail = getThumbnail(taskId);
         if (thumbnail != null) {
             thumbnail.setHasAlpha(false);
             // We use a dumb heuristic for now, if the thumbnail is purely transparent in the top
@@ -429,8 +430,12 @@
     /**
      * Returns a task thumbnail from the activity manager
      */
-    public static Bitmap getThumbnail(ActivityManager activityManager, int taskId) {
-        ActivityManager.TaskThumbnail taskThumbnail = activityManager.getTaskThumbnail(taskId);
+    public Bitmap getThumbnail(int taskId) {
+        if (mAm == null) {
+            return null;
+        }
+
+        ActivityManager.TaskThumbnail taskThumbnail = mAm.getTaskThumbnail(taskId);
         if (taskThumbnail == null) return null;
 
         Bitmap thumbnail = taskThumbnail.mainThumbnail;
@@ -847,4 +852,15 @@
             e.printStackTrace();
         }
     }
+
+    public void registerDockDividerVisibilityListener(IDockDividerVisibilityListener listener) {
+        if (mWm == null) return;
+
+        try {
+            WindowManagerGlobal.getWindowManagerService().registerDockDividerVisibilityListener(
+                    listener);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
index 5921d13..3bb89a3 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
@@ -128,7 +128,7 @@
             boolean isStackTask = true;
             if (debugFlags.isHistoryEnabled()) {
                 boolean isFreeformTask = SystemServicesProxy.isFreeformStack(t.stackId);
-                isStackTask = !isFreeformTask && (!isHistoricalTask(t) ||
+                isStackTask = isFreeformTask || (!isHistoricalTask(t) ||
                         (t.lastActiveTime >= lastStackActiveTime &&
                                 i >= (taskCount - MIN_NUM_TASKS)));
                 if (isStackTask && newLastStackActiveTime < 0) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/AnimateableViewBounds.java b/packages/SystemUI/src/com/android/systemui/recents/views/AnimateableViewBounds.java
index 945fdc1..271a2a0 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/AnimateableViewBounds.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/AnimateableViewBounds.java
@@ -16,7 +16,6 @@
 
 package com.android.systemui.recents.views;
 
-import android.animation.ObjectAnimator;
 import android.graphics.Outline;
 import android.graphics.Rect;
 import android.util.IntProperty;
@@ -65,6 +64,15 @@
         mCornerRadius = cornerRadius;
     }
 
+    /**
+     * Resets the right and bottom clip for this view.
+     */
+    public void reset() {
+        mClipRect.setEmpty();
+        mSourceView.invalidateOutline();
+        updateClipBounds();
+    }
+
     @Override
     public void getOutline(View view, Outline outline) {
         outline.setAlpha(mMinAlpha + mAlpha / (1f - mMinAlpha));
@@ -82,15 +90,6 @@
         }
     }
 
-    /**
-     * Animates the bottom clip.
-     */
-    public void animateClipBottom(int bottom) {
-        ObjectAnimator animator = ObjectAnimator.ofInt(this, CLIP_BOTTOM, getClipBottom(), bottom);
-        animator.setDuration(150);
-        animator.start();
-    }
-
     /** Sets the bottom clip. */
     public void setClipBottom(int bottom, boolean force) {
         if (bottom != mClipRect.bottom || force) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/DragView.java b/packages/SystemUI/src/com/android/systemui/recents/views/DragView.java
deleted file mode 100644
index 96dfaac..0000000
--- a/packages/SystemUI/src/com/android/systemui/recents/views/DragView.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recents.views;
-
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.Point;
-import android.widget.ImageView;
-
-public class DragView extends ImageView {
-
-    // The offset from the top-left of the dragBitmap
-    Point mTopLeftOffset;
-
-    public DragView(Context context, Bitmap dragBitmap, Point tlOffset) {
-        super(context);
-
-        mTopLeftOffset = tlOffset;
-        setImageBitmap(dragBitmap);
-    }
-
-    public Point getTopLeftOffset() {
-        return mTopLeftOffset;
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/FreeformWorkspaceLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/FreeformWorkspaceLayoutAlgorithm.java
index 90d62c1..a70b66d 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/FreeformWorkspaceLayoutAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/FreeformWorkspaceLayoutAlgorithm.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.recents.views;
 
+import android.graphics.Rect;
 import android.util.Log;
 import com.android.systemui.recents.misc.Utilities;
 import com.android.systemui.recents.model.Task;
@@ -52,8 +53,8 @@
         int numFreeformTasks = stackLayout.mNumFreeformTasks;
         if (!freeformTasks.isEmpty()) {
             // Calculate the cell width/height depending on the number of freeform tasks
-            mFreeformCellXCount = Math.max(2, (int) Math.ceil(Math.sqrt(numFreeformTasks)));
-            mFreeformCellYCount = Math.max(2, (int) Math.ceil((float) numFreeformTasks /
+            mFreeformCellXCount = Math.max(1, (int) Math.ceil(Math.sqrt(numFreeformTasks)));
+            mFreeformCellYCount = Math.max(1, (int) Math.ceil((float) numFreeformTasks /
                     mFreeformCellXCount));
             // For now, make the cells square
             mFreeformCellWidth = Math.min(stackLayout.mFreeformRect.width() / mFreeformCellXCount,
@@ -94,15 +95,21 @@
     public TaskViewTransform getTransform(Task task, TaskViewTransform transformOut,
             TaskStackLayoutAlgorithm stackLayout) {
         if (mTaskIndexMap.containsKey(task.key)) {
-            // This is a freeform task, so lay it out in the freeform workspace
+            Rect taskRect = stackLayout.mTaskRect;
             int taskIndex = mTaskIndexMap.get(task.key);
-            int topOffset = (stackLayout.mFreeformRect.top - stackLayout.mTaskRect.top);
+            int topOffset = (stackLayout.mFreeformRect.top - taskRect.top);
             int x = taskIndex % mFreeformCellXCount;
             int y = taskIndex / mFreeformCellXCount;
-            float scale = (float) mFreeformCellWidth / stackLayout.mTaskRect.width();
-            int scaleXOffset = (int) (((1f - scale) * stackLayout.mTaskRect.width()) / 2);
-            int scaleYOffset = (int) (((1f - scale) * stackLayout.mTaskRect.height()) / 2);
-            transformOut.scale = scale * 0.9f;
+
+            int bitmapWidth = task.thumbnail.getWidth();
+            int bitmapHeight = task.thumbnail.getHeight();
+            float thumbnailScale = Math.min((float) mFreeformCellWidth / bitmapWidth,
+                    (float) mFreeformCellHeight / bitmapHeight);
+            float thumbnailWidth = bitmapWidth * thumbnailScale;
+            float thumbnailHeight = bitmapHeight * thumbnailScale;
+            int scaleXOffset = (int) (((1f - thumbnailScale) * thumbnailWidth) / 2);
+            int scaleYOffset = (int) (((1f - thumbnailScale) * thumbnailHeight) / 2);
+            transformOut.scale = thumbnailScale * 0.9f;
             transformOut.translationX = x * mFreeformCellWidth - scaleXOffset;
             transformOut.translationY = topOffset + y * mFreeformCellHeight - scaleYOffset;
             transformOut.translationZ = stackLayout.mMaxTranslationZ;
@@ -115,7 +122,6 @@
             if (DEBUG) {
                 Log.d(TAG, "getTransform: " + task.key + ", " + transformOut);
             }
-
             return transformOut;
         }
         return null;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
index 0557f65..5616018 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
@@ -81,7 +81,6 @@
 
     RecentsTransitionHelper mTransitionHelper;
     RecentsViewTouchHandler mTouchHandler;
-    DragView mDragView;
     TaskStack.DockState[] mVisibleDockStates = {
             TaskStack.DockState.LEFT,
             TaskStack.DockState.TOP,
@@ -341,13 +340,6 @@
             mTaskStackView.measure(widthMeasureSpec, heightMeasureSpec);
         }
 
-        if (mDragView != null) {
-            Rect taskRect = mTaskStackView.mLayoutAlgorithm.mTaskRect;
-            mDragView.measure(
-                    MeasureSpec.makeMeasureSpec(taskRect.width(), MeasureSpec.AT_MOST),
-                    MeasureSpec.makeMeasureSpec(taskRect.height(), MeasureSpec.AT_MOST));
-        }
-
         // Measure the history button with the full space above the stack, but width-constrained
         // to the stack
         Rect historyButtonRect = mTaskStackView.mLayoutAlgorithm.mHistoryButtonRect;
@@ -379,11 +371,6 @@
             mTaskStackView.layout(left, top, left + getMeasuredWidth(), top + getMeasuredHeight());
         }
 
-        if (mDragView != null) {
-            mDragView.layout(left, top, left + mDragView.getMeasuredWidth(),
-                    top + mDragView.getMeasuredHeight());
-        }
-
         // Layout the history button left-aligned with the stack, but offset from the top of the
         // view
         Rect historyButtonRect = mTaskStackView.mLayoutAlgorithm.mHistoryButtonRect;
@@ -461,10 +448,6 @@
     /**** EventBus Events ****/
 
     public final void onBusEvent(DragStartEvent event) {
-        // Add the drag view
-        mDragView = event.dragView;
-        addView(mDragView);
-
         updateVisibleDockRegions(mTouchHandler.getDockStatesForCurrentOrientation(),
                 TaskStack.DockState.NONE.viewState.dockAreaAlpha);
     }
@@ -480,65 +463,17 @@
     }
 
     public final void onBusEvent(final DragEndEvent event) {
-        final Runnable cleanUpRunnable = new Runnable() {
-            @Override
-            public void run() {
-                // Remove the drag view
-                removeView(mDragView);
-                mDragView = null;
-            }
-        };
-
         // Animate the overlay alpha back to 0
         updateVisibleDockRegions(null, -1);
 
-        if (event.dropTarget == null) {
-            // No drop targets for hit, so just animate the task back to its place
-            event.postAnimationTrigger.increment();
-            event.postAnimationTrigger.addLastDecrementRunnable(new Runnable() {
-                @Override
-                public void run() {
-                    cleanUpRunnable.run();
-                }
-            });
-            // Animate the task back to where it was before then clean up afterwards
-            TaskViewTransform taskTransform = new TaskViewTransform();
-            TaskStackLayoutAlgorithm layoutAlgorithm = mTaskStackView.getStackAlgorithm();
-            layoutAlgorithm.getStackTransform(event.task,
-                    mTaskStackView.getScroller().getStackScroll(), taskTransform, null);
-            event.dragView.animate()
-                    .scaleX(taskTransform.scale)
-                    .scaleY(taskTransform.scale)
-                    .translationX((layoutAlgorithm.mTaskRect.left - event.dragView.getLeft())
-                            + taskTransform.translationX)
-                    .translationY((layoutAlgorithm.mTaskRect.top - event.dragView.getTop())
-                            + taskTransform.translationY)
-                    .setDuration(175)
-                    .setInterpolator(mFastOutSlowInInterpolator)
-                    .withEndAction(event.postAnimationTrigger.decrementAsRunnable())
-                    .start();
-
-        } else if (event.dropTarget instanceof TaskStack.DockState) {
+        // Handle the case where we drop onto a dock region
+        if (event.dropTarget instanceof TaskStack.DockState) {
             final TaskStack.DockState dockState = (TaskStack.DockState) event.dropTarget;
 
-            // For now, just remove the drag view and the original task
-            // TODO: Animate the task to the drop target rect before launching it above
-            cleanUpRunnable.run();
-
             // Dock the task and launch it
             SystemServicesProxy ssp = Recents.getSystemServices();
             ssp.startTaskInDockedMode(event.task.key.id, dockState.createMode);
             launchTask(event.task, null, INVALID_STACK_ID);
-
-        } else {
-            // We dropped on another drop target, so just add the cleanup to the post animation
-            // trigger
-            event.postAnimationTrigger.addLastDecrementRunnable(new Runnable() {
-                @Override
-                public void run() {
-                    cleanUpRunnable.run();
-                }
-            });
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewTouchHandler.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewTouchHandler.java
index c7edc92..c55f383 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewTouchHandler.java
@@ -64,8 +64,8 @@
 
     private Task mDragTask;
     private TaskView mTaskView;
-    private DragView mDragView;
 
+    private Point mTaskViewOffset = new Point();
     private Point mDownPos = new Point();
     private boolean mDragging;
 
@@ -115,13 +115,16 @@
         mDragging = true;
         mDragTask = event.task;
         mTaskView = event.taskView;
-        mDragView = event.dragView;
         mDropTargets.clear();
 
-        float x = mDownPos.x - mDragView.getTopLeftOffset().x;
-        float y = mDownPos.y - mDragView.getTopLeftOffset().y;
-        mDragView.setTranslationX(x);
-        mDragView.setTranslationY(y);
+        int[] recentsViewLocation = new int[2];
+        mRv.getLocationInWindow(recentsViewLocation);
+        mTaskViewOffset.set(mTaskView.getLeft() - recentsViewLocation[0] + event.tlOffset.x,
+                mTaskView.getTop() - recentsViewLocation[1] + event.tlOffset.y);
+        float x = mDownPos.x - mTaskViewOffset.x;
+        float y = mDownPos.y - mTaskViewOffset.y;
+        mTaskView.setTranslationX(x);
+        mTaskView.setTranslationY(y);
 
         RecentsConfiguration config = Recents.getConfiguration();
         if (!config.hasDockedTasks) {
@@ -140,7 +143,6 @@
         mDragging = false;
         mDragTask = null;
         mTaskView = null;
-        mDragView = null;
         mLastDropTarget = null;
     }
 
@@ -160,8 +162,8 @@
                     int height = mRv.getMeasuredHeight();
                     float evX = ev.getX();
                     float evY = ev.getY();
-                    float x = evX - mDragView.getTopLeftOffset().x;
-                    float y = evY - mDragView.getTopLeftOffset().y;
+                    float x = evX - mTaskViewOffset.x;
+                    float y = evY - mTaskViewOffset.y;
 
                     DropTarget currentDropTarget = null;
                     for (DropTarget target : mDropTargets) {
@@ -176,8 +178,8 @@
                                 currentDropTarget));
                     }
 
-                    mDragView.setTranslationX(x);
-                    mDragView.setTranslationY(y);
+                    mTaskView.setTranslationX(x);
+                    mTaskView.setTranslationY(y);
                 }
                 break;
             }
@@ -187,7 +189,7 @@
                     ReferenceCountedTrigger postAnimationTrigger = new ReferenceCountedTrigger(
                             mRv.getContext(), null, null, null);
                     postAnimationTrigger.increment();
-                    EventBus.getDefault().send(new DragEndEvent(mDragTask, mTaskView, mDragView,
+                    EventBus.getDefault().send(new DragEndEvent(mDragTask, mTaskView,
                             mLastDropTarget, postAnimationTrigger));
                     postAnimationTrigger.decrement();
                     break;
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 30efd5f..c02eaf8 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -16,6 +16,10 @@
 
 package com.android.systemui.recents.views;
 
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
 import android.animation.ValueAnimator;
 import android.content.ComponentName;
 import android.content.Context;
@@ -1420,7 +1424,12 @@
         }
     }
 
+    private AnimatorSet mDropAnimation;
+
     public final void onBusEvent(DragStartEvent event) {
+        // Cancel the existing drop animation
+        Utilities.cancelAnimationWithoutCallbacks(mDropAnimation);
+
         if (event.task.isFreeformTask()) {
             // Animate to the front of the stack
             mStackScroller.animateScroll(mStackScroller.getStackScroll(),
@@ -1441,56 +1450,84 @@
     }
 
     public final void onBusEvent(final DragEndEvent event) {
-        if (event.dropTarget != mFreeformWorkspaceDropTarget &&
-                event.dropTarget != mStackDropTarget) {
-            return;
-        }
-        if (event.task.isFreeformTask() && event.dropTarget == mFreeformWorkspaceDropTarget) {
-            // TODO: Animate back into view
-            return;
-        }
-        if (!event.task.isFreeformTask() && event.dropTarget == mStackDropTarget) {
-            // TODO: Animate back into view
+        // We don't handle drops on the dock regions
+        if (event.dropTarget instanceof TaskStack.DockState) {
             return;
         }
 
-        // Move the task to the right position in the stack (ie. the front of the stack if freeform
-        // or the front of the stack if fullscreen).  Note, we MUST move the tasks before we update
-        // their stack ids, otherwise, the keys will have changed.
-        if (event.dropTarget == mFreeformWorkspaceDropTarget) {
-            mStack.moveTaskToStack(event.task, FREEFORM_WORKSPACE_STACK_ID);
-            updateLayout(true);
-        } else if (event.dropTarget == mStackDropTarget) {
-            mStack.moveTaskToStack(event.task, FULLSCREEN_WORKSPACE_STACK_ID);
-            updateLayout(true);
+        boolean isFreeformTask = event.task.isFreeformTask();
+        boolean hasChangedStacks =
+                (!isFreeformTask && event.dropTarget == mFreeformWorkspaceDropTarget) ||
+                        (isFreeformTask && event.dropTarget == mStackDropTarget);
+        if (hasChangedStacks) {
+            ArrayList<Animator> animations = new ArrayList<>();
+
+            // Move the task to the right position in the stack (ie. the front of the stack if
+            // freeform or the front of the stack if fullscreen).  Note, we MUST move the tasks
+            // before we update their stack ids, otherwise, the keys will have changed.
+            if (event.dropTarget == mFreeformWorkspaceDropTarget) {
+                mStack.moveTaskToStack(event.task, FREEFORM_WORKSPACE_STACK_ID);
+                updateLayout(true);
+
+                // Update the clipping to match the scaled bitmap rect
+                TaskViewThumbnail thumbnailView = event.taskView.mThumbnailView;
+                float thumbnailScale = thumbnailView.computeThumbnailScale(true);
+                RectF bitmapRect = thumbnailView.getScaledBitmapRect(thumbnailScale);
+                AnimateableViewBounds viewBounds = event.taskView.getViewBounds();
+                int clipRight = (int) (thumbnailView.getMeasuredWidth() - bitmapRect.width());
+                int clipBottom = (int) (thumbnailView.getMeasuredHeight() - bitmapRect.height());
+                animations.add(ObjectAnimator.ofFloat(thumbnailView, TaskViewThumbnail.BITMAP_SCALE,
+                        thumbnailView.getBitmapScale(), thumbnailScale));
+                animations.add(ObjectAnimator.ofInt(viewBounds, AnimateableViewBounds.CLIP_BOTTOM,
+                        viewBounds.getClipBottom(), clipBottom));
+                animations.add(ObjectAnimator.ofInt(viewBounds, AnimateableViewBounds.CLIP_RIGHT,
+                        viewBounds.getClipRight(), clipRight));
+            } else if (event.dropTarget == mStackDropTarget) {
+                mStack.moveTaskToStack(event.task, FULLSCREEN_WORKSPACE_STACK_ID);
+                updateLayout(true);
+
+                // Reset the clipping when animating to the stack
+                TaskViewThumbnail thumbnailView = event.taskView.mThumbnailView;
+                float thumbnailScale = thumbnailView.computeThumbnailScale(false);
+                AnimateableViewBounds viewBounds = event.taskView.getViewBounds();
+                animations.add(ObjectAnimator.ofFloat(thumbnailView, TaskViewThumbnail.BITMAP_SCALE,
+                        thumbnailView.getBitmapScale(), thumbnailScale));
+                animations.add(ObjectAnimator.ofInt(viewBounds, AnimateableViewBounds.CLIP_BOTTOM,
+                        viewBounds.getClipBottom(), 0));
+                animations.add(ObjectAnimator.ofInt(viewBounds, AnimateableViewBounds.CLIP_RIGHT,
+                        viewBounds.getClipRight(), 0));
+            }
+
+            // Move the task to the new stack in the system after the animation completes
+            event.postAnimationTrigger.increment();
+            event.postAnimationTrigger.addLastDecrementRunnable(new Runnable() {
+                @Override
+                public void run() {
+                    SystemServicesProxy ssp = Recents.getSystemServices();
+                    ssp.moveTaskToStack(event.task.key.id, event.task.key.stackId);
+                }
+            });
+
+            // Animate the normal properties of the view
+            mDropAnimation = new AnimatorSet();
+            mDropAnimation.playTogether(animations);
+            mDropAnimation.setDuration(250);
+            mDropAnimation.setInterpolator(mFastOutSlowInInterpolator);
+            mDropAnimation.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    event.postAnimationTrigger.decrement();
+                }
+            });
+            mDropAnimation.start();
         }
 
         event.postAnimationTrigger.increment();
-        event.postAnimationTrigger.addLastDecrementRunnable(new Runnable() {
-            @Override
-            public void run() {
-                SystemServicesProxy ssp = Recents.getSystemServices();
-                ssp.moveTaskToStack(event.task.key.id, event.task.key.stackId);
-            }
-        });
+        event.taskView.animate()
+                .withEndAction(event.postAnimationTrigger.decrementAsRunnable());
 
-        // Animate the drag view to the new position
-        mLayoutAlgorithm.getStackTransform(event.task, mStackScroller.getStackScroll(),
-                mTmpTransform, null);
-        event.dragView.animate()
-                .scaleX(mTmpTransform.scale)
-                .scaleY(mTmpTransform.scale)
-                .translationX((mLayoutAlgorithm.mTaskRect.left - event.dragView.getLeft())
-                        + mTmpTransform.translationX)
-                .translationY((mLayoutAlgorithm.mTaskRect.top - event.dragView.getTop())
-                        + mTmpTransform.translationY)
-                .setDuration(175)
-                .setInterpolator(mFastOutSlowInInterpolator)
-                .withEndAction(event.postAnimationTrigger.decrementAsRunnable())
-                .start();
-
-        // Animate the other views into place
-        requestSynchronizeStackViewsWithModel(175);
+        // Animate the tack view back into position
+        requestSynchronizeStackViewsWithModel(250);
     }
 
     public final void onBusEvent(StackViewScrolledEvent event) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
index 6db2eb9..fb84a22 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
@@ -22,8 +22,6 @@
 import android.animation.ValueAnimator;
 import android.content.Context;
 import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.Outline;
 import android.graphics.Paint;
@@ -244,6 +242,8 @@
     void resetViewProperties() {
         setDim(0);
         setLayerType(View.LAYER_TYPE_NONE, null);
+        setVisibility(View.VISIBLE);
+        getViewBounds().reset();
         TaskViewTransform.reset(this);
         if (mActionButtonView != null) {
             mActionButtonView.setScaleX(1f);
@@ -251,18 +251,6 @@
             mActionButtonView.setAlpha(1f);
             mActionButtonView.setTranslationZ(mActionButtonTranslationZ);
         }
-        setVisibility(View.VISIBLE);
-    }
-
-    /**
-     * When we are un/filtering, this method will set up the transform that we are animating to,
-     * in order to hide the task.
-     */
-    void prepareTaskTransformForFilterTaskHidden(TaskViewTransform toTransform) {
-        // Fade the view out and slide it away
-        toTransform.alpha = 0f;
-        toTransform.translationY += 200;
-        toTransform.translationZ = 0;
     }
 
     /** Prepares this task view for the enter-recents animations.  This is called earlier in the
@@ -296,8 +284,6 @@
         }
         // Apply the current dim
         setDim(initialDim);
-        // Prepare the thumbnail view alpha
-        mThumbnailView.prepareEnterRecentsAnimation(isTaskViewLaunchTargetTask);
     }
 
     /** Animates this task view as it enters recents */
@@ -425,9 +411,6 @@
                 R.dimen.recents_task_view_affiliate_group_enter_offset);
 
         if (isLaunchingTask) {
-            // Animate the thumbnail alpha back into full opacity for the window animation out
-            mThumbnailView.startLaunchTaskAnimation(postAnimRunnable);
-
             // Animate the dim
             if (mDimAlpha > 0) {
                 ObjectAnimator anim = ObjectAnimator.ofInt(this, "dim", 0);
@@ -637,7 +620,6 @@
         mIsFocused = isFocused;
         mIsFocusAnimated = animated;
         mHeaderView.onTaskViewFocusChanged(isFocused, animated);
-        mThumbnailView.onFocusChanged(isFocused);
         if (isFocused) {
             if (requestViewFocus && !isFocused()) {
                 requestFocus();
@@ -746,54 +728,20 @@
             // Start listening for drag events
             setClipViewInStack(false);
 
+            // Enlarge the view slightly
             final float finalScale = getScaleX() * 1.05f;
-            final int width = getWidth();
-            final int height = getHeight();
-            Bitmap dragBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
-            Canvas c = new Canvas(dragBitmap);
-            mThumbnailView.draw(c);
-            mHeaderView.draw(c);
-            c.setBitmap(null);
+            animate()
+                    .scaleX(finalScale)
+                    .scaleY(finalScale)
+                    .setDuration(175)
+                    .setInterpolator(mFastOutSlowInInterpolator)
+                    .start();
 
-            // The downTouchPos is relative to the currently transformed TaskView, but we will be
-            // dragging a copy of the full task view, which makes it easier for us to animate them
-            // when the user drops
-            mDownTouchPos.x += ((1f - getScaleX()) * width) / 2;
-            mDownTouchPos.y += ((1f - getScaleY()) * height) / 2;
+            mDownTouchPos.x += ((1f - getScaleX()) * getWidth()) / 2;
+            mDownTouchPos.y += ((1f - getScaleY()) * getHeight()) / 2;
 
-            // Initiate the drag
-            final DragView dragView = new DragView(getContext(), dragBitmap, mDownTouchPos);
-            dragView.setOutlineProvider(new ViewOutlineProvider() {
-                @Override
-                public void getOutline(View view, Outline outline) {
-                    outline.setRect(0, 0, width, height);
-                }
-            });
-            dragView.setScaleX(getScaleX());
-            dragView.setScaleY(getScaleY());
-            dragView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
-                @Override
-                public void onViewAttachedToWindow(View v) {
-                    // Hide this task view after the drag view is attached
-                    setVisibility(View.INVISIBLE);
-                    // Animate the alpha slightly to indicate dragging
-                    dragView.setElevation(getElevation());
-                    dragView.setTranslationZ(getTranslationZ());
-                    dragView.animate()
-                            .scaleX(finalScale)
-                            .scaleY(finalScale)
-                            .setDuration(175)
-                            .setInterpolator(mFastOutSlowInInterpolator)
-                            .start();
-                }
-
-                @Override
-                public void onViewDetachedFromWindow(View v) {
-                    // Do nothing
-                }
-            });
             EventBus.getDefault().register(this, RecentsActivity.EVENT_BUS_PRIORITY + 1);
-            EventBus.getDefault().send(new DragStartEvent(mTask, this, dragView));
+            EventBus.getDefault().send(new DragStartEvent(mTask, this, mDownTouchPos));
             return true;
         }
         return false;
@@ -806,9 +754,6 @@
             event.postAnimationTrigger.addLastDecrementRunnable(new Runnable() {
                 @Override
                 public void run() {
-                    // Show this task view
-                    setVisibility(View.VISIBLE);
-
                     // Animate the drag view back from where it is, to the view location, then after
                     // it returns, update the clip state
                     setClipViewInStack(true);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java
index b3d263e..c288afb 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java
@@ -16,9 +16,6 @@
 
 package com.android.systemui.recents.views;
 
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ValueAnimator;
 import android.content.Context;
 import android.graphics.Bitmap;
 import android.graphics.BitmapShader;
@@ -31,11 +28,12 @@
 import android.graphics.RectF;
 import android.graphics.Shader;
 import android.util.AttributeSet;
+import android.util.FloatProperty;
+import android.util.Property;
 import android.view.View;
 import android.view.animation.AnimationUtils;
 import android.view.animation.Interpolator;
 import com.android.systemui.R;
-import com.android.systemui.recents.misc.Utilities;
 import com.android.systemui.recents.model.Task;
 
 
@@ -45,6 +43,19 @@
  */
 public class TaskViewThumbnail extends View {
 
+    public static final Property<TaskViewThumbnail, Float> BITMAP_SCALE =
+            new FloatProperty<TaskViewThumbnail>("bitmapScale") {
+                @Override
+                public void setValue(TaskViewThumbnail object, float scale) {
+                    object.setBitmapScale(scale);
+                }
+
+                @Override
+                public Float get(TaskViewThumbnail object) {
+                    return object.getBitmapScale();
+                }
+            };
+
     private Task mTask;
 
     // Drawing
@@ -60,18 +71,6 @@
 
     Interpolator mFastOutSlowInInterpolator;
 
-    // Thumbnail alpha
-    float mThumbnailAlpha;
-    ValueAnimator mThumbnailAlphaAnimator;
-    ValueAnimator.AnimatorUpdateListener mThumbnailAlphaUpdateListener
-            = new ValueAnimator.AnimatorUpdateListener() {
-        @Override
-        public void onAnimationUpdate(ValueAnimator animation) {
-            mThumbnailAlpha = (float) animation.getAnimatedValue();
-            updateThumbnailPaintFilter();
-        }
-    };
-
     // Task bar clipping, the top of this thumbnail can be clipped against the opaque header
     // bar that overlaps this thumbnail
     View mTaskBar;
@@ -105,17 +104,11 @@
     }
 
     @Override
-    protected void onFinishInflate() {
-        mThumbnailAlpha = getResources().getFloat(R.dimen.recents_task_view_thumbnail_alpha);
-        updateThumbnailPaintFilter();
-    }
-
-    @Override
     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
         super.onLayout(changed, left, top, right, bottom);
         if (changed) {
             mLayoutRect.set(0, 0, getWidth(), getHeight());
-            updateThumbnailScale();
+            setBitmapScale(computeThumbnailScale(mTask != null ? mTask.isFreeformTask() : false));
         }
     }
 
@@ -137,12 +130,15 @@
                     Shader.TileMode.CLAMP);
             mDrawPaint.setShader(mBitmapShader);
             mBitmapRect.set(0, 0, bm.getWidth(), bm.getHeight());
-            updateThumbnailScale();
         } else {
             mBitmapShader = null;
             mDrawPaint.setShader(null);
         }
-        updateThumbnailPaintFilter();
+        if (mTask != null) {
+            setBitmapScale(computeThumbnailScale(mTask != null ? mTask.isFreeformTask() : false));
+        } else {
+            setBitmapScale(1f);
+        }
     }
 
     /** Updates the paint to draw the thumbnail. */
@@ -150,36 +146,61 @@
         if (mInvisible) {
             return;
         }
-        int mul = (int) ((1.0f - mDimAlpha) * mThumbnailAlpha * 255);
-        int add = (int) ((1.0f - mDimAlpha) * (1 - mThumbnailAlpha) * 255);
+        int mul = (int) ((1.0f - mDimAlpha) * 255);
         if (mBitmapShader != null) {
             mLightingColorFilter.setColorMultiply(Color.argb(255, mul, mul, mul));
-            mLightingColorFilter.setColorAdd(Color.argb(0, add, add, add));
             mDrawPaint.setColorFilter(mLightingColorFilter);
             mDrawPaint.setColor(0xffffffff);
         } else {
-            int grey = mul + add;
+            int grey = mul;
             mDrawPaint.setColorFilter(null);
             mDrawPaint.setColor(Color.argb(255, grey, grey, grey));
         }
         invalidate();
     }
 
-    /** Updates the thumbnail shader's scale transform. */
-    void updateThumbnailScale() {
-        if (mBitmapShader != null) {
-            if (mTask.isFreeformTask()) {
-                // For freeform tasks, we scale the bitmap rect to fit in the layout rect
-                mBitmapScale = Math.min(mLayoutRect.width() / mBitmapRect.width(),
-                        mLayoutRect.height() / mBitmapRect.height());
-            } else {
-                // For stack tasks, we scale the bitmap to fit the width
-                mBitmapScale = Math.max(1f, mLayoutRect.width() / mBitmapRect.width());
-            }
+    /**
+     * Returns the scale to apply to a thumbnail bitmap relative to this view rect.
+     */
+    public float computeThumbnailScale(boolean isFreeformTask) {
+        if (isFreeformTask) {
+            // For freeform tasks, we scale the bitmap rect to fit in the layout rect
+            return Math.min(mLayoutRect.width() / mBitmapRect.width(),
+                    mLayoutRect.height() / mBitmapRect.height());
+        } else {
+            // For stack tasks, we scale the bitmap to fit the width
+            return Math.max(1f, mLayoutRect.width() / mBitmapRect.width());
+        }
+    }
 
+    /**
+     * Returns the scaled bitmap rect.
+     */
+    public RectF getScaledBitmapRect(float scale) {
+        RectF scaledBitmapRect = new RectF(mBitmapRect);
+        scaledBitmapRect.left *= scale;
+        scaledBitmapRect.top *= scale;
+        scaledBitmapRect.right *= scale;
+        scaledBitmapRect.bottom *= scale;
+        return scaledBitmapRect;
+    }
+
+    /**
+     * Sets the scale of the bitmap relative to this view.
+     */
+    public void setBitmapScale(float scale) {
+        if (mBitmapShader != null) {
+            mBitmapScale = scale;
             mScaleMatrix.setScale(mBitmapScale, mBitmapScale);
             mBitmapShader.setLocalMatrix(mScaleMatrix);
         }
+        if (!mInvisible) {
+            invalidate();
+        }
+    }
+
+    public float getBitmapScale() {
+        return mBitmapScale;
     }
 
     /** Updates the clip rect based on the given task bar. */
@@ -227,67 +248,4 @@
         mTask = null;
         setThumbnail(null);
     }
-
-    /** Handles focus changes. */
-    void onFocusChanged(boolean focused) {
-        if (focused) {
-            if (Float.compare(getAlpha(), 1f) != 0) {
-                startFadeAnimation(1f, 150, null);
-            }
-        } else {
-            float taskViewThumbnailAlpha = getResources().getFloat(
-                    R.dimen.recents_task_view_thumbnail_alpha);
-            if (Float.compare(getAlpha(), taskViewThumbnailAlpha) != 0) {
-                startFadeAnimation(taskViewThumbnailAlpha, 150, null);
-            }
-        }
-    }
-
-    /**
-     * Prepares for the enter recents animation, this gets called before the the view
-     * is first visible and will be followed by a startEnterRecentsAnimation() call.
-     */
-    void prepareEnterRecentsAnimation(boolean isTaskViewLaunchTargetTask) {
-        if (isTaskViewLaunchTargetTask) {
-            mThumbnailAlpha = 1f;
-        } else {
-            mThumbnailAlpha = getResources().getFloat(
-                    R.dimen.recents_task_view_thumbnail_alpha);
-        }
-        updateThumbnailPaintFilter();
-    }
-
-    /** Animates this task thumbnail as it enters Recents. */
-    void startEnterRecentsAnimation(Runnable postAnimRunnable) {
-        float taskViewThumbnailAlpha = getResources().getFloat(
-                R.dimen.recents_task_view_thumbnail_alpha);
-        startFadeAnimation(taskViewThumbnailAlpha,
-                getResources().getInteger(R.integer.recents_task_enter_from_app_duration),
-                postAnimRunnable);
-    }
-
-    /** Animates this task thumbnail as it exits Recents. */
-    void startLaunchTaskAnimation(Runnable postAnimRunnable) {
-        int taskViewExitToAppDuration = mContext.getResources().getInteger(
-                R.integer.recents_task_exit_to_app_duration);
-        startFadeAnimation(1f, taskViewExitToAppDuration, postAnimRunnable);
-    }
-
-    /** Starts a new thumbnail alpha animation. */
-    void startFadeAnimation(float finalAlpha, int duration, final Runnable postAnimRunnable) {
-        Utilities.cancelAnimationWithoutCallbacks(mThumbnailAlphaAnimator);
-        mThumbnailAlphaAnimator = ValueAnimator.ofFloat(mThumbnailAlpha, finalAlpha);
-        mThumbnailAlphaAnimator.setDuration(duration);
-        mThumbnailAlphaAnimator.setInterpolator(mFastOutSlowInInterpolator);
-        mThumbnailAlphaAnimator.addUpdateListener(mThumbnailAlphaUpdateListener);
-        if (postAnimRunnable != null) {
-            mThumbnailAlphaAnimator.addListener(new AnimatorListenerAdapter() {
-                @Override
-                public void onAnimationEnd(Animator animation) {
-                    postAnimRunnable.run();
-                }
-            });
-        }
-        mThumbnailAlphaAnimator.start();
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java b/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
index 50e010f..6ff7a3e 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
@@ -17,10 +17,14 @@
 package com.android.systemui.stackdivider;
 
 import android.content.res.Configuration;
+import android.view.IDockDividerVisibilityListener;
 import android.view.LayoutInflater;
+import android.view.View;
 
 import com.android.systemui.R;
 import com.android.systemui.SystemUI;
+import com.android.systemui.recents.Recents;
+import com.android.systemui.recents.misc.SystemServicesProxy;
 
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
@@ -33,6 +37,8 @@
     private int mDividerWindowWidth;
     private DividerWindowManager mWindowManager;
     private DividerView mView;
+    private DockDividerVisibilityListener mDockDividerVisibilityListener;
+    private boolean mVisible = false;
 
     @Override
     public void start() {
@@ -41,6 +47,9 @@
                 com.android.internal.R.dimen.docked_stack_divider_thickness);
         update(mContext.getResources().getConfiguration());
         putComponent(Divider.class, this);
+        mDockDividerVisibilityListener = new DockDividerVisibilityListener();
+        SystemServicesProxy ssp = Recents.getSystemServices();
+        ssp.registerDockDividerVisibilityListener(mDockDividerVisibilityListener);
     }
 
     @Override
@@ -56,6 +65,7 @@
     private void addDivider(Configuration configuration) {
         mView = (DividerView)
                 LayoutInflater.from(mContext).inflate(R.layout.docked_stack_divider, null);
+        mView.setVisibility(mVisible ? View.VISIBLE : View.INVISIBLE);
         final boolean landscape = configuration.orientation == ORIENTATION_LANDSCAPE;
         final int width = landscape ? mDividerWindowWidth : MATCH_PARENT;
         final int height = landscape ? MATCH_PARENT : mDividerWindowWidth;
@@ -71,4 +81,23 @@
         removeDivider();
         addDivider(configuration);
     }
+
+    private void updateVisibility(final boolean visible) {
+        mView.post(new Runnable() {
+            @Override
+            public void run() {
+                if (mVisible != visible) {
+                    mVisible = visible;
+                    mView.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
+                }
+            }
+        });
+    }
+
+    class DockDividerVisibilityListener extends IDockDividerVisibilityListener.Stub {
+        @Override
+        public void onDockDividerVisibilityChanged(boolean visible) {
+            updateVisibility(visible);
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
index 93a8fd8..0917528 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
@@ -110,7 +110,7 @@
         // When enabling location, a user consent dialog will pop up, and the
         // setting won't be fully enabled until the user accepts the agreement.
         int mode = enabled
-                ? Settings.Secure.LOCATION_MODE_HIGH_ACCURACY : Settings.Secure.LOCATION_MODE_OFF;
+                ? Settings.Secure.LOCATION_MODE_PREVIOUS : Settings.Secure.LOCATION_MODE_OFF;
         // QuickSettings always runs as the owner, so specifically set the settings
         // for the current foreground user.
         return Settings.Secure
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 3d358ec..11fdbb5 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -433,7 +433,6 @@
                         AccessibilityEvent.obtain(event)).sendToTarget();
             }
             event.recycle();
-            getUserStateLocked(resolvedUserId).mHandledFeedbackTypes = 0;
         }
         return (OWN_PROCESS_ID != Binder.getCallingPid());
     }
@@ -1051,9 +1050,7 @@
                 Service service = state.mBoundServices.get(i);
 
                 if (service.mIsDefault == isDefault) {
-                    if (canDispatchEventToServiceLocked(service, event,
-                            state.mHandledFeedbackTypes)) {
-                        state.mHandledFeedbackTypes |= service.mFeedbackType;
+                    if (canDispatchEventToServiceLocked(service, event)) {
                         service.notifyAccessibilityEvent(event);
                     }
                 }
@@ -1088,19 +1085,14 @@
 
     /**
      * Determines if given event can be dispatched to a service based on the package of the
-     * event source and already notified services for that event type. Specifically, a
-     * service is notified if it is interested in events from the package and no other service
-     * providing the same feedback type has been notified. Exception are services the
-     * provide generic feedback (feedback type left as a safety net for unforeseen feedback
-     * types) which are always notified.
+     * event source. Specifically, a service is notified if it is interested in events from the
+     * package.
      *
      * @param service The potential receiver.
      * @param event The event.
-     * @param handledFeedbackTypes The feedback types for which services have been notified.
      * @return True if the listener should be notified, false otherwise.
      */
-    private boolean canDispatchEventToServiceLocked(Service service, AccessibilityEvent event,
-            int handledFeedbackTypes) {
+    private boolean canDispatchEventToServiceLocked(Service service, AccessibilityEvent event) {
 
         if (!service.canReceiveEventsLocked()) {
             return false;
@@ -1121,15 +1113,7 @@
         String packageName = (event.getPackageName() != null)
                 ? event.getPackageName().toString() : null;
 
-        if (packageNames.isEmpty() || packageNames.contains(packageName)) {
-            int feedbackType = service.mFeedbackType;
-            if ((handledFeedbackTypes & feedbackType) != feedbackType
-                    || feedbackType == AccessibilityServiceInfo.FEEDBACK_GENERIC) {
-                return true;
-            }
-        }
-
-        return false;
+        return (packageNames.isEmpty() || packageNames.contains(packageName));
     }
 
     private void unbindAllServicesLocked(UserState userState) {
@@ -3886,8 +3870,6 @@
         public final Set<ComponentName> mTouchExplorationGrantedServices =
                 new HashSet<>();
 
-        public int mHandledFeedbackTypes = 0;
-
         public int mLastSentClientState = -1;
 
         public boolean mIsAccessibilityEnabled;
@@ -3950,7 +3932,6 @@
             mBindingServices.clear();
 
             // Clear event management state.
-            mHandledFeedbackTypes = 0;
             mLastSentClientState = -1;
 
             // Clear state persisted in settings.
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index ae6874f..639753a 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -2533,7 +2533,7 @@
                 }
             }
         } else if (win.getAttrs().type == TYPE_DOCK_DIVIDER) {
-            if (transit == TRANSIT_ENTER) {
+            if (transit == TRANSIT_ENTER || transit == TRANSIT_SHOW) {
                 return R.anim.fade_in;
             } else if (transit == TRANSIT_EXIT) {
                 return R.anim.fade_out;
diff --git a/services/core/java/com/android/server/wm/DockedStackDividerController.java b/services/core/java/com/android/server/wm/DockedStackDividerController.java
index 6b62467..df8d5d6 100644
--- a/services/core/java/com/android/server/wm/DockedStackDividerController.java
+++ b/services/core/java/com/android/server/wm/DockedStackDividerController.java
@@ -18,7 +18,11 @@
 
 import android.content.Context;
 import android.graphics.Rect;
+import android.os.RemoteException;
 import android.util.Slog;
+import android.util.SparseArray;
+import android.util.SparseIntArray;
+import android.view.IDockDividerVisibilityListener;
 
 import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
 import static android.view.WindowManager.DOCKED_BOTTOM;
@@ -40,6 +44,8 @@
     private WindowState mWindow;
     private final Rect mTmpRect = new Rect();
     private final Rect mLastRect = new Rect();
+    private IDockDividerVisibilityListener mListener;
+    private boolean mLastVisibility = false;
 
     DockedStackDividerController(Context context, DisplayContent displayContent) {
         mDisplayContent = displayContent;
@@ -67,12 +73,21 @@
     }
 
     void reevaluateVisibility() {
-        if (mWindow == null) return;
+        if (mWindow == null) {
+            return;
+        }
         TaskStack stack = mDisplayContent.mService.mStackIdToStack.get(DOCKED_STACK_ID);
-        if (stack != null && stack.isVisibleLocked()) {
-            mWindow.showLw(true /* doAnimation */);
-        } else {
-            mWindow.hideLw(true /* doAnimation */);
+        final boolean visible = stack != null && stack.isVisibleLocked();
+        if (mLastVisibility == visible) {
+            return;
+        }
+        mLastVisibility = visible;
+        if (mListener != null) {
+            try {
+                mListener.onDockDividerVisibilityChanged(visible);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "visibility call failed: " + e);
+            }
         }
     }
 
@@ -110,4 +125,11 @@
         }
         mLastRect.set(frame);
     }
+
+    public void registerDockDividerVisibilityListener(IDockDividerVisibilityListener listener) {
+        if (mListener != null && listener != null) {
+            throw new IllegalStateException("Dock divider visibility listener already set!");
+        }
+        mListener = listener;
+    }
 }
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index ac90daf..395962c 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -121,6 +121,7 @@
 import android.view.Gravity;
 import android.view.IApplicationToken;
 import android.view.IAppTransitionAnimationSpecsFuture;
+import android.view.IDockDividerVisibilityListener;
 import android.view.IInputFilter;
 import android.view.IOnKeyguardExitResult;
 import android.view.IRotationWatcher;
@@ -2689,8 +2690,7 @@
                     // need to see about starting one.
                     final boolean notExitingOrAnimating =
                             !win.mExiting && !win.isAnimatingWithSavedSurface();
-                    result |= notExitingOrAnimating
-                            ? RELAYOUT_RES_SURFACE_CHANGED : 0;
+                    result |= notExitingOrAnimating ? RELAYOUT_RES_SURFACE_CHANGED : 0;
                     if (notExitingOrAnimating) {
                         focusMayChange = tryStartingAnimation(win, winAnimator, isDefaultDisplay,
                                 focusMayChange);
@@ -3075,7 +3075,7 @@
         // TODO:
     }
 
-    boolean checkCallingPermission(String permission, String func) {
+    private boolean checkCallingPermission(String permission, String func) {
         // Quick check: if the calling permission is me, it's all okay.
         if (Binder.getCallingPid() == Process.myPid()) {
             return true;
@@ -10211,6 +10211,29 @@
         mDestroySurface.add(win);
     }
 
+    @Override
+    public void registerDockDividerVisibilityListener(IDockDividerVisibilityListener listener) {
+        if (!checkCallingPermission(android.Manifest.permission.REGISTER_WINDOW_MANAGER_LISTENERS,
+                "registerDockDividerVisibilityListener()")) {
+            return;
+        }
+        // TODO(multi-display): The listener is registered on the default display only.
+        final DockedStackDividerController controller =
+                getDefaultDisplayContentLocked().getDockedDividerController();
+        controller.registerDockDividerVisibilityListener(listener);
+        try {
+            listener.asBinder().linkToDeath(new IBinder.DeathRecipient() {
+                @Override
+                public void binderDied() {
+                    getDefaultDisplayContentLocked().getDockedDividerController()
+                            .registerDockDividerVisibilityListener(null);
+                }
+            }, 0);
+        } catch (RemoteException e) {
+            controller.registerDockDividerVisibilityListener(null);
+        }
+    }
+
     private final class LocalService extends WindowManagerInternal {
         @Override
         public void requestTraversalFromDisplayManager() {
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 064b412..29cadf3 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -730,7 +730,7 @@
             mVisibleFrame.set(mContentFrame);
             mStableFrame.set(mContentFrame);
         } else if (mAttrs.type == TYPE_DOCK_DIVIDER) {
-            if (isVisibleLw()) {
+            if (isVisibleLw() || mWinAnimator.isAnimating()) {
                 // We don't adjust the dock divider frame for reasons other than performance. The
                 // real reason is that if it gets adjusted before it is shown for the first time,
                 // it would get size (0, 0). This causes a problem when we finally show the dock
diff --git a/tools/aapt/Resource.cpp b/tools/aapt/Resource.cpp
index f1a8f1c..fb0fe38 100644
--- a/tools/aapt/Resource.cpp
+++ b/tools/aapt/Resource.cpp
@@ -1537,19 +1537,20 @@
     std::queue<CompileResourceWorkItem>& workQueue = table.getWorkQueue();
     while (!workQueue.empty()) {
         CompileResourceWorkItem& workItem = workQueue.front();
-        if (workItem.xmlRoot != NULL) {
-            err = compileXmlFile(bundle, assets, workItem.resourceName, workItem.xmlRoot,
-                                 workItem.file, &table, xmlFlags);
-        } else {
-            err = compileXmlFile(bundle, assets, workItem.resourceName, workItem.file,
-                                 &table, xmlFlags);
+        int xmlCompilationFlags = xmlFlags | XML_COMPILE_PARSE_VALUES
+                | XML_COMPILE_ASSIGN_ATTRIBUTE_IDS;
+        if (!workItem.needsCompiling) {
+            xmlCompilationFlags &= ~XML_COMPILE_ASSIGN_ATTRIBUTE_IDS;
+            xmlCompilationFlags &= ~XML_COMPILE_PARSE_VALUES;
         }
+        err = compileXmlFile(bundle, assets, workItem.resourceName, workItem.xmlRoot,
+                             workItem.file, &table, xmlCompilationFlags);
 
         if (err == NO_ERROR) {
             assets->addResource(workItem.resPath.getPathLeaf(),
-                    workItem.resPath,
-                    workItem.file,
-                    workItem.file->getResourceType());
+                                workItem.resPath,
+                                workItem.file,
+                                workItem.file->getResourceType());
         } else {
             hasErrors = true;
         }
@@ -1744,9 +1745,7 @@
             manifestFile->getGroupEntry(),
             manifestFile->getResourceType());
     err = compileXmlFile(bundle, assets, String16(), manifestFile,
-            outManifestFile, &table,
-            XML_COMPILE_ASSIGN_ATTRIBUTE_IDS
-            | XML_COMPILE_STRIP_WHITESPACE | XML_COMPILE_STRIP_RAW_VALUES);
+            outManifestFile, &table, XML_COMPILE_STANDARD_RESOURCE & ~XML_COMPILE_STRIP_COMMENTS);
     if (err < NO_ERROR) {
         return err;
     }
diff --git a/tools/aapt/ResourceTable.cpp b/tools/aapt/ResourceTable.cpp
index 7d19828..0e470d9 100644
--- a/tools/aapt/ResourceTable.cpp
+++ b/tools/aapt/ResourceTable.cpp
@@ -100,9 +100,11 @@
         }
     }
 
-    status_t err = root->parseValues(assets, table);
-    if (err != NO_ERROR) {
-        hasErrors = true;
+    if ((options&XML_COMPILE_PARSE_VALUES) != 0) {
+        status_t err = root->parseValues(assets, table);
+        if (err != NO_ERROR) {
+            hasErrors = true;
+        }
     }
 
     if (hasErrors) {
@@ -117,7 +119,7 @@
         printf("Input XML Resource:\n");
         root->print();
     }
-    err = root->flatten(target,
+    status_t err = root->flatten(target,
             (options&XML_COMPILE_STRIP_COMMENTS) != 0,
             (options&XML_COMPILE_STRIP_RAW_VALUES) != 0);
     if (err != NO_ERROR) {
@@ -4788,9 +4790,10 @@
         item.resPath = resPath;
         item.file = newFile;
         item.xmlRoot = newRoot;
+        item.needsCompiling = false;    // This step occurs after we parse/assign, so we don't need
+                                        // to do it again.
         mWorkQueue.push(item);
     }
-
     return NO_ERROR;
 }
 
diff --git a/tools/aapt/ResourceTable.h b/tools/aapt/ResourceTable.h
index 8ef84f7..4b7b3cd 100644
--- a/tools/aapt/ResourceTable.h
+++ b/tools/aapt/ResourceTable.h
@@ -23,13 +23,14 @@
 enum {
     XML_COMPILE_STRIP_COMMENTS = 1<<0,
     XML_COMPILE_ASSIGN_ATTRIBUTE_IDS = 1<<1,
-    XML_COMPILE_COMPACT_WHITESPACE = 1<<2,
-    XML_COMPILE_STRIP_WHITESPACE = 1<<3,
-    XML_COMPILE_STRIP_RAW_VALUES = 1<<4,
-    XML_COMPILE_UTF8 = 1<<5,
+    XML_COMPILE_PARSE_VALUES = 1 << 2,
+    XML_COMPILE_COMPACT_WHITESPACE = 1<<3,
+    XML_COMPILE_STRIP_WHITESPACE = 1<<4,
+    XML_COMPILE_STRIP_RAW_VALUES = 1<<5,
+    XML_COMPILE_UTF8 = 1<<6,
 
     XML_COMPILE_STANDARD_RESOURCE =
-            XML_COMPILE_STRIP_COMMENTS | XML_COMPILE_ASSIGN_ATTRIBUTE_IDS
+            XML_COMPILE_STRIP_COMMENTS | XML_COMPILE_ASSIGN_ATTRIBUTE_IDS | XML_COMPILE_PARSE_VALUES
             | XML_COMPILE_STRIP_WHITESPACE | XML_COMPILE_STRIP_RAW_VALUES
 };
 
@@ -84,6 +85,7 @@
     String8 resPath;
     sp<AaptFile> file;
     sp<XMLNode> xmlRoot;
+    bool needsCompiling = true;
 };
 
 class ResourceTable : public ResTable::Accessor
diff --git a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
index 6951ede..eea254b 100644
--- a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
+++ b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
@@ -541,4 +541,8 @@
     @Override
     public void endProlongedAnimations() {
     }
+
+    @Override
+    public void registerDockDividerVisibilityListener(IDockDividerVisibilityListener listener) {
+    }
 }