Merge "Remove 2nd line from failure"
diff --git a/api/current.txt b/api/current.txt
index b73214f..ccc0e5c 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -2114,22 +2114,6 @@
     field public static final int Theme_Light_Panel = 16973914; // 0x103005a
     field public static final int Theme_Light_WallpaperSettings = 16973922; // 0x1030062
     field public static final int Theme_Material = 16974372; // 0x1030224
-    field public static final int Theme_Material_DayNight = 16974552; // 0x10302d8
-    field public static final int Theme_Material_DayNight_DarkActionBar = 16974553; // 0x10302d9
-    field public static final int Theme_Material_DayNight_Dialog = 16974554; // 0x10302da
-    field public static final int Theme_Material_DayNight_DialogWhenLarge = 16974560; // 0x10302e0
-    field public static final int Theme_Material_DayNight_DialogWhenLarge_DarkActionBar = 16974568; // 0x10302e8
-    field public static final int Theme_Material_DayNight_DialogWhenLarge_NoActionBar = 16974561; // 0x10302e1
-    field public static final int Theme_Material_DayNight_Dialog_Alert = 16974555; // 0x10302db
-    field public static final int Theme_Material_DayNight_Dialog_MinWidth = 16974556; // 0x10302dc
-    field public static final int Theme_Material_DayNight_Dialog_NoActionBar = 16974557; // 0x10302dd
-    field public static final int Theme_Material_DayNight_Dialog_NoActionBar_MinWidth = 16974558; // 0x10302de
-    field public static final int Theme_Material_DayNight_Dialog_Presentation = 16974559; // 0x10302df
-    field public static final int Theme_Material_DayNight_NoActionBar = 16974562; // 0x10302e2
-    field public static final int Theme_Material_DayNight_NoActionBar_Fullscreen = 16974563; // 0x10302e3
-    field public static final int Theme_Material_DayNight_NoActionBar_Overscan = 16974564; // 0x10302e4
-    field public static final int Theme_Material_DayNight_NoActionBar_TranslucentDecor = 16974565; // 0x10302e5
-    field public static final int Theme_Material_DayNight_Panel = 16974566; // 0x10302e6
     field public static final int Theme_Material_Dialog = 16974373; // 0x1030225
     field public static final int Theme_Material_DialogWhenLarge = 16974379; // 0x103022b
     field public static final int Theme_Material_DialogWhenLarge_NoActionBar = 16974380; // 0x103022c
@@ -2143,7 +2127,7 @@
     field public static final int Theme_Material_Light_DarkActionBar = 16974392; // 0x1030238
     field public static final int Theme_Material_Light_Dialog = 16974393; // 0x1030239
     field public static final int Theme_Material_Light_DialogWhenLarge = 16974399; // 0x103023f
-    field public static final int Theme_Material_Light_DialogWhenLarge_DarkActionBar = 16974567; // 0x10302e7
+    field public static final int Theme_Material_Light_DialogWhenLarge_DarkActionBar = 16974552; // 0x10302d8
     field public static final int Theme_Material_Light_DialogWhenLarge_NoActionBar = 16974400; // 0x1030240
     field public static final int Theme_Material_Light_Dialog_Alert = 16974394; // 0x103023a
     field public static final int Theme_Material_Light_Dialog_MinWidth = 16974395; // 0x103023b
@@ -3374,6 +3358,7 @@
     method public boolean dispatchTouchEvent(android.view.MotionEvent);
     method public boolean dispatchTrackballEvent(android.view.MotionEvent);
     method public void dump(java.lang.String, java.io.FileDescriptor, java.io.PrintWriter, java.lang.String[]);
+    method public void enterPictureInPictureMode();
     method public android.view.View findViewById(int);
     method public void finish();
     method public void finishActivity(int);
diff --git a/api/system-current.txt b/api/system-current.txt
index 84dbe62..923e053 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -2215,22 +2215,6 @@
     field public static final int Theme_Light_Panel = 16973914; // 0x103005a
     field public static final int Theme_Light_WallpaperSettings = 16973922; // 0x1030062
     field public static final int Theme_Material = 16974372; // 0x1030224
-    field public static final int Theme_Material_DayNight = 16974552; // 0x10302d8
-    field public static final int Theme_Material_DayNight_DarkActionBar = 16974553; // 0x10302d9
-    field public static final int Theme_Material_DayNight_Dialog = 16974554; // 0x10302da
-    field public static final int Theme_Material_DayNight_DialogWhenLarge = 16974560; // 0x10302e0
-    field public static final int Theme_Material_DayNight_DialogWhenLarge_DarkActionBar = 16974568; // 0x10302e8
-    field public static final int Theme_Material_DayNight_DialogWhenLarge_NoActionBar = 16974561; // 0x10302e1
-    field public static final int Theme_Material_DayNight_Dialog_Alert = 16974555; // 0x10302db
-    field public static final int Theme_Material_DayNight_Dialog_MinWidth = 16974556; // 0x10302dc
-    field public static final int Theme_Material_DayNight_Dialog_NoActionBar = 16974557; // 0x10302dd
-    field public static final int Theme_Material_DayNight_Dialog_NoActionBar_MinWidth = 16974558; // 0x10302de
-    field public static final int Theme_Material_DayNight_Dialog_Presentation = 16974559; // 0x10302df
-    field public static final int Theme_Material_DayNight_NoActionBar = 16974562; // 0x10302e2
-    field public static final int Theme_Material_DayNight_NoActionBar_Fullscreen = 16974563; // 0x10302e3
-    field public static final int Theme_Material_DayNight_NoActionBar_Overscan = 16974564; // 0x10302e4
-    field public static final int Theme_Material_DayNight_NoActionBar_TranslucentDecor = 16974565; // 0x10302e5
-    field public static final int Theme_Material_DayNight_Panel = 16974566; // 0x10302e6
     field public static final int Theme_Material_Dialog = 16974373; // 0x1030225
     field public static final int Theme_Material_DialogWhenLarge = 16974379; // 0x103022b
     field public static final int Theme_Material_DialogWhenLarge_NoActionBar = 16974380; // 0x103022c
@@ -2244,7 +2228,7 @@
     field public static final int Theme_Material_Light_DarkActionBar = 16974392; // 0x1030238
     field public static final int Theme_Material_Light_Dialog = 16974393; // 0x1030239
     field public static final int Theme_Material_Light_DialogWhenLarge = 16974399; // 0x103023f
-    field public static final int Theme_Material_Light_DialogWhenLarge_DarkActionBar = 16974567; // 0x10302e7
+    field public static final int Theme_Material_Light_DialogWhenLarge_DarkActionBar = 16974552; // 0x10302d8
     field public static final int Theme_Material_Light_DialogWhenLarge_NoActionBar = 16974400; // 0x1030240
     field public static final int Theme_Material_Light_Dialog_Alert = 16974394; // 0x103023a
     field public static final int Theme_Material_Light_Dialog_MinWidth = 16974395; // 0x103023b
@@ -3477,6 +3461,7 @@
     method public boolean dispatchTouchEvent(android.view.MotionEvent);
     method public boolean dispatchTrackballEvent(android.view.MotionEvent);
     method public void dump(java.lang.String, java.io.FileDescriptor, java.io.PrintWriter, java.lang.String[]);
+    method public void enterPictureInPictureMode();
     method public android.view.View findViewById(int);
     method public void finish();
     method public void finishActivity(int);
diff --git a/api/test-current.txt b/api/test-current.txt
index 390a017..27a7ae3 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -2114,22 +2114,6 @@
     field public static final int Theme_Light_Panel = 16973914; // 0x103005a
     field public static final int Theme_Light_WallpaperSettings = 16973922; // 0x1030062
     field public static final int Theme_Material = 16974372; // 0x1030224
-    field public static final int Theme_Material_DayNight = 16974552; // 0x10302d8
-    field public static final int Theme_Material_DayNight_DarkActionBar = 16974553; // 0x10302d9
-    field public static final int Theme_Material_DayNight_Dialog = 16974554; // 0x10302da
-    field public static final int Theme_Material_DayNight_DialogWhenLarge = 16974560; // 0x10302e0
-    field public static final int Theme_Material_DayNight_DialogWhenLarge_DarkActionBar = 16974568; // 0x10302e8
-    field public static final int Theme_Material_DayNight_DialogWhenLarge_NoActionBar = 16974561; // 0x10302e1
-    field public static final int Theme_Material_DayNight_Dialog_Alert = 16974555; // 0x10302db
-    field public static final int Theme_Material_DayNight_Dialog_MinWidth = 16974556; // 0x10302dc
-    field public static final int Theme_Material_DayNight_Dialog_NoActionBar = 16974557; // 0x10302dd
-    field public static final int Theme_Material_DayNight_Dialog_NoActionBar_MinWidth = 16974558; // 0x10302de
-    field public static final int Theme_Material_DayNight_Dialog_Presentation = 16974559; // 0x10302df
-    field public static final int Theme_Material_DayNight_NoActionBar = 16974562; // 0x10302e2
-    field public static final int Theme_Material_DayNight_NoActionBar_Fullscreen = 16974563; // 0x10302e3
-    field public static final int Theme_Material_DayNight_NoActionBar_Overscan = 16974564; // 0x10302e4
-    field public static final int Theme_Material_DayNight_NoActionBar_TranslucentDecor = 16974565; // 0x10302e5
-    field public static final int Theme_Material_DayNight_Panel = 16974566; // 0x10302e6
     field public static final int Theme_Material_Dialog = 16974373; // 0x1030225
     field public static final int Theme_Material_DialogWhenLarge = 16974379; // 0x103022b
     field public static final int Theme_Material_DialogWhenLarge_NoActionBar = 16974380; // 0x103022c
@@ -2143,7 +2127,7 @@
     field public static final int Theme_Material_Light_DarkActionBar = 16974392; // 0x1030238
     field public static final int Theme_Material_Light_Dialog = 16974393; // 0x1030239
     field public static final int Theme_Material_Light_DialogWhenLarge = 16974399; // 0x103023f
-    field public static final int Theme_Material_Light_DialogWhenLarge_DarkActionBar = 16974567; // 0x10302e7
+    field public static final int Theme_Material_Light_DialogWhenLarge_DarkActionBar = 16974552; // 0x10302d8
     field public static final int Theme_Material_Light_DialogWhenLarge_NoActionBar = 16974400; // 0x1030240
     field public static final int Theme_Material_Light_Dialog_Alert = 16974394; // 0x103023a
     field public static final int Theme_Material_Light_Dialog_MinWidth = 16974395; // 0x103023b
@@ -3374,6 +3358,7 @@
     method public boolean dispatchTouchEvent(android.view.MotionEvent);
     method public boolean dispatchTrackballEvent(android.view.MotionEvent);
     method public void dump(java.lang.String, java.io.FileDescriptor, java.io.PrintWriter, java.lang.String[]);
+    method public void enterPictureInPictureMode();
     method public android.view.View findViewById(int);
     method public void finish();
     method public void finishActivity(int);
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index ef84ab0..571bc6e 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -1782,6 +1782,17 @@
     }
 
     /**
+     * Puts the activity in picture-in-picture mode.
+     * @see android.R.attr#supportsPictureInPicture
+     */
+    public void enterPictureInPictureMode() {
+        try {
+            ActivityManagerNative.getDefault().enterPictureInPictureMode(mToken);
+        } catch (RemoteException e) {
+        }
+    }
+
+    /**
      * Called by the system when the device configuration changes while your
      * activity is running.  Note that this will <em>only</em> be called if
      * you have selected configurations you would like to handle with the
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index f11bf742..d724823 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -2759,6 +2759,13 @@
             reply.writeInt(pipMode ? 1 : 0);
             return true;
         }
+        case ENTER_PICTURE_IN_PICTURE_MODE_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            final IBinder token = data.readStrongBinder();
+            enterPictureInPictureMode(token);
+            reply.writeNoException();
+            return true;
+        }
         }
 
         return super.onTransact(code, data, reply, flags);
@@ -6433,5 +6440,17 @@
         return pipMode;
     }
 
+    @Override
+    public void enterPictureInPictureMode(IBinder token) throws RemoteException {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        data.writeStrongBinder(token);
+        mRemote.transact(ENTER_PICTURE_IN_PICTURE_MODE_TRANSACTION, data, reply, 0);
+        reply.readException();
+        data.recycle();
+        reply.recycle();
+    }
+
     private IBinder mRemote;
 }
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index 64c69af..f91a0be 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -550,6 +550,8 @@
 
     public boolean inPictureInPictureMode(IBinder token) throws RemoteException;
 
+    public void enterPictureInPictureMode(IBinder token) throws RemoteException;
+
     /*
      * Private non-Binder interfaces
      */
@@ -914,4 +916,5 @@
     int IN_MULTI_WINDOW_MODE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 352;
     int IN_PICTURE_IN_PICTURE_MODE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 353;
     int KILL_PACKAGE_DEPENDENTS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 354;
+    int ENTER_PICTURE_IN_PICTURE_MODE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 355;
 }
diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java
index f2a4d7b..0e4bc84 100644
--- a/core/java/android/view/ThreadedRenderer.java
+++ b/core/java/android/view/ThreadedRenderer.java
@@ -422,9 +422,10 @@
      * @return True if the initialization was successful, false otherwise.
      */
     boolean initialize(Surface surface) throws OutOfResourcesException {
+        boolean status = !mInitialized;
         mInitialized = true;
         updateEnabledState(surface);
-        boolean status = nInitialize(mNativeProxy, surface);
+        nInitialize(mNativeProxy, surface);
         return status;
     }
 
@@ -958,7 +959,7 @@
     private static native boolean nLoadSystemProperties(long nativeProxy);
     private static native void nSetName(long nativeProxy, String name);
 
-    private static native boolean nInitialize(long nativeProxy, Surface window);
+    private static native void nInitialize(long nativeProxy, Surface window);
     private static native void nUpdateSurface(long nativeProxy, Surface window);
     private static native boolean nPauseSurface(long nativeProxy, Surface window);
     private static native void nSetup(long nativeProxy, int width, int height,
diff --git a/core/jni/android_view_ThreadedRenderer.cpp b/core/jni/android_view_ThreadedRenderer.cpp
index 17eb876..5aa6a73 100644
--- a/core/jni/android_view_ThreadedRenderer.cpp
+++ b/core/jni/android_view_ThreadedRenderer.cpp
@@ -262,11 +262,11 @@
     env->ReleaseStringUTFChars(jname, name);
 }
 
-static jboolean android_view_ThreadedRenderer_initialize(JNIEnv* env, jobject clazz,
+static void android_view_ThreadedRenderer_initialize(JNIEnv* env, jobject clazz,
         jlong proxyPtr, jobject jsurface) {
     RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
     sp<ANativeWindow> window = android_view_Surface_getNativeWindow(env, jsurface);
-    return proxy->initialize(window);
+    proxy->initialize(window);
 }
 
 static void android_view_ThreadedRenderer_updateSurface(JNIEnv* env, jobject clazz,
@@ -492,7 +492,7 @@
     { "nDeleteProxy", "(J)V", (void*) android_view_ThreadedRenderer_deleteProxy },
     { "nLoadSystemProperties", "(J)Z", (void*) android_view_ThreadedRenderer_loadSystemProperties },
     { "nSetName", "(JLjava/lang/String;)V", (void*) android_view_ThreadedRenderer_setName },
-    { "nInitialize", "(JLandroid/view/Surface;)Z", (void*) android_view_ThreadedRenderer_initialize },
+    { "nInitialize", "(JLandroid/view/Surface;)V", (void*) android_view_ThreadedRenderer_initialize },
     { "nUpdateSurface", "(JLandroid/view/Surface;)V", (void*) android_view_ThreadedRenderer_updateSurface },
     { "nPauseSurface", "(JLandroid/view/Surface;)Z", (void*) android_view_ThreadedRenderer_pauseSurface },
     { "nSetup", "(JIIFII)V", (void*) android_view_ThreadedRenderer_setup },
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index ad36f3c..09c1717 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2686,23 +2686,7 @@
     <public type="attr" name="languageTag" />
     <public type="attr" name="pointerShape" />
 
-    <public type="style" name="Theme.Material.DayNight" />
-    <public type="style" name="Theme.Material.DayNight.DarkActionBar" />
-    <public type="style" name="Theme.Material.DayNight.Dialog" />
-    <public type="style" name="Theme.Material.DayNight.Dialog.Alert" />
-    <public type="style" name="Theme.Material.DayNight.Dialog.MinWidth" />
-    <public type="style" name="Theme.Material.DayNight.Dialog.NoActionBar" />
-    <public type="style" name="Theme.Material.DayNight.Dialog.NoActionBar.MinWidth" />
-    <public type="style" name="Theme.Material.DayNight.Dialog.Presentation" />
-    <public type="style" name="Theme.Material.DayNight.DialogWhenLarge" />
-    <public type="style" name="Theme.Material.DayNight.DialogWhenLarge.NoActionBar" />
-    <public type="style" name="Theme.Material.DayNight.NoActionBar" />
-    <public type="style" name="Theme.Material.DayNight.NoActionBar.Fullscreen" />
-    <public type="style" name="Theme.Material.DayNight.NoActionBar.Overscan" />
-    <public type="style" name="Theme.Material.DayNight.NoActionBar.TranslucentDecor" />
-    <public type="style" name="Theme.Material.DayNight.Panel" />
     <public type="style" name="Theme.Material.Light.DialogWhenLarge.DarkActionBar" />
-    <public type="style" name="Theme.Material.DayNight.DialogWhenLarge.DarkActionBar" />
 
     <public type="id" name="accessibilityActionSetProgress" />
     <public type="id" name="icon_frame" />
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 832984e..f6326f7 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2389,4 +2389,22 @@
   <java-symbol type="layout" name="work_widget_mask_view" />
   <java-symbol type="id" name="work_widget_app_icon" />
   <java-symbol type="drawable" name="work_widget_mask_view_background" />
+
+  <!-- Framework-private Material.DayNight styles. -->
+  <java-symbol type="style" name="Theme.Material.DayNight" />
+  <java-symbol type="style" name="Theme.Material.DayNight.DarkActionBar" />
+  <java-symbol type="style" name="Theme.Material.DayNight.Dialog" />
+  <java-symbol type="style" name="Theme.Material.DayNight.Dialog.Alert" />
+  <java-symbol type="style" name="Theme.Material.DayNight.Dialog.MinWidth" />
+  <java-symbol type="style" name="Theme.Material.DayNight.Dialog.NoActionBar" />
+  <java-symbol type="style" name="Theme.Material.DayNight.Dialog.NoActionBar.MinWidth" />
+  <java-symbol type="style" name="Theme.Material.DayNight.Dialog.Presentation" />
+  <java-symbol type="style" name="Theme.Material.DayNight.DialogWhenLarge" />
+  <java-symbol type="style" name="Theme.Material.DayNight.DialogWhenLarge.NoActionBar" />
+  <java-symbol type="style" name="Theme.Material.DayNight.NoActionBar" />
+  <java-symbol type="style" name="Theme.Material.DayNight.NoActionBar.Fullscreen" />
+  <java-symbol type="style" name="Theme.Material.DayNight.NoActionBar.Overscan" />
+  <java-symbol type="style" name="Theme.Material.DayNight.NoActionBar.TranslucentDecor" />
+  <java-symbol type="style" name="Theme.Material.DayNight.Panel" />
+  <java-symbol type="style" name="Theme.Material.DayNight.DialogWhenLarge.DarkActionBar" />
 </resources>
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 0620653..dfcac1d 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -128,14 +128,13 @@
     mSwapBehavior = swapBehavior;
 }
 
-bool CanvasContext::initialize(ANativeWindow* window) {
+void CanvasContext::initialize(ANativeWindow* window) {
     setSurface(window);
 #if !HWUI_NEW_OPS
-    if (mCanvas) return false;
+    if (mCanvas) return;
     mCanvas = new OpenGLRenderer(mRenderThread.renderState());
     mCanvas->initProperties();
 #endif
-    return true;
 }
 
 void CanvasContext::updateSurface(ANativeWindow* window) {
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index d36ce99..0946900 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -73,7 +73,7 @@
     // Won't take effect until next EGLSurface creation
     void setSwapBehavior(SwapBehavior swapBehavior);
 
-    bool initialize(ANativeWindow* window);
+    void initialize(ANativeWindow* window);
     void updateSurface(ANativeWindow* window);
     bool pauseSurface(ANativeWindow* window);
     bool hasSurface() { return mNativeWindow.get(); }
diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp
index 15ccd6a..43282c9 100644
--- a/libs/hwui/renderthread/RenderProxy.cpp
+++ b/libs/hwui/renderthread/RenderProxy.cpp
@@ -140,14 +140,15 @@
 }
 
 CREATE_BRIDGE2(initialize, CanvasContext* context, ANativeWindow* window) {
-    return (void*) args->context->initialize(args->window);
+    args->context->initialize(args->window);
+    return nullptr;
 }
 
-bool RenderProxy::initialize(const sp<ANativeWindow>& window) {
+void RenderProxy::initialize(const sp<ANativeWindow>& window) {
     SETUP_TASK(initialize);
     args->context = mContext;
     args->window = window.get();
-    return (bool) postAndWait(task);
+    post(task);
 }
 
 CREATE_BRIDGE2(updateSurface, CanvasContext* context, ANativeWindow* window) {
diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h
index 338fab6..1d30eb8 100644
--- a/libs/hwui/renderthread/RenderProxy.h
+++ b/libs/hwui/renderthread/RenderProxy.h
@@ -66,7 +66,7 @@
     ANDROID_API bool loadSystemProperties();
     ANDROID_API void setName(const char* name);
 
-    ANDROID_API bool initialize(const sp<ANativeWindow>& window);
+    ANDROID_API void initialize(const sp<ANativeWindow>& window);
     ANDROID_API void updateSurface(const sp<ANativeWindow>& window);
     ANDROID_API bool pauseSurface(const sp<ANativeWindow>& window);
     ANDROID_API void setup(int width, int height, float lightRadius,
diff --git a/packages/Shell/res/layout/dialog_bugreport_info.xml b/packages/Shell/res/layout/dialog_bugreport_info.xml
new file mode 100644
index 0000000..5d1e9f9
--- /dev/null
+++ b/packages/Shell/res/layout/dialog_bugreport_info.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content">
+    <EditText
+        android:id="@+id/name"
+        android:maxLength="30"
+        android:singleLine="true"
+        android:inputType="textNoSuggestions"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:hint="@string/bugreport_info_name"/>
+    <EditText
+        android:id="@+id/title"
+        android:maxLength="80"
+        android:singleLine="true"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:hint="@string/bugreport_info_title"/>
+    <EditText
+        android:id="@+id/description"
+        android:singleLine="false"
+        android:inputType="textMultiLine"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:hint="@string/bugreport_info_description"/>
+</LinearLayout>
diff --git a/packages/Shell/res/values/strings.xml b/packages/Shell/res/values/strings.xml
index 5c25576..a7f2df5 100644
--- a/packages/Shell/res/values/strings.xml
+++ b/packages/Shell/res/values/strings.xml
@@ -42,4 +42,19 @@
     <!-- Title for bug reports received from dumpstate without a name. [CHAR LIMIT=30]-->
     <string name="bugreport_unnamed">unnamed</string>
 
+    <!-- Title of the notification action that opens the dialog for the user-defined bug report details. -->
+    <string name="bugreport_info_action">Details</string>
+
+    <!--  Title of the dialog asking for user-defined bug report details like name, title, and description. -->
+    <string name="bugreport_info_dialog_title">Bug report details</string>
+
+    <!-- Text of the hint asking for the bug report name, which when set will define a suffix in the
+         bug report file names. [CHAR LIMIT=30] -->
+    <string name="bugreport_info_name">Short name</string>
+    <!-- Text of hint asking for the bug report title, which when set will define the
+         Subject of the email message. [CHAR LIMIT=60] -->
+    <string name="bugreport_info_title">1-line summary</string>
+    <!-- Text of hint asking for the bug report description, which when set will describe
+         what the bug report is about. [CHAR LIMIT=NONE] -->
+    <string name="bugreport_info_description">Detailed description</string>
 </resources>
diff --git a/packages/Shell/src/com/android/shell/BugreportProgressService.java b/packages/Shell/src/com/android/shell/BugreportProgressService.java
index e902589..82ee710 100644
--- a/packages/Shell/src/com/android/shell/BugreportProgressService.java
+++ b/packages/Shell/src/com/android/shell/BugreportProgressService.java
@@ -29,16 +29,18 @@
 import java.io.PrintWriter;
 import java.text.NumberFormat;
 import java.util.ArrayList;
-import java.util.Date;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipOutputStream;
 
 import libcore.io.Streams;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.google.android.collect.Lists;
 
 import android.accounts.Account;
 import android.accounts.AccountManager;
+import android.annotation.SuppressLint;
+import android.app.AlertDialog;
 import android.app.Notification;
 import android.app.Notification.Action;
 import android.app.NotificationManager;
@@ -46,6 +48,7 @@
 import android.app.Service;
 import android.content.ClipData;
 import android.content.Context;
+import android.content.DialogInterface;
 import android.content.Intent;
 import android.content.res.Configuration;
 import android.net.Uri;
@@ -59,10 +62,17 @@
 import android.os.Process;
 import android.os.SystemProperties;
 import android.support.v4.content.FileProvider;
+import android.text.TextUtils;
 import android.text.format.DateUtils;
 import android.util.Log;
 import android.util.Patterns;
 import android.util.SparseArray;
+import android.view.View;
+import android.view.WindowManager;
+import android.view.View.OnFocusChangeListener;
+import android.view.inputmethod.EditorInfo;
+import android.widget.Button;
+import android.widget.EditText;
 import android.widget.Toast;
 
 /**
@@ -103,19 +113,23 @@
     // Internal intents used on notification actions.
     static final String INTENT_BUGREPORT_CANCEL = "android.intent.action.BUGREPORT_CANCEL";
     static final String INTENT_BUGREPORT_SHARE = "android.intent.action.BUGREPORT_SHARE";
+    static final String INTENT_BUGREPORT_INFO_LAUNCH =
+            "android.intent.action.BUGREPORT_INFO_LAUNCH";
 
     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_TITLE = "android.intent.extra.TITLE";
+    static final String EXTRA_DESCRIPTION = "android.intent.extra.DESCRIPTION";
     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. */
-    static final long POLLING_FREQUENCY = 2000;
+    static final long POLLING_FREQUENCY = 2 * DateUtils.SECOND_IN_MILLIS;
 
     /** 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;
@@ -124,8 +138,9 @@
     private static final String DUMPSTATE_PREFIX = "dumpstate.";
     private static final String PROGRESS_SUFFIX = ".progress";
     private static final String MAX_SUFFIX = ".max";
+    private static final String NAME_SUFFIX = ".name";
 
-    /** System property (and value) used for stop dumpstate. */
+    /** System property (and value) used to stop dumpstate. */
     private static final String CTL_STOP = "ctl.stop";
     private static final String BUGREPORT_SERVICE = "bugreportplus";
 
@@ -135,6 +150,8 @@
     private Looper mServiceLooper;
     private ServiceHandler mServiceHandler;
 
+    private final BugreportInfoDialog mInfoDialog = new BugreportInfoDialog();
+
     @Override
     public void onCreate() {
         HandlerThread thread = new HandlerThread("BugreportProgressServiceThread",
@@ -242,6 +259,9 @@
                     }
                     onBugreportFinished(pid, intent);
                     break;
+                case INTENT_BUGREPORT_INFO_LAUNCH:
+                    launchBugreportInfoDialog(pid);
+                    break;
                 case INTENT_BUGREPORT_SHARE:
                     shareBugreport(pid);
                     break;
@@ -312,6 +332,13 @@
         final String percentText = nf.format((double) info.progress / info.max);
         final Action cancelAction = new Action.Builder(null, context.getString(
                 com.android.internal.R.string.cancel), newCancelIntent(context, info)).build();
+        final Intent infoIntent = new Intent(context, BugreportProgressService.class);
+        infoIntent.setAction(INTENT_BUGREPORT_INFO_LAUNCH);
+        infoIntent.putExtra(EXTRA_PID, info.pid);
+        final Action infoAction = new Action.Builder(null,
+                context.getString(R.string.bugreport_info_action),
+                PendingIntent.getService(context, info.pid, infoIntent,
+                        PendingIntent.FLAG_UPDATE_CURRENT)).build();
 
         final String title = context.getString(R.string.bugreport_in_progress_title);
         final String name =
@@ -328,6 +355,7 @@
                 .setLocalOnly(true)
                 .setColor(context.getColor(
                         com.android.internal.R.color.system_notification_accent_color))
+                .addAction(infoAction)
                 .addAction(cancelAction)
                 .build();
 
@@ -341,7 +369,8 @@
         final Intent intent = new Intent(INTENT_BUGREPORT_CANCEL);
         intent.setClass(context, BugreportProgressService.class);
         intent.putExtra(EXTRA_PID, info.pid);
-        return PendingIntent.getService(context, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);
+        return PendingIntent.getService(context, info.pid, intent,
+                PendingIntent.FLAG_UPDATE_CURRENT);
     }
 
     /**
@@ -356,7 +385,7 @@
             }
             stopSelfWhenDone();
         }
-        if (DEBUG) Log.v(TAG, "stopProgress(" + pid + "): cancel notification");
+        Log.v(TAG, "stopProgress(" + pid + "): cancel notification");
         NotificationManager.from(getApplicationContext()).cancel(TAG, pid);
     }
 
@@ -364,8 +393,14 @@
      * Cancels a bugreport upon user's request.
      */
     private void cancel(int pid) {
-        Log.i(TAG, "Cancelling PID " + pid + " on user's request");
-        SystemProperties.set(CTL_STOP, BUGREPORT_SERVICE);
+        Log.v(TAG, "cancel: pid=" + pid);
+        synchronized (mProcesses) {
+            BugreportInfo info = mProcesses.get(pid);
+            if (info != null && !info.finished) {
+                Log.i(TAG, "Cancelling bugreport service (pid=" + pid + ") on user's request");
+                setSystemProperty(CTL_STOP, BUGREPORT_SERVICE);
+            }
+        }
         stopProgress(pid);
     }
 
@@ -393,7 +428,6 @@
                 final int progress = SystemProperties.getInt(progressKey, 0);
                 if (progress == 0) {
                     Log.v(TAG, "System property " + progressKey + " is not set yet");
-                    continue;
                 }
                 final int max = SystemProperties.getInt(DUMPSTATE_PREFIX + pid + MAX_SUFFIX, 0);
                 final boolean maxChanged = max > 0 && max != info.max;
@@ -427,6 +461,30 @@
     }
 
     /**
+     * Fetches a {@link BugreportInfo} for a given process and launches a dialog where the user can
+     * change its values.
+     */
+    private void launchBugreportInfoDialog(int pid) {
+        // Copy values so it doesn't lock mProcesses while UI is being updated
+        final String name, title, description;
+        synchronized (mProcesses) {
+            final BugreportInfo info = mProcesses.get(pid);
+            if (info == null) {
+                Log.w(TAG, "No bugreport info for PID " + pid);
+                return;
+            }
+            name = info.name;
+            title = info.title;
+            description = info.description;
+        }
+
+        // Closes the notification bar first.
+        sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
+
+        mInfoDialog.initialize(getApplicationContext(), pid, name, title, description);
+    }
+
+    /**
      * Finishes the service when it's not monitoring any more processes.
      */
     private void stopSelfWhenDone() {
@@ -440,7 +498,11 @@
         }
     }
 
+    /**
+     * Handles the BUGREPORT_FINISHED intent sent by {@code dumpstate}.
+     */
     private void onBugreportFinished(int pid, Intent intent) {
+        mInfoDialog.onBugreportFinished(pid);
         final Context context = getApplicationContext();
         BugreportInfo info;
         synchronized (mProcesses) {
@@ -453,6 +515,7 @@
             }
             info.bugreportFile = getFileExtra(intent, EXTRA_BUGREPORT);
             info.screenshotFile = getFileExtra(intent, EXTRA_SCREENSHOT);
+            info.finished = true;
         }
 
         final Configuration conf = context.getResources().getConfiguration();
@@ -494,21 +557,32 @@
     /**
      * Build {@link Intent} that can be used to share the given bugreport.
      */
-    private static Intent buildSendIntent(Context context, Uri bugreportUri, Uri screenshotUri) {
+    private static Intent buildSendIntent(Context context, BugreportInfo info) {
+        // Files are kept on private storage, so turn into Uris that we can
+        // grant temporary permissions for.
+        final Uri bugreportUri = getUri(context, info.bugreportFile);
+        final Uri screenshotUri = getUri(context, info.screenshotFile);
+
         final Intent intent = new Intent(Intent.ACTION_SEND_MULTIPLE);
         final String mimeType = "application/vnd.android.bugreport";
         intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
         intent.addCategory(Intent.CATEGORY_DEFAULT);
         intent.setType(mimeType);
 
-        intent.putExtra(Intent.EXTRA_SUBJECT, bugreportUri.getLastPathSegment());
+        final String subject = info.title != null ? info.title : bugreportUri.getLastPathSegment();
+        intent.putExtra(Intent.EXTRA_SUBJECT, subject);
 
         // EXTRA_TEXT should be an ArrayList, but some clients are expecting a single String.
         // So, to avoid an exception on Intent.migrateExtraStreamToClipData(), we need to manually
         // create the ClipData object with the attachments URIs.
-        String messageBody = String.format("Build info: %s\nSerial number:%s",
-                SystemProperties.get("ro.build.description"), SystemProperties.get("ro.serialno"));
-        intent.putExtra(Intent.EXTRA_TEXT, messageBody);
+        StringBuilder messageBody = new StringBuilder("Build info: ")
+            .append(SystemProperties.get("ro.build.description"))
+            .append("\nSerial number: ")
+            .append(SystemProperties.get("ro.serialno"));
+        if (!TextUtils.isEmpty(info.description)) {
+            messageBody.append("\nDescription: ").append(info.description);
+        }
+        intent.putExtra(Intent.EXTRA_TEXT, messageBody.toString());
         final ClipData clipData = new ClipData(null, new String[] { mimeType },
                 new ClipData.Item(null, null, null, bugreportUri));
         final ArrayList<Uri> attachments = Lists.newArrayList(bugreportUri);
@@ -542,12 +616,7 @@
                 return;
             }
         }
-        // Files are kept on private storage, so turn into Uris that we can
-        // grant temporary permissions for.
-        final Uri bugreportUri = getUri(context, info.bugreportFile);
-        final Uri screenshotUri = getUri(context, info.screenshotFile);
-
-        final Intent sendIntent = buildSendIntent(context, bugreportUri, screenshotUri);
+        final Intent sendIntent = buildSendIntent(context, info);
         final Intent notifIntent;
 
         // Send through warning dialog by default
@@ -580,13 +649,17 @@
                 .setContentTitle(title)
                 .setTicker(title)
                 .setContentText(context.getString(R.string.bugreport_finished_text))
-                .setContentIntent(PendingIntent.getService(context, 0, shareIntent,
-                        PendingIntent.FLAG_CANCEL_CURRENT))
+                .setContentIntent(PendingIntent.getService(context, info.pid, shareIntent,
+                        PendingIntent.FLAG_UPDATE_CURRENT))
                 .setDeleteIntent(newCancelIntent(context, info))
                 .setLocalOnly(true)
                 .setColor(context.getColor(
                         com.android.internal.R.color.system_notification_accent_color));
 
+        if (!TextUtils.isEmpty(info.name)) {
+            builder.setContentInfo(info.name);
+        }
+
         NotificationManager.from(context).notify(TAG, info.pid, builder.build());
     }
 
@@ -684,6 +757,231 @@
         }
     }
 
+    private static boolean setSystemProperty(String key, String value) {
+        try {
+            if (DEBUG) Log.v(TAG, "Setting system property" + key + " to " + value);
+            SystemProperties.set(key, value);
+        } catch (IllegalArgumentException e) {
+            Log.e(TAG, "Could not set property " + key + " to " + value, e);
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Updates the system property used by {@code dumpstate} to rename the final bugreport files.
+     */
+    private boolean setBugreportNameProperty(int pid, String name) {
+        Log.d(TAG, "Updating bugreport name to " + name);
+        final String key = DUMPSTATE_PREFIX + pid + NAME_SUFFIX;
+        return setSystemProperty(key, name);
+    }
+
+    /**
+     * Updates the user-provided details of a bugreport.
+     */
+    private void updateBugreportInfo(int pid, String name, String title, String description) {
+        synchronized (mProcesses) {
+            final BugreportInfo info = mProcesses.get(pid);
+            if (info == null) {
+                Log.w(TAG, "No bugreport info for PID " + pid);
+                return;
+            }
+            info.title = title;
+            info.description = description;
+            if (name != null && !info.name.equals(name)) {
+                info.name = name;
+                updateProgress(info);
+            }
+        }
+    }
+
+    /**
+     * Checks whether a character is valid on bugreport names.
+     */
+    @VisibleForTesting
+    static boolean isValid(char c) {
+        return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9')
+                || c == '_' || c == '-';
+    }
+
+    /**
+     * Helper class encapsulating the UI elements and logic used to display a dialog where user
+     * can change the details of a bugreport.
+     */
+    private final class BugreportInfoDialog {
+        private EditText mInfoName;
+        private EditText mInfoTitle;
+        private EditText mInfoDescription;
+        private AlertDialog mDialog;
+        private Button mOkButton;
+        private int mPid;
+
+        /**
+         * Last "committed" value of the bugreport name.
+         * <p>
+         * Once initially set, it's only updated when user clicks the OK button.
+         */
+        private String mSavedName;
+
+        /**
+         * Last value of the bugreport name as entered by the user.
+         * <p>
+         * Every time it's changed the equivalent system property is changed as well, but if the
+         * user clicks CANCEL, the old value (stored on {@code mSavedName} is restored.
+         * <p>
+         * This logic handles the corner-case scenario where {@code dumpstate} finishes after the
+         * user changed the name but didn't clicked OK yet (for example, because the user is typing
+         * the description). The only drawback is that if the user changes the name while
+         * {@code dumpstate} is running but clicks CANCEL after it finishes, then the final name
+         * will be the one that has been canceled. But when {@code dumpstate} finishes the {code
+         * name} UI is disabled and the old name restored anyways, so the user will be "alerted" of
+         * such drawback.
+         */
+        private String mTempName;
+
+        /**
+         * Sets its internal state and displays the dialog.
+         */
+        private synchronized void initialize(Context context, int pid, String name, String title,
+                String description) {
+            // First initializes singleton.
+            if (mDialog == null) {
+                @SuppressLint("InflateParams")
+                // It's ok pass null ViewRoot on AlertDialogs.
+                final View view = View.inflate(context, R.layout.dialog_bugreport_info, null);
+
+                mInfoName = (EditText) view.findViewById(R.id.name);
+                mInfoTitle = (EditText) view.findViewById(R.id.title);
+                mInfoDescription = (EditText) view.findViewById(R.id.description);
+
+                mInfoName.setOnFocusChangeListener(new OnFocusChangeListener() {
+
+                    @Override
+                    public void onFocusChange(View v, boolean hasFocus) {
+                        if (hasFocus) {
+                            return;
+                        }
+                        sanitizeName();
+                    }
+                });
+
+                mDialog = new AlertDialog.Builder(context)
+                        .setView(view)
+                        .setTitle(context.getString(R.string.bugreport_info_dialog_title))
+                        .setCancelable(false)
+                        .setPositiveButton(context.getString(com.android.internal.R.string.ok),
+                                null)
+                        .setNegativeButton(context.getString(com.android.internal.R.string.cancel),
+                                new DialogInterface.OnClickListener()
+                                {
+                                    @Override
+                                    public void onClick(DialogInterface dialog, int id)
+                                    {
+                                        if (!mTempName.equals(mSavedName)) {
+                                            // Must restore dumpstate's name since it was changed
+                                            // before user clicked OK.
+                                            setBugreportNameProperty(mPid, mSavedName);
+                                        }
+                                    }
+                                })
+                        .create();
+
+                mDialog.getWindow().setAttributes(
+                        new WindowManager.LayoutParams(
+                                WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG));
+
+            }
+
+            // Then set fields.
+            mSavedName = mTempName = name;
+            mPid = pid;
+            if (!TextUtils.isEmpty(name)) {
+                mInfoName.setText(name);
+            }
+            if (!TextUtils.isEmpty(title)) {
+                mInfoTitle.setText(title);
+            }
+            if (!TextUtils.isEmpty(description)) {
+                mInfoDescription.setText(description);
+            }
+
+            // And finally display it.
+            mDialog.show();
+
+            // TODO: in a traditional AlertDialog, when the positive button is clicked the
+            // dialog is always closed, but we need to validate the name first, so we need to
+            // get a reference to it, which is only available after it's displayed.
+            // It would be cleaner to use a regular dialog instead, but let's keep this
+            // workaround for now and change it later, when we add another button to take
+            // extra screenshots.
+            if (mOkButton == null) {
+                mOkButton = mDialog.getButton(DialogInterface.BUTTON_POSITIVE);
+                mOkButton.setOnClickListener(new View.OnClickListener() {
+
+                    @Override
+                    public void onClick(View view) {
+                        sanitizeName();
+                        final String name = mInfoName.getText().toString();
+                        final String title = mInfoTitle.getText().toString();
+                        final String description = mInfoDescription.getText().toString();
+
+                        updateBugreportInfo(mPid, name, title, description);
+                        mDialog.dismiss();
+                    }
+                });
+            }
+        }
+
+        /**
+         * Sanitizes the user-provided value for the {@code name} field, automatically replacing
+         * invalid characters if necessary.
+         */
+        private synchronized void sanitizeName() {
+            String name = mInfoName.getText().toString();
+            if (name.equals(mTempName)) {
+                if (DEBUG) Log.v(TAG, "name didn't change, no need to sanitize: " + name);
+                return;
+            }
+            final StringBuilder safeName = new StringBuilder(name.length());
+            boolean changed = false;
+            for (int i = 0; i < name.length(); i++) {
+                final char c = name.charAt(i);
+                if (isValid(c)) {
+                    safeName.append(c);
+                } else {
+                    changed = true;
+                    safeName.append('_');
+                }
+            }
+            if (changed) {
+                Log.v(TAG, "changed invalid name '" + name + "' to '" + safeName + "'");
+                name = safeName.toString();
+                mInfoName.setText(name);
+            }
+            mTempName = name;
+
+            // Must update system property for the cases where dumpstate finishes
+            // while the user is still entering other fields (like title or
+            // description)
+            setBugreportNameProperty(mPid, name);
+        }
+
+       /**
+         * Notifies the dialog that the bugreport has finished so it disables the {@code name}
+         * field.
+         * <p>Once the bugreport is finished dumpstate has already generated the final files, so
+         * changing the name would have no effect.
+         */
+        private synchronized void onBugreportFinished(int pid) {
+            if (mInfoName != null) {
+                mInfoName.setEnabled(false);
+                mInfoName.setText(mSavedName);
+            }
+        }
+
+    }
+
     /**
      * Information about a bugreport process while its in progress.
      */
@@ -704,6 +1002,18 @@
         String name;
 
         /**
+         * User-provided, one-line summary of the bug; when set, will be used as the subject
+         * of the {@link Intent#ACTION_SEND_MULTIPLE} intent.
+         */
+        String title;
+
+        /**
+         * User-provided, detailed description of the bugreport; when set, will be added to the body
+         * of the {@link Intent#ACTION_SEND_MULTIPLE} intent.
+         */
+        String description;
+
+        /**
          * Maximum progress of the bugreport generation.
          */
         int max;
@@ -761,6 +1071,7 @@
         public String toString() {
             final float percent = ((float) progress * 100 / max);
             return "pid: " + pid + ", name: " + name + ", finished: " + finished
+                    + "\n\ttitle: " + title + "\n\tdescription: " + description
                     + "\n\tfile: " + bugreportFile + "\n\tscreenshot: " + screenshotFile
                     + "\n\tprogress: " + progress + "/" + max + "(" + percent + ")"
                     + "\n\tlast_update: " + getFormattedLastUpdate();
diff --git a/packages/Shell/tests/Android.mk b/packages/Shell/tests/Android.mk
index 62a37bc..1e0eaac 100644
--- a/packages/Shell/tests/Android.mk
+++ b/packages/Shell/tests/Android.mk
@@ -8,9 +8,7 @@
 
 LOCAL_JAVA_LIBRARIES := android.test.runner
 
-# TODO: update and/or remove
 LOCAL_STATIC_JAVA_LIBRARIES := ub-uiautomator
-#LOCAL_STATIC_JAVA_LIBRARIES := android-support-v4 mockito-target ub-uiautomator
 
 LOCAL_PACKAGE_NAME := ShellTests
 LOCAL_INSTRUMENTATION_FOR := Shell
diff --git a/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java b/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java
index 1f4d749..7f609fa 100644
--- a/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java
+++ b/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java
@@ -94,7 +94,11 @@
     private static final int PID = 42;
     private static final String PROGRESS_PROPERTY = "dumpstate.42.progress";
     private static final String MAX_PROPERTY = "dumpstate.42.max";
+    private static final String NAME_PROPERTY = "dumpstate.42.name";
     private static final String NAME = "BUG, Y U NO REPORT?";
+    private static final String NEW_NAME = "Bug_Forrest_Bug";
+    private static final String TITLE = "Wimbugdom Champion 2015";
+    private String mDescription;
 
     private String mPlainTextPath;
     private String mZipPath;
@@ -120,10 +124,17 @@
         createTextFile(mScreenshotPath, SCREENSHOT_CONTENT);
         createZipFile(mZipPath, BUGREPORT_FILE, BUGREPORT_CONTENT);
 
+        // Creates a multi-line description.
+        StringBuilder sb = new StringBuilder();
+        for (int i = 1; i <= 20; i++) {
+            sb.append("All work and no play makes Shell a dull app!\n");
+        }
+        mDescription = sb.toString();
+
         BugreportPrefs.setWarningState(mContext, BugreportPrefs.STATE_HIDE);
     }
 
-    public void testFullWorkflow() throws Exception {
+    public void testProgress() throws Exception {
         resetProperties();
         sendBugreportStarted(1000);
 
@@ -145,6 +156,81 @@
         assertServiceNotRunning();
     }
 
+    public void testProgress_changeDetails() throws Exception {
+        resetProperties();
+        sendBugreportStarted(1000);
+
+        DetailsUi detailsUi = new DetailsUi(mUiBot);
+
+        // Check initial name.
+        String actualName = detailsUi.nameField.getText().toString();
+        assertEquals("Wrong value on field 'name'", NAME, actualName);
+
+        // Change name - it should have changed system property once focus is changed.
+        detailsUi.nameField.setText(NEW_NAME);
+        detailsUi.focusAwayFromName();
+        assertPropertyValue(NAME_PROPERTY, NEW_NAME);
+
+        // Cancel the dialog to make sure property was restored.
+        detailsUi.clickCancel();
+        assertPropertyValue(NAME_PROPERTY, NAME);
+
+        // Now try to set an invalid name.
+        detailsUi.reOpen();
+        detailsUi.nameField.setText("/etc/passwd");
+        detailsUi.clickOk();
+        assertPropertyValue(NAME_PROPERTY, "_etc_passwd");
+
+        // Finally, make the real changes.
+        detailsUi.reOpen();
+        detailsUi.nameField.setText(NEW_NAME);
+        detailsUi.titleField.setText(TITLE);
+        detailsUi.descField.setText(mDescription);
+
+        detailsUi.clickOk();
+
+        assertPropertyValue(NAME_PROPERTY, NEW_NAME);
+        assertProgressNotification(NEW_NAME, "0.00%");
+
+        Bundle extras = sendBugreportFinishedAndGetSharedIntent(PID, mPlainTextPath,
+                mScreenshotPath);
+        assertActionSendMultiple(extras, TITLE, mDescription, BUGREPORT_CONTENT, SCREENSHOT_CONTENT);
+
+        assertServiceNotRunning();
+    }
+
+    public void testProgress_bugreportFinishedWhileChangingDetails() throws Exception {
+        resetProperties();
+        sendBugreportStarted(1000);
+
+        DetailsUi detailsUi = new DetailsUi(mUiBot);
+
+        // Finish the bugreport while user's still typing the name.
+        detailsUi.nameField.setText(NEW_NAME);
+        sendBugreportFinished(PID, mPlainTextPath, mScreenshotPath);
+
+        // Wait until the share notifcation is received...
+        mUiBot.getNotification(mContext.getString(R.string.bugreport_finished_title));
+        // ...then close notification bar.
+        mContext.sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
+
+        // Make sure UI was updated properly.
+        assertFalse("didn't disable name on UI", detailsUi.nameField.isEnabled());
+        assertEquals("didn't revert name on UI", NAME, detailsUi.nameField.getText().toString());
+
+        // Finish changing other fields.
+        detailsUi.titleField.setText(TITLE);
+        detailsUi.descField.setText(mDescription);
+        detailsUi.clickOk();
+
+        // Finally, share bugreport.
+        Bundle extras = acceptBugreportAndGetSharedIntent();
+        assertActionSendMultiple(extras, TITLE, mDescription, BUGREPORT_CONTENT,
+                SCREENSHOT_CONTENT);
+
+        assertServiceNotRunning();
+    }
+
     public void testBugreportFinished_withWarning() throws Exception {
         // Explicitly shows the warning.
         BugreportPrefs.setWarningState(mContext, BugreportPrefs.STATE_SHOW);
@@ -204,14 +290,18 @@
     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);
+        openProgressNotification();
         Log.v(TAG, "Looking for progress notification details: '" + name + "-" + percent + "'");
         mUiBot.getObject(name);
         mUiBot.getObject(percent);
     }
 
+    private void openProgressNotification() {
+        String title = mContext.getString(R.string.bugreport_in_progress_title);
+        Log.v(TAG, "Looking for progress notification title: '" + title + "'");
+        mUiBot.getNotification(title);
+    }
+
     void resetProperties() {
         // TODO: call method to remove property instead
         SystemProperties.set(PROGRESS_PROPERTY, "0");
@@ -270,7 +360,6 @@
 
     /**
      * Sends a "bugreport finished" intent.
-     *
      */
     private void sendBugreportFinished(Integer pid, String bugreportPath, String screenshotPath) {
         Intent intent = new Intent(INTENT_BUGREPORT_FINISHED);
@@ -292,13 +381,21 @@
      */
     private void assertActionSendMultiple(Bundle extras, String bugreportContent,
             String screenshotContent) throws IOException {
+        assertActionSendMultiple(extras, ZIP_FILE, null, bugreportContent, screenshotContent);
+    }
+
+    private void assertActionSendMultiple(Bundle extras, String subject, String description,
+            String bugreportContent, String screenshotContent) throws IOException {
         String body = extras.getString(Intent.EXTRA_TEXT);
         assertContainsRegex("missing build info",
                 SystemProperties.get("ro.build.description"), body);
         assertContainsRegex("missing serial number",
                 SystemProperties.get("ro.serialno"), body);
+        if (description != null) {
+            assertContainsRegex("missing description", description, body);
+        }
 
-        assertEquals("wrong subject", ZIP_FILE, extras.getString(Intent.EXTRA_SUBJECT));
+        assertEquals("wrong subject", subject, extras.getString(Intent.EXTRA_SUBJECT));
 
         List<Uri> attachments = extras.getParcelableArrayList(Intent.EXTRA_STREAM);
         int expectedSize = screenshotContent != null ? 2 : 1;
@@ -355,6 +452,11 @@
         fail("Did not find entry '" + entryName + "' on file '" + uri + "'");
     }
 
+    private void assertPropertyValue(String key, String expectedValue) {
+        String actualValue = SystemProperties.get(key);
+        assertEquals("Wrong value for property '" + key + "'", expectedValue, actualValue);
+    }
+
     private void assertServiceNotRunning() {
         String service = BugreportProgressService.class.getName();
         assertFalse("Service '" + service + "' is still running", isServiceRunning(service));
@@ -402,4 +504,55 @@
         Log.v(TAG, "Path for '" + file + "': " + path);
         return path;
     }
+
+    /**
+     * Helper class containing the UiObjects present in the bugreport info dialog.
+     */
+    private final class DetailsUi {
+
+        final UiObject detailsButton;
+        final UiObject nameField;
+        final UiObject titleField;
+        final UiObject descField;
+        final UiObject okButton;
+        final UiObject cancelButton;
+
+        /**
+         * Gets the UI objects by opening the progress notification and clicking DETAILS.
+         */
+        DetailsUi(UiBot uiBot) {
+            openProgressNotification();
+            detailsButton = mUiBot.getVisibleObject(
+                    mContext.getString(R.string.bugreport_info_action).toUpperCase());
+            mUiBot.click(detailsButton, "details_button");
+            // TODO: unhardcode resource ids
+            nameField = mUiBot.getVisibleObjectById("com.android.shell:id/name");
+            titleField = mUiBot.getVisibleObjectById("com.android.shell:id/title");
+            descField = mUiBot.getVisibleObjectById("com.android.shell:id/description");
+            okButton = mUiBot.getObjectById("android:id/button1");
+            cancelButton = mUiBot.getObjectById("android:id/button2");
+        }
+
+        /**
+         * Takes focus away from the name field so it can be validated.
+         */
+        void focusAwayFromName() {
+            mUiBot.click(titleField, "title_field"); // Change focus.
+            mUiBot.pressBack(); // Dismiss keyboard.
+        }
+
+        void reOpen() {
+            openProgressNotification();
+            mUiBot.click(detailsButton, "details_button");
+
+        }
+
+        void clickOk() {
+            mUiBot.click(okButton, "details_ok_button");
+        }
+
+        void clickCancel() {
+            mUiBot.click(cancelButton, "details_cancel_button");
+        }
+    }
 }
diff --git a/packages/Shell/tests/src/com/android/shell/UiBot.java b/packages/Shell/tests/src/com/android/shell/UiBot.java
index c871727..384c3da 100644
--- a/packages/Shell/tests/src/com/android/shell/UiBot.java
+++ b/packages/Shell/tests/src/com/android/shell/UiBot.java
@@ -79,6 +79,17 @@
     }
 
     /**
+     * Gets an object that might not yet be available in current UI.
+     *
+     * @param id Object's fully-qualified resource id (like {@code android:id/button1})
+     */
+    public UiObject getObjectById(String id) {
+        boolean gotIt = mDevice.wait(Until.hasObject(By.res(id)), mTimeout);
+        assertTrue("object with id '(" + id + "') not visible yet", gotIt);
+        return getVisibleObjectById(id);
+    }
+
+    /**
      * Gets an object which is guaranteed to be present in the current UI.
      *
      * @param text Object's text as displayed by the UI.
@@ -90,6 +101,18 @@
     }
 
     /**
+     * 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 getVisibleObjectById(String id) {
+        UiObject uiObject = mDevice.findObject(new UiSelector().resourceId(id));
+        assertTrue("could not find object with id '" + id+ "'", uiObject.exists());
+        return uiObject;
+    }
+
+
+    /**
      * Clicks on a UI element.
      *
      * @param uiObject UI element to be clicked.
@@ -151,4 +174,8 @@
             click(activity, name);
         }
     }
+
+    public void pressBack() {
+        mDevice.pressBack();
+    }
 }
diff --git a/packages/Shell/tests/src/com/android/shell/UtilitiesTest.java b/packages/Shell/tests/src/com/android/shell/UtilitiesTest.java
new file mode 100644
index 0000000..51b7ba8
--- /dev/null
+++ b/packages/Shell/tests/src/com/android/shell/UtilitiesTest.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.shell;
+
+import android.test.suitebuilder.annotation.SmallTest;
+import junit.framework.TestCase;
+import static com.android.shell.BugreportProgressService.isValid;
+
+@SmallTest
+public class UtilitiesTest extends TestCase {
+
+    public void testIsValidChar_valid() {
+        String validChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
+        for (int i = 0; i < validChars.length(); i++) {
+            char c = validChars.charAt(i);
+            assertTrue("char '" + c + "' should be valid", isValid(c));
+        }
+    }
+
+    public void testIsValidChar_invalid() {
+        String validChars = "/.<>;:'\'\"\\+=*&^%$#@!`~áéíóúãñÂÊÎÔÛ";
+        for (int i = 0; i < validChars.length(); i++) {
+            char c = validChars.charAt(i);
+            assertFalse("char '" + c + "' should not be valid", isValid(c));
+        }
+    }
+}
diff --git a/packages/SystemUI/Android.mk b/packages/SystemUI/Android.mk
index eb63e5d..61cad2f 100644
--- a/packages/SystemUI/Android.mk
+++ b/packages/SystemUI/Android.mk
@@ -8,7 +8,11 @@
 
 LOCAL_STATIC_JAVA_LIBRARIES := \
     Keyguard \
-    android-support-v7-recyclerview
+    android-support-v7-recyclerview \
+    android-support-v7-preference \
+    android-support-v7-appcompat \
+    android-support-v14-preference
+
 LOCAL_JAVA_LIBRARIES := telephony-common
 
 LOCAL_PACKAGE_NAME := SystemUI
@@ -22,10 +26,13 @@
 LOCAL_RESOURCE_DIR := \
     frameworks/base/packages/Keyguard/res \
     $(LOCAL_PATH)/res \
+    frameworks/support/v7/preference/res \
+    frameworks/support/v14/preference/res \
+    frameworks/support/v7/appcompat/res \
     frameworks/support/v7/recyclerview/res
 
 LOCAL_AAPT_FLAGS := --auto-add-overlay \
-     --extra-packages com.android.keyguard:android.support.v7.recyclerview
+    --extra-packages com.android.keyguard:android.support.v7.recyclerview:android.support.v7.preference:android.support.v14.preference:android.support.v7.appcompat
 
 ifneq ($(SYSTEM_UI_INCREMENTAL_BUILDS),)
     LOCAL_PROGUARD_ENABLED := disabled
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 51b84f5..02ddae6 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -186,7 +186,7 @@
         <activity android:name=".tuner.TunerActivity"
                   android:enabled="false"
                   android:icon="@drawable/tuner"
-                  android:theme="@android:style/Theme.Material.Settings"
+                  android:theme="@style/TunerSettings"
                   android:label="@string/system_ui_tuner"
                   android:process=":tuner"
                   android:exported="true">
diff --git a/packages/SystemUI/proguard.flags b/packages/SystemUI/proguard.flags
index 75e7959..6a10c2c 100644
--- a/packages/SystemUI/proguard.flags
+++ b/packages/SystemUI/proguard.flags
@@ -27,3 +27,9 @@
     public float getTaskProgress();
     public void setTaskProgress(float);
 }
+
+-keepclasseswithmembers class * {
+    public <init>(android.content.Context, android.util.AttributeSet);
+}
+
+-keep class ** extends android.support.v14.preference.PreferenceFragment
diff --git a/packages/SystemUI/res/layout/recents_history_task.xml b/packages/SystemUI/res/layout/recents_history_task.xml
index b9de156..ae11006 100644
--- a/packages/SystemUI/res/layout/recents_history_task.xml
+++ b/packages/SystemUI/res/layout/recents_history_task.xml
@@ -13,15 +13,30 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<TextView
+<LinearLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:theme="@android:style/Theme.Material"
     android:layout_width="match_parent"
     android:layout_height="48dp"
-    android:paddingLeft="32dp"
-    android:gravity="start|center_vertical"
-    android:textSize="14sp"
-    android:textColor="#FFFFFF"
-    android:fontFamily="sans-serif-medium"
-    android:background="?android:selectableItemBackground"
-    android:alpha="1" />
\ No newline at end of file
+    android:orientation="horizontal"
+    android:clickable="true"
+    android:focusable="true"
+    android:background="?android:selectableItemBackground">
+    <ImageView
+        android:id="@+id/icon"
+        android:layout_width="32dp"
+        android:layout_height="32dp"
+        android:layout_gravity="center"
+        android:layout_marginStart="16dp" />
+    <TextView
+        android:id="@+id/description"
+        android:layout_width="0dp"
+        android:layout_height="match_parent"
+        android:layout_weight="1"
+        android:layout_gravity="end"
+        android:paddingStart="16dp"
+        android:gravity="start|center_vertical"
+        android:textSize="14sp"
+        android:textColor="#FFFFFF"
+        android:fontFamily="sans-serif-medium" />
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 47ad6dc..aad428a 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -315,4 +315,8 @@
         <item name="android:layout_height">48dp</item>
     </style>
 
+    <style name="TunerSettings" parent="@android:style/Theme.Material.Settings">
+        <item name="preferenceTheme">@android:style/Theme.Material.Settings</item>
+    </style>
+
 </resources>
diff --git a/packages/SystemUI/res/xml/tuner_prefs.xml b/packages/SystemUI/res/xml/tuner_prefs.xml
index 43359b3..6103216 100644
--- a/packages/SystemUI/res/xml/tuner_prefs.xml
+++ b/packages/SystemUI/res/xml/tuner_prefs.xml
@@ -24,6 +24,7 @@
         sysui:defValue="true" />
 
     <PreferenceScreen
+        android:key="status_bar"
         android:title="@string/status_bar" >
 
         <com.android.systemui.tuner.StatusBarSwitch
@@ -70,6 +71,7 @@
 
 
     <PreferenceScreen
+        android:key="overview"
         android:title="@string/overview" >
 
         <com.android.systemui.tuner.TunerSwitch
@@ -97,7 +99,8 @@
 
     <Preference
         android:key="demo_mode"
-        android:title="@string/demo_mode" />
+        android:title="@string/demo_mode"
+        android:fragment="com.android.systemui.tuner.DemoModeFragment" />
 
     <!-- Warning, this goes last. -->
     <Preference
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Constants.java b/packages/SystemUI/src/com/android/systemui/recents/Constants.java
index c08fb05..070b395 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Constants.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Constants.java
@@ -27,6 +27,7 @@
         public static final int DismissSourceKeyboard = 0;
         public static final int DismissSourceSwipeGesture = 1;
         public static final int DismissSourceHeaderButton = 2;
+        public static final int DismissSourceHistorySwipeGesture = 3;
     }
 
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
index 9f0ac35..b3ce4a6 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
@@ -51,10 +51,12 @@
 import com.android.systemui.recents.events.activity.LaunchTaskFailedEvent;
 import com.android.systemui.recents.events.activity.LaunchTaskSucceededEvent;
 import com.android.systemui.recents.events.activity.ShowHistoryEvent;
+import com.android.systemui.recents.events.activity.TaskStackUpdatedEvent;
 import com.android.systemui.recents.events.activity.ToggleRecentsEvent;
 import com.android.systemui.recents.events.component.RecentsVisibilityChangedEvent;
 import com.android.systemui.recents.events.component.ScreenPinningRequestEvent;
 import com.android.systemui.recents.events.ui.AllTaskViewsDismissedEvent;
+import com.android.systemui.recents.events.ui.DismissTaskEvent;
 import com.android.systemui.recents.events.ui.DismissTaskViewEvent;
 import com.android.systemui.recents.events.ui.ShowApplicationInfoEvent;
 import com.android.systemui.recents.events.ui.StackViewScrolledEvent;
@@ -526,6 +528,22 @@
     }
 
     @Override
+    public void onMultiWindowModeChanged(boolean multiWindowMode) {
+        super.onMultiWindowModeChanged(multiWindowMode);
+        if (!multiWindowMode) {
+            RecentsTaskLoader loader = Recents.getTaskLoader();
+            RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options();
+            launchOpts.loadIcons = false;
+            launchOpts.loadThumbnails = false;
+            launchOpts.onlyLoadForCache = true;
+            RecentsTaskLoadPlan loadPlan = loader.createLoadPlan(this);
+            loader.preloadTasks(loadPlan, false);
+            loader.loadTasks(this, loadPlan, launchOpts);
+            EventBus.getDefault().send(new TaskStackUpdatedEvent(loadPlan.getTaskStack()));
+        }
+    }
+
+    @Override
     public boolean onKeyDown(int keyCode, KeyEvent event) {
         switch (keyCode) {
             case KeyEvent.KEYCODE_TAB: {
@@ -709,7 +727,7 @@
         MetricsLogger.count(this, "overview_app_info", 1);
     }
 
-    public final void onBusEvent(DismissTaskViewEvent event) {
+    public final void onBusEvent(DismissTaskEvent event) {
         // Remove any stored data from the loader
         RecentsTaskLoader loader = Recents.getTaskLoader();
         loader.deleteTaskData(event.task, false);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/activity/TaskStackUpdatedEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/activity/TaskStackUpdatedEvent.java
new file mode 100644
index 0000000..b94ed7b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/activity/TaskStackUpdatedEvent.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recents.events.activity;
+
+import com.android.systemui.recents.RecentsAppWidgetHost;
+import com.android.systemui.recents.events.EventBus;
+import com.android.systemui.recents.model.TaskStack;
+
+/**
+ * This is sent by the activity whenever the task stach has changed.
+ */
+public class TaskStackUpdatedEvent extends EventBus.Event {
+
+    /**
+     * A new TaskStack instance representing the latest stack state.
+     */
+    public final TaskStack stack;
+
+    public TaskStackUpdatedEvent(TaskStack stack) {
+        this.stack = stack;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/ui/DismissTaskEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/ui/DismissTaskEvent.java
new file mode 100644
index 0000000..bcbbde8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/ui/DismissTaskEvent.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recents.events.ui;
+
+import com.android.systemui.recents.events.EventBus;
+import com.android.systemui.recents.model.Task;
+import com.android.systemui.recents.views.TaskView;
+
+/**
+ * This is sent when a {@link Task} has been dismissed.
+ */
+public class DismissTaskEvent extends EventBus.Event {
+
+    public final Task task;
+
+    public DismissTaskEvent(Task task) {
+        this.task = task;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryAdapter.java b/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryAdapter.java
index 06265bd..76439c0 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryAdapter.java
@@ -20,14 +20,22 @@
 import android.content.Context;
 import android.support.v7.widget.RecyclerView;
 import android.text.format.DateFormat;
+import android.util.SparseIntArray;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
+import android.widget.ImageView;
 import android.widget.TextView;
 import com.android.systemui.R;
 import com.android.systemui.recents.Recents;
+import com.android.systemui.recents.events.EventBus;
+import com.android.systemui.recents.events.activity.HideHistoryButtonEvent;
+import com.android.systemui.recents.events.activity.HideHistoryEvent;
+import com.android.systemui.recents.misc.ReferenceCountedTrigger;
 import com.android.systemui.recents.misc.SystemServicesProxy;
+import com.android.systemui.recents.model.RecentsTaskLoader;
 import com.android.systemui.recents.model.Task;
+import com.android.systemui.recents.model.TaskStack;
 
 import java.util.ArrayList;
 import java.util.Calendar;
@@ -49,28 +57,48 @@
     static final int TASK_ROW_VIEW_TYPE = 1;
 
     /**
-     * View holder implementation.
+     * View holder implementation. The {@param TaskCallbacks} are only called for TaskRow view
+     * holders.
      */
-    public static class ViewHolder extends RecyclerView.ViewHolder {
-        public View mContent;
+    public static class ViewHolder extends RecyclerView.ViewHolder implements Task.TaskCallbacks {
+        public final View content;
 
         public ViewHolder(View v) {
             super(v);
-            mContent = v;
+            content = v;
+        }
+
+        @Override
+        public void onTaskDataLoaded(Task task) {
+            // This callback is only made for TaskRow view holders
+            ImageView iv = (ImageView) content.findViewById(R.id.icon);
+            iv.setImageDrawable(task.applicationIcon);
+        }
+
+        @Override
+        public void onTaskDataUnloaded() {
+            // This callback is only made for TaskRow view holders
+            ImageView iv = (ImageView) content.findViewById(R.id.icon);
+            iv.setImageBitmap(null);
+        }
+
+        @Override
+        public void onTaskStackIdChanged() {
+            // Do nothing, this callback is only made for TaskRow view holders
         }
     }
 
     /**
      * A single row of content.
      */
-    private interface Row {
+    interface Row {
         int getViewType();
     }
 
     /**
      * A date row.
      */
-    private static class DateRow implements Row {
+    static class DateRow implements Row {
 
         public final String date;
 
@@ -87,20 +115,20 @@
     /**
      * A task row.
      */
-    private static class TaskRow implements Row, View.OnClickListener {
+    static class TaskRow implements Row, View.OnClickListener {
 
-        public final String description;
-        private final int mTaskId;
+        public final Task task;
+        public final int dateKey;
 
-        public TaskRow(Task task) {
-            mTaskId = task.key.id;
-            description = task.activityLabel;
+        public TaskRow(Task task, int dateKey) {
+            this.task = task;
+            this.dateKey = dateKey;
         }
 
         @Override
         public void onClick(View v) {
             SystemServicesProxy ssp = Recents.getSystemServices();
-            ssp.startActivityFromRecents(v.getContext(), mTaskId, description,
+            ssp.startActivityFromRecents(v.getContext(), task.key.id, task.activityLabel,
                     ActivityOptions.makeBasic());
         }
 
@@ -114,6 +142,8 @@
     private LayoutInflater mLayoutInflater;
     private final List<Task> mTasks = new ArrayList<>();
     private final List<Row> mRows = new ArrayList<>();
+    private final SparseIntArray mTaskRowCount = new SparseIntArray();
+    private TaskStack mStack;
 
     public RecentsHistoryAdapter(Context context) {
         mLayoutInflater = LayoutInflater.from(context);
@@ -122,17 +152,17 @@
     /**
      * Updates this adapter with the given tasks.
      */
-    public void updateTasks(Context context, List<Task> tasks) {
+    public void updateTasks(Context context, TaskStack stack) {
         mContext = context;
-        mTasks.clear();
-        mTasks.addAll(tasks);
+        mStack = stack;
 
         final Locale l = context.getResources().getConfiguration().locale;
         final String dateFormatStr = DateFormat.getBestDateTimePattern(l, "EEEEMMMMd");
-        final List<Task> tasksMostRecent = new ArrayList<>(tasks);
+        final List<Task> tasksMostRecent = new ArrayList<>(stack.getHistoricalTasks());
         Collections.reverse(tasksMostRecent);
-        int prevDayKey = -1;
+        int prevDateKey = -1;
         mRows.clear();
+        mTaskRowCount.clear();
         for (Task task : tasksMostRecent) {
             if (task.isFreeformTask()) {
                 continue;
@@ -140,34 +170,46 @@
 
             Calendar cal = Calendar.getInstance(l);
             cal.setTimeInMillis(task.key.lastActiveTime);
-            int dayKey = Objects.hash(cal.get(Calendar.YEAR), cal.get(Calendar.DAY_OF_YEAR));
-            if (dayKey != prevDayKey) {
-                prevDayKey = dayKey;
+            int dateKey = Objects.hash(cal.get(Calendar.YEAR), cal.get(Calendar.DAY_OF_YEAR));
+            if (dateKey != prevDateKey) {
+                prevDateKey = dateKey;
                 mRows.add(new DateRow(DateFormat.format(dateFormatStr, cal).toString()));
             }
-            mRows.add(new TaskRow(task));
+            mRows.add(new TaskRow(task, dateKey));
+            mTaskRowCount.put(dateKey, mTaskRowCount.get(dateKey, 0) + 1);
         }
         notifyDataSetChanged();
     }
 
     /**
-     * Removes historical tasks beloning to the specified package and user.
+     * Removes historical tasks belonging to the specified package and user. We do not need to
+     * remove the task from the TaskStack since the TaskStackView will also receive this event.
      */
     public void removeTasks(String packageName, int userId) {
         boolean packagesRemoved = false;
-        for (int i = mTasks.size() - 1; i >= 0; i--) {
-            Task task = mTasks.get(i);
-            String taskPackage = task.key.getComponent().getPackageName();
-            if (task.key.userId == userId && taskPackage.equals(packageName)) {
-                mTasks.remove(i);
-                packagesRemoved = true;
+        for (int i = mRows.size() - 1; i >= 0; i--) {
+            Row row = mRows.get(i);
+            if (row.getViewType() == TASK_ROW_VIEW_TYPE) {
+                TaskRow taskRow = (TaskRow) row;
+                Task task = taskRow.task;
+                String taskPackage = task.key.getComponent().getPackageName();
+                if (task.key.userId == userId && taskPackage.equals(packageName)) {
+                    i = removeTaskRow(i);
+                }
             }
         }
-        if (packagesRemoved) {
-            updateTasks(mContext, new ArrayList<Task>(mTasks));
+        if (mRows.isEmpty()) {
+            dismissHistory();
         }
     }
 
+    /**
+     * Returns the row at the given {@param position}.
+     */
+    public Row getRow(int position) {
+        return mRows.get(position);
+    }
+
     @Override
     public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
         switch (viewType) {
@@ -184,25 +226,55 @@
 
     @Override
     public void onBindViewHolder(ViewHolder holder, int position) {
+        RecentsTaskLoader loader = Recents.getTaskLoader();
+
         Row row = mRows.get(position);
-        int viewType = mRows.get(position).getViewType();
+        int viewType = row.getViewType();
         switch (viewType) {
             case DATE_ROW_VIEW_TYPE: {
-                TextView tv = (TextView) holder.mContent;
+                TextView tv = (TextView) holder.content;
                 tv.setText(((DateRow) row).date);
                 break;
             }
             case TASK_ROW_VIEW_TYPE: {
-                TextView tv = (TextView) holder.mContent;
                 TaskRow taskRow = (TaskRow) row;
-                tv.setText(taskRow.description);
-                tv.setOnClickListener(taskRow);
+                taskRow.task.addCallback(holder);
+                TextView tv = (TextView) holder.content.findViewById(R.id.description);
+                tv.setText(taskRow.task.activityLabel);
+                holder.content.setOnClickListener(taskRow);
+                loader.loadTaskData(taskRow.task);
                 break;
             }
         }
     }
 
     @Override
+    public void onViewRecycled(ViewHolder holder) {
+        RecentsTaskLoader loader = Recents.getTaskLoader();
+
+        int position = holder.getAdapterPosition();
+        if (position != RecyclerView.NO_POSITION) {
+            Row row = mRows.get(position);
+            int viewType = row.getViewType();
+            if (viewType == TASK_ROW_VIEW_TYPE) {
+                TaskRow taskRow = (TaskRow) row;
+                taskRow.task.removeCallback(holder);
+                loader.unloadTaskData(taskRow.task);
+            }
+        }
+    }
+
+    public void onTaskRemoved(Task task, int position) {
+        // Since this is removed from the history, we need to update the stack as well to ensure
+        // that the model is correct
+        mStack.removeTask(task);
+        removeTaskRow(position);
+        if (mRows.isEmpty()) {
+            dismissHistory();
+        }
+    }
+
+    @Override
     public int getItemCount() {
         return mRows.size();
     }
@@ -211,4 +283,40 @@
     public int getItemViewType(int position) {
         return mRows.get(position).getViewType();
     }
+
+    /**
+     * Removes a task row, also removing the associated {@link DateRow} if there are no more tasks
+     * in that date group.
+     *
+     * @param position an adapter position of a task row such that 0 < position < num rows.
+     * @return the index of the last removed row
+     */
+    private int removeTaskRow(int position) {
+        // Remove the task at that row
+        TaskRow taskRow = (TaskRow) mRows.remove(position);
+        int numTasks = mTaskRowCount.get(taskRow.dateKey) - 1;
+        mTaskRowCount.put(taskRow.dateKey, numTasks);
+        notifyItemRemoved(position);
+
+        if (numTasks == 0) {
+            // If that was the last task row in the group, then remove the date as well
+            mRows.remove(position - 1);
+            mTaskRowCount.removeAt(mTaskRowCount.indexOfKey(taskRow.dateKey));
+            notifyItemRemoved(position - 1);
+            return position - 1;
+        } else {
+            return position;
+        }
+    }
+
+    /**
+     * Dismisses history back to the stack view.
+     */
+    private void dismissHistory() {
+        ReferenceCountedTrigger t = new ReferenceCountedTrigger(mContext);
+        t.increment();
+        EventBus.getDefault().send(new HideHistoryEvent(true /* animate */, t));
+        t.decrement();
+        EventBus.getDefault().send(new HideHistoryButtonEvent());
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryItemTouchCallbacks.java b/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryItemTouchCallbacks.java
new file mode 100644
index 0000000..e0a2730
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryItemTouchCallbacks.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recents.history;
+
+import android.content.Context;
+import android.support.v7.widget.RecyclerView;
+import android.support.v7.widget.helper.ItemTouchHelper;
+import com.android.internal.logging.MetricsLogger;
+import com.android.systemui.recents.Constants;
+import com.android.systemui.recents.events.EventBus;
+import com.android.systemui.recents.events.ui.DismissTaskEvent;
+
+
+/**
+ * An item touch handler for items in the history view.
+ */
+public class RecentsHistoryItemTouchCallbacks extends ItemTouchHelper.SimpleCallback {
+
+    private Context mContext;
+    private RecentsHistoryAdapter mAdapter;
+
+    public RecentsHistoryItemTouchCallbacks(Context context, RecentsHistoryAdapter adapter) {
+        super(0, ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT);
+        mContext = context;
+        mAdapter = adapter;
+    }
+
+    @Override
+    public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder,
+            RecyclerView.ViewHolder target) {
+        return false;
+    }
+
+    @Override
+    public int getSwipeDirs(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
+        int viewType = mAdapter.getItemViewType(viewHolder.getAdapterPosition());
+        switch (viewType) {
+            case RecentsHistoryAdapter.DATE_ROW_VIEW_TYPE:
+                // Disallow swiping
+                return 0;
+            default:
+                return super.getSwipeDirs(recyclerView, viewHolder);
+        }
+    }
+
+    @Override
+    public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
+        int position = viewHolder.getAdapterPosition();
+        if (position != RecyclerView.NO_POSITION) {
+            RecentsHistoryAdapter.Row row = mAdapter.getRow(position);
+            RecentsHistoryAdapter.TaskRow taskRow = (RecentsHistoryAdapter.TaskRow) row;
+
+            // Remove the task from the system
+            EventBus.getDefault().send(new DismissTaskEvent(taskRow.task));
+            mAdapter.onTaskRemoved(taskRow.task, position);
+
+            // Keep track of deletions by swiping within history
+            MetricsLogger.histogram(mContext, "overview_task_dismissed_source",
+                    Constants.Metrics.DismissSourceHistorySwipeGesture);
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryView.java b/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryView.java
index 1163f14..9524da5 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryView.java
@@ -16,12 +16,12 @@
 
 package com.android.systemui.recents.history;
 
-import android.content.ComponentName;
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Rect;
 import android.support.v7.widget.LinearLayoutManager;
 import android.support.v7.widget.RecyclerView;
+import android.support.v7.widget.helper.ItemTouchHelper;
 import android.util.AttributeSet;
 import android.view.View;
 import android.view.WindowInsets;
@@ -36,12 +36,7 @@
 import com.android.systemui.recents.events.activity.PackagesChangedEvent;
 import com.android.systemui.recents.misc.ReferenceCountedTrigger;
 import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.model.Task;
 import com.android.systemui.recents.model.TaskStack;
-import com.android.systemui.recents.views.TaskView;
-
-import java.util.ArrayList;
-import java.util.HashSet;
 
 /**
  * A list of the recent tasks that are not in the stack.
@@ -53,6 +48,7 @@
 
     private RecyclerView mRecyclerView;
     private RecentsHistoryAdapter mAdapter;
+    private RecentsHistoryItemTouchCallbacks mItemTouchHandler;
     private boolean mIsVisible;
     private Rect mSystemInsets = new Rect();
 
@@ -77,6 +73,7 @@
         super(context, attrs, defStyleAttr, defStyleRes);
         Resources res = context.getResources();
         mAdapter = new RecentsHistoryAdapter(context);
+        mItemTouchHandler = new RecentsHistoryItemTouchCallbacks(context, mAdapter);
         mHistoryTransitionDuration = res.getInteger(R.integer.recents_history_transition_duration);
         mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(context,
                 com.android.internal.R.interpolator.fast_out_slow_in);
@@ -101,7 +98,7 @@
                         .start();
             }
         });
-        mAdapter.updateTasks(getContext(), stack.getHistoricalTasks());
+        mAdapter.updateTasks(getContext(), stack);
         mIsVisible = true;
     }
 
@@ -156,6 +153,8 @@
         mRecyclerView = (RecyclerView) findViewById(R.id.list);
         mRecyclerView.setAdapter(mAdapter);
         mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
+        ItemTouchHelper touchHelper = new ItemTouchHelper(mItemTouchHandler);
+        touchHelper.attachToRecyclerView(mRecyclerView);
     }
 
     @Override
@@ -180,7 +179,6 @@
 
     @Override
     protected void onAttachedToWindow() {
-        SystemServicesProxy ssp = Recents.getSystemServices();
         EventBus.getDefault().register(this, RecentsActivity.EVENT_BUS_PRIORITY + 1);
         super.onAttachedToWindow();
     }
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 dae6e94..0884695d 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
@@ -200,7 +200,7 @@
         allTasks.addAll(stackTasks);
         allTasks.addAll(freeformTasks);
         mStack = new TaskStack();
-        mStack.setTasks(allTasks);
+        mStack.setTasks(allTasks, false /* notifyStackChanges */);
         mStack.createAffiliatedGroupings(mContext);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
index 3aed3f3..73c0adb 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
@@ -26,6 +26,7 @@
 import com.android.systemui.recents.misc.SystemServicesProxy;
 import com.android.systemui.recents.misc.Utilities;
 
+import java.util.ArrayList;
 import java.util.Objects;
 
 
@@ -36,7 +37,7 @@
     /* Task callbacks */
     public interface TaskCallbacks {
         /* Notifies when a task has been bound */
-        public void onTaskDataLoaded();
+        public void onTaskDataLoaded(Task task);
         /* Notifies when a task has been unbound */
         public void onTaskDataUnloaded();
         /* Notifies when a task's stack id has changed. */
@@ -110,7 +111,7 @@
     public String iconFilename;
     public Rect bounds;
 
-    private TaskCallbacks mCb;
+    private ArrayList<TaskCallbacks> mCallbacks = new ArrayList<>();
 
     public Task() {
         // Do nothing
@@ -157,16 +158,24 @@
         this.bounds = o.bounds;
     }
 
-    /** Set the callbacks */
-    public void setCallbacks(TaskCallbacks cb) {
-        mCb = cb;
+    /**
+     * Add a callback.
+     */
+    public void addCallback(TaskCallbacks cb) {
+        if (!mCallbacks.contains(cb)) {
+            mCallbacks.add(cb);
+        }
+    }
+
+    /**
+     * Remove a callback.
+     */
+    public void removeCallback(TaskCallbacks cb) {
+        mCallbacks.remove(cb);
     }
 
     /** Set the grouping */
     public void setGroup(TaskGrouping group) {
-        if (group != null && this.group != null) {
-            throw new RuntimeException("This task is already assigned to a group.");
-        }
         this.group = group;
     }
 
@@ -175,8 +184,9 @@
      */
     public void setStackId(int stackId) {
         key.stackId = stackId;
-        if (mCb != null) {
-            mCb.onTaskStackIdChanged();
+        int callbackCount = mCallbacks.size();
+        for (int i = 0; i < callbackCount; i++) {
+            mCallbacks.get(i).onTaskStackIdChanged();
         }
     }
 
@@ -192,8 +202,9 @@
     public void notifyTaskDataLoaded(Bitmap thumbnail, Drawable applicationIcon) {
         this.applicationIcon = applicationIcon;
         this.thumbnail = thumbnail;
-        if (mCb != null) {
-            mCb.onTaskDataLoaded();
+        int callbackCount = mCallbacks.size();
+        for (int i = 0; i < callbackCount; i++) {
+            mCallbacks.get(i).onTaskDataLoaded(this);
         }
     }
 
@@ -201,8 +212,9 @@
     public void notifyTaskDataUnloaded(Bitmap defaultThumbnail, Drawable defaultApplicationIcon) {
         applicationIcon = defaultApplicationIcon;
         thumbnail = defaultThumbnail;
-        if (mCb != null) {
-            mCb.onTaskDataUnloaded();
+        int callbackCount = mCallbacks.size();
+        for (int i = 0; i < callbackCount; i++) {
+            mCallbacks.get(i).onTaskDataUnloaded();
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
index 5d07b10..c4a71b3 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
@@ -206,11 +206,20 @@
 
     /** Task stack callbacks */
     public interface TaskStackCallbacks {
-        /* Notifies when a task has been removed from the stack */
+        /**
+         * Notifies when a new task has been added to the stack.
+         */
+        void onStackTaskAdded(TaskStack stack, Task newTask);
+
+        /**
+         * Notifies when a task has been removed from the stack.
+         */
         void onStackTaskRemoved(TaskStack stack, Task removedTask, boolean wasFrontMostTask,
             Task newFrontMostTask);
 
-        /* Notifies when a task has been removed from the history */
+        /**
+         * Notifies when a task has been removed from the history.
+         */
         void onHistoryTaskRemoved(TaskStack stack, Task removedTask);
     }
 
@@ -315,6 +324,7 @@
     // The task offset to apply to a task id as a group affiliation
     static final int IndividualTaskIdOffset = 1 << 16;
 
+    ArrayList<Task> mRawTaskList = new ArrayList<>();
     FilteredTaskList mStackTaskList = new FilteredTaskList();
     FilteredTaskList mHistoryTaskList = new FilteredTaskList();
     TaskStackCallbacks mCb;
@@ -397,9 +407,11 @@
         taskList.remove(t);
         // Remove it from the group as well, and if it is empty, remove the group
         TaskGrouping group = t.group;
-        group.removeTask(t);
-        if (group.getTaskCount() == 0) {
-            removeGroup(group);
+        if (group != null) {
+            group.removeTask(t);
+            if (group.getTaskCount() == 0) {
+                removeGroup(group);
+            }
         }
         // Update the lock-to-app state
         t.lockToThisTask = false;
@@ -409,7 +421,6 @@
     public void removeTask(Task t) {
         if (mStackTaskList.contains(t)) {
             boolean wasFrontMostTask = (getStackFrontMostTask() == t);
-            int removedTaskIndex = indexOfStackTask(t);
             removeTaskImpl(mStackTaskList, t);
             Task newFrontMostTask = getStackFrontMostTask();
             if (newFrontMostTask != null && newFrontMostTask.lockToTaskEnabled) {
@@ -430,19 +441,73 @@
 
     /**
      * Sets a few tasks in one go, without calling any callbacks.
+     *
+     * @param tasks the new set of tasks to replace the current set.
+     * @param notifyStackChanges whether or not to callback on specific changes to the list of tasks.
      */
-    public void setTasks(List<Task> tasks) {
+    public void setTasks(List<Task> tasks, boolean notifyStackChanges) {
+        // Compute a has set for each of the tasks
+        HashMap<Task.TaskKey, Task> currentTasksMap = createTaskKeyMapFromList(mRawTaskList);
+        HashMap<Task.TaskKey, Task> newTasksMap = createTaskKeyMapFromList(tasks);
+
+        ArrayList<Task> newTasks = new ArrayList<>();
+
+        // Disable notifications if there are no callbacks
+        if (mCb == null) {
+            notifyStackChanges = false;
+        }
+
+        // Remove any tasks that no longer exist
+        int taskCount = mRawTaskList.size();
+        for (int i = 0; i < taskCount; i++) {
+            Task task = mRawTaskList.get(i);
+            if (!newTasksMap.containsKey(task.key)) {
+                if (notifyStackChanges) {
+                    mCb.onStackTaskRemoved(this, task, i == (taskCount - 1), null);
+                }
+            }
+            task.setGroup(null);
+        }
+
+        // Add any new tasks
+        taskCount = tasks.size();
+        for (int i = 0; i < taskCount; i++) {
+            Task task = tasks.get(i);
+            if (!currentTasksMap.containsKey(task.key)) {
+                if (notifyStackChanges) {
+                    mCb.onStackTaskAdded(this, task);
+                }
+                newTasks.add(task);
+            } else {
+                newTasks.add(currentTasksMap.get(task.key));
+            }
+        }
+
+        // Sort all the tasks to ensure they are ordered correctly
+        Collections.sort(newTasks, LAST_ACTIVE_TIME_COMPARATOR);
+
+        // TODO: Update screen pinning for the new front-most task post refactoring lockToTask out
+        // of the Task
+
+        // Filter out the historical tasks from this new list
         ArrayList<Task> stackTasks = new ArrayList<>();
         ArrayList<Task> historyTasks = new ArrayList<>();
-        for (Task task : tasks) {
+        int newTaskCount = newTasks.size();
+        for (int i = 0; i < newTaskCount; i++) {
+            Task task = newTasks.get(i);
             if (task.isHistorical) {
                 historyTasks.add(task);
             } else {
                 stackTasks.add(task);
             }
         }
+
         mStackTaskList.set(stackTasks);
         mHistoryTaskList.set(historyTasks);
+        mRawTaskList.clear();
+        mRawTaskList.addAll(newTasks);
+        mGroups.clear();
+        mAffinitiesGroups.clear();
     }
 
     /** Gets the front task */
@@ -714,4 +779,17 @@
         }
         return str;
     }
-}
\ No newline at end of file
+
+    /**
+     * Given a list of tasks, returns a map of each task's key to the task.
+     */
+    private HashMap<Task.TaskKey, Task> createTaskKeyMapFromList(List<Task> tasks) {
+        HashMap<Task.TaskKey, Task> map = new HashMap<>();
+        int taskCount = tasks.size();
+        for (int i = 0; i < taskCount; i++) {
+            Task task = tasks.get(i);
+            map.put(task.key, task);
+        }
+        return map;
+    }
+}
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 21c08d1..53c02cb 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
@@ -50,6 +50,7 @@
 import com.android.systemui.recents.events.activity.LaunchTaskEvent;
 import com.android.systemui.recents.events.activity.ShowHistoryButtonEvent;
 import com.android.systemui.recents.events.activity.ShowHistoryEvent;
+import com.android.systemui.recents.events.activity.TaskStackUpdatedEvent;
 import com.android.systemui.recents.events.component.RecentsVisibilityChangedEvent;
 import com.android.systemui.recents.events.ui.DraggingInRecentsEndedEvent;
 import com.android.systemui.recents.events.ui.DraggingInRecentsEvent;
@@ -599,6 +600,11 @@
         hideHistoryButton(100);
     }
 
+    public final void onBusEvent(TaskStackUpdatedEvent event) {
+        mStack.setTasks(event.stack.computeAllTasksList(), true /* notifyStackChanges */);
+        mStack.createAffiliatedGroupings(getContext());
+    }
+
     /**
      * Shows the history button.
      */
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 4e75d5a..b9ca9fd 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -57,6 +57,7 @@
 import com.android.systemui.recents.events.activity.ShowHistoryEvent;
 import com.android.systemui.recents.events.component.RecentsVisibilityChangedEvent;
 import com.android.systemui.recents.events.ui.AllTaskViewsDismissedEvent;
+import com.android.systemui.recents.events.ui.DismissTaskEvent;
 import com.android.systemui.recents.events.ui.DismissTaskViewEvent;
 import com.android.systemui.recents.events.ui.StackViewScrolledEvent;
 import com.android.systemui.recents.events.ui.UpdateFreeformTaskViewVisibilityEvent;
@@ -103,6 +104,8 @@
     private static final float SHOW_HISTORY_BUTTON_SCROLL_THRESHOLD = 0.3f;
     private static final float HIDE_HISTORY_BUTTON_SCROLL_THRESHOLD = 0.3f;
 
+    private static final int DEFAULT_SYNC_STACK_DURATION = 200;
+
     public static final Property<Drawable, Integer> DRAWABLE_ALPHA =
             new IntProperty<Drawable>("drawableAlpha") {
                 @Override
@@ -1199,6 +1202,15 @@
     /**** TaskStackCallbacks Implementation ****/
 
     @Override
+    public void onStackTaskAdded(TaskStack stack, Task newTask) {
+        // Update the min/max scroll and animate other task views into their new positions
+        updateLayout(true);
+
+        // Animate all the tasks into place
+        requestSynchronizeStackViewsWithModel(DEFAULT_SYNC_STACK_DURATION);
+    }
+
+    @Override
     public void onStackTaskRemoved(TaskStack stack, Task removedTask, boolean wasFrontMostTask,
             Task newFrontMostTask) {
         if (mFocusedTask == removedTask) {
@@ -1244,7 +1256,7 @@
             }
 
             // Animate all the tasks into place
-            requestSynchronizeStackViewsWithModel(200);
+            requestSynchronizeStackViewsWithModel(DEFAULT_SYNC_STACK_DURATION);
         } else {
             // Remove the view associated with this task, we can't rely on updateTransforms
             // to work here because the task is no longer in the list
@@ -1257,7 +1269,7 @@
             updateLayout(true);
 
             // Animate all the tasks into place
-            requestSynchronizeStackViewsWithModel(200);
+            requestSynchronizeStackViewsWithModel(DEFAULT_SYNC_STACK_DURATION);
         }
 
         // Update the new front most task
@@ -1419,6 +1431,7 @@
 
     public final void onBusEvent(DismissTaskViewEvent event) {
         removeTaskViewFromStack(event.taskView);
+        EventBus.getDefault().send(new DismissTaskEvent(event.task));
     }
 
     public final void onBusEvent(FocusNextTaskViewEvent 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 ab51d5f..df7b9a6 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
@@ -341,11 +341,10 @@
 
         if (launchState.launchedFromAppWithThumbnail) {
             if (mTask.isLaunchTarget) {
+                ctx.postAnimationTrigger.increment();
                 // Immediately start the dim animation
                 animateDimToProgress(taskViewEnterFromAppDuration,
                         ctx.postAnimationTrigger.decrementOnAnimationEnd());
-                ctx.postAnimationTrigger.increment();
-
                 // Animate the action button in
                 fadeInActionButton(taskViewEnterFromAppDuration);
             } else {
@@ -678,7 +677,7 @@
     /** Binds this task view to the task */
     public void onTaskBound(Task t) {
         mTask = t;
-        mTask.setCallbacks(this);
+        mTask.addCallback(this);
 
         // Hide the action button if lock to app is disabled for this view
         int lockButtonVisibility = (!t.lockToTaskEnabled || !t.lockToThisTask) ? GONE : VISIBLE;
@@ -689,7 +688,7 @@
     }
 
     @Override
-    public void onTaskDataLoaded() {
+    public void onTaskDataLoaded(Task task) {
         if (mThumbnailView != null && mHeaderView != null) {
             // Bind each of the views to the new task data
             mThumbnailView.rebindToTask(mTask);
@@ -706,7 +705,7 @@
     public void onTaskDataUnloaded() {
         if (mThumbnailView != null && mHeaderView != null) {
             // Unbind each of the views from the task data and remove the task callback
-            mTask.setCallbacks(null);
+            mTask.removeCallback(this);
             mThumbnailView.unbindFromTask();
             mHeaderView.unbindFromTask();
             // Unbind any listeners
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 37d8cd6..8edfae0 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java
@@ -129,6 +129,7 @@
             mBitmapShader = null;
             mDrawPaint.setShader(null);
         }
+        invalidate();
     }
 
     /** Updates the paint to draw the thumbnail. */
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/DemoModeFragment.java b/packages/SystemUI/src/com/android/systemui/tuner/DemoModeFragment.java
index a2b062c..f1de234 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/DemoModeFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/DemoModeFragment.java
@@ -22,12 +22,12 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Looper;
-import android.preference.Preference;
-import android.preference.Preference.OnPreferenceChangeListener;
-import android.preference.PreferenceFragment;
-import android.preference.PreferenceScreen;
-import android.preference.SwitchPreference;
 import android.provider.Settings;
+import android.support.v14.preference.PreferenceFragment;
+import android.support.v14.preference.SwitchPreference;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.Preference.OnPreferenceChangeListener;
+import android.support.v7.preference.PreferenceScreen;
 import android.view.MenuItem;
 
 import com.android.internal.logging.MetricsLogger;
@@ -56,9 +56,7 @@
     private SwitchPreference mOnSwitch;
 
     @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
+    public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
         Context context = getContext();
         mEnabledSwitch = new SwitchPreference(context);
         mEnabledSwitch.setTitle(R.string.enable_demo_mode);
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/StatusBarSwitch.java b/packages/SystemUI/src/com/android/systemui/tuner/StatusBarSwitch.java
index dcb0d8d..920ec75 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/StatusBarSwitch.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/StatusBarSwitch.java
@@ -18,8 +18,8 @@
 import android.app.ActivityManager;
 import android.content.ContentResolver;
 import android.content.Context;
-import android.preference.SwitchPreference;
 import android.provider.Settings;
+import android.support.v14.preference.SwitchPreference;
 import android.text.TextUtils;
 import android.util.AttributeSet;
 
@@ -38,15 +38,15 @@
     }
 
     @Override
-    protected void onAttachedToActivity() {
-        super.onAttachedToActivity();
+    public void onAttached() {
+        super.onAttached();
         TunerService.get(getContext()).addTunable(this, StatusBarIconController.ICON_BLACKLIST);
     }
 
     @Override
-    protected void onDetachedFromActivity() {
+    public void onDetached() {
         TunerService.get(getContext()).removeTunable(this);
-        super.onDetachedFromActivity();
+        super.onDetached();
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerActivity.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerActivity.java
index c84f618..4173ecc 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/TunerActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerActivity.java
@@ -15,16 +15,64 @@
  */
 package com.android.systemui.tuner;
 
-import android.app.Activity;
+import android.app.Fragment;
+import android.app.FragmentTransaction;
 import android.os.Bundle;
+import android.support.v14.preference.PreferenceFragment;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.PreferenceScreen;
+import android.util.Log;
 
-public class TunerActivity extends Activity {
+import com.android.settingslib.drawer.SettingsDrawerActivity;
+import com.android.systemui.R;
+
+public class TunerActivity extends SettingsDrawerActivity implements
+        PreferenceFragment.OnPreferenceStartFragmentCallback,
+        PreferenceFragment.OnPreferenceStartScreenCallback {
 
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
 
-        getFragmentManager().beginTransaction().replace(android.R.id.content, new TunerFragment())
+        getFragmentManager().beginTransaction().replace(R.id.content_frame, new TunerFragment())
                 .commit();
     }
 
+    @Override
+    public boolean onPreferenceStartFragment(PreferenceFragment caller, Preference pref) {
+        try {
+            Class<?> cls = Class.forName(pref.getFragment());
+            Fragment fragment = (Fragment) cls.newInstance();
+            FragmentTransaction transaction = getFragmentManager().beginTransaction();
+            transaction.replace(R.id.content_frame, fragment);
+            transaction.addToBackStack("PreferenceFragment");
+            transaction.commit();
+            return true;
+        } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
+            Log.d("TunerActivity", "Problem launching fragment", e);
+            return false;
+        }
+    }
+
+    @Override
+    public boolean onPreferenceStartScreen(PreferenceFragment caller, PreferenceScreen pref) {
+        FragmentTransaction transaction = getFragmentManager().beginTransaction();
+        SubSettingsFragment fragment = new SubSettingsFragment();
+        final Bundle b = new Bundle(1);
+        b.putString(PreferenceFragment.ARG_PREFERENCE_ROOT, pref.getKey());
+        fragment.setArguments(b);
+        fragment.setTargetFragment(caller, 0);
+        transaction.replace(R.id.content_frame, fragment);
+        transaction.addToBackStack("PreferenceFragment");
+        transaction.commit();
+        return true;
+    }
+
+    public static class SubSettingsFragment extends PreferenceFragment {
+        @Override
+        public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
+            setPreferenceScreen((PreferenceScreen) ((PreferenceFragment) getTargetFragment())
+                    .getPreferenceScreen().findPreference(rootKey));
+        }
+    }
+
 }
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerFragment.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerFragment.java
index b620b50b..a3fe6bb 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/TunerFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerFragment.java
@@ -24,20 +24,19 @@
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.Handler;
-import android.preference.Preference;
-import android.preference.Preference.OnPreferenceChangeListener;
-import android.preference.Preference.OnPreferenceClickListener;
-import android.preference.PreferenceFragment;
-import android.preference.SwitchPreference;
 import android.provider.Settings;
 import android.provider.Settings.System;
+import android.support.v14.preference.PreferenceFragment;
+import android.support.v14.preference.SwitchPreference;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.Preference.OnPreferenceChangeListener;
+import android.support.v7.preference.Preference.OnPreferenceClickListener;
 import android.view.Menu;
 import android.view.MenuInflater;
 import android.view.MenuItem;
+
 import com.android.internal.logging.MetricsLogger;
 import com.android.systemui.R;
-import com.android.systemui.qs.QSPanel;
-import com.android.systemui.tuner.TunerService.Tunable;
 
 import static com.android.systemui.BatteryMeterDrawable.SHOW_PERCENT_SETTING;
 
@@ -45,8 +44,6 @@
 
     private static final String TAG = "TunerFragment";
 
-    private static final String KEY_QS_TUNER = "qs_tuner";
-    private static final String KEY_DEMO_MODE = "demo_mode";
     private static final String KEY_BATTERY_PCT = "battery_pct";
 
     public static final String SETTING_SEEN_TUNER_WARNING = "seen_tuner_warning";
@@ -59,23 +56,18 @@
 
     private SwitchPreference mBatteryPct;
 
+    @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
 
-        addPreferencesFromResource(R.xml.tuner_prefs);
         getActivity().getActionBar().setDisplayHomeAsUpEnabled(true);
         setHasOptionsMenu(true);
+    }
 
-        findPreference(KEY_DEMO_MODE).setOnPreferenceClickListener(new OnPreferenceClickListener() {
-            @Override
-            public boolean onPreferenceClick(Preference preference) {
-                FragmentTransaction ft = getFragmentManager().beginTransaction();
-                ft.replace(android.R.id.content, new DemoModeFragment(), "DemoMode");
-                ft.addToBackStack(null);
-                ft.commit();
-                return true;
-            }
-        });
+    @Override
+    public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
+        addPreferencesFromResource(R.xml.tuner_prefs);
+
         mBatteryPct = (SwitchPreference) findPreference(KEY_BATTERY_PCT);
         if (Settings.Secure.getInt(getContext().getContentResolver(), SETTING_SEEN_TUNER_WARNING,
                 0) == 0) {
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerSwitch.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerSwitch.java
index 54078b0..7ad752e 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/TunerSwitch.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerSwitch.java
@@ -2,8 +2,8 @@
 
 import android.content.Context;
 import android.content.res.TypedArray;
-import android.preference.SwitchPreference;
 import android.provider.Settings;
+import android.support.v14.preference.SwitchPreference;
 import android.util.AttributeSet;
 
 import com.android.systemui.R;
@@ -21,15 +21,15 @@
     }
 
     @Override
-    protected void onAttachedToActivity() {
-        super.onAttachedToActivity();
+    public void onAttached() {
+        super.onAttached();
         TunerService.get(getContext()).addTunable(this, getKey());
     }
 
     @Override
-    protected void onDetachedFromActivity() {
+    public void onDetached() {
         TunerService.get(getContext()).removeTunable(this);
-        super.onDetachedFromActivity();
+        super.onDetached();
     }
 
     @Override
diff --git a/packages/SystemUI/tests/Android.mk b/packages/SystemUI/tests/Android.mk
index 9cf64d3..b7a41e2 100644
--- a/packages/SystemUI/tests/Android.mk
+++ b/packages/SystemUI/tests/Android.mk
@@ -21,7 +21,8 @@
 LOCAL_PROTOC_FLAGS := -I$(LOCAL_PATH)/..
 LOCAL_PROTO_JAVA_OUTPUT_PARAMS := optional_field_style=accessors
 
-LOCAL_AAPT_FLAGS := --auto-add-overlay --extra-packages com.android.systemui:com.android.keyguard
+LOCAL_AAPT_FLAGS := --auto-add-overlay \
+    --extra-packages com.android.systemui:com.android.keyguard:android.support.v14.preference:android.support.v7.preference:android.support.v7.appcompat:android.support.v7.recyclerview
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src) \
     $(call all-Iaidl-files-under, src) \
@@ -30,6 +31,10 @@
     src/com/android/systemui/EventLogTags.logtags
 
 LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res \
+    frameworks/support/v7/preference/res \
+    frameworks/support/v14/preference/res \
+    frameworks/support/v7/appcompat/res \
+    frameworks/support/v7/recyclerview/res \
     frameworks/base/packages/SystemUI/res \
     frameworks/base/packages/Keyguard/res
 
@@ -40,7 +45,10 @@
 LOCAL_STATIC_JAVA_LIBRARIES := \
     mockito-target \
     Keyguard \
-    android-support-v7-recyclerview
+    android-support-v7-recyclerview \
+    android-support-v7-preference \
+    android-support-v7-appcompat \
+    android-support-v14-preference
 
 # sign this with platform cert, so this test is allowed to inject key events into
 # UI it doesn't own. This is necessary to allow screenshots to be taken
diff --git a/packages/WallpaperCropper/res/values/styles.xml b/packages/WallpaperCropper/res/values/styles.xml
index e438c84..0f9e247 100644
--- a/packages/WallpaperCropper/res/values/styles.xml
+++ b/packages/WallpaperCropper/res/values/styles.xml
@@ -15,7 +15,7 @@
 -->
 
 <resources>
-    <style name="Theme.WallpaperCropper" parent="@android:style/Theme.Material.DayNight">
+    <style name="Theme.WallpaperCropper" parent="@*android:style/Theme.Material.DayNight">
         <item name="android:actionBarStyle">@style/WallpaperCropperActionBar</item>
         <item name="android:windowFullscreen">true</item>
         <item name="android:windowActionBarOverlay">true</item>
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index d8724b27..bcd8efd 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -7171,24 +7171,64 @@
 
     @Override
     public boolean inMultiWindowMode(IBinder token) {
-        synchronized(this) {
-            final ActivityRecord r = ActivityRecord.isInStackLocked(token);
-            if (r == null) {
-                return false;
+        final long origId = Binder.clearCallingIdentity();
+        try {
+            synchronized(this) {
+                final ActivityRecord r = ActivityRecord.isInStackLocked(token);
+                if (r == null) {
+                    return false;
+                }
+                // An activity is consider to be in multi-window mode if its task isn't fullscreen.
+                return !r.task.mFullscreen;
             }
-            // An activity is consider to be in multi-window mode if its task isn't fullscreen.
-            return !r.task.mFullscreen;
+        } finally {
+            Binder.restoreCallingIdentity(origId);
         }
     }
 
     @Override
     public boolean inPictureInPictureMode(IBinder token) {
-        synchronized(this) {
-            final ActivityStack stack = ActivityRecord.getStackLocked(token);
-            if (stack == null) {
-                return false;
+        final long origId = Binder.clearCallingIdentity();
+        try {
+            synchronized(this) {
+                final ActivityStack stack = ActivityRecord.getStackLocked(token);
+                if (stack == null) {
+                    return false;
+                }
+                return stack.mStackId == PINNED_STACK_ID;
             }
-            return stack.mStackId == PINNED_STACK_ID;
+        } finally {
+            Binder.restoreCallingIdentity(origId);
+        }
+    }
+
+    @Override
+    public void enterPictureInPictureMode(IBinder token) {
+        final long origId = Binder.clearCallingIdentity();
+        try {
+            synchronized(this) {
+                if (!mSupportsPictureInPicture) {
+                    throw new IllegalStateException("enterPictureInPictureMode: "
+                            + "Device doesn't support picture-in-picture mode.");
+                }
+
+                final ActivityRecord r = ActivityRecord.forTokenLocked(token);
+
+                if (r == null) {
+                    throw new IllegalStateException("enterPictureInPictureMode: "
+                            + "Can't find activity for token=" + token);
+                }
+
+                if (!r.info.supportsPip) {
+                    throw new IllegalArgumentException("enterPictureInPictureMode: "
+                            + "Picture-In-Picture not supported for r=" + r);
+                }
+
+                mStackSupervisor.moveActivityToStackLocked(
+                        r, PINNED_STACK_ID, "enterPictureInPictureMode", null);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(origId);
         }
     }
 
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 9117806..f7d66af 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -3623,24 +3623,30 @@
             return false;
         }
 
+        moveActivityToStackLocked(r, PINNED_STACK_ID, "moveTopActivityToPinnedStack", bounds);
+        return true;
+    }
+
+    void moveActivityToStackLocked(ActivityRecord r, int stackId, String reason, Rect bounds) {
         final TaskRecord task = r.task;
         if (task.mActivities.size() == 1) {
             // There is only one activity in the task. So, we can just move the task over to the
-            // pinned stack without re-parenting the activity in a different task.
-            moveTaskToStackLocked(task.taskId, PINNED_STACK_ID, ON_TOP, FORCE_FOCUS,
-                    "moveTopActivityToPinnedStack", true /* animate */);
+            // stack without re-parenting the activity in a different task.
+            moveTaskToStackLocked(
+                    task.taskId, stackId, ON_TOP, FORCE_FOCUS, reason, true /* animate */);
         } else {
             final ActivityStack pinnedStack = getStack(PINNED_STACK_ID, CREATE_IF_NEEDED, ON_TOP);
             pinnedStack.moveActivityToStack(r);
         }
 
-        resizeStackLocked(PINNED_STACK_ID, bounds, !PRESERVE_WINDOWS, true);
+        if (bounds != null) {
+            resizeStackLocked(PINNED_STACK_ID, bounds, !PRESERVE_WINDOWS, true);
+        }
 
         // The task might have already been running and its visibility needs to be synchronized with
         // the visibility of the stack / windows.
         ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS);
         resumeTopActivitiesLocked();
-        return true;
     }
 
     void positionTaskInStackLocked(int taskId, int stackId, int position) {
diff --git a/services/core/java/com/android/server/notification/ZenModeConditions.java b/services/core/java/com/android/server/notification/ZenModeConditions.java
index cee9ec8..9cdece5 100644
--- a/services/core/java/com/android/server/notification/ZenModeConditions.java
+++ b/services/core/java/com/android/server/notification/ZenModeConditions.java
@@ -109,7 +109,6 @@
         if (DEBUG) Log.d(TAG, "onConditionChanged " + id + " " + condition);
         ZenModeConfig config = mHelper.getConfig();
         if (config == null) return;
-        config = config.copy();
         boolean updated = updateCondition(id, condition, config.manualRule);
         for (ZenRule automaticRule : config.automaticRules.values()) {
             updated |= updateCondition(id, condition, automaticRule);
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index 6030bab..85c3cf8 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -77,6 +77,9 @@
     static final String TAG = "ZenModeHelper";
     static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
+    // The amount of time rules instances can exist without their owning app being installed.
+    private static final int RULE_INSTANCE_GRACE_PERIOD = 1000 * 60 * 60 * 72;
+
     private final Context mContext;
     private final H mHandler;
     private final SettingsObserver mSettingsObserver;
@@ -93,6 +96,7 @@
     private int mUser = UserHandle.USER_SYSTEM;
     private ZenModeConfig mConfig;
     private AudioManagerInternal mAudioManager;
+    private PackageManager mPm;
     private boolean mEffectsSuppressed;
 
     public ZenModeHelper(Context context, Looper looper, ConditionProviders conditionProviders) {
@@ -170,7 +174,9 @@
         if (mAudioManager != null) {
             mAudioManager.setRingerModeDelegate(mRingerModeDelegate);
         }
+        mPm = mContext.getPackageManager();
         mHandler.postMetricsTimer();
+        cleanUpZenRules();
         evaluateZenMode("onSystemReady", true);
     }
 
@@ -184,7 +190,10 @@
             config = mDefaultConfig.copy();
             config.user = user;
         }
-        setConfig(config, "onUserSwitched");
+        synchronized (mConfig) {
+            setConfig(config, "onUserSwitched");
+        }
+        cleanUpZenRules();
     }
 
     public void onUserRemoved(int user) {
@@ -253,14 +262,15 @@
                 throw new IllegalArgumentException("Rule already exists");
             }
             newConfig = mConfig.copy();
-        }
-        ZenRule rule = new ZenRule();
-        populateZenRule(automaticZenRule, rule, true);
-        newConfig.automaticRules.put(rule.id, rule);
-        if (setConfig(newConfig, reason, true)) {
-            return createAutomaticZenRule(rule);
-        } else {
-            return null;
+
+            ZenRule rule = new ZenRule();
+            populateZenRule(automaticZenRule, rule, true);
+            newConfig.automaticRules.put(rule.id, rule);
+            if (setConfig(newConfig, reason, true)) {
+                return createAutomaticZenRule(rule);
+            } else {
+                return null;
+            }
         }
     }
 
@@ -273,21 +283,21 @@
                         + " reason=" + reason);
             }
             newConfig = mConfig.copy();
-        }
-        final String ruleId = automaticZenRule.getId();
-        ZenModeConfig.ZenRule rule;
-        if (ruleId == null) {
-            throw new IllegalArgumentException("Rule doesn't exist");
-        } else {
-            rule = newConfig.automaticRules.get(ruleId);
-            if (rule == null || !canManageAutomaticZenRule(rule)) {
-                throw new SecurityException(
-                        "Cannot update rules not owned by your condition provider");
+            final String ruleId = automaticZenRule.getId();
+            ZenModeConfig.ZenRule rule;
+            if (ruleId == null) {
+                throw new IllegalArgumentException("Rule doesn't exist");
+            } else {
+                rule = newConfig.automaticRules.get(ruleId);
+                if (rule == null || !canManageAutomaticZenRule(rule)) {
+                    throw new SecurityException(
+                            "Cannot update rules not owned by your condition provider");
+                }
             }
+            populateZenRule(automaticZenRule, rule, false);
+            newConfig.automaticRules.put(ruleId, rule);
+            return setConfig(newConfig, reason, true);
         }
-        populateZenRule(automaticZenRule, rule, false);
-        newConfig.automaticRules.put(ruleId, rule);
-        return setConfig(newConfig, reason, true);
     }
 
     public boolean removeAutomaticZenRule(String id, String reason) {
@@ -295,17 +305,17 @@
         synchronized (mConfig) {
             if (mConfig == null) return false;
             newConfig = mConfig.copy();
+            ZenRule rule = newConfig.automaticRules.get(id);
+            if (rule == null) return false;
+            if (canManageAutomaticZenRule(rule)) {
+                newConfig.automaticRules.remove(id);
+                if (DEBUG) Log.d(TAG, "removeZenRule zenRule=" + id + " reason=" + reason);
+            } else {
+                throw new SecurityException(
+                        "Cannot delete rules not owned by your condition provider");
+            }
+            return setConfig(newConfig, reason, true);
         }
-        ZenRule rule = newConfig.automaticRules.get(id);
-        if (rule == null) return false;
-        if (canManageAutomaticZenRule(rule)) {
-            newConfig.automaticRules.remove(id);
-            if (DEBUG) Log.d(TAG, "removeZenRule zenRule=" + id + " reason=" + reason);
-        } else {
-            throw new SecurityException(
-                     "Cannot delete rules not owned by your condition provider");
-        }
-        return setConfig(newConfig, reason, true);
     }
 
     public boolean removeAutomaticZenRules(String packageName, String reason) {
@@ -313,15 +323,15 @@
         synchronized (mConfig) {
             if (mConfig == null) return false;
             newConfig = mConfig.copy();
-        }
-        for (int i = newConfig.automaticRules.size() - 1; i >= 0; i--) {
-            ZenRule rule = newConfig.automaticRules.get(newConfig.automaticRules.keyAt(i));
-            if (rule.component.getPackageName().equals(packageName)
-                    && canManageAutomaticZenRule(rule)) {
-                newConfig.automaticRules.removeAt(i);
+            for (int i = newConfig.automaticRules.size() - 1; i >= 0; i--) {
+                ZenRule rule = newConfig.automaticRules.get(newConfig.automaticRules.keyAt(i));
+                if (rule.component.getPackageName().equals(packageName)
+                        && canManageAutomaticZenRule(rule)) {
+                    newConfig.automaticRules.removeAt(i);
+                }
             }
+            return setConfig(newConfig, reason, true);
         }
-        return setConfig(newConfig, reason, true);
     }
 
     public boolean canManageAutomaticZenRule(ZenRule rule) {
@@ -332,8 +342,7 @@
                 == PackageManager.PERMISSION_GRANTED) {
             return true;
         } else {
-            String[] packages =
-                    mContext.getPackageManager().getPackagesForUid(Binder.getCallingUid());
+            String[] packages = mPm.getPackagesForUid(Binder.getCallingUid());
             if (packages != null) {
                 final int packageCount = packages.length;
                 for (int i = 0; i < packageCount; i++) {
@@ -384,22 +393,22 @@
                     + " conditionId=" + conditionId + " reason=" + reason
                     + " setRingerMode=" + setRingerMode);
             newConfig = mConfig.copy();
-        }
-        if (zenMode == Global.ZEN_MODE_OFF) {
-            newConfig.manualRule = null;
-            for (ZenRule automaticRule : newConfig.automaticRules.values()) {
-                if (automaticRule.isAutomaticActive()) {
-                    automaticRule.snoozing = true;
+            if (zenMode == Global.ZEN_MODE_OFF) {
+                newConfig.manualRule = null;
+                for (ZenRule automaticRule : newConfig.automaticRules.values()) {
+                    if (automaticRule.isAutomaticActive()) {
+                        automaticRule.snoozing = true;
+                    }
                 }
+            } else {
+                final ZenRule newRule = new ZenRule();
+                newRule.enabled = true;
+                newRule.zenMode = zenMode;
+                newRule.conditionId = conditionId;
+                newConfig.manualRule = newRule;
             }
-        } else {
-            final ZenRule newRule = new ZenRule();
-            newRule.enabled = true;
-            newRule.zenMode = zenMode;
-            newRule.conditionId = conditionId;
-            newConfig.manualRule = newRule;
+            setConfig(newConfig, reason, setRingerMode);
         }
-        setConfig(newConfig, reason, setRingerMode);
     }
 
     public void dump(PrintWriter pw, String prefix) {
@@ -450,16 +459,20 @@
                     return;
                 }
                 config.manualRule = null;  // don't restore the manual rule
+                long time = System.currentTimeMillis();
                 if (config.automaticRules != null) {
                     for (ZenRule automaticRule : config.automaticRules.values()) {
                         // don't restore transient state from restored automatic rules
                         automaticRule.snoozing = false;
                         automaticRule.condition = null;
+                        automaticRule.creationTime = time;
                     }
                 }
             }
             if (DEBUG) Log.d(TAG, "readXml");
-            setConfig(config, "readXml");
+            synchronized (mConfig) {
+                setConfig(config, "readXml");
+            }
         }
     }
 
@@ -484,11 +497,39 @@
 
     public void setNotificationPolicy(Policy policy) {
         if (policy == null || mConfig == null) return;
-        final ZenModeConfig newConfig = mConfig.copy();
-        newConfig.applyNotificationPolicy(policy);
-        setConfig(newConfig, "setNotificationPolicy");
+        synchronized (mConfig) {
+            final ZenModeConfig newConfig = mConfig.copy();
+            newConfig.applyNotificationPolicy(policy);
+            setConfig(newConfig, "setNotificationPolicy");
+        }
     }
 
+    /**
+     * Removes old rule instances whose owner is not installed.
+     */
+    private void cleanUpZenRules() {
+        long currentTime = System.currentTimeMillis();
+        synchronized (mConfig) {
+            final ZenModeConfig newConfig = mConfig.copy();
+            if (newConfig.automaticRules != null) {
+                for (int i = newConfig.automaticRules.size() - 1; i >= 0; i--) {
+                    ZenRule rule = newConfig.automaticRules.get(newConfig.automaticRules.keyAt(i));
+                    if (RULE_INSTANCE_GRACE_PERIOD < (currentTime - rule.creationTime)) {
+                        try {
+                            mPm.getPackageInfo(rule.component.getPackageName(), 0);
+                        } catch (PackageManager.NameNotFoundException e) {
+                            newConfig.automaticRules.removeAt(i);
+                        }
+                    }
+                }
+            }
+            setConfig(newConfig, "cleanUpZenRules");
+        }
+    }
+
+    /**
+     * @return a copy of the zen mode configuration
+     */
     public ZenModeConfig getConfig() {
         synchronized (mConfig) {
             return mConfig.copy();
@@ -517,19 +558,17 @@
                 return true;
             }
             mConditions.evaluateConfig(config, false /*processSubscriptions*/);  // may modify config
-            synchronized (mConfig) {
-                mConfigs.put(config.user, config);
-                if (DEBUG) Log.d(TAG, "setConfig reason=" + reason, new Throwable());
-                ZenLog.traceConfig(reason, mConfig, config);
-                final boolean policyChanged = !Objects.equals(getNotificationPolicy(mConfig),
-                        getNotificationPolicy(config));
-                mConfig = config;
-                if (config.equals(mConfig)) {
-                    dispatchOnConfigChanged();
-                }
-                if (policyChanged) {
-                    dispatchOnPolicyChanged();
-                }
+            mConfigs.put(config.user, config);
+            if (DEBUG) Log.d(TAG, "setConfig reason=" + reason, new Throwable());
+            ZenLog.traceConfig(reason, mConfig, config);
+            final boolean policyChanged = !Objects.equals(getNotificationPolicy(mConfig),
+                    getNotificationPolicy(config));
+            mConfig = config;
+            if (config.equals(mConfig)) {
+                dispatchOnConfigChanged();
+            }
+            if (policyChanged) {
+                dispatchOnPolicyChanged();
             }
             final String val = Integer.toString(config.hashCode());
             Global.putString(mContext.getContentResolver(), Global.ZEN_MODE_CONFIG_ETAG, val);
@@ -994,7 +1033,9 @@
                     break;
                 case MSG_SET_CONFIG:
                     ConfigMessageData configData = (ConfigMessageData)msg.obj;
-                    setConfig(configData.config, configData.reason);
+                    synchronized (mConfig) {
+                        setConfig(configData.config, configData.reason);
+                    }
                     break;
             }
         }
diff --git a/services/core/java/com/android/server/policy/GlobalActions.java b/services/core/java/com/android/server/policy/GlobalActions.java
index c8523c9..5948d3c 100644
--- a/services/core/java/com/android/server/policy/GlobalActions.java
+++ b/services/core/java/com/android/server/policy/GlobalActions.java
@@ -278,7 +278,7 @@
             } else if (GLOBAL_ACTION_KEY_BUGREPORT.equals(actionKey)) {
                 if (Settings.Global.getInt(mContext.getContentResolver(),
                         Settings.Global.BUGREPORT_IN_POWER_MENU, 0) != 0 && isCurrentUserOwner()) {
-                    mItems.add(getBugReportAction());
+                    mItems.add(new BugReportAction());
                 }
             } else if (GLOBAL_ACTION_KEY_SILENT.equals(actionKey)) {
                 if (mShowSilentToggle) {
@@ -367,60 +367,67 @@
         }
     }
 
-    private Action getBugReportAction() {
-        return new SinglePressAction(com.android.internal.R.drawable.ic_lock_bugreport,
-                R.string.bugreport_title) {
+    private class BugReportAction extends SinglePressAction implements LongPressAction {
 
-            public void onPress() {
-                AlertDialog.Builder builder = new AlertDialog.Builder(mContext);
-                builder.setTitle(com.android.internal.R.string.bugreport_title);
-                builder.setMessage(com.android.internal.R.string.bugreport_message);
-                builder.setNegativeButton(com.android.internal.R.string.cancel, null);
-                builder.setPositiveButton(com.android.internal.R.string.report,
-                        new DialogInterface.OnClickListener() {
-                            @Override
-                            public void onClick(DialogInterface dialog, int which) {
-                                // don't actually trigger the bugreport if we are running stability
-                                // tests via monkey
-                                if (ActivityManager.isUserAMonkey()) {
-                                    return;
-                                }
-                                // Add a little delay before executing, to give the
-                                // dialog a chance to go away before it takes a
-                                // screenshot.
-                                mHandler.postDelayed(new Runnable() {
-                                    @Override public void run() {
-                                        //  TODO: select 'progress' flag according to menu choice
-                                        try {
-                                            ActivityManagerNative.getDefault()
-                                                    .requestBugReport(true);
-                                        } catch (RemoteException e) {
-                                        }
-                                    }
-                                }, 500);
-                            }
-                        });
-                AlertDialog dialog = builder.create();
-                dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
-                dialog.show();
+        public BugReportAction() {
+            super(com.android.internal.R.drawable.ic_lock_bugreport, R.string.bugreport_title);
+        }
+
+        @Override
+        public void onPress() {
+            // don't actually trigger the bugreport if we are running stability
+            // tests via monkey
+            if (ActivityManager.isUserAMonkey()) {
+                return;
             }
+            // Add a little delay before executing, to give the
+            // dialog a chance to go away before it takes a
+            // screenshot.
+            // TODO: remove once screenshots are handled by Shell (instead of dumpstate)
+            mHandler.postDelayed(new Runnable() {
+                @Override
+                public void run() {
+                    try {
+                        // Take a "heavy" bugreport: it's more user friendly, but causes more
+                        // interference.
+                        ActivityManagerNative.getDefault().requestBugReport(true);
+                    } catch (RemoteException e) {
+                    }
+                }
+            }, 500);
+        }
 
-            public boolean showDuringKeyguard() {
-                return true;
-            }
-
-            public boolean showBeforeProvisioning() {
+        @Override
+        public boolean onLongPress() {
+            // don't actually trigger the bugreport if we are running stability
+            // tests via monkey
+            if (ActivityManager.isUserAMonkey()) {
                 return false;
             }
-
-            @Override
-            public String getStatus() {
-                return mContext.getString(
-                        com.android.internal.R.string.bugreport_status,
-                        Build.VERSION.RELEASE,
-                        Build.ID);
+            try {
+                // Take a "light" bugreport, with less interference.
+                ActivityManagerNative.getDefault().requestBugReport(false);
+            } catch (RemoteException e) {
             }
-        };
+            return true;
+        }
+
+        public boolean showDuringKeyguard() {
+            return true;
+        }
+
+        @Override
+        public boolean showBeforeProvisioning() {
+            return false;
+        }
+
+        @Override
+        public String getStatus() {
+            return mContext.getString(
+                    com.android.internal.R.string.bugreport_status,
+                    Build.VERSION.RELEASE,
+                    Build.ID);
+        }
     }
 
     private Action getSettingsAction() {
@@ -742,13 +749,6 @@
             mIcon = icon;
         }
 
-        protected SinglePressAction(int iconResId, CharSequence message) {
-            mIconResId = iconResId;
-            mMessageResId = 0;
-            mMessage = message;
-            mIcon = null;
-        }
-
         public boolean isEnabled() {
             return true;
         }
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 189ed33..fb5f21a 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -141,6 +141,14 @@
             "com.android.server.MountService$Lifecycle";
     private static final String PERSISTENT_DATA_BLOCK_PROP = "ro.frp.pst";
 
+    /**
+     * Default theme used by the system context. This is used to style
+     * system-provided dialogs, such as the Power Off dialog, and other
+     * visual content.
+     */
+    private static final int DEFAULT_SYSTEM_THEME =
+            com.android.internal.R.style.Theme_Material_DayNight_DarkActionBar;
+
     private final int mFactoryTestMode;
     private Timer mProfilerSnapshotTimer;
 
@@ -320,7 +328,7 @@
     private void createSystemContext() {
         ActivityThread activityThread = ActivityThread.systemMain();
         mSystemContext = activityThread.getSystemContext();
-        mSystemContext.setTheme(android.R.style.Theme_Material_DayNight_DarkActionBar);
+        mSystemContext.setTheme(DEFAULT_SYSTEM_THEME);
     }
 
     /**