Merge "Moved window manager wallpaper control into separate class"
diff --git a/api/current.txt b/api/current.txt
index 3e1f5a5..acc9c4b 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -34141,6 +34141,15 @@
     field public static final int RTL = 1; // 0x1
   }
 
+  public final class LocaleList {
+    ctor public LocaleList();
+    ctor public LocaleList(java.util.Locale[]);
+    method public java.util.Locale get(int);
+    method public java.util.Locale getPrimary();
+    method public boolean isEmpty();
+    method public int size();
+  }
+
   public final class Log {
     method public static int d(java.lang.String, java.lang.String);
     method public static int d(java.lang.String, java.lang.String, java.lang.Throwable);
diff --git a/api/system-current.txt b/api/system-current.txt
index 984d395..955fb07 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -36474,6 +36474,15 @@
     field public static final int RTL = 1; // 0x1
   }
 
+  public final class LocaleList {
+    ctor public LocaleList();
+    ctor public LocaleList(java.util.Locale[]);
+    method public java.util.Locale get(int);
+    method public java.util.Locale getPrimary();
+    method public boolean isEmpty();
+    method public int size();
+  }
+
   public final class Log {
     method public static int d(java.lang.String, java.lang.String);
     method public static int d(java.lang.String, java.lang.String, java.lang.Throwable);
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index ed7a2a3..924df1b 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -4702,7 +4702,7 @@
 
     private static boolean copyNeeded(int flags, Package p,
             PackageUserState state, Bundle metaData, int userId) {
-        if (userId != UserHandle.USER_OWNER) {
+        if (userId != UserHandle.USER_SYSTEM) {
             // We always need to copy for other users, since we need
             // to fix up the uid.
             return true;
diff --git a/core/java/android/content/pm/RegisteredServicesCache.java b/core/java/android/content/pm/RegisteredServicesCache.java
index b293e2a..b3f03c3 100644
--- a/core/java/android/content/pm/RegisteredServicesCache.java
+++ b/core/java/android/content/pm/RegisteredServicesCache.java
@@ -213,7 +213,7 @@
         @Override
         public void onReceive(Context context, Intent intent) {
             // External apps can't coexist with multi-user, so scan owner
-            handlePackageEvent(intent, UserHandle.USER_OWNER);
+            handlePackageEvent(intent, UserHandle.USER_SYSTEM);
         }
     };
 
diff --git a/core/java/android/hardware/camera2/params/OutputConfiguration.java b/core/java/android/hardware/camera2/params/OutputConfiguration.java
index 7aa9787..a04cdce 100644
--- a/core/java/android/hardware/camera2/params/OutputConfiguration.java
+++ b/core/java/android/hardware/camera2/params/OutputConfiguration.java
@@ -19,7 +19,9 @@
 
 import android.hardware.camera2.CameraDevice;
 import android.hardware.camera2.utils.HashCodeHelpers;
+import android.hardware.camera2.utils.SurfaceUtils;
 import android.util.Log;
+import android.util.Size;
 import android.view.Surface;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -66,9 +68,7 @@
      *
      */
     public OutputConfiguration(Surface surface) {
-        checkNotNull(surface, "Surface must not be null");
-        mSurface = surface;
-        mRotation = ROTATION_0;
+        this(surface, ROTATION_0);
     }
 
     /**
@@ -94,6 +94,9 @@
         checkArgumentInRange(rotation, ROTATION_0, ROTATION_270, "Rotation constant");
         mSurface = surface;
         mRotation = rotation;
+        mConfiguredSize = SurfaceUtils.getSurfaceSize(surface);
+        mConfiguredFormat = SurfaceUtils.getSurfaceFormat(surface);
+        mConfiguredDataspace = SurfaceUtils.getSurfaceDataspace(surface);
     }
 
     /**
@@ -106,6 +109,9 @@
         checkArgumentInRange(rotation, ROTATION_0, ROTATION_270, "Rotation constant");
         mSurface = surface;
         mRotation = rotation;
+        mConfiguredSize = SurfaceUtils.getSurfaceSize(mSurface);
+        mConfiguredFormat = SurfaceUtils.getSurfaceFormat(mSurface);
+        mConfiguredDataspace = SurfaceUtils.getSurfaceDataspace(mSurface);
     }
 
     /**
@@ -163,8 +169,9 @@
     /**
      * Check if this {@link OutputConfiguration} is equal to another {@link OutputConfiguration}.
      *
-     * <p>Two output configurations are only equal if and only if the underlying surface and
-     * all other configuration parameters are equal. </p>
+     * <p>Two output configurations are only equal if and only if the underlying surfaces, surface
+     * properties (width, height, format, dataspace) when the output configurations are created,
+     * and all other configuration parameters are equal. </p>
      *
      * @return {@code true} if the objects were equal, {@code false} otherwise
      */
@@ -176,7 +183,11 @@
             return true;
         } else if (obj instanceof OutputConfiguration) {
             final OutputConfiguration other = (OutputConfiguration) obj;
-            return (mSurface == other.mSurface && mRotation == other.mRotation);
+            return mSurface == other.mSurface &&
+                   mRotation == other.mRotation &&
+                   mConfiguredSize.equals(other.mConfiguredSize) &&
+                   mConfiguredFormat == other.mConfiguredFormat &&
+                   mConfiguredDataspace == other.mConfiguredDataspace;
         }
         return false;
     }
@@ -192,4 +203,9 @@
     private static final String TAG = "OutputConfiguration";
     private final Surface mSurface;
     private final int mRotation;
+
+    // The size, format, and dataspace of the surface when OutputConfiguration is created.
+    private final Size mConfiguredSize;
+    private final int mConfiguredFormat;
+    private final int mConfiguredDataspace;
 }
diff --git a/core/java/android/net/NetworkCapabilities.java b/core/java/android/net/NetworkCapabilities.java
index a6d477f..3bd12c0 100644
--- a/core/java/android/net/NetworkCapabilities.java
+++ b/core/java/android/net/NetworkCapabilities.java
@@ -351,8 +351,12 @@
     public void maybeMarkCapabilitiesRestricted() {
         // If all the capabilities are typically provided by restricted networks, conclude that this
         // network is restricted.
-        if ((mNetworkCapabilities & ~(DEFAULT_CAPABILITIES | RESTRICTED_CAPABILITIES)) == 0)
+        if ((mNetworkCapabilities & ~(DEFAULT_CAPABILITIES | RESTRICTED_CAPABILITIES)) == 0 &&
+                // Must have at least some restricted capabilities, otherwise a request for an
+                // internet-less network will get marked restricted.
+                (mNetworkCapabilities & RESTRICTED_CAPABILITIES) != 0) {
             removeCapability(NET_CAPABILITY_NOT_RESTRICTED);
+        }
     }
 
     /**
diff --git a/core/java/android/util/LocaleList.java b/core/java/android/util/LocaleList.java
new file mode 100644
index 0000000..017735a
--- /dev/null
+++ b/core/java/android/util/LocaleList.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.util;
+
+import android.annotation.Nullable;
+
+import java.util.HashSet;
+import java.util.Locale;
+
+// TODO: We don't except too many LocaleLists to exist at the same time, and
+// we need access to the data at native level, so we should pass the data
+// down to the native level, create a mapt of every list seen there, take a
+// pointer back, and just keep that pointed in the Java-level object, so
+// things could be copied very quickly.
+
+/**
+ * LocaleList is an immutable list of Locales, typically used to keep an
+ * ordered user preferences for locales.
+ */
+public final class LocaleList {
+    private final Locale[] mList;
+    private static final Locale[] sEmptyList = new Locale[0];
+
+    public Locale get(int location) {
+        return location < mList.length ? mList[location] : null;
+    }
+
+    public Locale getPrimary() {
+        return mList.length == 0 ? null : get(0);
+    }
+
+    public boolean isEmpty() {
+        return mList.length == 0;
+    }
+
+    public int size() {
+        return mList.length;
+    }
+
+    public LocaleList() {
+        mList = sEmptyList;
+    }
+
+    /**
+     * @throws NullPointerException if any of the input locales is <code>null</code>.
+     * @throws IllegalArgumentException if any of the input locales repeat.
+     */
+    public LocaleList(@Nullable Locale[] list) {
+        if (list == null || list.length == 0) {
+            mList = sEmptyList;
+        } else {
+            final Locale[] localeList = new Locale[list.length];
+            final HashSet<Locale> seenLocales = new HashSet<Locale>();
+            for (int i = 0; i < list.length; ++i) {
+                final Locale l = list[i];
+                if (l == null) {
+                    throw new NullPointerException();
+                } else if (seenLocales.contains(l)) {
+                    throw new IllegalArgumentException();
+                } else {
+                    seenLocales.add(l);
+                    localeList[i] = (Locale) l.clone();
+                }
+            }
+            mList = localeList;
+        }
+    }
+}
diff --git a/core/java/android/view/AppTransitionAnimationSpec.aidl b/core/java/android/view/AppTransitionAnimationSpec.aidl
new file mode 100644
index 0000000..8388347
--- /dev/null
+++ b/core/java/android/view/AppTransitionAnimationSpec.aidl
@@ -0,0 +1,20 @@
+/*
+** Copyright 2015, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+package android.view;
+
+/** @hide */
+parcelable AppTransitionAnimationSpec;
diff --git a/core/java/android/view/AppTransitionAnimationSpec.java b/core/java/android/view/AppTransitionAnimationSpec.java
new file mode 100644
index 0000000..c6e1989
--- /dev/null
+++ b/core/java/android/view/AppTransitionAnimationSpec.java
@@ -0,0 +1,61 @@
+package android.view;
+
+import android.graphics.Bitmap;
+import android.graphics.Rect;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Holds information about how the next app transition animation should be executed.
+ *
+ * This class is intended to be used with IWindowManager.overridePendingAppTransition* methods when
+ * simple arguments are not enough to describe the animation.
+ *
+ * @hide
+ */
+public class AppTransitionAnimationSpec implements Parcelable {
+    public final int taskId;
+    public final Bitmap bitmap;
+    public final Rect rect;
+
+    public AppTransitionAnimationSpec(int taskId, Bitmap bitmap, Rect rect) {
+        this.taskId = taskId;
+        this.bitmap = bitmap;
+        this.rect = rect;
+    }
+
+    public AppTransitionAnimationSpec(Parcel in) {
+        taskId = in.readInt();
+        bitmap = in.readParcelable(null);
+        rect = in.readParcelable(null);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(taskId);
+        dest.writeParcelable(bitmap, 0 /* flags */);
+        dest.writeParcelable(rect, 0 /* flags */);
+
+    }
+
+    public static final Parcelable.Creator<AppTransitionAnimationSpec> CREATOR
+            = new Parcelable.Creator<AppTransitionAnimationSpec>() {
+        public AppTransitionAnimationSpec createFromParcel(Parcel in) {
+            return new AppTransitionAnimationSpec(in);
+        }
+
+        public AppTransitionAnimationSpec[] newArray(int size) {
+            return new AppTransitionAnimationSpec[size];
+        }
+    };
+
+    @Override
+    public String toString() {
+        return "{taskId: " + taskId + ", bitmap: " + bitmap + ", rect: " + rect + "}";
+    }
+}
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 0a4b982..f86adfe 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -39,6 +39,7 @@
 import android.view.InputChannel;
 import android.view.InputDevice;
 import android.view.IInputFilter;
+import android.view.AppTransitionAnimationSpec;
 import android.view.WindowContentFrameStats;
 
 /**
@@ -127,6 +128,16 @@
     void overridePendingAppTransitionAspectScaledThumb(in Bitmap srcThumb, int startX,
             int startY, int targetWidth, int targetHeight, IRemoteCallback startedCallback,
             boolean scaleUp);
+    /**
+     * Overrides animation for app transition that exits from an application to a multi-window
+     * environment and allows specifying transition animation parameters for each window.
+     *
+     * @param specs Array of transition animation descriptions for entering windows.
+     *
+     * @hide
+     */
+    void overridePendingAppTransitionMultiThumb(in AppTransitionAnimationSpec[] specs,
+            IRemoteCallback startedCallback, boolean scaleUp);
     void overridePendingAppTransitionInPlace(String packageName, int anim);
     void executeAppTransition();
     void setAppStartingWindow(IBinder token, String pkg, int theme,
diff --git a/core/java/android/view/TextureView.java b/core/java/android/view/TextureView.java
index cdc196e..197ea09 100644
--- a/core/java/android/view/TextureView.java
+++ b/core/java/android/view/TextureView.java
@@ -23,6 +23,7 @@
 import android.graphics.Paint;
 import android.graphics.Rect;
 import android.graphics.SurfaceTexture;
+import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
 import android.util.Log;
 
@@ -294,6 +295,22 @@
     public void buildLayer() {
     }
 
+    @Override
+    public void setForeground(Drawable foreground) {
+        if (foreground != null) {
+            throw new UnsupportedOperationException(
+                    "TextureView doesn't support displaying a foreground drawable");
+        }
+    }
+
+    @Override
+    public void setBackgroundDrawable(Drawable background) {
+        if (background != null) {
+            throw new UnsupportedOperationException(
+                    "TextureView doesn't support displaying a background drawable");
+        }
+    }
+
     /**
      * Subclasses of TextureView cannot do their own rendering
      * with the {@link Canvas} object.
diff --git a/core/java/android/widget/RelativeLayout.java b/core/java/android/widget/RelativeLayout.java
index abcd614..df01fc1 100644
--- a/core/java/android/widget/RelativeLayout.java
+++ b/core/java/android/widget/RelativeLayout.java
@@ -1226,6 +1226,12 @@
 
         private int mLeft, mTop, mRight, mBottom;
 
+        /**
+         * Whether this view had any relative rules modified following the most
+         * recent resolution of layout direction.
+         */
+        private boolean mNeedsLayoutResolution;
+
         private boolean mRulesChanged = false;
         private boolean mIsRtlCompatibilityMode = false;
 
@@ -1374,47 +1380,69 @@
         }
 
         /**
-         * Adds a layout rule to be interpreted by the RelativeLayout. This
-         * method should only be used for constraints that don't refer to another sibling
-         * (e.g., CENTER_IN_PARENT) or take a boolean value ({@link RelativeLayout#TRUE}
-         * for true or 0 for false). To specify a verb that takes a subject, use
-         * {@link #addRule(int, int)} instead.
+         * Adds a layout rule to be interpreted by the RelativeLayout.
+         * <p>
+         * This method should only be used for verbs that don't refer to a
+         * sibling (ex. {@link #ALIGN_RIGHT}) or take a boolean
+         * value ({@link #TRUE} for true or 0 for false). To
+         * specify a verb that takes a subject, use {@link #addRule(int, int)}.
+         * <p>
+         * If the rule is relative to the layout direction (ex.
+         * {@link #ALIGN_PARENT_START}), then the layout direction must be
+         * resolved using {@link #resolveLayoutDirection(int)} before calling
+         * {@link #getRule(int)} an absolute rule (ex.
+         * {@link #ALIGN_PARENT_LEFT}.
          *
-         * @param verb One of the verbs defined by
-         *        {@link android.widget.RelativeLayout RelativeLayout}, such as
-         *        ALIGN_WITH_PARENT_LEFT.
+         * @param verb a layout verb, such as {@link #ALIGN_PARENT_LEFT}
          * @see #addRule(int, int)
+         * @see #removeRule(int)
          * @see #getRule(int)
          */
         public void addRule(int verb) {
-            mRules[verb] = TRUE;
-            mInitialRules[verb] = TRUE;
-            mRulesChanged = true;
+            addRule(verb, TRUE);
         }
 
         /**
-         * Adds a layout rule to be interpreted by the RelativeLayout. Use this for
-         * verbs that take a target, such as a sibling (ALIGN_RIGHT) or a boolean
-         * value (VISIBLE).
+         * Adds a layout rule to be interpreted by the RelativeLayout.
+         * <p>
+         * Use this for verbs that refer to a sibling (ex.
+         * {@link #ALIGN_RIGHT}) or take a boolean value (ex.
+         * {@link #CENTER_IN_PARENT}).
+         * <p>
+         * If the rule is relative to the layout direction (ex.
+         * {@link #START_OF}), then the layout direction must be resolved using
+         * {@link #resolveLayoutDirection(int)} before calling
+         * {@link #getRule(int)} with an absolute rule (ex. {@link #LEFT_OF}.
          *
-         * @param verb One of the verbs defined by
-         *        {@link android.widget.RelativeLayout RelativeLayout}, such as
-         *         ALIGN_WITH_PARENT_LEFT.
-         * @param anchor The id of another view to use as an anchor,
-         *        or a boolean value (represented as {@link RelativeLayout#TRUE}
-         *        for true or 0 for false).  For verbs that don't refer to another sibling
-         *        (for example, ALIGN_WITH_PARENT_BOTTOM) just use -1.
+         * @param verb a layout verb, such as {@link #ALIGN_RIGHT}
+         * @param subject the ID of another view to use as an anchor, or a
+         *                boolean value (represented as {@link #TRUE} for true
+         *                or 0 for false)
          * @see #addRule(int)
+         * @see #removeRule(int)
          * @see #getRule(int)
          */
-        public void addRule(int verb, int anchor) {
-            mRules[verb] = anchor;
-            mInitialRules[verb] = anchor;
+        public void addRule(int verb, int subject) {
+            // If we're removing a relative rule, we'll need to force layout
+            // resolution the next time it's requested.
+            if (!mNeedsLayoutResolution && isRelativeRule(verb)
+                    && mInitialRules[verb] != 0 && subject == 0) {
+                mNeedsLayoutResolution = true;
+            }
+
+            mRules[verb] = subject;
+            mInitialRules[verb] = subject;
             mRulesChanged = true;
         }
 
         /**
          * Removes a layout rule to be interpreted by the RelativeLayout.
+         * <p>
+         * If the rule is relative to the layout direction (ex.
+         * {@link #START_OF}, {@link #ALIGN_PARENT_START}, etc.) then the
+         * layout direction must be resolved using
+         * {@link #resolveLayoutDirection(int)} before before calling
+         * {@link #getRule(int)} with an absolute rule (ex. {@link #LEFT_OF}.
          *
          * @param verb One of the verbs defined by
          *        {@link android.widget.RelativeLayout RelativeLayout}, such as
@@ -1424,9 +1452,7 @@
          * @see #getRule(int)
          */
         public void removeRule(int verb) {
-            mRules[verb] = 0;
-            mInitialRules[verb] = 0;
-            mRulesChanged = true;
+            addRule(verb, 0);
         }
 
         /**
@@ -1451,6 +1477,12 @@
                     mInitialRules[ALIGN_PARENT_START] != 0 || mInitialRules[ALIGN_PARENT_END] != 0);
         }
 
+        private boolean isRelativeRule(int rule) {
+            return rule == START_OF || rule == END_OF
+                    || rule == ALIGN_START || rule == ALIGN_END
+                    || rule == ALIGN_PARENT_START || rule == ALIGN_PARENT_END;
+        }
+
         // The way we are resolving rules depends on the layout direction and if we are pre JB MR1
         // or not.
         //
@@ -1578,7 +1610,9 @@
                     mRules[ALIGN_PARENT_END] = 0;
                 }
             }
+
             mRulesChanged = false;
+            mNeedsLayoutResolution = false;
         }
 
         /**
@@ -1596,13 +1630,7 @@
          * @hide
          */
         public int[] getRules(int layoutDirection) {
-            if (hasRelativeRules() &&
-                    (mRulesChanged || layoutDirection != getLayoutDirection())) {
-                resolveRules(layoutDirection);
-                if (layoutDirection != getLayoutDirection()) {
-                    setLayoutDirection(layoutDirection);
-                }
-            }
+            resolveLayoutDirection(layoutDirection);
             return mRules;
         }
 
@@ -1618,16 +1646,30 @@
             return mRules;
         }
 
+        /**
+         * This will be called by {@link android.view.View#requestLayout()} to
+         * resolve layout parameters that are relative to the layout direction.
+         * <p>
+         * After this method is called, any rules using layout-relative verbs
+         * (ex. {@link #START_OF}) previously added via {@link #addRule(int)}
+         * may only be accessed via their resolved absolute verbs (ex.
+         * {@link #LEFT_OF}).
+         */
         @Override
         public void resolveLayoutDirection(int layoutDirection) {
-            final boolean isLayoutRtl = isLayoutRtl();
-            if (hasRelativeRules() && layoutDirection != getLayoutDirection()) {
+            if (shouldResolveLayoutDirection(layoutDirection)) {
                 resolveRules(layoutDirection);
             }
-            // This will set the layout direction
+
+            // This will set the layout direction.
             super.resolveLayoutDirection(layoutDirection);
         }
 
+        private boolean shouldResolveLayoutDirection(int layoutDirection) {
+            return (mNeedsLayoutResolution || hasRelativeRules())
+                    && (mRulesChanged || layoutDirection != getLayoutDirection());
+        }
+
         /** @hide */
         @Override
         protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) {
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 6c7e298..4ff7869 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -25,7 +25,6 @@
 import android.net.NetworkStats;
 import android.net.wifi.WifiActivityEnergyInfo;
 import android.net.wifi.WifiManager;
-import android.os.BadParcelableException;
 import android.os.BatteryManager;
 import android.os.BatteryStats;
 import android.os.Build;
diff --git a/core/java/com/android/internal/widget/ExploreByTouchHelper.java b/core/java/com/android/internal/widget/ExploreByTouchHelper.java
index f5637dd..50ad547 100644
--- a/core/java/com/android/internal/widget/ExploreByTouchHelper.java
+++ b/core/java/com/android/internal/widget/ExploreByTouchHelper.java
@@ -307,6 +307,10 @@
     private AccessibilityEvent createEventForHost(int eventType) {
         final AccessibilityEvent event = AccessibilityEvent.obtain(eventType);
         mView.onInitializeAccessibilityEvent(event);
+
+        // Allow the client to populate the event.
+        onPopulateEventForHost(event);
+
         return event;
     }
 
@@ -369,6 +373,10 @@
     private AccessibilityNodeInfo createNodeForHost() {
         final AccessibilityNodeInfo node = AccessibilityNodeInfo.obtain(mView);
         mView.onInitializeAccessibilityNodeInfo(node);
+        final int realNodeCount = node.getChildCount();
+
+        // Allow the client to populate the host node.
+        onPopulateNodeForHost(node);
 
         // Add the virtual descendants.
         if (mTempArray == null) {
@@ -378,6 +386,9 @@
         }
         final IntArray virtualViewIds = mTempArray;
         getVisibleVirtualViews(virtualViewIds);
+        if (realNodeCount > 0 && virtualViewIds.size() > 0) {
+            throw new RuntimeException("Views cannot have both real and virtual children");
+        }
 
         final int N = virtualViewIds.size();
         for (int i = 0; i < N; i++) {
@@ -692,6 +703,18 @@
             int virtualViewId, AccessibilityEvent event);
 
     /**
+     * Populates an {@link AccessibilityEvent} with information about the host
+     * view.
+     * <p>
+     * The default implementation is a no-op.
+     *
+     * @param event the event to populate with information about the host view
+     */
+    protected void onPopulateEventForHost(AccessibilityEvent event) {
+        // Default implementation is no-op.
+    }
+
+    /**
      * Populates an {@link AccessibilityNodeInfo} with information
      * about the specified item.
      * <p>
@@ -750,6 +773,18 @@
             int virtualViewId, AccessibilityNodeInfo node);
 
     /**
+     * Populates an {@link AccessibilityNodeInfo} with information about the
+     * host view.
+     * <p>
+     * The default implementation is a no-op.
+     *
+     * @param node the node to populate with information about the host view
+     */
+    protected void onPopulateNodeForHost(AccessibilityNodeInfo node) {
+        // Default implementation is no-op.
+    }
+
+    /**
      * Performs the specified accessibility action on the item associated
      * with the virtual view identifier. See
      * {@link AccessibilityNodeInfo#performAction(int, Bundle)} for
diff --git a/core/java/com/android/internal/widget/NonClientDecorView.java b/core/java/com/android/internal/widget/NonClientDecorView.java
index 05711b5..92812f8 100644
--- a/core/java/com/android/internal/widget/NonClientDecorView.java
+++ b/core/java/com/android/internal/widget/NonClientDecorView.java
@@ -20,6 +20,7 @@
 import android.graphics.Rect;
 import android.os.RemoteException;
 import android.util.AttributeSet;
+import android.view.MotionEvent;
 import android.view.View;
 import android.widget.LinearLayout;
 import android.view.ViewGroup;
@@ -63,18 +64,31 @@
     private final int DECOR_SHADOW_FOCUSED_HEIGHT_IN_DIP = 20;
     // The height of a window which has not in DIP.
     private final int DECOR_SHADOW_UNFOCUSED_HEIGHT_IN_DIP = 5;
-
     private PhoneWindow mOwner = null;
-    boolean mWindowHasShadow = false;
-    boolean mShowDecor = false;
+    private boolean mWindowHasShadow = false;
+    private boolean mShowDecor = false;
+
+    // True if the window is being dragged.
+    private boolean mDragging = false;
+
+    // The bounds of the window and the absolute mouse pointer coordinates from before we started to
+    // drag the window. They will be used to determine the next window position.
+    private final Rect mWindowOriginalBounds = new Rect();
+    private float mStartDragX;
+    private float mStartDragY;
+    // True when the left mouse button got released while dragging.
+    private boolean mLeftMouseButtonReleased;
+
+    // Avoiding re-creation of Rect's by keeping a temporary window drag bound.
+    private final Rect mWindowDragBounds = new Rect();
 
     // The current focus state of the window for updating the window elevation.
-    boolean mWindowHasFocus = true;
+    private boolean mWindowHasFocus = true;
 
     // Cludge to address b/22668382: Set the shadow size to the maximum so that the layer
     // size calculation takes the shadow size into account. We set the elevation currently
     // to max until the first layout command has been executed.
-    boolean mAllowUpdateElevation = false;
+    private boolean mAllowUpdateElevation = false;
 
     public NonClientDecorView(Context context) {
         super(context);
@@ -103,6 +117,62 @@
         findViewById(R.id.close_window).setOnClickListener(this);
     }
 
+    @Override
+    public boolean onTouchEvent(MotionEvent e) {
+        // Note: There are no mixed events. When a new device gets used (e.g. 1. Mouse, 2. touch)
+        // the old input device events get cancelled first. So no need to remember the kind of
+        // input device we are listening to.
+        switch (e.getActionMasked()) {
+            case MotionEvent.ACTION_DOWN:
+                // A drag action is started if we aren't dragging already and the starting event is
+                // either a left mouse button or any other input device.
+                if (!mDragging &&
+                        (e.getToolType(e.getActionIndex()) != MotionEvent.TOOL_TYPE_MOUSE ||
+                                (e.getButtonState() & MotionEvent.BUTTON_PRIMARY) != 0)) {
+                    mDragging = true;
+                    mWindowOriginalBounds.set(getActivityBounds());
+                    mLeftMouseButtonReleased = false;
+                    mStartDragX = e.getRawX();
+                    mStartDragY = e.getRawY();
+                }
+                break;
+
+            case MotionEvent.ACTION_MOVE:
+                if (mDragging && !mLeftMouseButtonReleased) {
+                    if (e.getToolType(e.getActionIndex()) == MotionEvent.TOOL_TYPE_MOUSE &&
+                            (e.getButtonState() & MotionEvent.BUTTON_PRIMARY) == 0) {
+                        // There is no separate mouse button up call and if the user mixes mouse
+                        // button drag actions, we stop dragging once he releases the button.
+                        mLeftMouseButtonReleased = true;
+                        break;
+                    }
+                    mWindowDragBounds.set(mWindowOriginalBounds);
+                    mWindowDragBounds.offset(Math.round(e.getRawX() - mStartDragX),
+                            Math.round(e.getRawY() - mStartDragY));
+                    setActivityBounds(mWindowDragBounds);
+                }
+                break;
+
+            case MotionEvent.ACTION_UP:
+                if (mDragging) {
+                    // Since the window is already where it should be we don't have to do anything
+                    // special at this time.
+                    mDragging = false;
+                    return true;
+                }
+                break;
+
+            case MotionEvent.ACTION_CANCEL:
+                if (mDragging) {
+                    mDragging = false;
+                    setActivityBounds(mWindowOriginalBounds);
+                    return true;
+                }
+                break;
+        }
+        return mDragging;
+    }
+
     /**
      * The phone window configuration has changed and the decor needs to be updated.
      * @param showDecor True if the decor should be shown.
diff --git a/core/jni/android/graphics/AvoidXfermode.h b/core/jni/android/graphics/AvoidXfermode.h
index 318d7be..8b7fb71 100644
--- a/core/jni/android/graphics/AvoidXfermode.h
+++ b/core/jni/android/graphics/AvoidXfermode.h
@@ -40,7 +40,7 @@
                 Tolerance near 255: draw on any colors even remotely similar to the op-color
      */
     static AvoidXfermode* Create(SkColor opColor, U8CPU tolerance, Mode mode) {
-        return SkNEW_ARGS(AvoidXfermode, (opColor, tolerance, mode));
+        return new AvoidXfermode(opColor, tolerance, mode);
     }
 
     // overrides from SkXfermode
diff --git a/libs/hwui/AmbientShadow.cpp b/libs/hwui/AmbientShadow.cpp
index 751531e..20ecda2 100644
--- a/libs/hwui/AmbientShadow.cpp
+++ b/libs/hwui/AmbientShadow.cpp
@@ -52,14 +52,14 @@
 // If this is set to negative value, then all the edge will be tessellated.
 #define ALPHA_THRESHOLD (0.1f / 255.0f)
 
-#include <math.h>
-#include <utils/Log.h>
-
 #include "AmbientShadow.h"
+
 #include "ShadowTessellator.h"
 #include "Vertex.h"
 #include "VertexBuffer.h"
-#include "utils/MathUtils.h"
+
+#include <algorithm>
+#include <utils/Log.h>
 
 namespace android {
 namespace uirenderer {
@@ -78,7 +78,7 @@
 // The input z value will be converted to be non-negative inside.
 // The output must be ranged from 0 to 1.
 inline float getAlphaFromFactoredZ(float factoredZ) {
-    return 1.0 / (1 + MathUtils::max(factoredZ, 0.0f));
+    return 1.0 / (1 + std::max(factoredZ, 0.0f));
 }
 
 // The shader is using gaussian function e^-(1-x)*(1-x)*4, therefore, we transform
diff --git a/libs/hwui/Android.common.mk b/libs/hwui/Android.common.mk
deleted file mode 100644
index 5c48709..0000000
--- a/libs/hwui/Android.common.mk
+++ /dev/null
@@ -1,118 +0,0 @@
-# getConfig in external/skia/include/core/SkBitmap.h is deprecated.
-# Allow Gnu extension: in-class initializer of static 'const float' member.
-# DeferredLayerUpdater.h: private field 'mRenderThread' is not used.
-
-LOCAL_SRC_FILES := \
-    font/CacheTexture.cpp \
-    font/Font.cpp \
-    renderstate/Blend.cpp \
-    renderstate/MeshState.cpp \
-    renderstate/PixelBufferState.cpp \
-    renderstate/RenderState.cpp \
-    renderstate/Scissor.cpp \
-    renderstate/Stencil.cpp \
-    renderstate/TextureState.cpp \
-    renderthread/CanvasContext.cpp \
-    renderthread/DrawFrameTask.cpp \
-    renderthread/EglManager.cpp \
-    renderthread/RenderProxy.cpp \
-    renderthread/RenderTask.cpp \
-    renderthread/RenderThread.cpp \
-    renderthread/TimeLord.cpp \
-    thread/TaskManager.cpp \
-    utils/Blur.cpp \
-    utils/GLUtils.cpp \
-    utils/LinearAllocator.cpp \
-    utils/NinePatchImpl.cpp \
-    AmbientShadow.cpp \
-    AnimationContext.cpp \
-    Animator.cpp \
-    AnimatorManager.cpp \
-    AssetAtlas.cpp \
-    Caches.cpp \
-    CanvasState.cpp \
-    ClipArea.cpp \
-    DamageAccumulator.cpp \
-    DeferredDisplayList.cpp \
-    DeferredLayerUpdater.cpp \
-    DisplayList.cpp \
-    DisplayListCanvas.cpp \
-    Dither.cpp \
-    Extensions.cpp \
-    FboCache.cpp \
-    FontRenderer.cpp \
-    FrameInfo.cpp \
-    FrameInfoVisualizer.cpp \
-    GammaFontRenderer.cpp \
-    GlopBuilder.cpp \
-    GradientCache.cpp \
-    Image.cpp \
-    Interpolator.cpp \
-    JankTracker.cpp \
-    Layer.cpp \
-    LayerCache.cpp \
-    LayerRenderer.cpp \
-    Matrix.cpp \
-    OpenGLRenderer.cpp \
-    Patch.cpp \
-    PatchCache.cpp \
-    PathCache.cpp \
-    PathTessellator.cpp \
-    PixelBuffer.cpp \
-    Program.cpp \
-    ProgramCache.cpp \
-    Properties.cpp \
-    RenderBufferCache.cpp \
-    RenderNode.cpp \
-    RenderProperties.cpp \
-    ResourceCache.cpp \
-    ShadowTessellator.cpp \
-    SkiaCanvas.cpp \
-    SkiaCanvasProxy.cpp \
-    SkiaShader.cpp \
-    Snapshot.cpp \
-    SpotShadow.cpp \
-    TessellationCache.cpp \
-    TextDropShadowCache.cpp \
-    Texture.cpp \
-    TextureCache.cpp
-
-intermediates := $(call intermediates-dir-for,STATIC_LIBRARIES,libRS,TARGET,)
-
-LOCAL_C_INCLUDES += \
-    external/skia/src/core
-
-LOCAL_CFLAGS += -DEGL_EGLEXT_PROTOTYPES -DGL_GLEXT_PROTOTYPES
-LOCAL_SHARED_LIBRARIES := liblog libcutils libutils libEGL libGLESv2 libskia libui libgui
-
-ifneq (false,$(ANDROID_ENABLE_RENDERSCRIPT))
-    LOCAL_CFLAGS += -DANDROID_ENABLE_RENDERSCRIPT
-    LOCAL_SHARED_LIBRARIES += libRS libRScpp
-    LOCAL_C_INCLUDES += \
-        $(intermediates) \
-        frameworks/rs/cpp \
-        frameworks/rs \
-
-endif
-
-ifndef HWUI_COMPILE_SYMBOLS
-    LOCAL_CFLAGS += -fvisibility=hidden
-endif
-
-ifdef HWUI_COMPILE_FOR_PERF
-    # TODO: Non-arm?
-    LOCAL_CFLAGS += -fno-omit-frame-pointer -marm -mapcs
-endif
-
-ifeq (true, $(HWUI_NULL_GPU))
-    LOCAL_SRC_FILES += \
-        tests/nullegl.cpp \
-        tests/nullgles.cpp
-
-    LOCAL_CFLAGS += -DHWUI_NULL_GPU
-endif
-
-# Defaults for ATRACE_TAG and LOG_TAG for libhwui
-LOCAL_CFLAGS += -DATRACE_TAG=ATRACE_TAG_VIEW -DLOG_TAG=\"OpenGLRenderer\"
-LOCAL_CFLAGS += -Wall -Wno-unused-parameter -Wunreachable-code
-LOCAL_CFLAGS += -ffast-math -O3 -Werror
diff --git a/libs/hwui/Canvas.h b/libs/hwui/Canvas.h
index 3c77b3d..4bd4ac8 100644
--- a/libs/hwui/Canvas.h
+++ b/libs/hwui/Canvas.h
@@ -85,10 +85,6 @@
     virtual void getMatrix(SkMatrix* outMatrix) const = 0;
     virtual void setMatrix(const SkMatrix& matrix) = 0;
 
-    /// Like setMatrix(), but to be translated into local / view-relative coordinates
-    /// rather than executed in global / device coordinates at rendering time.
-    virtual void setLocalMatrix(const SkMatrix& matrix) = 0;
-
     virtual void concat(const SkMatrix& matrix) = 0;
     virtual void rotate(float degrees) = 0;
     virtual void scale(float sx, float sy) = 0;
diff --git a/libs/hwui/CanvasState.cpp b/libs/hwui/CanvasState.cpp
index e22b0d3..54fb5f2 100644
--- a/libs/hwui/CanvasState.cpp
+++ b/libs/hwui/CanvasState.cpp
@@ -138,7 +138,7 @@
 }
 
 void CanvasState::setMatrix(const Matrix4& matrix) {
-    mSnapshot->transform->load(matrix);
+    *(mSnapshot->transform) = matrix;
 }
 
 void CanvasState::concatMatrix(const SkMatrix& matrix) {
diff --git a/libs/hwui/DisplayListCanvas.cpp b/libs/hwui/DisplayListCanvas.cpp
index 61c5883..bb149fe 100644
--- a/libs/hwui/DisplayListCanvas.cpp
+++ b/libs/hwui/DisplayListCanvas.cpp
@@ -168,11 +168,6 @@
     mState.setMatrix(matrix);
 }
 
-void DisplayListCanvas::setLocalMatrix(const SkMatrix& matrix) {
-    addStateOp(new (alloc()) SetLocalMatrixOp(matrix));
-    mState.setMatrix(matrix);
-}
-
 void DisplayListCanvas::concat(const SkMatrix& matrix) {
     addStateOp(new (alloc()) ConcatMatrixOp(matrix));
     mState.concatMatrix(matrix);
diff --git a/libs/hwui/DisplayListCanvas.h b/libs/hwui/DisplayListCanvas.h
index 3b61904..f29e835 100644
--- a/libs/hwui/DisplayListCanvas.h
+++ b/libs/hwui/DisplayListCanvas.h
@@ -143,7 +143,6 @@
     // Matrix
     virtual void getMatrix(SkMatrix* outMatrix) const override { mState.getMatrix(outMatrix); }
     virtual void setMatrix(const SkMatrix& matrix) override;
-    virtual void setLocalMatrix(const SkMatrix& matrix) override;
 
     virtual void concat(const SkMatrix& matrix) override;
     virtual void rotate(float degrees) override;
diff --git a/libs/hwui/DisplayListOp.h b/libs/hwui/DisplayListOp.h
index 8ff58d4..8bb892f 100644
--- a/libs/hwui/DisplayListOp.h
+++ b/libs/hwui/DisplayListOp.h
@@ -472,7 +472,9 @@
             : mMatrix(matrix) {}
 
     virtual void applyState(OpenGLRenderer& renderer, int saveCount) const override {
-        renderer.setMatrix(mMatrix);
+        // Setting a matrix on a Canvas isn't equivalent to setting a total matrix on the scene.
+        // Set a canvas-relative matrix on the renderer instead.
+        renderer.setLocalMatrix(mMatrix);
     }
 
     virtual void output(int level, uint32_t logFlags) const override {
@@ -489,25 +491,6 @@
     const SkMatrix mMatrix;
 };
 
-class SetLocalMatrixOp : public StateOp {
-public:
-    SetLocalMatrixOp(const SkMatrix& matrix)
-            : mMatrix(matrix) {}
-
-    virtual void applyState(OpenGLRenderer& renderer, int saveCount) const override {
-        renderer.setLocalMatrix(mMatrix);
-    }
-
-    virtual void output(int level, uint32_t logFlags) const override {
-        OP_LOG("SetLocalMatrix " SK_MATRIX_STRING, SK_MATRIX_ARGS(&mMatrix));
-    }
-
-    virtual const char* name() override { return "SetLocalMatrix"; }
-
-private:
-    const SkMatrix mMatrix;
-};
-
 class ConcatMatrixOp : public StateOp {
 public:
     ConcatMatrixOp(const SkMatrix& matrix)
diff --git a/libs/hwui/FontRenderer.cpp b/libs/hwui/FontRenderer.cpp
index 057c231..75c3ead 100644
--- a/libs/hwui/FontRenderer.cpp
+++ b/libs/hwui/FontRenderer.cpp
@@ -26,14 +26,12 @@
 #include "Rect.h"
 #include "renderstate/RenderState.h"
 #include "utils/Blur.h"
-#include "utils/MathUtils.h"
 #include "utils/Timing.h"
 
+#include <algorithm>
+#include <cutils/properties.h>
 #include <SkGlyph.h>
 #include <SkUtils.h>
-
-#include <cutils/properties.h>
-
 #include <utils/Log.h>
 
 #ifdef ANDROID_ENABLE_RENDERSCRIPT
@@ -118,10 +116,10 @@
 
     uint32_t maxTextureSize = (uint32_t) Caches::getInstance().maxTextureSize;
 
-    mSmallCacheWidth = MathUtils::min(mSmallCacheWidth, maxTextureSize);
-    mSmallCacheHeight = MathUtils::min(mSmallCacheHeight, maxTextureSize);
-    mLargeCacheWidth = MathUtils::min(mLargeCacheWidth, maxTextureSize);
-    mLargeCacheHeight = MathUtils::min(mLargeCacheHeight, maxTextureSize);
+    mSmallCacheWidth = std::min(mSmallCacheWidth, maxTextureSize);
+    mSmallCacheHeight = std::min(mSmallCacheHeight, maxTextureSize);
+    mLargeCacheWidth = std::min(mLargeCacheWidth, maxTextureSize);
+    mLargeCacheHeight = std::min(mLargeCacheHeight, maxTextureSize);
 
     if (sLogFontRendererCreate) {
         INIT_LOGD("  Text cache sizes, in pixels: %i x %i, %i x %i, %i x %i, %i x %i",
diff --git a/libs/hwui/GlopBuilder.cpp b/libs/hwui/GlopBuilder.cpp
index 288fed3..c9e3880 100644
--- a/libs/hwui/GlopBuilder.cpp
+++ b/libs/hwui/GlopBuilder.cpp
@@ -467,8 +467,8 @@
         const int transformFlags) {
     TRIGGER_STAGE(kTransformStage);
 
-    mOutGlop->transform.ortho.load(ortho);
-    mOutGlop->transform.canvas.load(canvas);
+    mOutGlop->transform.ortho = ortho;
+    mOutGlop->transform.canvas = canvas;
     mOutGlop->transform.transformFlags = transformFlags;
 }
 
@@ -615,7 +615,7 @@
             shaderMatrix.loadInverse(mOutGlop->transform.canvas);
             shaderMatrix.multiply(mOutGlop->transform.modelView);
         } else {
-            shaderMatrix.load(mOutGlop->transform.modelView);
+            shaderMatrix = mOutGlop->transform.modelView;
         }
         SkiaShader::store(mCaches, *mShader, shaderMatrix,
                 &textureUnit, &mDescription, &(mOutGlop->fill.skiaShaderData));
diff --git a/libs/hwui/Interpolator.cpp b/libs/hwui/Interpolator.cpp
index e1b0fc3..cc47f00 100644
--- a/libs/hwui/Interpolator.cpp
+++ b/libs/hwui/Interpolator.cpp
@@ -16,11 +16,11 @@
 
 #include "Interpolator.h"
 
-#include <cmath>
-#include <cutils/log.h>
-
 #include "utils/MathUtils.h"
 
+#include <algorithm>
+#include <cutils/log.h>
+
 namespace android {
 namespace uirenderer {
 
@@ -106,7 +106,7 @@
     weight = modff(lutpos, &ipart);
 
     int i1 = (int) ipart;
-    int i2 = MathUtils::min(i1 + 1, (int) mSize - 1);
+    int i2 = std::min(i1 + 1, (int) mSize - 1);
 
     LOG_ALWAYS_FATAL_IF(i1 < 0 || i2 < 0, "negatives in interpolation!"
             " i1=%d, i2=%d, input=%f, lutpos=%f, size=%zu, values=%p, ipart=%f, weight=%f",
diff --git a/libs/hwui/Matrix.cpp b/libs/hwui/Matrix.cpp
index 115e23c..73ebd1304 100644
--- a/libs/hwui/Matrix.cpp
+++ b/libs/hwui/Matrix.cpp
@@ -152,10 +152,6 @@
     mType = kTypeUnknown;
 }
 
-void Matrix4::load(const Matrix4& v) {
-    *this = v;
-}
-
 void Matrix4::load(const SkMatrix& v) {
     memset(data, 0, sizeof(data));
 
diff --git a/libs/hwui/Matrix.h b/libs/hwui/Matrix.h
index ed54a25..ed517ac 100644
--- a/libs/hwui/Matrix.h
+++ b/libs/hwui/Matrix.h
@@ -114,7 +114,6 @@
     void loadIdentity();
 
     void load(const float* v);
-    void load(const Matrix4& v);
     void load(const SkMatrix& v);
 
     void loadInverse(const Matrix4& v);
@@ -139,7 +138,7 @@
     void multiply(const Matrix4& v) {
         Matrix4 u;
         u.loadMultiply(*this, v);
-        load(u);
+        *this = u;
     }
 
     void multiply(float v);
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index 7a56d42..b35c017 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -1141,7 +1141,7 @@
 
     // Transform and alpha always deferred, since they are used by state operations
     // (Note: saveLayer/restore use colorFilter and alpha, so we just save restore everything)
-    state.mMatrix.load(*currentMatrix);
+    state.mMatrix = *currentMatrix;
     state.mAlpha = currentSnapshot()->alpha;
 
     // always store/restore, since these are just pointers
@@ -1151,7 +1151,7 @@
 }
 
 void OpenGLRenderer::restoreDisplayState(const DeferredDisplayState& state, bool skipClipRestore) {
-    setMatrix(state.mMatrix);
+    setGlobalMatrix(state.mMatrix);
     writableSnapshot()->alpha = state.mAlpha;
     writableSnapshot()->roundRectClipState = state.mRoundRectClipState;
     writableSnapshot()->projectionPathMask = state.mProjectionPathMask;
@@ -2098,8 +2098,9 @@
     mState.skew(sx, sy);
 }
 
-void OpenGLRenderer::setMatrix(const Matrix4& matrix) {
-    mState.setMatrix(matrix);
+void OpenGLRenderer::setLocalMatrix(const Matrix4& matrix) {
+    mState.setMatrix(mBaseTransform);
+    mState.concatMatrix(matrix);
 }
 
 void OpenGLRenderer::setLocalMatrix(const SkMatrix& matrix) {
diff --git a/libs/hwui/OpenGLRenderer.h b/libs/hwui/OpenGLRenderer.h
index 4f75482..45662a7 100755
--- a/libs/hwui/OpenGLRenderer.h
+++ b/libs/hwui/OpenGLRenderer.h
@@ -367,8 +367,10 @@
     void restore();
     void restoreToCount(int saveCount);
 
-    void getMatrix(SkMatrix* outMatrix) const { mState.getMatrix(outMatrix); }
-    void setMatrix(const SkMatrix& matrix) { mState.setMatrix(matrix); }
+    void setGlobalMatrix(const Matrix4& matrix) {
+        mState.setMatrix(matrix);
+    }
+    void setLocalMatrix(const Matrix4& matrix);
     void setLocalMatrix(const SkMatrix& matrix);
     void concatMatrix(const SkMatrix& matrix) { mState.concatMatrix(matrix); }
 
diff --git a/libs/hwui/Patch.cpp b/libs/hwui/Patch.cpp
index 500f9e9..b471e78 100644
--- a/libs/hwui/Patch.cpp
+++ b/libs/hwui/Patch.cpp
@@ -14,16 +14,16 @@
  * limitations under the License.
  */
 
-#include <cmath>
-
-#include <utils/Log.h>
+#include "Patch.h"
 
 #include "Caches.h"
-#include "Patch.h"
 #include "Properties.h"
 #include "UvMapper.h"
 #include "utils/MathUtils.h"
 
+#include <algorithm>
+#include <utils/Log.h>
+
 namespace android {
 namespace uirenderer {
 
@@ -189,10 +189,10 @@
     const uint32_t oldQuadCount = quadCount;
     quadCount++;
 
-    x1 = MathUtils::max(x1, 0.0f);
-    x2 = MathUtils::max(x2, 0.0f);
-    y1 = MathUtils::max(y1, 0.0f);
-    y2 = MathUtils::max(y2, 0.0f);
+    x1 = std::max(x1, 0.0f);
+    x2 = std::max(x2, 0.0f);
+    y1 = std::max(y1, 0.0f);
+    y2 = std::max(y2, 0.0f);
 
     // Skip degenerate and transparent (empty) quads
     if ((mColors[oldQuadCount] == 0) || x1 >= x2 || y1 >= y2) {
diff --git a/libs/hwui/PathTessellator.cpp b/libs/hwui/PathTessellator.cpp
index 8fa187c..b57b8f0 100644
--- a/libs/hwui/PathTessellator.cpp
+++ b/libs/hwui/PathTessellator.cpp
@@ -32,6 +32,15 @@
 #define DEBUG_DUMP_BUFFER()
 #endif
 
+#include "PathTessellator.h"
+
+#include "Matrix.h"
+#include "Vector.h"
+#include "Vertex.h"
+#include "utils/MathUtils.h"
+
+#include <algorithm>
+
 #include <SkPath.h>
 #include <SkPaint.h>
 #include <SkPoint.h>
@@ -44,12 +53,6 @@
 #include <utils/Log.h>
 #include <utils/Trace.h>
 
-#include "PathTessellator.h"
-#include "Matrix.h"
-#include "Vector.h"
-#include "Vertex.h"
-#include "utils/MathUtils.h"
-
 namespace android {
 namespace uirenderer {
 
@@ -152,7 +155,7 @@
             // always use 2 points for hairline
             if (halfStrokeWidth == 0.0f) return 2;
 
-            float threshold = MathUtils::min(inverseScaleX, inverseScaleY) * ROUND_CAP_THRESH;
+            float threshold = std::min(inverseScaleX, inverseScaleY) * ROUND_CAP_THRESH;
             return MathUtils::divisionsNeededToApproximateArc(halfStrokeWidth, PI, threshold);
         }
         return 0;
diff --git a/libs/hwui/PathTessellator.h b/libs/hwui/PathTessellator.h
index b66e832..cddfb04 100644
--- a/libs/hwui/PathTessellator.h
+++ b/libs/hwui/PathTessellator.h
@@ -22,8 +22,12 @@
 #include "Vertex.h"
 #include "VertexBuffer.h"
 
+#include <algorithm>
 #include <vector>
 
+class SkPath;
+class SkPaint;
+
 namespace android {
 namespace uirenderer {
 
@@ -38,7 +42,7 @@
         : thresholdSquared(pixelThreshold * pixelThreshold)
         , sqrInvScaleX(invScaleX * invScaleX)
         , sqrInvScaleY(invScaleY * invScaleY)
-        , thresholdForConicQuads(pixelThreshold * MathUtils::min(invScaleX, invScaleY) / 2.0f) {
+        , thresholdForConicQuads(pixelThreshold * std::min(invScaleX, invScaleY) / 2.0f) {
     };
 
     const float thresholdSquared;
diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp
index 48da3e8..73c0107 100644
--- a/libs/hwui/RenderNode.cpp
+++ b/libs/hwui/RenderNode.cpp
@@ -532,7 +532,7 @@
     if (properties().getProjectBackwards()) {
         // composited projectee, flag for out of order draw, save matrix, and store in proj surface
         opState->mSkipInOrderDraw = true;
-        opState->mTransformFromCompositingAncestor.load(localTransformFromProjectionSurface);
+        opState->mTransformFromCompositingAncestor = localTransformFromProjectionSurface;
         compositedChildrenOfProjectionSurface->push_back(opState);
     } else {
         // standard in order draw
@@ -719,7 +719,7 @@
     // Apply the base transform of the parent of the 3d children. This isolates
     // 3d children of the current chunk from transformations made in previous chunks.
     int rootRestoreTo = renderer.save(SkCanvas::kMatrix_SaveFlag);
-    renderer.setMatrix(initialTransform);
+    renderer.setGlobalMatrix(initialTransform);
 
     /**
      * Draw shadows and (potential) casters mostly in order, but allow the shadows of casters
diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp
index ceca607..0c5b4b7 100644
--- a/libs/hwui/SkiaCanvas.cpp
+++ b/libs/hwui/SkiaCanvas.cpp
@@ -74,7 +74,6 @@
 
     virtual void getMatrix(SkMatrix* outMatrix) const override;
     virtual void setMatrix(const SkMatrix& matrix) override;
-    virtual void setLocalMatrix(const SkMatrix& matrix) override { this->setMatrix(matrix); }
     virtual void concat(const SkMatrix& matrix) override;
     virtual void rotate(float degrees) override;
     virtual void scale(float sx, float sy) override;
@@ -321,7 +320,7 @@
     }
 
     if (NULL == mSaveStack.get()) {
-        mSaveStack.reset(SkNEW_ARGS(SkDeque, (sizeof(struct SaveRec), 8)));
+        mSaveStack.reset(new SkDeque(sizeof(struct SaveRec), 8));
     }
 
     SaveRec* rec = static_cast<SaveRec*>(mSaveStack->push_back());
diff --git a/libs/hwui/SkiaCanvasProxy.cpp b/libs/hwui/SkiaCanvasProxy.cpp
index 5e6d774..41152dc 100644
--- a/libs/hwui/SkiaCanvasProxy.cpp
+++ b/libs/hwui/SkiaCanvasProxy.cpp
@@ -142,7 +142,7 @@
         const SkPaint* paint) {
     // TODO: if bitmap is a subset, do we need to add pixelRefOrigin to src?
     mCanvas->save(SkCanvas::kMatrixClip_SaveFlag);
-    mCanvas->setLocalMatrix(SkMatrix::I());
+    mCanvas->setMatrix(SkMatrix::I());
     mCanvas->drawBitmap(bitmap, left, top, paint);
     mCanvas->restore();
 }
@@ -192,9 +192,7 @@
 }
 
 void SkiaCanvasProxy::didSetMatrix(const SkMatrix& matrix) {
-    // SkCanvas setMatrix() is relative to the Canvas origin, but OpenGLRenderer's
-    // setMatrix() is relative to device origin; call setLocalMatrix() instead.
-    mCanvas->setLocalMatrix(matrix);
+    mCanvas->setMatrix(matrix);
 }
 
 void SkiaCanvasProxy::onDrawDRRect(const SkRRect& outer, const SkRRect& inner,
diff --git a/libs/hwui/Snapshot.cpp b/libs/hwui/Snapshot.cpp
index fd077d9..ca19a42 100644
--- a/libs/hwui/Snapshot.cpp
+++ b/libs/hwui/Snapshot.cpp
@@ -58,7 +58,7 @@
         , mViewportData(s->mViewportData)
         , mRelativeLightCenter(s->mRelativeLightCenter) {
     if (saveFlags & SkCanvas::kMatrix_SaveFlag) {
-        mTransformRoot.load(*s->transform);
+        mTransformRoot = *s->transform;
         transform = &mTransformRoot;
     } else {
         transform = s->transform;
@@ -190,8 +190,7 @@
     state->highPriority = highPriority;
 
     // store the inverse drawing matrix
-    Matrix4 roundRectDrawingMatrix;
-    roundRectDrawingMatrix.load(getOrthoMatrix());
+    Matrix4 roundRectDrawingMatrix = getOrthoMatrix();
     roundRectDrawingMatrix.multiply(*transform);
     state->matrix.loadInverse(roundRectDrawingMatrix);
 
diff --git a/libs/hwui/SpotShadow.cpp b/libs/hwui/SpotShadow.cpp
index eaf0303..9b0a1aa 100644
--- a/libs/hwui/SpotShadow.cpp
+++ b/libs/hwui/SpotShadow.cpp
@@ -46,17 +46,18 @@
 #define TRANSFORMED_PENUMBRA_ALPHA 1.0f
 #define TRANSFORMED_UMBRA_ALPHA 0.0f
 
+#include "SpotShadow.h"
+
+#include "ShadowTessellator.h"
+#include "Vertex.h"
+#include "VertexBuffer.h"
+#include "utils/MathUtils.h"
+
 #include <algorithm>
 #include <math.h>
 #include <stdlib.h>
 #include <utils/Log.h>
 
-#include "ShadowTessellator.h"
-#include "SpotShadow.h"
-#include "Vertex.h"
-#include "VertexBuffer.h"
-#include "utils/MathUtils.h"
-
 // TODO: After we settle down the new algorithm, we can remove the old one and
 // its utility functions.
 // Right now, we still need to keep it for comparison purpose and future expansion.
@@ -543,7 +544,7 @@
         }
 
         float ratioVI = outlineData[i].radius / distOutline;
-        minRaitoVI = MathUtils::min(minRaitoVI, ratioVI);
+        minRaitoVI = std::min(minRaitoVI, ratioVI);
         if (ratioVI >= (1 - FAKE_UMBRA_SIZE_RATIO)) {
             ratioVI = (1 - FAKE_UMBRA_SIZE_RATIO);
         }
diff --git a/libs/hwui/VertexBuffer.h b/libs/hwui/VertexBuffer.h
index 9be4d84..c0373ac 100644
--- a/libs/hwui/VertexBuffer.h
+++ b/libs/hwui/VertexBuffer.h
@@ -17,7 +17,7 @@
 #ifndef ANDROID_HWUI_VERTEX_BUFFER_H
 #define ANDROID_HWUI_VERTEX_BUFFER_H
 
-#include "utils/MathUtils.h"
+#include <algorithm>
 
 namespace android {
 namespace uirenderer {
@@ -129,10 +129,10 @@
     unsigned int getSize() const { return mByteCount; }
     unsigned int getIndexCount() const { return mIndexCount; }
     void updateIndexCount(unsigned int newCount)  {
-        mIndexCount = MathUtils::min(newCount, mAllocatedIndexCount);
+        mIndexCount = std::min(newCount, mAllocatedIndexCount);
     }
     void updateVertexCount(unsigned int newCount)  {
-        mVertexCount = MathUtils::min(newCount, mAllocatedVertexCount);
+        mVertexCount = std::min(newCount, mAllocatedVertexCount);
     }
     MeshFeatureFlags getMeshFeatureFlags() const { return mMeshFeatureFlags; }
     void setMeshFeatureFlags(int flags) {
diff --git a/libs/hwui/renderstate/RenderState.cpp b/libs/hwui/renderstate/RenderState.cpp
index 1e39bfa..b5ed9e6 100644
--- a/libs/hwui/renderstate/RenderState.cpp
+++ b/libs/hwui/renderstate/RenderState.cpp
@@ -19,6 +19,8 @@
 #include "renderthread/EglManager.h"
 #include "utils/GLUtils.h"
 
+#include <algorithm>
+
 namespace android {
 namespace uirenderer {
 
@@ -320,7 +322,7 @@
         GLsizei elementsCount = mesh.elementCount;
         const GLbyte* vertexData = static_cast<const GLbyte*>(vertices.position);
         while (elementsCount > 0) {
-            GLsizei drawCount = MathUtils::min(elementsCount, (GLsizei) kMaxNumberOfQuads * 6);
+            GLsizei drawCount = std::min(elementsCount, (GLsizei) kMaxNumberOfQuads * 6);
 
             // rebind pointers without forcing, since initial bind handled above
             meshState().bindPositionVertexPointer(false, vertexData, vertices.stride);
diff --git a/libs/hwui/utils/MathUtils.h b/libs/hwui/utils/MathUtils.h
index 9c3787c..8d20f21 100644
--- a/libs/hwui/utils/MathUtils.h
+++ b/libs/hwui/utils/MathUtils.h
@@ -16,6 +16,7 @@
 #ifndef MATHUTILS_H
 #define MATHUTILS_H
 
+#include <algorithm>
 #include <math.h>
 
 namespace android {
@@ -82,18 +83,8 @@
     }
 
     template<typename T>
-    static inline T max(T a, T b) {
-        return a > b ? a : b;
-    }
-
-    template<typename T>
-    static inline T min(T a, T b) {
-        return a < b ? a : b;
-    }
-
-    template<typename T>
     static inline T clamp(T a, T minValue, T maxValue) {
-        return min(max(a, minValue), maxValue);
+        return std::min(std::max(a, minValue), maxValue);
     }
 
     inline static float lerp(float v1, float v2, float t) {
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeService.java b/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
index 328ee35..36efead 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
@@ -256,6 +256,8 @@
 
     private void continuePulsing(int reason) {
         if (mHost.isPulsingBlocked()) {
+            mPulsing = false;
+            mWakeLock.release();
             return;
         }
         mHost.pulseWhileDozing(new DozeHost.PulseCallback() {
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 947c19c..00ac5f9 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.recents.views;
 
+import android.app.ActivityManager;
 import android.app.ActivityOptions;
 import android.app.TaskStackBuilder;
 import android.content.Context;
@@ -31,6 +32,8 @@
 import android.provider.Settings;
 import android.util.AttributeSet;
 import android.util.Log;
+import android.util.SparseArray;
+import android.view.AppTransitionAnimationSpec;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.WindowInsets;
@@ -60,6 +63,8 @@
 
     private static final String TAG = "RecentsView";
 
+    private static final boolean ADD_HEADER_BITMAP = true;
+
     /** The RecentsView callbacks */
     public interface RecentsViewCallbacks {
         public void onTaskViewClicked();
@@ -443,62 +448,158 @@
         }
     }
 
-    private void postDrawHeaderThumbnailTransitionRunnable(final TaskView tv, final int offsetX,
-            final int offsetY, final TaskViewTransform transform,
+    private void postDrawHeaderThumbnailTransitionRunnable(final TaskStackView view,
+            final TaskView clickedView, final int offsetX, final int offsetY,
+            final float stackScroll,
             final ActivityOptions.OnAnimationStartedListener animStartedListener) {
         Runnable r = new Runnable() {
             @Override
             public void run() {
-                // Disable any focused state before we draw the header
-                if (tv.isFocusedTask()) {
-                    tv.unsetFocusedTask();
-                }
+                overrideDrawHeaderThumbnailTransition(view, clickedView, offsetX, offsetY,
+                        stackScroll, animStartedListener);
 
-                float scale = tv.getScaleX();
-                int fromHeaderWidth = (int) (tv.mHeaderView.getMeasuredWidth() * scale);
-                int fromHeaderHeight = (int) (tv.mHeaderView.getMeasuredHeight() * scale);
-
-                Bitmap b = Bitmap.createBitmap(fromHeaderWidth, fromHeaderHeight,
-                        Bitmap.Config.ARGB_8888);
-                if (Constants.DebugFlags.App.EnableTransitionThumbnailDebugMode) {
-                    b.eraseColor(0xFFff0000);
-                } else {
-                    Canvas c = new Canvas(b);
-                    c.scale(tv.getScaleX(), tv.getScaleY());
-                    tv.mHeaderView.draw(c);
-                    c.setBitmap(null);
-                }
-                b = b.createAshmemBitmap();
-                int[] pts = new int[2];
-                tv.getLocationOnScreen(pts);
-                try {
-                    WindowManagerGlobal.getWindowManagerService()
-                            .overridePendingAppTransitionAspectScaledThumb(b,
-                                    pts[0] + offsetX,
-                                    pts[1] + offsetY,
-                                    transform.rect.width(),
-                                    transform.rect.height(),
-                                    new IRemoteCallback.Stub() {
-                                        @Override
-                                        public void sendResult(Bundle data)
-                                                throws RemoteException {
-                                            post(new Runnable() {
-                                                @Override
-                                                public void run() {
-                                                    if (animStartedListener != null) {
-                                                        animStartedListener.onAnimationStarted();
-                                                    }
-                                                }
-                                            });
-                                        }
-                                    }, true);
-                } catch (RemoteException e) {
-                    Log.w(TAG, "Error overriding app transition", e);
-                }
             }
         };
+
         mCb.runAfterPause(r);
     }
+
+    private void overrideDrawHeaderThumbnailTransition(TaskStackView stackView,
+            TaskView clickedTask, int offsetX, int offsetY, float stackScroll,
+            final ActivityOptions.OnAnimationStartedListener animStartedListener) {
+        List<AppTransitionAnimationSpec> specs = getAppTransitionAnimationSpecs(stackView,
+                clickedTask, offsetX, offsetY, stackScroll);
+        if (specs == null) {
+            return;
+        }
+
+        IRemoteCallback.Stub callback = new IRemoteCallback.Stub() {
+            @Override
+            public void sendResult(Bundle data) throws RemoteException {
+                post(new Runnable() {
+                    @Override
+                    public void run() {
+                        if (animStartedListener != null) {
+                            animStartedListener.onAnimationStarted();
+                        }
+                    }
+                });
+            }
+        };
+
+        AppTransitionAnimationSpec[] specsArray =
+                new AppTransitionAnimationSpec[specs.size()];
+        try {
+            WindowManagerGlobal.getWindowManagerService().overridePendingAppTransitionMultiThumb(
+                    specs.toArray(specsArray), callback, true /* scaleUp */);
+
+        } catch (RemoteException e) {
+            Log.w(TAG, "Error overriding app transition", e);
+        }
+    }
+
+    private List<AppTransitionAnimationSpec> getAppTransitionAnimationSpecs(TaskStackView stackView,
+            TaskView clickedTask, int offsetX, int offsetY, float stackScroll) {
+        final int targetStackId = clickedTask.getTask().key.stackId;
+        if (targetStackId != ActivityManager.FREEFORM_WORKSPACE_STACK_ID
+                && targetStackId != ActivityManager.FULLSCREEN_WORKSPACE_STACK_ID) {
+            return null;
+        }
+        // If this is a full screen stack, the transition will be towards the single, full screen
+        // task. We only need the transition spec for this task.
+        List<AppTransitionAnimationSpec> specs = new ArrayList<>();
+        if (targetStackId == ActivityManager.FULLSCREEN_WORKSPACE_STACK_ID) {
+            specs.add(createThumbnailHeaderAnimationSpec(
+                    stackView, offsetX, offsetY, stackScroll, clickedTask,
+                    clickedTask.getTask().key.id, ADD_HEADER_BITMAP));
+            return specs;
+        }
+        // This is a free form stack or full screen stack, so there will be multiple windows
+        // animating from thumbnails. We need transition animation specs for all of them.
+
+        // We will use top and bottom task views as a base for tasks, that aren't visible on the
+        // screen. This is necessary for cascade recents list, where some of the tasks might be
+        // hidden.
+        List<TaskView> taskViews = stackView.getTaskViews();
+        int childCount = taskViews.size();
+        TaskView topChild = taskViews.get(0);
+        TaskView bottomChild = taskViews.get(childCount - 1);
+        SparseArray<TaskView> taskViewsByTaskId = new SparseArray<>();
+        for (int i = 0; i < childCount; i++) {
+            TaskView taskView = taskViews.get(i);
+            taskViewsByTaskId.put(taskView.getTask().key.id, taskView);
+        }
+
+        TaskStack stack = stackView.getStack();
+        // We go through all tasks now and for each generate transition animation spec. If there is
+        // a view associated with a task, we use that view as a base for the animation. If there
+        // isn't, we use bottom or top view, depending on which one would be closer to the task
+        // view if it existed.
+        ArrayList<Task> tasks = stack.getTasks();
+        boolean passedClickedTask = false;
+        for (int i = 0, n = tasks.size(); i < n; i++) {
+            Task task = tasks.get(i);
+            TaskView taskView = taskViewsByTaskId.get(task.key.id);
+            if (taskView != null) {
+                specs.add(createThumbnailHeaderAnimationSpec(stackView, offsetX, offsetY,
+                        stackScroll, taskView, taskView.getTask().key.id, ADD_HEADER_BITMAP));
+                if (taskView == clickedTask) {
+                    passedClickedTask = true;
+                }
+            } else {
+                taskView = passedClickedTask ? bottomChild : topChild;
+                specs.add(createThumbnailHeaderAnimationSpec(stackView, offsetX, offsetY,
+                        stackScroll, taskView, task.key.id, !ADD_HEADER_BITMAP));
+            }
+        }
+
+        return specs;
+    }
+
+    private AppTransitionAnimationSpec createThumbnailHeaderAnimationSpec(TaskStackView stackView,
+            int offsetX, int offsetY, float stackScroll, TaskView tv, int taskId,
+            boolean addHeaderBitmap) {
+        // Disable any focused state before we draw the header
+        // Upfront the processing of the thumbnail
+        if (tv.isFocusedTask()) {
+            tv.unsetFocusedTask();
+        }
+        TaskViewTransform transform = new TaskViewTransform();
+        transform = stackView.getStackAlgorithm().getStackTransform(tv.mTask, stackScroll,
+                transform, null);
+
+        float scale = tv.getScaleX();
+        int fromHeaderWidth = (int) (tv.mHeaderView.getMeasuredWidth() * scale);
+        int fromHeaderHeight = (int) (tv.mHeaderView.getMeasuredHeight() * scale);
+
+        Bitmap b = null;
+        if (addHeaderBitmap) {
+            b = Bitmap.createBitmap(fromHeaderWidth, fromHeaderHeight,
+                    Bitmap.Config.ARGB_8888);
+
+            if (Constants.DebugFlags.App.EnableTransitionThumbnailDebugMode) {
+                b.eraseColor(0xFFff0000);
+            } else {
+                Canvas c = new Canvas(b);
+                c.scale(tv.getScaleX(), tv.getScaleY());
+                tv.mHeaderView.draw(c);
+                c.setBitmap(null);
+
+            }
+            b = b.createAshmemBitmap();
+        }
+
+        int[] pts = new int[2];
+        tv.getLocationOnScreen(pts);
+
+        final int left = pts[0] + offsetX;
+        final int top = pts[1] + offsetY;
+        final Rect rect = new Rect(left, top, left + transform.rect.width(),
+                top + transform.rect.height());
+
+        return new AppTransitionAnimationSpec(taskId, b, rect);
+    }
+
     /**** TaskStackView.TaskStackCallbacks Implementation ****/
 
     @Override
@@ -521,12 +622,10 @@
             // and then offset to the expected transform rect, but bound this to just
             // outside the display rect (to ensure we don't animate from too far away)
             sourceView = stackView;
-            transform = stackView.getStackAlgorithm().getStackTransform(task, stackScroll, transform, null);
             offsetX = transform.rect.left;
             offsetY = mConfig.displayRect.height();
         } else {
             sourceView = tv.mThumbnailView;
-            transform = stackView.getStackAlgorithm().getStackTransform(task, stackScroll, transform, null);
         }
 
         // Compute the thumbnail to scale up from
@@ -553,10 +652,8 @@
                     }
                 };
             }
-            if (tv != null) {
-                postDrawHeaderThumbnailTransitionRunnable(tv, offsetX, offsetY, transform,
-                        animStartedListener);
-            }
+            postDrawHeaderThumbnailTransitionRunnable(stackView, tv, offsetX, offsetY, stackScroll,
+                    animStartedListener);
             if (mConfig.multiStackEnabled) {
                 opts = ActivityOptions.makeCustomAnimation(sourceView.getContext(),
                         R.anim.recents_from_unknown_enter,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java
index 4d3e57e..3ff69c9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java
@@ -104,13 +104,11 @@
      * Aborts pulsing immediately.
      */
     public void abortPulsing() {
-        mHandler.removeCallbacks(mPulseIn);
-        abortAnimations();
+        cancelPulsing();
         if (mDozing) {
             mScrimController.setDozeBehindAlpha(1f);
             mScrimController.setDozeInFrontAlpha(1f);
         }
-        mPulseCallback = null;
     }
 
     public void onScreenTurnedOn() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarApps.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarApps.java
index d74c5b0..cb5c125 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarApps.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarApps.java
@@ -20,7 +20,6 @@
 import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.ActivityOptions;
-import android.app.AppGlobals;
 import android.content.BroadcastReceiver;
 import android.content.ClipData;
 import android.content.ClipDescription;
@@ -29,13 +28,10 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.ApplicationInfo;
-import android.content.pm.ActivityInfo;
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
 import android.graphics.Rect;
 import android.os.Bundle;
-import android.os.RemoteException;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.util.AttributeSet;
@@ -49,6 +45,7 @@
 import android.widget.LinearLayout;
 import android.widget.Toast;
 
+import com.android.internal.content.PackageMonitor;
 import com.android.systemui.R;
 
 import java.util.List;
@@ -75,6 +72,8 @@
     private final PackageManager mPackageManager;
     private final UserManager mUserManager;
     private final LayoutInflater mLayoutInflater;
+    private final AppPackageMonitor mAppPackageMonitor;
+
 
     // This view has two roles:
     // 1) If the drag started outside the pinned apps list, it is a placeholder icon with a null
@@ -106,6 +105,7 @@
         mPackageManager = context.getPackageManager();
         mUserManager = (UserManager) getContext().getSystemService(Context.USER_SERVICE);
         mLayoutInflater = LayoutInflater.from(context);
+        mAppPackageMonitor = new AppPackageMonitor();
 
         // Dragging an icon removes and adds back the dragged icon. Use the layout transitions to
         // trigger animation. By default all transitions animate, so turn off the unneeded ones.
@@ -121,6 +121,76 @@
         setLayoutTransition(transition);
     }
 
+    // Monitor that catches events like "app uninstalled".
+    private class AppPackageMonitor extends PackageMonitor {
+        @Override
+        public void onPackageRemoved(String packageName, int uid) {
+            postRemoveIfUnlauncheable(packageName, new UserHandle(getChangingUserId()));
+            super.onPackageRemoved(packageName, uid);
+        }
+
+        @Override
+        public void onPackageModified(String packageName) {
+            postRemoveIfUnlauncheable(packageName, new UserHandle(getChangingUserId()));
+            super.onPackageModified(packageName);
+        }
+
+        @Override
+        public void onPackagesAvailable(String[] packages) {
+            if (isReplacing()) {
+                UserHandle user = new UserHandle(getChangingUserId());
+
+                for (String packageName : packages) {
+                    postRemoveIfUnlauncheable(packageName, user);
+                }
+            }
+            super.onPackagesAvailable(packages);
+        }
+
+        @Override
+        public void onPackagesUnavailable(String[] packages) {
+            if (!isReplacing()) {
+                UserHandle user = new UserHandle(getChangingUserId());
+
+                for (String packageName : packages) {
+                    postRemoveIfUnlauncheable(packageName, user);
+                }
+            }
+            super.onPackagesUnavailable(packages);
+        }
+    }
+
+    private void postRemoveIfUnlauncheable(final String packageName, final UserHandle user) {
+        // This method doesn't necessarily get called in the main thread. Redirect the call into
+        // the main thread.
+        post(new Runnable() {
+            @Override
+            public void run() {
+                if (!isAttachedToWindow()) return;
+                removeIfUnlauncheable(packageName, user);
+            }
+        });
+    }
+
+    private void removeIfUnlauncheable(String packageName, UserHandle user) {
+        long appUserSerialNumber = mUserManager.getSerialNumberForUser(user);
+
+        // Remove icons for all apps that match a package that perhaps became unlauncheable.
+        for(int i = sAppsModel.getAppCount() - 1; i >= 0; --i) {
+            AppInfo appInfo = sAppsModel.getApp(i);
+            if (appInfo.getUserSerialNumber() != appUserSerialNumber) continue;
+
+            ComponentName appComponentName = appInfo.getComponentName();
+            if (!appComponentName.getPackageName().equals(packageName)) continue;
+
+            if (sAppsModel.buildAppLaunchIntent(appComponentName, user) != null) continue;
+
+            removeViewAt(i);
+            sAppsModel.removeApp(i);
+            sAppsModel.savePrefs();
+        }
+    }
+
     @Override
     protected void onAttachedToWindow() {
       super.onAttachedToWindow();
@@ -145,12 +215,15 @@
         IntentFilter filter = new IntentFilter();
         filter.addAction(Intent.ACTION_USER_SWITCHED);
         mContext.registerReceiver(mBroadcastReceiver, filter);
+
+        mAppPackageMonitor.register(mContext, null, UserHandle.ALL, true);
     }
 
     @Override
     protected void onDetachedFromWindow() {
         super.onDetachedFromWindow();
         mContext.unregisterReceiver(mBroadcastReceiver);
+        mAppPackageMonitor.unregister();
     }
 
     /**
@@ -470,7 +543,6 @@
             ComponentName component = appInfo.getComponentName();
 
             long appUserSerialNumber = appInfo.getUserSerialNumber();
-
             UserHandle appUser = mUserManager.getUserForSerialNumber(appUserSerialNumber);
             if (appUser == null) {
                 Toast.makeText(getContext(), R.string.activity_not_found, Toast.LENGTH_SHORT).show();
@@ -478,7 +550,12 @@
                         " because its user doesn't exist.");
                 return;
             }
-            int appUserId = appUser.getIdentifier();
+
+            Intent launchIntent = sAppsModel.buildAppLaunchIntent(component, appUser);
+            if (launchIntent == null) {
+                Toast.makeText(getContext(), R.string.activity_not_found, Toast.LENGTH_SHORT).show();
+                return;
+            }
 
             // Play a scale-up animation while launching the activity.
             // TODO: Consider playing a different animation, or no animation, if the activity is
@@ -489,54 +566,9 @@
             ActivityOptions opts =
                     ActivityOptions.makeScaleUpAnimation(v, 0, 0, v.getWidth(), v.getHeight());
             Bundle optsBundle = opts.toBundle();
-
-            // Launch the activity. This code is based on LauncherAppsService.startActivityAsUser code.
-            Intent launchIntent = new Intent(Intent.ACTION_MAIN);
-            launchIntent.addCategory(Intent.CATEGORY_LAUNCHER);
             launchIntent.setSourceBounds(sourceBounds);
-            launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-            launchIntent.setPackage(component.getPackageName());
 
-            IPackageManager pm = AppGlobals.getPackageManager();
-            try {
-                ActivityInfo info = pm.getActivityInfo(component, 0, appUserId);
-                if (info == null) {
-                    Toast.makeText(getContext(), R.string.activity_not_found, Toast.LENGTH_SHORT).show();
-                    Log.e(TAG, "Can't start activity " + component + " because it's not installed.");
-                    return;
-                }
-
-                if (!info.exported) {
-                    Toast.makeText(getContext(), R.string.activity_not_found, Toast.LENGTH_SHORT).show();
-                    Log.e(TAG, "Can't start activity " + component + " because it doesn't have 'exported' attribute.");
-                    return;
-                }
-            } catch (RemoteException e) {
-                Toast.makeText(getContext(), R.string.activity_not_found, Toast.LENGTH_SHORT).show();
-                Log.e(TAG, "Failed to get activity info for " + component, e);
-                return;
-            }
-
-            // Check that the component actually has Intent.CATEGORY_LAUCNCHER
-            // as calling startActivityAsUser ignores the category and just
-            // resolves based on the component if present.
-            List<ResolveInfo> apps = getContext().getPackageManager().queryIntentActivitiesAsUser(launchIntent,
-                    0 /* flags */, appUserId);
-            final int size = apps.size();
-            for (int i = 0; i < size; ++i) {
-                ActivityInfo activityInfo = apps.get(i).activityInfo;
-                if (activityInfo.packageName.equals(component.getPackageName()) &&
-                        activityInfo.name.equals(component.getClassName())) {
-                    // Found an activity with category launcher that matches
-                    // this component so ok to launch.
-                    launchIntent.setComponent(component);
-                    mContext.startActivityAsUser(launchIntent, optsBundle, appUser);
-                    return;
-                }
-            }
-
-            Toast.makeText(getContext(), R.string.activity_not_found, Toast.LENGTH_SHORT).show();
-            Log.e(TAG, "Attempt to launch activity without category Intent.CATEGORY_LAUNCHER " + component);
+            mContext.startActivityAsUser(launchIntent, optsBundle, appUser);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarAppsModel.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarAppsModel.java
index b8764cf..c4c31fd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarAppsModel.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarAppsModel.java
@@ -16,16 +16,23 @@
 
 package com.android.systemui.statusbar.phone;
 
+import android.app.AppGlobals;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.SharedPreferences;
+import android.content.pm.ActivityInfo;
+import android.content.pm.IPackageManager;
 import android.content.pm.ResolveInfo;
 import android.content.pm.UserInfo;
+import android.os.RemoteException;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.util.Log;
 import android.util.Slog;
 
+import com.android.internal.annotations.VisibleForTesting;
+
 import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.List;
@@ -94,6 +101,58 @@
         }
     }
 
+    @VisibleForTesting
+    protected IPackageManager getPackageManager() {
+        return AppGlobals.getPackageManager();
+    }
+
+    // Returns a launch intent for a given component, or null if the component is unlauncheable.
+    public Intent buildAppLaunchIntent(ComponentName component, UserHandle appUser) {
+        int appUserId = appUser.getIdentifier();
+
+        // This code is based on LauncherAppsService.startActivityAsUser code.
+        Intent launchIntent = new Intent(Intent.ACTION_MAIN);
+        launchIntent.addCategory(Intent.CATEGORY_LAUNCHER);
+        launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        launchIntent.setPackage(component.getPackageName());
+
+        try {
+            ActivityInfo info = getPackageManager().getActivityInfo(component, 0, appUserId);
+            if (info == null) {
+                Log.e(TAG, "Activity " + component + " is not installed.");
+                return null;
+            }
+
+            if (!info.exported) {
+                Log.e(TAG, "Activity " + component + " doesn't have 'exported' attribute.");
+                return null;
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to get activity info for " + component, e);
+            return null;
+        }
+
+        // Check that the component actually has Intent.CATEGORY_LAUNCHER
+        // as calling startActivityAsUser ignores the category and just
+        // resolves based on the component if present.
+        List<ResolveInfo> apps = mContext.getPackageManager().queryIntentActivitiesAsUser(launchIntent,
+                0 /* flags */, appUserId);
+        final int size = apps.size();
+        for (int i = 0; i < size; ++i) {
+            ActivityInfo activityInfo = apps.get(i).activityInfo;
+            if (activityInfo.packageName.equals(component.getPackageName()) &&
+                    activityInfo.name.equals(component.getClassName())) {
+                // Found an activity with category launcher that matches
+                // this component so ok to launch.
+                launchIntent.setComponent(component);
+                return launchIntent;
+            }
+        }
+
+        Log.e(TAG, "Activity doesn't have category Intent.CATEGORY_LAUNCHER " + component);
+        return null;
+    }
+
     /**
      * Reinitializes the model for a new user.
      */
@@ -199,6 +258,10 @@
 
     /** Loads the list of apps from SharedPreferences. */
     private void loadAppsFromPrefs() {
+        UserManager mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+
+        boolean hadUnlauncheableApps = false;
+
         int appCount = mPrefs.getInt(userPrefixed(APP_COUNT_PREF), -1);
         for (int i = 0; i < appCount; i++) {
             String prefValue = mPrefs.getString(prefNameForApp(i), null);
@@ -214,8 +277,15 @@
                 // Couldn't find the saved state. Just skip this item.
                 continue;
             }
-            mApps.add(new AppInfo(componentName, userSerialNumber));
+            UserHandle appUser = mUserManager.getUserForSerialNumber(userSerialNumber);
+            if (appUser != null && buildAppLaunchIntent(componentName, appUser) != null) {
+                mApps.add(new AppInfo(componentName, userSerialNumber));
+            } else {
+                hadUnlauncheableApps = true;
+            }
         }
+
+        if (hadUnlauncheableApps) savePrefs();
     }
 
     /** Adds the first few apps from the owner profile. Used for demo purposes. */
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarAppsModelTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarAppsModelTest.java
index 62213ab..4d0e28b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarAppsModelTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarAppsModelTest.java
@@ -16,21 +16,26 @@
 
 package com.android.systemui.statusbar.phone;
 
+import org.mockito.InOrder;
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.anyString;
 import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.SharedPreferences;
 import android.content.pm.ActivityInfo;
+import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.content.pm.UserInfo;
+import android.os.RemoteException;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.test.AndroidTestCase;
@@ -45,6 +50,7 @@
 /** Tests for the data model for the navigation bar app icons. */
 public class NavigationBarAppsModelTest extends AndroidTestCase {
     private PackageManager mMockPackageManager;
+    private IPackageManager mMockIPackageManager;
     private SharedPreferences mMockPrefs;
     private SharedPreferences.Editor mMockEdit;
     private UserManager mMockUserManager;
@@ -61,6 +67,7 @@
 
         final Context context = mock(Context.class);
         mMockPackageManager = mock(PackageManager.class);
+        mMockIPackageManager = mock(IPackageManager.class);
         mMockPrefs = mock(SharedPreferences.class);
         mMockEdit = mock(SharedPreferences.Editor.class);
         mMockUserManager = mock(UserManager.class);
@@ -78,8 +85,71 @@
         when(mMockPrefs.edit()).thenReturn(mMockEdit);
 
         when(mMockUserManager.getSerialNumberForUser(new UserHandle(2))).thenReturn(22L);
+        when(mMockUserManager.getUserForSerialNumber(45L)).thenReturn(new UserHandle(4));
+        when(mMockUserManager.getUserForSerialNumber(239L)).thenReturn(new UserHandle(5));
 
-        mModel = new NavigationBarAppsModel(context);
+        mModel = new NavigationBarAppsModel(context) {
+            @Override
+            protected IPackageManager getPackageManager() {
+                return mMockIPackageManager;
+            }
+        };
+    }
+
+    /** Tests buildAppLaunchIntent(). */
+    public void testBuildAppLaunchIntent() {
+        ActivityInfo mockNonExportedActivityInfo = new ActivityInfo();
+        mockNonExportedActivityInfo.exported = false;
+        ActivityInfo mockExportedActivityInfo = new ActivityInfo();
+        mockExportedActivityInfo.exported = true;
+        try {
+            when(mMockIPackageManager.getActivityInfo(
+                    new ComponentName("package1", "class1"), 0, 4)).
+                    thenReturn(mockNonExportedActivityInfo);
+            when(mMockIPackageManager.getActivityInfo(
+                    new ComponentName("package2", "class2"), 0, 5)).
+                    thenThrow(new RemoteException());
+            when(mMockIPackageManager.getActivityInfo(
+                    new ComponentName("package3", "class3"), 0, 6)).
+                    thenReturn(mockExportedActivityInfo);
+            when(mMockIPackageManager.getActivityInfo(
+                    new ComponentName("package4", "class4"), 0, 7)).
+                    thenReturn(mockExportedActivityInfo);
+        } catch (RemoteException e) {
+            fail("RemoteException can't happen in the test, but it happened.");
+        }
+
+        // Assume some installed activities.
+        ActivityInfo ai0 = new ActivityInfo();
+        ai0.packageName = "package0";
+        ai0.name = "class0";
+        ActivityInfo ai1 = new ActivityInfo();
+        ai1.packageName = "package4";
+        ai1.name = "class4";
+        ResolveInfo ri0 = new ResolveInfo();
+        ri0.activityInfo = ai0;
+        ResolveInfo ri1 = new ResolveInfo();
+        ri1.activityInfo = ai1;
+        when(mMockPackageManager
+                .queryIntentActivitiesAsUser(any(Intent.class), eq(0), any(int.class)))
+                .thenReturn(Arrays.asList(ri0, ri1));
+
+        // Unlauncheable (for various reasons) apps.
+        assertEquals(null, mModel.buildAppLaunchIntent(
+                new ComponentName("package0", "class0"), new UserHandle(3)));
+        assertEquals(null, mModel.buildAppLaunchIntent(
+                new ComponentName("package1", "class1"), new UserHandle(4)));
+        assertEquals(null, mModel.buildAppLaunchIntent(
+                new ComponentName("package2", "class2"), new UserHandle(5)));
+        assertEquals(null, mModel.buildAppLaunchIntent(
+                new ComponentName("package3", "class3"), new UserHandle(6)));
+
+        // A launcheable app.
+        Intent intent = mModel.buildAppLaunchIntent(
+                new ComponentName("package4", "class4"), new UserHandle(7));
+        assertNotNull(intent);
+        assertEquals(new ComponentName("package4", "class4"), intent.getComponent());
+        assertEquals("package4", intent.getPackage());
     }
 
     /** Initializes the model from SharedPreferences for a few app activites. */
@@ -93,6 +163,39 @@
         when(mMockPrefs.getString("22|app_2", null)).thenReturn("package2/class2");
         when(mMockPrefs.getLong("22|app_user_2", -1)).thenReturn(239L);
 
+        ActivityInfo mockActivityInfo = new ActivityInfo();
+        mockActivityInfo.exported = true;
+        try {
+            when(mMockIPackageManager.getActivityInfo(
+                    new ComponentName("package0", "class0"), 0, 5)).thenReturn(mockActivityInfo);
+            when(mMockIPackageManager.getActivityInfo(
+                    new ComponentName("package1", "class1"), 0, 4)).thenReturn(mockActivityInfo);
+            when(mMockIPackageManager.getActivityInfo(
+                    new ComponentName("package2", "class2"), 0, 5)).thenReturn(mockActivityInfo);
+        } catch (RemoteException e) {
+            fail("RemoteException can't happen in the test, but it happened.");
+        }
+
+        // Assume some installed activities.
+        ActivityInfo ai0 = new ActivityInfo();
+        ai0.packageName = "package0";
+        ai0.name = "class0";
+        ActivityInfo ai1 = new ActivityInfo();
+        ai1.packageName = "package1";
+        ai1.name = "class1";
+        ActivityInfo ai2 = new ActivityInfo();
+        ai2.packageName = "package2";
+        ai2.name = "class2";
+        ResolveInfo ri0 = new ResolveInfo();
+        ri0.activityInfo = ai0;
+        ResolveInfo ri1 = new ResolveInfo();
+        ri1.activityInfo = ai1;
+        ResolveInfo ri2 = new ResolveInfo();
+        ri2.activityInfo = ai2;
+        when(mMockPackageManager
+                .queryIntentActivitiesAsUser(any(Intent.class), eq(0), any(int.class)))
+                .thenReturn(Arrays.asList(ri0, ri1, ri2));
+
         mModel.setCurrentUser(2);
     }
 
@@ -133,6 +236,15 @@
         assertEquals(22L, mModel.getApp(0).getUserSerialNumber());
         assertEquals("package2/class2", mModel.getApp(1).getComponentName().flattenToString());
         assertEquals(22L, mModel.getApp(1).getUserSerialNumber());
+        InOrder order = inOrder(mMockEdit);
+        order.verify(mMockEdit).apply();
+        order.verify(mMockEdit).putInt("22|app_count", 2);
+        order.verify(mMockEdit).putString("22|app_0", "package1/class1");
+        order.verify(mMockEdit).putLong("22|app_user_0", 22L);
+        order.verify(mMockEdit).putString("22|app_1", "package2/class2");
+        order.verify(mMockEdit).putLong("22|app_user_1", 22L);
+        order.verify(mMockEdit).apply();
+        verifyNoMoreInteractions(mMockEdit);
     }
 
     /** Tests initializing the model if one of the prefs is missing. */
@@ -145,11 +257,72 @@
         // But assume one pref is missing.
         when(mMockPrefs.getString("22|app_1", null)).thenReturn(null);
 
+        ActivityInfo mockActivityInfo = new ActivityInfo();
+        mockActivityInfo.exported = true;
+        try {
+            when(mMockIPackageManager.getActivityInfo(
+                    new ComponentName("package0", "class0"), 0, 5)).thenReturn(mockActivityInfo);
+        } catch (RemoteException e) {
+            fail("RemoteException can't happen in the test, but it happened.");
+        }
+
+        ActivityInfo ai0 = new ActivityInfo();
+        ai0.packageName = "package0";
+        ai0.name = "class0";
+        ResolveInfo ri0 = new ResolveInfo();
+        ri0.activityInfo = ai0;
+        when(mMockPackageManager
+                .queryIntentActivitiesAsUser(any(Intent.class), eq(0), any(int.class)))
+                .thenReturn(Arrays.asList(ri0));
+
         // Initializing the model should load from prefs and skip the missing one.
         mModel.setCurrentUser(2);
         assertEquals(1, mModel.getAppCount());
         assertEquals("package0/class0", mModel.getApp(0).getComponentName().flattenToString());
         assertEquals(239L, mModel.getApp(0).getUserSerialNumber());
+        verifyNoMoreInteractions(mMockEdit);
+    }
+
+    /** Tests initializing the model if one of the apps is unlauncheable. */
+    public void testInitializeWithUnlauncheableApp() {
+        // Assume two apps are nominally stored.
+        when(mMockPrefs.getInt("22|app_count", -1)).thenReturn(2);
+        when(mMockPrefs.getString("22|app_0", null)).thenReturn("package0/class0");
+        when(mMockPrefs.getLong("22|app_user_0", -1)).thenReturn(239L);
+        when(mMockPrefs.getString("22|app_1", null)).thenReturn("package1/class1");
+        when(mMockPrefs.getLong("22|app_user_1", -1)).thenReturn(45L);
+
+        ActivityInfo mockActivityInfo = new ActivityInfo();
+        mockActivityInfo.exported = true;
+        try {
+            when(mMockIPackageManager.getActivityInfo(
+                    new ComponentName("package0", "class0"), 0, 5)).thenReturn(mockActivityInfo);
+        } catch (RemoteException e) {
+            fail("RemoteException can't happen in the test, but it happened.");
+        }
+
+        ActivityInfo ai0 = new ActivityInfo();
+        ai0.packageName = "package0";
+        ai0.name = "class0";
+        ResolveInfo ri0 = new ResolveInfo();
+        ri0.activityInfo = ai0;
+        when(mMockPackageManager
+                .queryIntentActivitiesAsUser(any(Intent.class), eq(0), any(int.class)))
+                .thenReturn(Arrays.asList(ri0));
+
+        // Initializing the model should load from prefs and skip the unlauncheable one.
+        mModel.setCurrentUser(2);
+        assertEquals(1, mModel.getAppCount());
+        assertEquals("package0/class0", mModel.getApp(0).getComponentName().flattenToString());
+        assertEquals(239L, mModel.getApp(0).getUserSerialNumber());
+
+        // Once an unlauncheable app is detected, the model should save all apps excluding the
+        // unlauncheable one.
+        verify(mMockEdit).putInt("22|app_count", 1);
+        verify(mMockEdit).putString("22|app_0", "package0/class0");
+        verify(mMockEdit).putLong("22|app_user_0", 239L);
+        verify(mMockEdit).apply();
+        verifyNoMoreInteractions(mMockEdit);
     }
 
     /** Tests saving the model to SharedPreferences. */
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index f3ea1c4..b6cd477 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -16022,7 +16022,7 @@
                     "Call does not support special user #" + targetUserId);
         }
         // Check shell permission
-        if (callingUid == Process.SHELL_UID && targetUserId >= UserHandle.USER_OWNER) {
+        if (callingUid == Process.SHELL_UID && targetUserId >= UserHandle.USER_SYSTEM) {
             if (mUserManager.hasUserRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES,
                     targetUserId)) {
                 throw new SecurityException("Shell does not have permission to access user "
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index d54c16f..24b90d8 100755
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -513,7 +513,7 @@
         configDestroy = false;
         keysPaused = false;
         inHistory = false;
-        visible = true;
+        visible = false;
         nowVisible = false;
         idle = false;
         hasBeenLaunched = false;
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index d0f68f0..9bf4f5f 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -975,7 +975,9 @@
         pw.println("Battery stats (batterystats) dump options:");
         pw.println("  [--checkin] [--history] [--history-start] [--charged] [-c]");
         pw.println("  [--daily] [--reset] [--write] [--new-daily] [--read-daily] [-h] [<package.name>]");
-        pw.println("  --checkin: format output for a checkin report.");
+        pw.println("  --checkin: generate output for a checkin report; will write (and clear) the");
+        pw.println("             last old completed stats when they had been reset.");
+        pw.println("  --c: write the current stats in checkin format.");
         pw.println("  --history: show only history data.");
         pw.println("  --history-start <num>: show only history data starting at given time offset.");
         pw.println("  --charged: only output data since last charged.");
diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java
index c998c2c..292aff9 100644
--- a/services/core/java/com/android/server/content/SyncManager.java
+++ b/services/core/java/com/android/server/content/SyncManager.java
@@ -790,7 +790,7 @@
 
         for (AccountAndUser account : accounts) {
             // If userId is specified, do not sync accounts of other users
-            if (userId >= UserHandle.USER_OWNER && account.userId >= UserHandle.USER_OWNER
+            if (userId >= UserHandle.USER_SYSTEM && account.userId >= UserHandle.USER_SYSTEM
                     && userId != account.userId) {
                 continue;
             }
diff --git a/services/core/java/com/android/server/location/LocationBlacklist.java b/services/core/java/com/android/server/location/LocationBlacklist.java
index 6f22689..3f3f828 100644
--- a/services/core/java/com/android/server/location/LocationBlacklist.java
+++ b/services/core/java/com/android/server/location/LocationBlacklist.java
@@ -50,7 +50,7 @@
     private String[] mWhitelist = new String[0];
     private String[] mBlacklist = new String[0];
 
-    private int mCurrentUserId = UserHandle.USER_OWNER;
+    private int mCurrentUserId = UserHandle.USER_SYSTEM;
     
     public LocationBlacklist(Context context, Handler handler) {
         super(handler);
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 47729f9..153bd3b 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -1925,8 +1925,7 @@
 
             mFoundPolicyFile = SELinuxMMAC.readInstallPolicy();
 
-            mRestoredSettings = mSettings.readLPw(this, sUserManager.getUsers(false),
-                    mSdkVersion, mOnlyCore);
+            mRestoredSettings = mSettings.readLPw(sUserManager.getUsers(false));
 
             String customResolverActivity = Resources.getSystem().getString(
                     R.string.config_customResolverActivity);
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 943e649..715dd2c 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -29,6 +29,7 @@
 import static android.os.Process.PACKAGE_INFO_GID;
 import static com.android.server.pm.PackageManagerService.DEBUG_DOMAIN_VERIFICATION;
 
+import android.annotation.NonNull;
 import android.content.IntentFilter;
 import android.content.pm.ActivityInfo;
 import android.content.pm.IntentFilterVerificationInfo;
@@ -2486,8 +2487,7 @@
         }
     }
 
-    boolean readLPw(PackageManagerService service, List<UserInfo> users, int sdkVersion,
-            boolean onlyCore) {
+    boolean readLPw(@NonNull List<UserInfo> users) {
         FileInputStream str = null;
         if (mBackupSettingsFilename.exists()) {
             try {
@@ -2583,7 +2583,7 @@
                     String userStr = parser.getAttributeValue(null, ATTR_USER);
                     String codeStr = parser.getAttributeValue(null, ATTR_CODE);
                     if (name != null) {
-                        int userId = 0;
+                        int userId = UserHandle.USER_SYSTEM;
                         boolean andCode = true;
                         try {
                             if (userStr != null) {
@@ -2672,14 +2672,8 @@
         if (PackageManagerService.CLEAR_RUNTIME_PERMISSIONS_ON_UPGRADE) {
             final VersionInfo internal = getInternalVersion();
             if (!Build.FINGERPRINT.equals(internal.fingerprint)) {
-                if (users == null) {
-                    mRuntimePermissionsPersistence.deleteUserRuntimePermissionsFile(
-                            UserHandle.USER_OWNER);
-                } else {
-                    for (UserInfo user : users) {
-                        mRuntimePermissionsPersistence.deleteUserRuntimePermissionsFile(
-                                user.id);
-                    }
+                for (UserInfo user : users) {
+                    mRuntimePermissionsPersistence.deleteUserRuntimePermissionsFile(user.id);
                 }
             }
         }
@@ -2722,23 +2716,15 @@
             mBackupStoppedPackagesFilename.delete();
             mStoppedPackagesFilename.delete();
             // Migrate to new file format
-            writePackageRestrictionsLPr(0);
+            writePackageRestrictionsLPr(UserHandle.USER_SYSTEM);
         } else {
-            if (users == null) {
-                readPackageRestrictionsLPr(0);
-            } else {
-                for (UserInfo user : users) {
-                    readPackageRestrictionsLPr(user.id);
-                }
+            for (UserInfo user : users) {
+                readPackageRestrictionsLPr(user.id);
             }
         }
 
-        if (users == null) {
-            mRuntimePermissionsPersistence.readStateForUserSyncLPr(UserHandle.USER_OWNER);
-        } else {
-            for (UserInfo user : users) {
-                mRuntimePermissionsPersistence.readStateForUserSyncLPr(user.id);
-            }
+        for (UserInfo user : users) {
+            mRuntimePermissionsPersistence.readStateForUserSyncLPr(user.id);
         }
 
         /*
@@ -4321,7 +4307,7 @@
             pw.print("    sourcePackage="); pw.println(p.sourcePackage);
             pw.print("    uid="); pw.print(p.uid);
                     pw.print(" gids="); pw.print(Arrays.toString(
-                            p.computeGids(UserHandle.USER_OWNER)));
+                            p.computeGids(UserHandle.USER_SYSTEM)));
                     pw.print(" type="); pw.print(p.type);
                     pw.print(" prot=");
                     pw.println(PermissionInfo.protectionToString(p.protectionLevel));
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index fcfb5e8..fe205e5 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -16,6 +16,7 @@
 
 package com.android.server.pm;
 
+import android.annotation.NonNull;
 import android.app.Activity;
 import android.app.ActivityManager;
 import android.app.ActivityManagerNative;
@@ -280,7 +281,7 @@
     }
 
     @Override
-    public List<UserInfo> getUsers(boolean excludeDying) {
+    public @NonNull List<UserInfo> getUsers(boolean excludeDying) {
         checkManageUsersPermission("query users");
         synchronized (mPackagesLock) {
             ArrayList<UserInfo> users = new ArrayList<UserInfo>(mUsers.size());
@@ -418,9 +419,10 @@
         return ui;
     }
 
+    /** Called by PackageManagerService */
     public boolean exists(int userId) {
         synchronized (mPackagesLock) {
-            return ArrayUtils.contains(mUserIds, userId);
+            return mUsers.get(userId) != null;
         }
     }
 
diff --git a/services/core/java/com/android/server/policy/StatusBarController.java b/services/core/java/com/android/server/policy/StatusBarController.java
index d1b50da..da23f45 100644
--- a/services/core/java/com/android/server/policy/StatusBarController.java
+++ b/services/core/java/com/android/server/policy/StatusBarController.java
@@ -122,7 +122,7 @@
      *
      * @return the desired start time of the status bar transition, in uptime millis
      */
-    private long calculateStatusBarTransitionStartTime(Animation openAnimation,
+    private static long calculateStatusBarTransitionStartTime(Animation openAnimation,
             Animation closeAnimation) {
         if (openAnimation != null && closeAnimation != null) {
             TranslateAnimation openTranslateAnimation = findTranslateAnimation(openAnimation);
@@ -151,7 +151,7 @@
      *
      * @return the found animation, {@code null} otherwise
      */
-    private TranslateAnimation findTranslateAnimation(Animation animation) {
+    private static TranslateAnimation findTranslateAnimation(Animation animation) {
         if (animation instanceof TranslateAnimation) {
             return (TranslateAnimation) animation;
         } else if (animation instanceof AnimationSet) {
@@ -170,7 +170,7 @@
      * Binary searches for a {@code t} such that there exists a {@code -0.01 < eps < 0.01} for which
      * {@code interpolator(t + eps) > 0.99}.
      */
-    private float findAlmostThereFraction(Interpolator interpolator) {
+    private static float findAlmostThereFraction(Interpolator interpolator) {
         float val = 0.5f;
         float adj = 0.25f;
         while (adj >= 0.01f) {
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index dfdb29c..d1145d0 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -26,6 +26,8 @@
 import android.os.IBinder;
 import android.os.IRemoteCallback;
 import android.util.Slog;
+import android.util.SparseArray;
+import android.view.AppTransitionAnimationSpec;
 import android.view.WindowManager;
 import android.view.animation.AlphaAnimation;
 import android.view.animation.Animation;
@@ -153,22 +155,25 @@
     private static final int THUMBNAIL_TRANSITION_EXIT_SCALE_DOWN = 3;
 
     private String mNextAppTransitionPackage;
-    private Bitmap mNextAppTransitionThumbnail;
     // Used for thumbnail transitions. True if we're scaling up, false if scaling down
     private boolean mNextAppTransitionScaleUp;
     private IRemoteCallback mNextAppTransitionCallback;
     private int mNextAppTransitionEnter;
     private int mNextAppTransitionExit;
     private int mNextAppTransitionInPlace;
-    private int mNextAppTransitionStartX;
-    private int mNextAppTransitionStartY;
-    private int mNextAppTransitionStartWidth;
-    private int mNextAppTransitionStartHeight;
+
+    // Keyed by task id.
+    private final SparseArray<AppTransitionAnimationSpec> mNextAppTransitionAnimationsSpecs
+            = new SparseArray<>();
+    private AppTransitionAnimationSpec mDefaultNextAppTransitionAnimationSpec;
+
     private Rect mNextAppTransitionInsets = new Rect();
 
     private Rect mTmpFromClipRect = new Rect();
     private Rect mTmpToClipRect = new Rect();
 
+    private final Rect mTmpStartRect = new Rect();
+
     private final static int APP_STATE_IDLE = 0;
     private final static int APP_STATE_READY = 1;
     private final static int APP_STATE_RUNNING = 2;
@@ -276,8 +281,9 @@
         mAppTransitionState = APP_STATE_TIMEOUT;
     }
 
-    Bitmap getNextAppTransitionThumbnail() {
-        return mNextAppTransitionThumbnail;
+    Bitmap getAppTransitionThumbnailHeader(int taskId) {
+        AppTransitionAnimationSpec spec = mNextAppTransitionAnimationsSpecs.get(taskId);
+        return spec != null ? spec.bitmap : null;
     }
 
     /** Returns whether the next thumbnail transition is aspect scaled up. */
@@ -291,14 +297,6 @@
         return mNextAppTransitionScaleUp;
     }
 
-    int getStartingX() {
-        return mNextAppTransitionStartX;
-    }
-
-    int getStartingY() {
-        return mNextAppTransitionStartY;
-    }
-
     boolean prepare() {
         if (!isRunning()) {
             mAppTransitionState = APP_STATE_IDLE;
@@ -321,7 +319,7 @@
     void clear() {
         mNextAppTransitionType = NEXT_TRANSIT_TYPE_NONE;
         mNextAppTransitionPackage = null;
-        mNextAppTransitionThumbnail = null;
+        mNextAppTransitionAnimationsSpecs.clear();
     }
 
     void freeze() {
@@ -459,16 +457,17 @@
         return -startPos / denom;
     }
 
-    private Animation createScaleUpAnimationLocked(int transit, boolean enter,
-                                                   int appWidth, int appHeight) {
+    private Animation createScaleUpAnimationLocked(
+            int transit, boolean enter, int appWidth, int appHeight) {
         Animation a = null;
+        getDefaultNextAppTransitionStartRect(mTmpStartRect);
         if (enter) {
             // Entering app zooms out from the center of the initial rect.
-            float scaleW = mNextAppTransitionStartWidth / (float) appWidth;
-            float scaleH = mNextAppTransitionStartHeight / (float) appHeight;
+            float scaleW = mTmpStartRect.width() / (float) appWidth;
+            float scaleH = mTmpStartRect.height() / (float) appHeight;
             Animation scale = new ScaleAnimation(scaleW, 1, scaleH, 1,
-                    computePivot(mNextAppTransitionStartX, scaleW),
-                    computePivot(mNextAppTransitionStartY, scaleH));
+                    computePivot(mTmpStartRect.left, scaleW),
+                    computePivot(mTmpStartRect.right, scaleH));
             scale.setInterpolator(mDecelerateInterpolator);
 
             Animation alpha = new AlphaAnimation(0, 1);
@@ -512,6 +511,32 @@
         return a;
     }
 
+    private void getDefaultNextAppTransitionStartRect(Rect rect) {
+        if (mDefaultNextAppTransitionAnimationSpec == null ||
+                mDefaultNextAppTransitionAnimationSpec.rect == null) {
+            Slog.wtf(TAG, "Starting rect for app requested, but none available", new Throwable());
+            rect.setEmpty();
+        } else {
+            rect.set(mDefaultNextAppTransitionAnimationSpec.rect);
+        }
+    }
+
+    void getNextAppTransitionStartRect(int taskId, Rect rect) {
+        AppTransitionAnimationSpec spec = mNextAppTransitionAnimationsSpecs.get(taskId);
+        if (spec == null || spec.rect == null) {
+            Slog.wtf(TAG, "Starting rect for task: " + taskId + " requested, but not available",
+                    new Throwable());
+            rect.setEmpty();
+        } else {
+            rect.set(spec.rect);
+        }
+    }
+
+    private void putDefaultNextAppTransitionCoordinates(int left, int top, int width, int height) {
+        mDefaultNextAppTransitionAnimationSpec = new AppTransitionAnimationSpec(-1 /* taskId */,
+                null /* bitmap */, new Rect(left, top, left + width, top + height));
+    }
+
     private Animation createClipRevealAnimationLocked(int transit, boolean enter, Rect appFrame) {
         final Animation anim;
         if (enter) {
@@ -519,28 +544,27 @@
 
             final int appWidth = appFrame.width();
             final int appHeight = appFrame.height();
+            getDefaultNextAppTransitionStartRect(mTmpStartRect);
 
             float t = 0f;
             if (appHeight > 0) {
-                t = (float) mNextAppTransitionStartY / appHeight;
+                t = (float) mTmpStartRect.left / appHeight;
             }
             int translationY = mClipRevealTranslationY
                     + (int)(appHeight / 7f * t);
 
-            int centerX = mNextAppTransitionStartX + mNextAppTransitionStartWidth / 2;
-            int centerY = mNextAppTransitionStartY + mNextAppTransitionStartHeight / 2;
+            int centerX = mTmpStartRect.centerX();
+            int centerY = mTmpStartRect.centerY();
+            int halfWidth = mTmpStartRect.width() / 2;
+            int halfHeight = mTmpStartRect.height() / 2;
 
             // Clip third of the from size of launch icon, expand to full width/height
             Animation clipAnimLR = new ClipRectLRAnimation(
-                    centerX - mNextAppTransitionStartWidth / 2,
-                    centerX + mNextAppTransitionStartWidth / 2,
-                    0, appWidth);
+                    centerX - halfWidth, centerX + halfWidth, 0, appWidth);
             clipAnimLR.setInterpolator(mClipHorizontalInterpolator);
             clipAnimLR.setDuration((long) (DEFAULT_APP_TRANSITION_DURATION / 2.5f));
-            Animation clipAnimTB = new ClipRectTBAnimation(
-                    centerY - mNextAppTransitionStartHeight / 2 - translationY,
-                    centerY + mNextAppTransitionStartHeight / 2 - translationY,
-                    0, appHeight);
+            Animation clipAnimTB = new ClipRectTBAnimation(centerY - halfHeight - translationY,
+                    centerY + halfHeight/ 2 - translationY, 0, appHeight);
             clipAnimTB.setInterpolator(mTouchResponseInterpolator);
             clipAnimTB.setDuration(DEFAULT_APP_TRANSITION_DURATION);
 
@@ -649,31 +673,32 @@
 
     /**
      * This animation runs for the thumbnail that gets cross faded with the enter/exit activity
-     * when a thumbnail is specified with the activity options.
+     * when a thumbnail is specified with the pending animation override.
      */
-    Animation createThumbnailAspectScaleAnimationLocked(Rect appRect) {
+    Animation createThumbnailAspectScaleAnimationLocked(Rect appRect, Bitmap thumbnailHeader,
+            final int taskId) {
         Animation a;
-        final int thumbWidthI = mNextAppTransitionThumbnail.getWidth();
+        final int thumbWidthI = thumbnailHeader.getWidth();
         final float thumbWidth = thumbWidthI > 0 ? thumbWidthI : 1;
-        final int thumbHeightI = mNextAppTransitionThumbnail.getHeight();
+        final int thumbHeightI = thumbnailHeader.getHeight();
         final float thumbHeight = thumbHeightI > 0 ? thumbHeightI : 1;
         final int appWidth = appRect.width();
 
         float scaleW = appWidth / thumbWidth;
         float unscaledHeight = thumbHeight * scaleW;
-        float unscaledStartY = mNextAppTransitionStartY - (unscaledHeight - thumbHeight) / 2f;
+        getNextAppTransitionStartRect(taskId, mTmpStartRect);
+        float unscaledStartY = mTmpStartRect.top - (unscaledHeight - thumbHeight) / 2f;
         if (mNextAppTransitionScaleUp) {
             // Animation up from the thumbnail to the full screen
             Animation scale = new ScaleAnimation(1f, scaleW, 1f, scaleW,
-                    mNextAppTransitionStartX + (thumbWidth / 2f),
-                    mNextAppTransitionStartY + (thumbHeight / 2f));
+                    mTmpStartRect.left + (thumbWidth / 2f), mTmpStartRect.top + (thumbHeight / 2f));
             scale.setInterpolator(mTouchResponseInterpolator);
             scale.setDuration(THUMBNAIL_APP_TRANSITION_DURATION);
             Animation alpha = new AlphaAnimation(1, 0);
             alpha.setInterpolator(mThumbnailFadeOutInterpolator);
             alpha.setDuration(THUMBNAIL_APP_TRANSITION_ALPHA_DURATION);
             final float toX = appRect.left + appRect.width() / 2 -
-                    (mNextAppTransitionStartX + thumbWidth / 2);
+                    (mTmpStartRect.left + thumbWidth / 2);
             final float toY = appRect.top + mNextAppTransitionInsets.top + -unscaledStartY;
             Animation translate = new TranslateAnimation(0, toX, 0, toY);
             translate.setInterpolator(mTouchResponseInterpolator);
@@ -688,8 +713,7 @@
         } else {
             // Animation down from the full screen to the thumbnail
             Animation scale = new ScaleAnimation(scaleW, 1f, scaleW, 1f,
-                    mNextAppTransitionStartX + (thumbWidth / 2f),
-                    mNextAppTransitionStartY + (thumbHeight / 2f));
+                    mTmpStartRect.left + (thumbWidth / 2f), mTmpStartRect.top + (thumbHeight / 2f));
             scale.setInterpolator(mTouchResponseInterpolator);
             scale.setDuration(THUMBNAIL_APP_TRANSITION_DURATION);
             Animation alpha = new AlphaAnimation(0f, 1f);
@@ -718,11 +742,13 @@
      */
     Animation createAspectScaledThumbnailEnterExitAnimationLocked(int thumbTransitState,
             int appWidth, int appHeight, int orientation, int transit, Rect containingFrame,
-            Rect contentInsets, @Nullable Rect surfaceInsets, boolean resizedWindow) {
+            Rect contentInsets, @Nullable Rect surfaceInsets, boolean resizedWindow,
+            int taskId) {
         Animation a;
-        final int thumbWidthI = mNextAppTransitionStartWidth;
+        getDefaultNextAppTransitionStartRect(mTmpStartRect);
+        final int thumbWidthI = mTmpStartRect.width();
         final float thumbWidth = thumbWidthI > 0 ? thumbWidthI : 1;
-        final int thumbHeightI = mNextAppTransitionStartHeight;
+        final int thumbHeightI = mTmpStartRect.height();
         final float thumbHeight = thumbHeightI > 0 ? thumbHeightI : 1;
 
         // Used for the ENTER_SCALE_UP and EXIT_SCALE_DOWN transitions
@@ -733,7 +759,7 @@
             case THUMBNAIL_TRANSITION_ENTER_SCALE_UP: {
                 if (resizedWindow) {
                     a = createAspectScaledThumbnailEnterNonFullscreenAnimationLocked(
-                            containingFrame, surfaceInsets);
+                            containingFrame, surfaceInsets, taskId);
                 } else {
                     // App window scaling up to become full screen
                     if (orientation == Configuration.ORIENTATION_PORTRAIT) {
@@ -759,8 +785,8 @@
                     mNextAppTransitionInsets.set(contentInsets);
 
                     Animation scaleAnim = new ScaleAnimation(scale, 1, scale, 1,
-                            computePivot(mNextAppTransitionStartX, scale),
-                            computePivot(mNextAppTransitionStartY, scale));
+                            computePivot(mTmpStartRect.left, scale),
+                            computePivot(mTmpStartRect.top, scale));
                     Animation clipAnim = new ClipRectAnimation(mTmpFromClipRect, mTmpToClipRect);
                     Animation translateAnim = new TranslateAnimation(0, 0, -scaledTopDecor, 0);
 
@@ -819,8 +845,8 @@
                 mNextAppTransitionInsets.set(contentInsets);
 
                 Animation scaleAnim = new ScaleAnimation(1, scale, 1, scale,
-                        computePivot(mNextAppTransitionStartX, scale),
-                        computePivot(mNextAppTransitionStartY, scale));
+                        computePivot(mTmpStartRect.left, scale),
+                        computePivot(mTmpStartRect.top, scale));
                 Animation clipAnim = new ClipRectAnimation(mTmpFromClipRect, mTmpToClipRect);
                 Animation translateAnim = new TranslateAnimation(0, 0, 0, -scaledTopDecor);
 
@@ -844,11 +870,12 @@
     }
 
     private Animation createAspectScaledThumbnailEnterNonFullscreenAnimationLocked(
-            Rect containingFrame, @Nullable Rect surfaceInsets) {
+            Rect containingFrame, @Nullable Rect surfaceInsets, int taskId) {
+        getNextAppTransitionStartRect(taskId, mTmpStartRect);
         float width = containingFrame.width();
         float height = containingFrame.height();
-        float scaleWidth = mNextAppTransitionStartWidth / width;
-        float scaleHeight = mNextAppTransitionStartHeight / height;
+        float scaleWidth = mTmpStartRect.width() / width;
+        float scaleHeight = mTmpStartRect.height() / height;
         AnimationSet set = new AnimationSet(true);
         int surfaceInsetsHorizontal = surfaceInsets == null
                 ? 0 : surfaceInsets.left + surfaceInsets.right;
@@ -858,9 +885,9 @@
         // we need to account for surface insets that will be used to enlarge the surface.
         ScaleAnimation scale = new ScaleAnimation(scaleWidth, 1, scaleHeight, 1,
                 (width + surfaceInsetsHorizontal) / 2, (height + surfaceInsetsVertical) / 2);
-        int fromX = mNextAppTransitionStartX + mNextAppTransitionStartWidth / 2
+        int fromX = mTmpStartRect.left + mTmpStartRect.width() / 2
                 - (containingFrame.left + containingFrame.width() / 2);
-        int fromY = mNextAppTransitionStartY + mNextAppTransitionStartHeight / 2
+        int fromY = mTmpStartRect.top + mTmpStartRect.height() / 2
                 - (containingFrame.top + containingFrame.height() / 2);
         TranslateAnimation translation = new TranslateAnimation(fromX, 0, fromY, 0);
         set.addAnimation(scale);
@@ -870,13 +897,15 @@
 
     /**
      * This animation runs for the thumbnail that gets cross faded with the enter/exit activity
-     * when a thumbnail is specified with the activity options.
+     * when a thumbnail is specified with the pending animation override.
      */
-    Animation createThumbnailScaleAnimationLocked(int appWidth, int appHeight, int transit) {
+    Animation createThumbnailScaleAnimationLocked(int appWidth, int appHeight, int transit,
+            Bitmap thumbnailHeader) {
         Animation a;
-        final int thumbWidthI = mNextAppTransitionThumbnail.getWidth();
+        getDefaultNextAppTransitionStartRect(mTmpStartRect);
+        final int thumbWidthI = thumbnailHeader.getWidth();
         final float thumbWidth = thumbWidthI > 0 ? thumbWidthI : 1;
-        final int thumbHeightI = mNextAppTransitionThumbnail.getHeight();
+        final int thumbHeightI = thumbnailHeader.getHeight();
         final float thumbHeight = thumbHeightI > 0 ? thumbHeightI : 1;
 
         if (mNextAppTransitionScaleUp) {
@@ -884,8 +913,8 @@
             float scaleW = appWidth / thumbWidth;
             float scaleH = appHeight / thumbHeight;
             Animation scale = new ScaleAnimation(1, scaleW, 1, scaleH,
-                    computePivot(mNextAppTransitionStartX, 1 / scaleW),
-                    computePivot(mNextAppTransitionStartY, 1 / scaleH));
+                    computePivot(mTmpStartRect.left, 1 / scaleW),
+                    computePivot(mTmpStartRect.top, 1 / scaleH));
             scale.setInterpolator(mDecelerateInterpolator);
 
             Animation alpha = new AlphaAnimation(1, 0);
@@ -901,8 +930,8 @@
             float scaleW = appWidth / thumbWidth;
             float scaleH = appHeight / thumbHeight;
             a = new ScaleAnimation(scaleW, 1, scaleH, 1,
-                    computePivot(mNextAppTransitionStartX, 1 / scaleW),
-                    computePivot(mNextAppTransitionStartY, 1 / scaleH));
+                    computePivot(mTmpStartRect.left, 1 / scaleW),
+                    computePivot(mTmpStartRect.top, 1 / scaleH));
         }
 
         return prepareThumbnailAnimation(a, appWidth, appHeight, transit);
@@ -913,11 +942,13 @@
      * leaving, and the activity that is entering.
      */
     Animation createThumbnailEnterExitAnimationLocked(int thumbTransitState, int appWidth,
-            int appHeight, int transit) {
+            int appHeight, int transit, int taskId) {
+        Bitmap thumbnailHeader = getAppTransitionThumbnailHeader(taskId);
         Animation a;
-        final int thumbWidthI = mNextAppTransitionThumbnail.getWidth();
+        getDefaultNextAppTransitionStartRect(mTmpStartRect);
+        final int thumbWidthI = thumbnailHeader != null ? thumbnailHeader.getWidth() : appWidth;
         final float thumbWidth = thumbWidthI > 0 ? thumbWidthI : 1;
-        final int thumbHeightI = mNextAppTransitionThumbnail.getHeight();
+        final int thumbHeightI = thumbnailHeader != null ? thumbnailHeader.getHeight() : appHeight;
         final float thumbHeight = thumbHeightI > 0 ? thumbHeightI : 1;
 
         switch (thumbTransitState) {
@@ -926,8 +957,8 @@
                 float scaleW = thumbWidth / appWidth;
                 float scaleH = thumbHeight / appHeight;
                 a = new ScaleAnimation(scaleW, 1, scaleH, 1,
-                        computePivot(mNextAppTransitionStartX, scaleW),
-                        computePivot(mNextAppTransitionStartY, scaleH));
+                        computePivot(mTmpStartRect.left, scaleW),
+                        computePivot(mTmpStartRect.top, scaleH));
                 break;
             }
             case THUMBNAIL_TRANSITION_EXIT_SCALE_UP: {
@@ -954,8 +985,8 @@
                 float scaleW = thumbWidth / appWidth;
                 float scaleH = thumbHeight / appHeight;
                 Animation scale = new ScaleAnimation(1, scaleW, 1, scaleH,
-                        computePivot(mNextAppTransitionStartX, scaleW),
-                        computePivot(mNextAppTransitionStartY, scaleH));
+                        computePivot(mTmpStartRect.left, scaleW),
+                        computePivot(mTmpStartRect.top, scaleH));
 
                 Animation alpha = new AlphaAnimation(1, 0);
 
@@ -987,7 +1018,7 @@
     Animation loadAnimation(WindowManager.LayoutParams lp, int transit, boolean enter,
             int appWidth, int appHeight, int orientation, Rect containingFrame, Rect contentInsets,
             @Nullable Rect surfaceInsets, Rect appFrame, boolean isVoiceInteraction,
-            boolean resizedWindow) {
+            boolean resizedWindow, int taskId) {
         Animation a;
         if (isVoiceInteraction && (transit == TRANSIT_ACTIVITY_OPEN
                 || transit == TRANSIT_TASK_OPEN
@@ -1042,7 +1073,7 @@
             mNextAppTransitionScaleUp =
                     (mNextAppTransitionType == NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_UP);
             a = createThumbnailEnterExitAnimationLocked(getThumbnailTransitionState(enter),
-                    appWidth, appHeight, transit);
+                    appWidth, appHeight, transit, taskId);
             if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) {
                 String animName = mNextAppTransitionScaleUp ?
                         "ANIM_THUMBNAIL_SCALE_UP" : "ANIM_THUMBNAIL_SCALE_DOWN";
@@ -1057,7 +1088,7 @@
                     (mNextAppTransitionType == NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_UP);
             a = createAspectScaledThumbnailEnterExitAnimationLocked(
                     getThumbnailTransitionState(enter), appWidth, appHeight, orientation, transit,
-                    containingFrame, contentInsets, surfaceInsets, resizedWindow);
+                    containingFrame, contentInsets, surfaceInsets, resizedWindow, taskId);
             if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) {
                 String animName = mNextAppTransitionScaleUp ?
                         "ANIM_THUMBNAIL_ASPECT_SCALE_UP" : "ANIM_THUMBNAIL_ASPECT_SCALE_DOWN";
@@ -1147,7 +1178,7 @@
         if (isTransitionSet()) {
             mNextAppTransitionType = NEXT_TRANSIT_TYPE_CUSTOM;
             mNextAppTransitionPackage = packageName;
-            mNextAppTransitionThumbnail = null;
+            mNextAppTransitionAnimationsSpecs.clear();
             mNextAppTransitionEnter = enterAnim;
             mNextAppTransitionExit = exitAnim;
             postAnimationCallback();
@@ -1158,15 +1189,13 @@
     }
 
     void overridePendingAppTransitionScaleUp(int startX, int startY, int startWidth,
-                                                    int startHeight) {
+            int startHeight) {
         if (isTransitionSet()) {
             mNextAppTransitionType = NEXT_TRANSIT_TYPE_SCALE_UP;
             mNextAppTransitionPackage = null;
-            mNextAppTransitionThumbnail = null;
-            mNextAppTransitionStartX = startX;
-            mNextAppTransitionStartY = startY;
-            mNextAppTransitionStartWidth = startWidth;
-            mNextAppTransitionStartHeight = startHeight;
+            mNextAppTransitionAnimationsSpecs.clear();
+            putDefaultNextAppTransitionCoordinates(startX, startY, startX + startWidth,
+                    startY + startHeight);
             postAnimationCallback();
             mNextAppTransitionCallback = null;
         }
@@ -1176,10 +1205,7 @@
                                                 int startWidth, int startHeight) {
         if (isTransitionSet()) {
             mNextAppTransitionType = NEXT_TRANSIT_TYPE_CLIP_REVEAL;
-            mNextAppTransitionStartX = startX;
-            mNextAppTransitionStartY = startY;
-            mNextAppTransitionStartWidth = startWidth;
-            mNextAppTransitionStartHeight = startHeight;
+            putDefaultNextAppTransitionCoordinates(startX, startY, startWidth, startHeight);
             postAnimationCallback();
             mNextAppTransitionCallback = null;
         }
@@ -1191,10 +1217,9 @@
             mNextAppTransitionType = scaleUp ? NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_UP
                     : NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_DOWN;
             mNextAppTransitionPackage = null;
-            mNextAppTransitionThumbnail = srcThumb;
+            mNextAppTransitionAnimationsSpecs.clear();
             mNextAppTransitionScaleUp = scaleUp;
-            mNextAppTransitionStartX = startX;
-            mNextAppTransitionStartY = startY;
+            putDefaultNextAppTransitionCoordinates(startX, startY, 0 ,0);
             postAnimationCallback();
             mNextAppTransitionCallback = startedCallback;
         } else {
@@ -1208,12 +1233,9 @@
             mNextAppTransitionType = scaleUp ? NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_UP
                     : NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_DOWN;
             mNextAppTransitionPackage = null;
-            mNextAppTransitionThumbnail = srcThumb;
+            mNextAppTransitionAnimationsSpecs.clear();
             mNextAppTransitionScaleUp = scaleUp;
-            mNextAppTransitionStartX = startX;
-            mNextAppTransitionStartY = startY;
-            mNextAppTransitionStartWidth = targetWidth;
-            mNextAppTransitionStartHeight = targetHeight;
+            putDefaultNextAppTransitionCoordinates(startX, startY, targetWidth, targetHeight);
             postAnimationCallback();
             mNextAppTransitionCallback = startedCallback;
         } else {
@@ -1221,6 +1243,27 @@
         }
     }
 
+    public void overridePendingAppTransitionMultiThumb(AppTransitionAnimationSpec[] specs,
+            IRemoteCallback callback, boolean scaleUp) {
+        if (isTransitionSet()) {
+            mNextAppTransitionType = scaleUp ? NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_UP
+                    : NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_DOWN;
+            mNextAppTransitionPackage = null;
+            mNextAppTransitionAnimationsSpecs.clear();
+            mNextAppTransitionScaleUp = scaleUp;
+            for (int i = 0; i < specs.length; i++) {
+                AppTransitionAnimationSpec spec = specs[i];
+                if (spec != null) {
+                    mNextAppTransitionAnimationsSpecs.put(spec.taskId, spec);
+                }
+            }
+            postAnimationCallback();
+            mNextAppTransitionCallback = callback;
+        } else {
+            postAnimationCallback();
+        }
+    }
+
     void overrideInPlaceAppTransition(String packageName, int anim) {
         if (isTransitionSet()) {
             mNextAppTransitionType = NEXT_TRANSIT_TYPE_CUSTOM_IN_PLACE;
@@ -1350,33 +1393,30 @@
                 pw.print(prefix); pw.print("mNextAppTransitionInPlace=0x");
                         pw.print(Integer.toHexString(mNextAppTransitionInPlace));
                 break;
-            case NEXT_TRANSIT_TYPE_SCALE_UP:
+            case NEXT_TRANSIT_TYPE_SCALE_UP: {
+                getDefaultNextAppTransitionStartRect(mTmpStartRect);
                 pw.print(prefix); pw.print("mNextAppTransitionStartX=");
-                        pw.print(mNextAppTransitionStartX);
+                        pw.print(mTmpStartRect.left);
                         pw.print(" mNextAppTransitionStartY=");
-                        pw.println(mNextAppTransitionStartY);
+                        pw.println(mTmpStartRect.top);
                 pw.print(prefix); pw.print("mNextAppTransitionStartWidth=");
-                        pw.print(mNextAppTransitionStartWidth);
+                        pw.print(mTmpStartRect.width());
                         pw.print(" mNextAppTransitionStartHeight=");
-                        pw.println(mNextAppTransitionStartHeight);
+                        pw.println(mTmpStartRect.height());
                 break;
+            }
             case NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_UP:
             case NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_DOWN:
             case NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_UP:
-            case NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_DOWN:
-                pw.print(prefix); pw.print("mNextAppTransitionThumbnail=");
-                        pw.print(mNextAppTransitionThumbnail);
-                        pw.print(" mNextAppTransitionStartX=");
-                        pw.print(mNextAppTransitionStartX);
-                        pw.print(" mNextAppTransitionStartY=");
-                        pw.println(mNextAppTransitionStartY);
-                pw.print(prefix); pw.print("mNextAppTransitionStartWidth=");
-                        pw.print(mNextAppTransitionStartWidth);
-                        pw.print(" mNextAppTransitionStartHeight=");
-                        pw.println(mNextAppTransitionStartHeight);
+            case NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_DOWN: {
+                pw.print(prefix); pw.print("mDefaultNextAppTransitionAnimationSpec=");
+                        pw.println(mDefaultNextAppTransitionAnimationSpec);
+                pw.print(prefix); pw.print("mNextAppTransitionAnimationsSpecs=");
+                        pw.println(mNextAppTransitionAnimationsSpecs);
                 pw.print(prefix); pw.print("mNextAppTransitionScaleUp=");
                         pw.println(mNextAppTransitionScaleUp);
                 break;
+            }
         }
         if (mNextAppTransitionCallback != null) {
             pw.print(prefix); pw.print("mNextAppTransitionCallback=");
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index d6396a4..dc5effd 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -78,6 +78,7 @@
 import android.util.SparseIntArray;
 import android.util.TimeUtils;
 import android.util.TypedValue;
+import android.view.AppTransitionAnimationSpec;
 import android.view.Choreographer;
 import android.view.Display;
 import android.view.DisplayInfo;
@@ -442,6 +443,7 @@
 
     final float[] mTmpFloats = new float[9];
     final Rect mTmpContentRect = new Rect();
+    private final Rect mTmpStartRect = new Rect();
 
     boolean mDisplayReady;
     boolean mSafeMode;
@@ -2829,7 +2831,8 @@
             final boolean resizedWindow = !fullscreen && !dialogWindow;
             Animation a = mAppTransition.loadAnimation(lp, transit, enter, containingWidth,
                     containingHeight, mCurConfiguration.orientation, containingFrame, contentInsets,
-                    surfaceInsets, appFrame, isVoiceInteraction, resizedWindow);
+                    surfaceInsets, appFrame, isVoiceInteraction, resizedWindow,
+                    atoken.mTask.mTaskId);
             if (a != null) {
                 if (DEBUG_ANIM) {
                     RuntimeException e = null;
@@ -3553,6 +3556,14 @@
     }
 
     @Override
+    public void overridePendingAppTransitionMultiThumb(AppTransitionAnimationSpec[] specs,
+            IRemoteCallback callback, boolean scaleUp) {
+        synchronized (mWindowMap) {
+            mAppTransition.overridePendingAppTransitionMultiThumb(specs, callback, scaleUp);
+        }
+    }
+
+    @Override
     public void overridePendingAppTransitionInPlace(String packageName, int anim) {
         synchronized(mWindowMap) {
             mAppTransition.overrideInPlaceAppTransition(packageName, anim);
@@ -8707,11 +8718,6 @@
                 animLp = null;
             }
 
-            AppWindowToken topOpeningApp = null;
-            AppWindowToken topClosingApp = null;
-            int topOpeningLayer = 0;
-            int topClosingLayer = 0;
-
             // Process all applications animating in place
             if (transit == AppTransition.TRANSIT_TASK_IN_PLACE) {
                 // Find the focused window
@@ -8736,49 +8742,8 @@
                 }
             }
 
-            appsCount = mOpeningApps.size();
-            for (i = 0; i < appsCount; i++) {
-                AppWindowToken wtoken = mOpeningApps.valueAt(i);
-                final AppWindowAnimator appAnimator = wtoken.mAppAnimator;
-                if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "Now opening app" + wtoken);
-
-                if (!appAnimator.usingTransferredAnimation) {
-                    appAnimator.clearThumbnail();
-                    appAnimator.animation = null;
-                }
-                wtoken.inPendingTransaction = false;
-                if (!setTokenVisibilityLocked(
-                        wtoken, animLp, true, transit, false, voiceInteraction)){
-                    // This token isn't going to be animating. Add it to the list of tokens to
-                    // be notified of app transition complete since the notification will not be
-                    // sent be the app window animator.
-                    mNoAnimationNotifyOnTransitionFinished.add(wtoken.token);
-                }
-                wtoken.updateReportedVisibilityLocked();
-                wtoken.waitingToShow = false;
-
-                appAnimator.mAllAppWinAnimators.clear();
-                final int windowsCount = wtoken.allAppWindows.size();
-                for (int j = 0; j < windowsCount; j++) {
-                    appAnimator.mAllAppWinAnimators.add(wtoken.allAppWindows.get(j).mWinAnimator);
-                }
-                mAnimator.mAnimating |= appAnimator.showAllWindowsLocked();
-                mAnimator.mAppWindowAnimating |= appAnimator.isAnimating();
-
-                if (animLp != null) {
-                    int layer = -1;
-                    for (int j = 0; j < wtoken.windows.size(); j++) {
-                        WindowState win = wtoken.windows.get(j);
-                        if (win.mWinAnimator.mAnimLayer > layer) {
-                            layer = win.mWinAnimator.mAnimLayer;
-                        }
-                    }
-                    if (topOpeningApp == null || layer > topOpeningLayer) {
-                        topOpeningApp = wtoken;
-                        topOpeningLayer = layer;
-                    }
-                }
-            }
+            AppWindowToken topClosingApp = null;
+            int topClosingLayer = 0;
             appsCount = mClosingApps.size();
             for (i = 0; i < appsCount; i++) {
                 AppWindowToken wtoken = mClosingApps.valueAt(i);
@@ -8816,77 +8781,57 @@
                 }
             }
 
-            AppWindowAnimator openingAppAnimator = (topOpeningApp == null) ? null :
+            AppWindowToken topOpeningApp = null;
+            appsCount = mOpeningApps.size();
+            for (i = 0; i < appsCount; i++) {
+                AppWindowToken wtoken = mOpeningApps.valueAt(i);
+                final AppWindowAnimator appAnimator = wtoken.mAppAnimator;
+                if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "Now opening app" + wtoken);
+
+                if (!appAnimator.usingTransferredAnimation) {
+                    appAnimator.clearThumbnail();
+                    appAnimator.animation = null;
+                }
+                wtoken.inPendingTransaction = false;
+                if (!setTokenVisibilityLocked(
+                        wtoken, animLp, true, transit, false, voiceInteraction)){
+                    // This token isn't going to be animating. Add it to the list of tokens to
+                    // be notified of app transition complete since the notification will not be
+                    // sent be the app window animator.
+                    mNoAnimationNotifyOnTransitionFinished.add(wtoken.token);
+                }
+                wtoken.updateReportedVisibilityLocked();
+                wtoken.waitingToShow = false;
+
+                appAnimator.mAllAppWinAnimators.clear();
+                final int windowsCount = wtoken.allAppWindows.size();
+                for (int j = 0; j < windowsCount; j++) {
+                    appAnimator.mAllAppWinAnimators.add(wtoken.allAppWindows.get(j).mWinAnimator);
+                }
+                mAnimator.mAnimating |= appAnimator.showAllWindowsLocked();
+                mAnimator.mAppWindowAnimating |= appAnimator.isAnimating();
+
+                int topOpeningLayer = 0;
+                if (animLp != null) {
+                    int layer = -1;
+                    for (int j = 0; j < wtoken.windows.size(); j++) {
+                        WindowState win = wtoken.windows.get(j);
+                        if (win.mWinAnimator.mAnimLayer > layer) {
+                            layer = win.mWinAnimator.mAnimLayer;
+                        }
+                    }
+                    if (topOpeningApp == null || layer > topOpeningLayer) {
+                        topOpeningApp = wtoken;
+                        topOpeningLayer = layer;
+                    }
+                }
+                createThumbnailAppAnimator(transit, wtoken, topOpeningLayer, topClosingLayer);
+            }
+
+            AppWindowAnimator openingAppAnimator = (topOpeningApp == null) ?  null :
                     topOpeningApp.mAppAnimator;
             AppWindowAnimator closingAppAnimator = (topClosingApp == null) ? null :
                     topClosingApp.mAppAnimator;
-            Bitmap nextAppTransitionThumbnail = mAppTransition.getNextAppTransitionThumbnail();
-            if (nextAppTransitionThumbnail != null
-                    && openingAppAnimator != null && openingAppAnimator.animation != null &&
-                    nextAppTransitionThumbnail.getConfig() != Config.ALPHA_8) {
-                // This thumbnail animation is very special, we need to have
-                // an extra surface with the thumbnail included with the animation.
-                Rect dirty = new Rect(0, 0, nextAppTransitionThumbnail.getWidth(),
-                        nextAppTransitionThumbnail.getHeight());
-                try {
-                    // TODO(multi-display): support other displays
-                    final DisplayContent displayContent = getDefaultDisplayContentLocked();
-                    final Display display = displayContent.getDisplay();
-                    final DisplayInfo displayInfo = displayContent.getDisplayInfo();
-
-                    // Create a new surface for the thumbnail
-                    SurfaceControl surfaceControl = new SurfaceControl(mFxSession,
-                            "thumbnail anim", dirty.width(), dirty.height(),
-                            PixelFormat.TRANSLUCENT, SurfaceControl.HIDDEN);
-                    surfaceControl.setLayerStack(display.getLayerStack());
-                    if (SHOW_TRANSACTIONS) {
-                        Slog.i(TAG, "  THUMBNAIL " + surfaceControl + ": CREATE");
-                    }
-
-                    // Draw the thumbnail onto the surface
-                    Surface drawSurface = new Surface();
-                    drawSurface.copyFrom(surfaceControl);
-                    Canvas c = drawSurface.lockCanvas(dirty);
-                    c.drawBitmap(nextAppTransitionThumbnail, 0, 0, null);
-                    drawSurface.unlockCanvasAndPost(c);
-                    drawSurface.release();
-
-                    // Get the thumbnail animation
-                    Animation anim;
-                    if (mAppTransition.isNextThumbnailTransitionAspectScaled()) {
-                        // If this is a multi-window scenario, we use the windows frame as
-                        // destination of the thumbnail header animation. If this is a full screen
-                        // window scenario, we use the whole display as the target.
-                        WindowState win = topOpeningApp.findMainWindow();
-                        Rect appRect = win != null ? win.getContentFrameLw() :
-                                new Rect(0, 0, displayInfo.appWidth, displayInfo.appHeight);
-                        // For the new aspect-scaled transition, we want it to always show
-                        // above the animating opening/closing window, and we want to
-                        // synchronize its thumbnail surface with the surface for the
-                        // open/close animation (only on the way down)
-                        anim = mAppTransition.createThumbnailAspectScaleAnimationLocked(appRect);
-                        openingAppAnimator.thumbnailForceAboveLayer = Math.max(topOpeningLayer,
-                                topClosingLayer);
-                        openingAppAnimator.deferThumbnailDestruction =
-                                !mAppTransition.isNextThumbnailTransitionScaleUp();
-                    } else {
-                        anim = mAppTransition.createThumbnailScaleAnimationLocked(
-                                displayInfo.appWidth, displayInfo.appHeight, transit);
-                    }
-                    anim.restrictDuration(MAX_ANIMATION_DURATION);
-                    anim.scaleCurrentDuration(getTransitionAnimationScaleLocked());
-
-                    openingAppAnimator.thumbnail = surfaceControl;
-                    openingAppAnimator.thumbnailLayer = topOpeningLayer;
-                    openingAppAnimator.thumbnailAnimation = anim;
-                    openingAppAnimator.thumbnailX = mAppTransition.getStartingX();
-                    openingAppAnimator.thumbnailY = mAppTransition.getStartingY();
-                } catch (OutOfResourcesException e) {
-                    Slog.e(TAG, "Can't allocate thumbnail/Canvas surface w=" + dirty.width()
-                            + " h=" + dirty.height(), e);
-                    openingAppAnimator.clearThumbnail();
-                }
-            }
 
             mAppTransition.goodToGo(openingAppAnimator, closingAppAnimator);
             mAppTransition.postAnimationCallback();
@@ -8914,6 +8859,83 @@
         return changes;
     }
 
+    private void createThumbnailAppAnimator(int transit, AppWindowToken appToken,
+            int openingLayer, int closingLayer) {
+        AppWindowAnimator openingAppAnimator = (appToken == null) ? null : appToken.mAppAnimator;
+        if (openingAppAnimator == null || openingAppAnimator.animation == null) {
+            return;
+        }
+        final int taskId = appToken.mTask.mTaskId;
+        Bitmap thumbnailHeader = mAppTransition.getAppTransitionThumbnailHeader(taskId);
+        if (thumbnailHeader == null || thumbnailHeader.getConfig() == Config.ALPHA_8) {
+            return;
+        }
+        // This thumbnail animation is very special, we need to have
+        // an extra surface with the thumbnail included with the animation.
+        Rect dirty = new Rect(0, 0, thumbnailHeader.getWidth(), thumbnailHeader.getHeight());
+        try {
+            // TODO(multi-display): support other displays
+            final DisplayContent displayContent = getDefaultDisplayContentLocked();
+            final Display display = displayContent.getDisplay();
+            final DisplayInfo displayInfo = displayContent.getDisplayInfo();
+
+            // Create a new surface for the thumbnail
+            SurfaceControl surfaceControl = new SurfaceControl(mFxSession,
+                    "thumbnail anim", dirty.width(), dirty.height(),
+                    PixelFormat.TRANSLUCENT, SurfaceControl.HIDDEN);
+            surfaceControl.setLayerStack(display.getLayerStack());
+            if (SHOW_TRANSACTIONS) {
+                Slog.i(TAG, "  THUMBNAIL " + surfaceControl + ": CREATE");
+            }
+
+            // Draw the thumbnail onto the surface
+            Surface drawSurface = new Surface();
+            drawSurface.copyFrom(surfaceControl);
+            Canvas c = drawSurface.lockCanvas(dirty);
+            c.drawBitmap(thumbnailHeader, 0, 0, null);
+            drawSurface.unlockCanvasAndPost(c);
+            drawSurface.release();
+
+            // Get the thumbnail animation
+            Animation anim;
+            if (mAppTransition.isNextThumbnailTransitionAspectScaled()) {
+                // If this is a multi-window scenario, we use the windows frame as
+                // destination of the thumbnail header animation. If this is a full screen
+                // window scenario, we use the whole display as the target.
+                WindowState win = appToken.findMainWindow();
+                Rect appRect = win != null ? win.getContentFrameLw() :
+                        new Rect(0, 0, displayInfo.appWidth, displayInfo.appHeight);
+                // For the new aspect-scaled transition, we want it to always show
+                // above the animating opening/closing window, and we want to
+                // synchronize its thumbnail surface with the surface for the
+                // open/close animation (only on the way down)
+                anim = mAppTransition.createThumbnailAspectScaleAnimationLocked(appRect,
+                        thumbnailHeader, taskId);
+                Log.d(TAG, "assigning thumbnail force above layer: " + openingLayer + " " +
+                        closingLayer);
+                openingAppAnimator.thumbnailForceAboveLayer = Math.max(openingLayer, closingLayer);
+                openingAppAnimator.deferThumbnailDestruction =
+                        !mAppTransition.isNextThumbnailTransitionScaleUp();
+            } else {
+                anim = mAppTransition.createThumbnailScaleAnimationLocked(
+                        displayInfo.appWidth, displayInfo.appHeight, transit, thumbnailHeader);
+            }
+            anim.restrictDuration(MAX_ANIMATION_DURATION);
+            anim.scaleCurrentDuration(getTransitionAnimationScaleLocked());
+
+            openingAppAnimator.thumbnail = surfaceControl;
+            openingAppAnimator.thumbnailLayer = openingLayer;
+            openingAppAnimator.thumbnailAnimation = anim;
+            mAppTransition.getNextAppTransitionStartRect(taskId, mTmpStartRect);
+            openingAppAnimator.thumbnailX = mTmpStartRect.left;
+            openingAppAnimator.thumbnailY = mTmpStartRect.top;
+        } catch (OutOfResourcesException e) {
+            Slog.e(TAG, "Can't allocate thumbnail/Canvas surface w=" + dirty.width()
+                    + " h=" + dirty.height(), e);
+            openingAppAnimator.clearThumbnail();
+        }
+    }
+
     /**
      * Extracted from {@link #performLayoutAndPlaceSurfacesLockedInner} to reduce size of method.
      * @return bitmap indicating if another pass through layout must be made.
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index b702f5a..b56b1f9 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wm;
 
+import static android.app.ActivityManager.FREEFORM_WORKSPACE_STACK_ID;
 import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW;
 import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
 import static android.view.WindowManager.LayoutParams.LAST_SUB_WINDOW;
@@ -37,6 +38,7 @@
 import android.os.RemoteCallbackList;
 import android.os.SystemClock;
 import android.os.WorkSource;
+import android.util.DisplayMetrics;
 import android.util.TimeUtils;
 import android.view.Display;
 import android.view.IWindowFocusObserver;
@@ -77,6 +79,10 @@
 final class WindowState implements WindowManagerPolicy.WindowState {
     static final String TAG = "WindowState";
 
+    // The minimal size of a window within the usable area of the freeform stack.
+    static final int MINIMUM_VISIBLE_WIDTH_IN_DP = 48;
+    static final int MINIMUM_VISIBLE_HEIGHT_IN_DP = 32;
+
     final WindowManagerService mService;
     final WindowManagerPolicy mPolicy;
     final Context mContext;
@@ -535,6 +541,8 @@
         mHaveFrame = true;
 
         final Task task = mAppToken != null ? getTask() : null;
+        final boolean isFreeFormWorkspace = task != null && task.mStack != null &&
+                task.mStack.mStackId == FREEFORM_WORKSPACE_STACK_ID;
         final boolean nonFullscreenTask = task != null && !task.isFullscreen();
         if (nonFullscreenTask) {
             task.getBounds(mContainingFrame);
@@ -544,10 +552,20 @@
                 // IME is up and obscuring this window. Adjust the window position so it is visible.
                 mContainingFrame.top -= mContainingFrame.bottom - cf.bottom;
             }
-            // Make sure the containing frame is within the content frame so we don't layout
-            // resized window under screen decorations.
-            if (!mContainingFrame.intersect(cf)) {
-                mContainingFrame.set(cf);
+
+            if (isFreeFormWorkspace) {
+                // In free form mode we have only to set the rectangle if it wasn't set already. No
+                // need to intersect it with the (visible) "content frame" since it is allowed to
+                // be outside the visible desktop.
+                if (mContainingFrame.isEmpty()) {
+                    mContainingFrame.set(cf);
+                }
+            } else {
+                // Make sure the containing frame is within the content frame so we don't layout
+                // resized window under screen decorations.
+                if (!mContainingFrame.intersect(cf)) {
+                    mContainingFrame.set(cf);
+                }
             }
             mDisplayFrame.set(mContainingFrame);
         } else {
@@ -651,20 +669,38 @@
 
         // Make sure the content and visible frames are inside of the
         // final window frame.
-        mContentFrame.set(Math.max(mContentFrame.left, mFrame.left),
-                Math.max(mContentFrame.top, mFrame.top),
-                Math.min(mContentFrame.right, mFrame.right),
-                Math.min(mContentFrame.bottom, mFrame.bottom));
+        if (isFreeFormWorkspace && !mFrame.isEmpty()) {
+            // Keep the frame out of the blocked system area, limit it in size to the content area
+            // and make sure that there is always a minimum visible so that the user can drag it
+            // into a usable area..
+            final int height = Math.min(mFrame.height(), mContentFrame.height());
+            final int width = Math.min(mContentFrame.width(), mFrame.width());
+            final int minVisibleHeight = calculatePixelFromDp(MINIMUM_VISIBLE_HEIGHT_IN_DP);
+            final int minVisibleWidth = calculatePixelFromDp(MINIMUM_VISIBLE_WIDTH_IN_DP);
+            final int top = Math.max(mContentFrame.top,
+                    Math.min(mFrame.top, mContentFrame.bottom - minVisibleHeight));
+            final int left = Math.max(mContentFrame.left + minVisibleWidth - width,
+                    Math.min(mFrame.left, mContentFrame.right - minVisibleWidth));
+            mFrame.set(left, top, left + width, top + height);
+            mContentFrame.set(mFrame);
+            mVisibleFrame.set(mContentFrame);
+            mStableFrame.set(mContentFrame);
+        } else {
+            mContentFrame.set(Math.max(mContentFrame.left, mFrame.left),
+                    Math.max(mContentFrame.top, mFrame.top),
+                    Math.min(mContentFrame.right, mFrame.right),
+                    Math.min(mContentFrame.bottom, mFrame.bottom));
 
-        mVisibleFrame.set(Math.max(mVisibleFrame.left, mFrame.left),
-                Math.max(mVisibleFrame.top, mFrame.top),
-                Math.min(mVisibleFrame.right, mFrame.right),
-                Math.min(mVisibleFrame.bottom, mFrame.bottom));
+            mVisibleFrame.set(Math.max(mVisibleFrame.left, mFrame.left),
+                    Math.max(mVisibleFrame.top, mFrame.top),
+                    Math.min(mVisibleFrame.right, mFrame.right),
+                    Math.min(mVisibleFrame.bottom, mFrame.bottom));
 
-        mStableFrame.set(Math.max(mStableFrame.left, mFrame.left),
-                Math.max(mStableFrame.top, mFrame.top),
-                Math.min(mStableFrame.right, mFrame.right),
-                Math.min(mStableFrame.bottom, mFrame.bottom));
+            mStableFrame.set(Math.max(mStableFrame.left, mFrame.left),
+                    Math.max(mStableFrame.top, mFrame.top),
+                    Math.min(mStableFrame.right, mFrame.right),
+                    Math.min(mStableFrame.bottom, mFrame.bottom));
+        }
 
         mOverscanInsets.set(Math.max(mOverscanFrame.left - mFrame.left, 0),
                 Math.max(mOverscanFrame.top - mFrame.top, 0),
@@ -1576,6 +1612,13 @@
         }
     }
 
+    private int calculatePixelFromDp(int dp) {
+        final Configuration serviceConfig = mService.mCurConfiguration;
+        // TODO(multidisplay): Update Dp to that of display stack is on.
+        final float density = serviceConfig.densityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE;
+        return (int)(dp * density);
+    }
+
     void dump(PrintWriter pw, String prefix, boolean dumpAll) {
         final TaskStack stack = getStack();
         pw.print(prefix); pw.print("mDisplayId="); pw.print(getDisplayId());
diff --git a/services/tests/servicestests/Android.mk b/services/tests/servicestests/Android.mk
index 33979b1..3ad26d3 100644
--- a/services/tests/servicestests/Android.mk
+++ b/services/tests/servicestests/Android.mk
@@ -13,6 +13,7 @@
     services.net \
     easymocklib \
     guava \
+    android-support-test \
     mockito-target
 
 LOCAL_JAVA_LIBRARIES := android.test.runner
diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml
index 919293a..386a9cb 100644
--- a/services/tests/servicestests/AndroidManifest.xml
+++ b/services/tests/servicestests/AndroidManifest.xml
@@ -69,7 +69,7 @@
     </application>
 
     <instrumentation
-    	android:name="android.test.InstrumentationTestRunner"
+    	android:name="android.support.test.runner.AndroidJUnitRunner"
     	android:targetPackage="com.android.frameworks.servicestests"
     	android:label="Frameworks Services Tests" />
 </manifest>
diff --git a/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java b/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java
index 696f106..b4c76b7 100644
--- a/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java
@@ -943,8 +943,7 @@
         if (capability == NET_CAPABILITY_CBS || capability == NET_CAPABILITY_DUN ||
                 capability == NET_CAPABILITY_EIMS || capability == NET_CAPABILITY_FOTA ||
                 capability == NET_CAPABILITY_IA || capability == NET_CAPABILITY_IMS ||
-                capability == NET_CAPABILITY_RCS || capability == NET_CAPABILITY_XCAP ||
-                capability == NET_CAPABILITY_TRUSTED || capability == NET_CAPABILITY_NOT_VPN) {
+                capability == NET_CAPABILITY_RCS || capability == NET_CAPABILITY_XCAP) {
             assertFalse(nc.hasCapability(NET_CAPABILITY_NOT_RESTRICTED));
         } else {
             assertTrue(nc.hasCapability(NET_CAPABILITY_NOT_RESTRICTED));
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java b/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java
index ed1db6f..bf6343f 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java
@@ -21,8 +21,11 @@
 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER;
 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
 
+import android.annotation.NonNull;
 import android.content.Context;
 import android.content.pm.PackageParser;
+import android.content.pm.UserInfo;
+import android.os.UserHandle;
 import android.test.AndroidTestCase;
 import android.util.ArrayMap;
 import android.util.ArraySet;
@@ -36,6 +39,8 @@
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.security.PublicKey;
+import java.util.ArrayList;
+import java.util.List;
 
 public class PackageManagerSettingsTests extends AndroidTestCase {
     private static final String PACKAGE_NAME_2 = "com.google.app2";
@@ -45,6 +50,12 @@
     public static final String TAG = "PackageManagerSettingsTests";
     protected final String PREFIX = "android.content.pm";
 
+    private @NonNull List<UserInfo> createFakeUsers() {
+        ArrayList<UserInfo> users = new ArrayList<>();
+        users.add(new UserInfo(UserHandle.USER_SYSTEM, "test user", UserInfo.FLAG_INITIALIZED));
+        return users;
+    }
+
     private void writeFile(File file, byte[] data) {
         file.mkdirs();
         try {
@@ -245,7 +256,7 @@
         writeOldFiles();
         createUserManagerServiceRef();
         Settings settings = new Settings(getContext().getFilesDir(), new Object());
-        assertEquals(true, settings.readLPw(null, null, 0, false));
+        assertEquals(true, settings.readLPw(createFakeUsers()));
         verifyKeySetMetaData(settings);
     }
 
@@ -257,11 +268,11 @@
         writeOldFiles();
         createUserManagerServiceRef();
         Settings settings = new Settings(getContext().getFilesDir(), new Object());
-        assertEquals(true, settings.readLPw(null, null, 0, false));
+        assertEquals(true, settings.readLPw(createFakeUsers()));
 
         /* write out, read back in and verify the same */
         settings.writeLPr();
-        assertEquals(true, settings.readLPw(null, null, 0, false));
+        assertEquals(true, settings.readLPw(createFakeUsers()));
         verifyKeySetMetaData(settings);
     }
 
@@ -269,7 +280,7 @@
         // Write the package files and make sure they're parsed properly the first time
         writeOldFiles();
         Settings settings = new Settings(getContext().getFilesDir(), new Object());
-        assertEquals(true, settings.readLPw(null, null, 0, false));
+        assertEquals(true, settings.readLPw(createFakeUsers()));
         assertNotNull(settings.peekPackageLPr(PACKAGE_NAME_3));
         assertNotNull(settings.peekPackageLPr(PACKAGE_NAME_1));
 
@@ -289,12 +300,12 @@
         writeOldFiles();
         createUserManagerServiceRef();
         Settings settings = new Settings(getContext().getFilesDir(), new Object());
-        assertEquals(true, settings.readLPw(null, null, 0, false));
+        assertEquals(true, settings.readLPw(createFakeUsers()));
         settings.writeLPr();
 
         // Create Settings again to make it read from the new files
         settings = new Settings(getContext().getFilesDir(), new Object());
-        assertEquals(true, settings.readLPw(null, null, 0, false));
+        assertEquals(true, settings.readLPw(createFakeUsers()));
 
         PackageSetting ps = settings.peekPackageLPr(PACKAGE_NAME_2);
         assertEquals(COMPONENT_ENABLED_STATE_DISABLED_USER, ps.getEnabled(0));
@@ -305,7 +316,7 @@
         // Write the package files and make sure they're parsed properly the first time
         writeOldFiles();
         Settings settings = new Settings(getContext().getFilesDir(), new Object());
-        assertEquals(true, settings.readLPw(null, null, 0, false));
+        assertEquals(true, settings.readLPw(createFakeUsers()));
 
         // Enable/Disable a package
         PackageSetting ps = settings.peekPackageLPr(PACKAGE_NAME_1);
diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java
index d91fa90..2d31a78 100644
--- a/telecomm/java/android/telecom/Call.java
+++ b/telecomm/java/android/telecom/Call.java
@@ -512,8 +512,8 @@
                         Objects.equals(mGatewayInfo, d.mGatewayInfo) &&
                         Objects.equals(mVideoState, d.mVideoState) &&
                         Objects.equals(mStatusHints, d.mStatusHints) &&
-                        Objects.equals(mExtras, d.mExtras) &&
-                        Objects.equals(mIntentExtras, d.mIntentExtras);
+                        areBundlesEqual(mExtras, d.mExtras) &&
+                        areBundlesEqual(mIntentExtras, d.mIntentExtras);
             }
             return false;
         }
@@ -1253,4 +1253,32 @@
             });
         }
     }
+
+    /**
+     * Determines if two bundles are equal.
+     *
+     * @param bundle The original bundle.
+     * @param newBundle The bundle to compare with.
+     * @retrun {@code true} if the bundles are equal, {@code false} otherwise.
+     */
+    private static boolean areBundlesEqual(Bundle bundle, Bundle newBundle) {
+        if (bundle == null || newBundle == null) {
+            return bundle == newBundle;
+        }
+
+        if (bundle.size() != newBundle.size()) {
+            return false;
+        }
+
+        for(String key : bundle.keySet()) {
+            if (key != null) {
+                final Object value = bundle.get(key);
+                final Object newValue = newBundle.get(key);
+                if (!Objects.equals(value, newValue)) {
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
 }
diff --git a/tools/aapt2/Files.cpp b/tools/aapt2/Files.cpp
index 8484148..b24ff6b 100644
--- a/tools/aapt2/Files.cpp
+++ b/tools/aapt2/Files.cpp
@@ -22,7 +22,7 @@
 #include <string>
 #include <sys/stat.h>
 
-#ifdef HAVE_MS_C_RUNTIME
+#ifdef _WIN32
 // Windows includes.
 #include <direct.h>
 #endif
@@ -83,7 +83,7 @@
 }
 
 inline static int mkdirImpl(const StringPiece& path) {
-#ifdef HAVE_MS_C_RUNTIME
+#ifdef _WIN32
     return _mkdir(path.toString().c_str());
 #else
     return mkdir(path.toString().c_str(), S_IRUSR|S_IWUSR|S_IXUSR|S_IRGRP|S_IXGRP);
diff --git a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
index 62859ec..a8fd91c 100644
--- a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
+++ b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
@@ -30,6 +30,7 @@
 import android.os.IRemoteCallback;
 import android.os.RemoteException;
 import android.util.DisplayMetrics;
+import android.view.AppTransitionAnimationSpec;
 
 import java.lang.Override;
 
@@ -241,6 +242,12 @@
     }
 
     @Override
+    public void overridePendingAppTransitionMultiThumb(AppTransitionAnimationSpec[] specs,
+            IRemoteCallback callback, boolean scaleUp) {
+        // TODO Auto-generated method stub
+    }
+
+    @Override
     public void pauseKeyDispatching(IBinder arg0) throws RemoteException {
         // TODO Auto-generated method stub