Merge "Send onUnlockUser() before PRE_BOOT broadcasts." into nyc-dev
diff --git a/api/current.txt b/api/current.txt
index dfdff24..e11b389 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -7885,6 +7885,7 @@
     method public static boolean isSyncPending(android.accounts.Account, java.lang.String);
     method public void notifyChange(android.net.Uri, android.database.ContentObserver);
     method public void notifyChange(android.net.Uri, android.database.ContentObserver, boolean);
+    method public void notifyChange(android.net.Uri, android.database.ContentObserver, int);
     method public final android.content.res.AssetFileDescriptor openAssetFileDescriptor(android.net.Uri, java.lang.String) throws java.io.FileNotFoundException;
     method public final android.content.res.AssetFileDescriptor openAssetFileDescriptor(android.net.Uri, java.lang.String, android.os.CancellationSignal) throws java.io.FileNotFoundException;
     method public final android.os.ParcelFileDescriptor openFileDescriptor(android.net.Uri, java.lang.String) throws java.io.FileNotFoundException;
@@ -7915,6 +7916,8 @@
     field public static final java.lang.String CURSOR_DIR_BASE_TYPE = "vnd.android.cursor.dir";
     field public static final java.lang.String CURSOR_ITEM_BASE_TYPE = "vnd.android.cursor.item";
     field public static final java.lang.String EXTRA_SIZE = "android.content.extra.SIZE";
+    field public static final int NOTIFY_SKIP_NOTIFY_FOR_DESCENDANTS = 2; // 0x2
+    field public static final int NOTIFY_SYNC_TO_NETWORK = 1; // 0x1
     field public static final java.lang.String SCHEME_ANDROID_RESOURCE = "android.resource";
     field public static final java.lang.String SCHEME_CONTENT = "content";
     field public static final java.lang.String SCHEME_FILE = "file";
diff --git a/api/system-current.txt b/api/system-current.txt
index 96b1e28..98f70e2 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -8183,6 +8183,7 @@
     method public static boolean isSyncPending(android.accounts.Account, java.lang.String);
     method public void notifyChange(android.net.Uri, android.database.ContentObserver);
     method public void notifyChange(android.net.Uri, android.database.ContentObserver, boolean);
+    method public void notifyChange(android.net.Uri, android.database.ContentObserver, int);
     method public final android.content.res.AssetFileDescriptor openAssetFileDescriptor(android.net.Uri, java.lang.String) throws java.io.FileNotFoundException;
     method public final android.content.res.AssetFileDescriptor openAssetFileDescriptor(android.net.Uri, java.lang.String, android.os.CancellationSignal) throws java.io.FileNotFoundException;
     method public final android.os.ParcelFileDescriptor openFileDescriptor(android.net.Uri, java.lang.String) throws java.io.FileNotFoundException;
@@ -8213,6 +8214,8 @@
     field public static final java.lang.String CURSOR_DIR_BASE_TYPE = "vnd.android.cursor.dir";
     field public static final java.lang.String CURSOR_ITEM_BASE_TYPE = "vnd.android.cursor.item";
     field public static final java.lang.String EXTRA_SIZE = "android.content.extra.SIZE";
+    field public static final int NOTIFY_SKIP_NOTIFY_FOR_DESCENDANTS = 2; // 0x2
+    field public static final int NOTIFY_SYNC_TO_NETWORK = 1; // 0x1
     field public static final java.lang.String SCHEME_ANDROID_RESOURCE = "android.resource";
     field public static final java.lang.String SCHEME_CONTENT = "content";
     field public static final java.lang.String SCHEME_FILE = "file";
diff --git a/api/test-current.txt b/api/test-current.txt
index 8a745bf..11abb4c 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -7890,6 +7890,7 @@
     method public static boolean isSyncPending(android.accounts.Account, java.lang.String);
     method public void notifyChange(android.net.Uri, android.database.ContentObserver);
     method public void notifyChange(android.net.Uri, android.database.ContentObserver, boolean);
+    method public void notifyChange(android.net.Uri, android.database.ContentObserver, int);
     method public final android.content.res.AssetFileDescriptor openAssetFileDescriptor(android.net.Uri, java.lang.String) throws java.io.FileNotFoundException;
     method public final android.content.res.AssetFileDescriptor openAssetFileDescriptor(android.net.Uri, java.lang.String, android.os.CancellationSignal) throws java.io.FileNotFoundException;
     method public final android.os.ParcelFileDescriptor openFileDescriptor(android.net.Uri, java.lang.String) throws java.io.FileNotFoundException;
@@ -7920,6 +7921,8 @@
     field public static final java.lang.String CURSOR_DIR_BASE_TYPE = "vnd.android.cursor.dir";
     field public static final java.lang.String CURSOR_ITEM_BASE_TYPE = "vnd.android.cursor.item";
     field public static final java.lang.String EXTRA_SIZE = "android.content.extra.SIZE";
+    field public static final int NOTIFY_SKIP_NOTIFY_FOR_DESCENDANTS = 2; // 0x2
+    field public static final int NOTIFY_SYNC_TO_NETWORK = 1; // 0x1
     field public static final java.lang.String SCHEME_ANDROID_RESOURCE = "android.resource";
     field public static final java.lang.String SCHEME_CONTENT = "content";
     field public static final java.lang.String SCHEME_FILE = "file";
diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java
index cd67b3e..4db4567 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -17,6 +17,7 @@
 package android.content;
 
 import android.accounts.Account;
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
@@ -60,6 +61,8 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Random;
@@ -289,6 +292,31 @@
     /** @hide */
     public static final int SYNC_OBSERVER_TYPE_ALL = 0x7fffffff;
 
+    /** @hide */
+    @IntDef(flag = true,
+            value = {
+                NOTIFY_SYNC_TO_NETWORK,
+                NOTIFY_SKIP_NOTIFY_FOR_DESCENDANTS
+            })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface NotifyFlags {}
+
+    /**
+     * Flag for {@link #notifyChange(Uri, ContentObserver, int)}: attempt to sync the change
+     * to the network.
+     */
+    public static final int NOTIFY_SYNC_TO_NETWORK = 1<<0;
+
+    /**
+     * Flag for {@link #notifyChange(Uri, ContentObserver, int)}: if set, this notification
+     * will be skipped if it is being delivered to the root URI of a ContentObserver that is
+     * using "notify for descendants."  The purpose of this is to allow the provide to send
+     * a general notification of "something under X" changed that observers of that specific
+     * URI can receive, while also sending a specific URI under X.  It would use this flag
+     * when sending the former, so that observers of "X and descendants" only see the latter.
+     */
+    public static final int NOTIFY_SKIP_NOTIFY_FOR_DESCENDANTS = 1<<1;
+
     // Always log queries which take 500ms+; shorter queries are
     // sampled accordingly.
     private static final boolean ENABLE_CONTENT_SAMPLE = false;
@@ -1676,7 +1704,7 @@
      * The observer that originated the change will only receive the notification if it
      * has requested to receive self-change notifications by implementing
      * {@link ContentObserver#deliverSelfNotifications()} to return true.
-     * @param syncToNetwork If true, attempt to sync the change to the network.
+     * @param syncToNetwork If true, same as {@link #NOTIFY_SYNC_TO_NETWORK}.
      * @see #requestSync(android.accounts.Account, String, android.os.Bundle)
      */
     public void notifyChange(@NonNull Uri uri, @Nullable ContentObserver observer,
@@ -1690,6 +1718,32 @@
     }
 
     /**
+     * Notify registered observers that a row was updated.
+     * To register, call {@link #registerContentObserver(android.net.Uri, boolean, android.database.ContentObserver) registerContentObserver()}.
+     * By default, CursorAdapter objects will get this notification.
+     * If syncToNetwork is true, this will attempt to schedule a local sync using the sync
+     * adapter that's registered for the authority of the provided uri. No account will be
+     * passed to the sync adapter, so all matching accounts will be synchronized.
+     *
+     * @param uri The uri of the content that was changed.
+     * @param observer The observer that originated the change, may be <code>null</null>.
+     * The observer that originated the change will only receive the notification if it
+     * has requested to receive self-change notifications by implementing
+     * {@link ContentObserver#deliverSelfNotifications()} to return true.
+     * @param flags Additional flags: {@link #NOTIFY_SYNC_TO_NETWORK}.
+     * @see #requestSync(android.accounts.Account, String, android.os.Bundle)
+     */
+    public void notifyChange(@NonNull Uri uri, @Nullable ContentObserver observer,
+            @NotifyFlags int flags) {
+        Preconditions.checkNotNull(uri, "uri");
+        notifyChange(
+                ContentProvider.getUriWithoutUserId(uri),
+                observer,
+                flags,
+                ContentProvider.getUserIdFromUri(uri, UserHandle.myUserId()));
+    }
+
+    /**
      * Notify registered observers within the designated user(s) that a row was updated.
      *
      * @hide
@@ -1699,7 +1753,24 @@
         try {
             getContentService().notifyChange(
                     uri, observer == null ? null : observer.getContentObserver(),
-                    observer != null && observer.deliverSelfNotifications(), syncToNetwork,
+                    observer != null && observer.deliverSelfNotifications(),
+                    syncToNetwork ? NOTIFY_SYNC_TO_NETWORK : 0,
+                    userHandle);
+        } catch (RemoteException e) {
+        }
+    }
+
+    /**
+     * Notify registered observers within the designated user(s) that a row was updated.
+     *
+     * @hide
+     */
+    public void notifyChange(Uri uri, ContentObserver observer, @NotifyFlags int flags,
+            @UserIdInt int userHandle) {
+        try {
+            getContentService().notifyChange(
+                    uri, observer == null ? null : observer.getContentObserver(),
+                    observer != null && observer.deliverSelfNotifications(), flags,
                     userHandle);
         } catch (RemoteException e) {
         }
diff --git a/core/java/android/content/IContentService.aidl b/core/java/android/content/IContentService.aidl
index d47e780..3446e03 100644
--- a/core/java/android/content/IContentService.aidl
+++ b/core/java/android/content/IContentService.aidl
@@ -52,7 +52,7 @@
      *     USER_CURRENT are properly interpreted.
      */
     void notifyChange(in Uri uri, IContentObserver observer,
-            boolean observerWantsSelfNotifications, boolean syncToNetwork,
+            boolean observerWantsSelfNotifications, int flags,
             int userHandle);
 
     void requestSync(in Account account, String authority, in Bundle extras);
diff --git a/core/java/android/content/IntentFilter.java b/core/java/android/content/IntentFilter.java
index ed5dfa5..22ab43b 100644
--- a/core/java/android/content/IntentFilter.java
+++ b/core/java/android/content/IntentFilter.java
@@ -449,11 +449,12 @@
     }
 
     /**
-     * Modify priority of this filter.  The default priority is 0. Positive
-     * values will be before the default, lower values will be after it.
-     * Applications must use a value that is larger than
-     * {@link #SYSTEM_LOW_PRIORITY} and smaller than
-     * {@link #SYSTEM_HIGH_PRIORITY} .
+     * Modify priority of this filter.  This only affects receiver filters.
+     * The priority of activity filters are set in XML and cannot be changed
+     * programatically. The default priority is 0. Positive values will be
+     * before the default, lower values will be after it. Applications should
+     * use a value that is larger than {@link #SYSTEM_LOW_PRIORITY} and
+     * smaller than {@link #SYSTEM_HIGH_PRIORITY} .
      *
      * @param priority The new priority value.
      *
diff --git a/core/java/com/android/internal/widget/ResolverDrawerLayout.java b/core/java/com/android/internal/widget/ResolverDrawerLayout.java
index c4347f8..25b487e 100644
--- a/core/java/com/android/internal/widget/ResolverDrawerLayout.java
+++ b/core/java/com/android/internal/widget/ResolverDrawerLayout.java
@@ -17,9 +17,14 @@
 
 package com.android.internal.widget;
 
+import android.annotation.NonNull;
 import android.content.Context;
 import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Color;
 import android.graphics.Rect;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -85,6 +90,10 @@
     private final float mMinFlingVelocity;
     private final OverScroller mScroller;
     private final VelocityTracker mVelocityTracker;
+    private final Drawable mScrollIndicatorDrawable;
+    private final Drawable mFakeForeground;
+
+    private View mButtonBar;
 
     private OnDismissedListener mOnDismissedListener;
     private RunOnDismissedListener mRunOnDismissedListener;
@@ -106,6 +115,8 @@
                 }
             };
 
+    private final int[] mTempOffset = new int[2];
+
     public ResolverDrawerLayout(Context context) {
         this(context, null);
     }
@@ -127,6 +138,9 @@
                 mMaxCollapsedHeight);
         a.recycle();
 
+        mScrollIndicatorDrawable = mContext.getDrawable(R.drawable.scroll_indicator_material);
+        mFakeForeground = new ColorDrawable(Color.TRANSPARENT);
+
         mScroller = new OverScroller(context, AnimationUtils.loadInterpolator(context,
                 android.R.interpolator.decelerate_quint));
         mVelocityTracker = VelocityTracker.obtain();
@@ -138,6 +152,13 @@
         setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
     }
 
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+
+        mButtonBar = findViewById(R.id.button_bar);
+    }
+
     public void setSmallCollapsed(boolean smallCollapsed) {
         mSmallCollapsed = smallCollapsed;
         requestLayout();
@@ -202,8 +223,7 @@
             }
             final boolean isCollapsedNew = mCollapseOffset != 0;
             if (isCollapsedOld != isCollapsedNew) {
-                notifyViewAccessibilityStateChangedIfNeeded(
-                        AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
+                onCollapsedChanged(isCollapsedNew);
             }
         } else {
             // Start out collapsed at first unless we restored state for otherwise
@@ -442,8 +462,7 @@
             mTopOffset += dy;
             final boolean isCollapsedNew = newPos != 0;
             if (isCollapsedOld != isCollapsedNew) {
-                notifyViewAccessibilityStateChangedIfNeeded(
-                        AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
+                onCollapsedChanged(isCollapsedNew);
             }
             postInvalidateOnAnimation();
             return dy;
@@ -451,6 +470,14 @@
         return 0;
     }
 
+    private void onCollapsedChanged(boolean isCollapsed) {
+        notifyViewAccessibilityStateChangedIfNeeded(
+                AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
+
+        // Set a fake foreground so that we receive onDrawForeground().
+        setForeground(isCollapsed ? mFakeForeground : null);
+    }
+
     void dispatchOnDismissed() {
         if (mOnDismissedListener != null) {
             mOnDismissedListener.onDismissed();
@@ -709,6 +736,23 @@
     }
 
     @Override
+    public void onDrawForeground(Canvas canvas) {
+        if (isCollapsed() && mButtonBar != null) {
+            // Draw the scroll indicator directly above the button bar.
+            final int height = mScrollIndicatorDrawable.getIntrinsicHeight();
+            mButtonBar.getLocationInWindow(mTempOffset);
+            final int barTop = mTempOffset[1];
+            getLocationInWindow(mTempOffset);
+            final int myTop = mTempOffset[1];
+            final int top = (barTop - myTop) - height;
+            mScrollIndicatorDrawable.setBounds(0, top, getWidth(), top + height);
+            mScrollIndicatorDrawable.draw(canvas);
+        }
+
+        super.onDrawForeground(canvas);
+    }
+
+    @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         final int sourceWidth = MeasureSpec.getSize(widthMeasureSpec);
         int widthSize = sourceWidth;
diff --git a/core/res/res/anim/slide_in_enter_micro.xml b/core/res/res/anim/slide_in_enter_micro.xml
index 14a5290..c70874c 100644
--- a/core/res/res/anim/slide_in_enter_micro.xml
+++ b/core/res/res/anim/slide_in_enter_micro.xml
@@ -19,7 +19,7 @@
 
 <set xmlns:android="http://schemas.android.com/apk/res/android"
      android:zAdjustment="top">
-    <translate android:fromYDelta="5%p" android:toXDelta="0"
+    <translate android:fromYDelta="5%p" android:toYDelta="0"
                android:duration="417"
                android:interpolator="@android:interpolator/launch_task_micro_ydelta" />
     <alpha android:fromAlpha="0.0" android:toAlpha="1.0"
diff --git a/core/res/res/layout/resolver_list.xml b/core/res/res/layout/resolver_list.xml
index 4b8640c..fe43e1c 100644
--- a/core/res/res/layout/resolver_list.xml
+++ b/core/res/res/layout/resolver_list.xml
@@ -30,33 +30,37 @@
         android:layout_height="wrap_content"
         android:layout_alwaysShow="true"
         android:elevation="8dp"
-        android:background="@color/white" >
-        <TextView android:id="@+id/profile_button"
-                  android:layout_width="wrap_content"
-                  android:layout_height="48dp"
-                  android:layout_marginEnd="8dp"
-                  android:paddingStart="8dp"
-                  android:paddingEnd="8dp"
-                  android:visibility="gone"
-                  style="?attr/borderlessButtonStyle"
-                  android:textAppearance="?attr/textAppearanceButton"
-                  android:textColor="@color/material_deep_teal_500"
-                  android:gravity="center_vertical"
-                  android:layout_alignParentTop="true"
-                  android:layout_alignParentRight="true"
-                  android:singleLine="true"/>
-        <TextView android:id="@+id/title"
-                  android:layout_width="wrap_content"
-                  android:layout_height="wrap_content"
-                  android:minHeight="56dp"
-                  android:textAppearance="?attr/textAppearanceMedium"
-                  android:gravity="start|center_vertical"
-                  android:paddingStart="?attr/dialogPreferredPadding"
-                  android:paddingEnd="?attr/dialogPreferredPadding"
-                  android:paddingTop="8dp"
-                  android:layout_below="@id/profile_button"
-                  android:layout_alignParentLeft="true"
-                  android:paddingBottom="8dp" />
+        android:background="@color/white">
+
+        <TextView
+            android:id="@+id/profile_button"
+            android:layout_width="wrap_content"
+            android:layout_height="48dp"
+            android:layout_marginEnd="8dp"
+            android:paddingStart="8dp"
+            android:paddingEnd="8dp"
+            android:visibility="gone"
+            style="?attr/borderlessButtonStyle"
+            android:textAppearance="?attr/textAppearanceButton"
+            android:textColor="@color/material_deep_teal_500"
+            android:gravity="center_vertical"
+            android:layout_alignParentTop="true"
+            android:layout_alignParentRight="true"
+            android:singleLine="true" />
+
+        <TextView
+            android:id="@+id/title"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:minHeight="56dp"
+            android:textAppearance="?attr/textAppearanceMedium"
+            android:gravity="start|center_vertical"
+            android:paddingStart="?attr/dialogPreferredPadding"
+            android:paddingEnd="?attr/dialogPreferredPadding"
+            android:paddingTop="8dp"
+            android:layout_below="@id/profile_button"
+            android:layout_alignParentLeft="true"
+            android:paddingBottom="8dp" />
     </RelativeLayout>
 
     <ListView
@@ -68,21 +72,23 @@
         android:background="@color/white"
         android:elevation="8dp"
         android:nestedScrollingEnabled="true"
+        android:scrollIndicators="top|bottom"
         android:divider="@null" />
 
-    <TextView android:id="@+id/empty"
-              android:layout_width="match_parent"
-              android:layout_height="match_parent"
-              android:layout_alwaysShow="true"
-              android:text="@string/noApplications"
-              android:padding="32dp"
-              android:gravity="center"
-              android:visibility="gone" />
+    <TextView
+        android:id="@+id/empty"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_alwaysShow="true"
+        android:text="@string/noApplications"
+        android:padding="32dp"
+        android:gravity="center"
+        android:visibility="gone" />
 
     <LinearLayout
         android:id="@+id/button_bar"
         android:visibility="gone"
-        style="?android:attr/buttonBarStyle"
+        style="?attr/buttonBarStyle"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:layout_ignoreOffset="true"
@@ -97,26 +103,30 @@
         android:paddingStart="12dp"
         android:paddingEnd="12dp"
         android:elevation="8dp">
-        <Button android:id="@+id/button_once"
-                android:layout_width="wrap_content"
-                android:layout_gravity="start"
-                android:maxLines="2"
-                style="?android:attr/buttonBarNegativeButtonStyle"
-                android:minHeight="@dimen/alert_dialog_button_bar_height"
-                android:layout_height="wrap_content"
-                android:enabled="false"
-                android:text="@string/activity_resolver_use_once"
-                android:onClick="onButtonClick" />
-        <Button android:id="@+id/button_always"
-                android:layout_width="wrap_content"
-                android:layout_gravity="end"
-                android:maxLines="2"
-                android:minHeight="@dimen/alert_dialog_button_bar_height"
-                style="?android:attr/buttonBarPositiveButtonStyle"
-                android:layout_height="wrap_content"
-                android:enabled="false"
-                android:text="@string/activity_resolver_use_always"
-                android:onClick="onButtonClick" />
+
+        <Button
+            android:id="@+id/button_once"
+            android:layout_width="wrap_content"
+            android:layout_gravity="start"
+            android:maxLines="2"
+            style="?attr/buttonBarNegativeButtonStyle"
+            android:minHeight="@dimen/alert_dialog_button_bar_height"
+            android:layout_height="wrap_content"
+            android:enabled="false"
+            android:text="@string/activity_resolver_use_once"
+            android:onClick="onButtonClick" />
+
+        <Button
+            android:id="@+id/button_always"
+            android:layout_width="wrap_content"
+            android:layout_gravity="end"
+            android:maxLines="2"
+            android:minHeight="@dimen/alert_dialog_button_bar_height"
+            style="?attr/buttonBarPositiveButtonStyle"
+            android:layout_height="wrap_content"
+            android:enabled="false"
+            android:text="@string/activity_resolver_use_always"
+            android:onClick="onButtonClick" />
     </LinearLayout>
 
 </com.android.internal.widget.ResolverDrawerLayout>
diff --git a/core/res/res/layout/resolver_list_with_default.xml b/core/res/res/layout/resolver_list_with_default.xml
index 31361e5..ed7ef5e 100644
--- a/core/res/res/layout/resolver_list_with_default.xml
+++ b/core/res/res/layout/resolver_list_with_default.xml
@@ -22,8 +22,7 @@
     android:layout_height="match_parent"
     android:maxWidth="@dimen/resolver_max_width"
     android:maxCollapsedHeight="144dp"
-    android:id="@id/contentPanel"
-    >
+    android:id="@id/contentPanel">
 
     <LinearLayout
         android:layout_width="match_parent"
@@ -31,66 +30,75 @@
         android:layout_alwaysShow="true"
         android:orientation="vertical"
         android:background="@color/white"
-        android:elevation="8dp" >
+        android:elevation="8dp">
 
         <LinearLayout
             android:layout_width="match_parent"
             android:layout_height="64dp"
-            android:orientation="horizontal" >
+            android:orientation="horizontal">
 
-            <ImageView android:id="@+id/icon"
-                       android:layout_width="24dp"
-                       android:layout_height="24dp"
-                       android:layout_gravity="start|top"
-                       android:layout_marginStart="16dp"
-                       android:layout_marginEnd="16dp"
-                       android:layout_marginTop="20dp"
-                       android:scaleType="fitCenter" />
-            <TextView android:id="@+id/title"
-                      android:layout_width="0dp"
-                      android:layout_weight="1"
-                      android:layout_height="?android:attr/listPreferredItemHeight"
-                      android:layout_marginStart="16dp"
-                      android:textAppearance="?android:attr/textAppearanceMedium"
-                      android:gravity="start|center_vertical"
-                      android:paddingEnd="16dp" />
-            <LinearLayout android:id="@+id/profile_button"
-                          android:layout_width="wrap_content"
-                          android:layout_height="48dp"
-                          android:layout_marginTop="4dp"
-                          android:layout_marginEnd="4dp"
-                          android:paddingStart="8dp"
-                          android:paddingEnd="8dp"
-                          android:paddingTop="4dp"
-                          android:paddingBottom="4dp"
-                          android:focusable="true"
-                          android:visibility="gone"
-                          style="?attr/borderlessButtonStyle">
-                <ImageView android:id="@+id/icon"
-                           android:layout_width="24dp"
-                           android:layout_height="24dp"
-                           android:layout_gravity="start|center_vertical"
-                           android:layout_marginEnd="?attr/listPreferredItemPaddingEnd"
-                           android:layout_marginTop="12dp"
-                           android:layout_marginBottom="12dp"
-                           android:scaleType="fitCenter" />
-                <TextView android:id="@id/text1"
-                          android:layout_width="wrap_content"
-                          android:layout_height="wrap_content"
-                          android:layout_gravity="start|center_vertical"
-                          android:layout_marginEnd="?attr/listPreferredItemPaddingEnd"
-                          android:textAppearance="?attr/textAppearanceButton"
-                          android:textColor="?attr/textColorPrimary"
-                          android:minLines="1"
-                          android:maxLines="1"
-                          android:ellipsize="marquee" />
+            <ImageView
+                android:id="@+id/icon"
+                android:layout_width="24dp"
+                android:layout_height="24dp"
+                android:layout_gravity="start|top"
+                android:layout_marginStart="16dp"
+                android:layout_marginEnd="16dp"
+                android:layout_marginTop="20dp"
+                android:scaleType="fitCenter" />
+
+            <TextView
+                android:id="@+id/title"
+                android:layout_width="0dp"
+                android:layout_weight="1"
+                android:layout_height="?attr/listPreferredItemHeight"
+                android:layout_marginStart="16dp"
+                android:textAppearance="?attr/textAppearanceMedium"
+                android:gravity="start|center_vertical"
+                android:paddingEnd="16dp" />
+
+            <LinearLayout
+                android:id="@+id/profile_button"
+                android:layout_width="wrap_content"
+                android:layout_height="48dp"
+                android:layout_marginTop="4dp"
+                android:layout_marginEnd="4dp"
+                android:paddingStart="8dp"
+                android:paddingEnd="8dp"
+                android:paddingTop="4dp"
+                android:paddingBottom="4dp"
+                android:focusable="true"
+                android:visibility="gone"
+                style="?attr/borderlessButtonStyle">
+
+                <ImageView
+                    android:id="@+id/icon"
+                    android:layout_width="24dp"
+                    android:layout_height="24dp"
+                    android:layout_gravity="start|center_vertical"
+                    android:layout_marginEnd="?attr/listPreferredItemPaddingEnd"
+                    android:layout_marginTop="12dp"
+                    android:layout_marginBottom="12dp"
+                    android:scaleType="fitCenter" />
+
+                <TextView
+                    android:id="@id/text1"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_gravity="start|center_vertical"
+                    android:layout_marginEnd="?attr/listPreferredItemPaddingEnd"
+                    android:textAppearance="?attr/textAppearanceButton"
+                    android:textColor="?attr/textColorPrimary"
+                    android:minLines="1"
+                    android:maxLines="1"
+                    android:ellipsize="marquee" />
             </LinearLayout>
         </LinearLayout>
 
         <LinearLayout
             android:id="@+id/button_bar"
             android:visibility="gone"
-            style="?android:attr/buttonBarStyle"
+            style="?attr/buttonBarStyle"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:layout_alwaysShow="true"
@@ -104,30 +112,36 @@
             android:paddingEnd="12dp"
             android:background="@color/white"
             android:elevation="8dp">
-            <Button android:id="@+id/button_once"
-                    android:layout_width="wrap_content"
-                    android:layout_gravity="start"
-                    android:maxLines="2"
-                    style="?android:attr/buttonBarNegativeButtonStyle"
-                    android:minHeight="@dimen/alert_dialog_button_bar_height"
-                    android:layout_height="wrap_content"
-                    android:enabled="false"
-                    android:text="@string/activity_resolver_use_once"
-                    android:onClick="onButtonClick" />
-            <Button android:id="@+id/button_always"
-                    android:layout_width="wrap_content"
-                    android:layout_gravity="end"
-                    android:maxLines="2"
-                    android:minHeight="@dimen/alert_dialog_button_bar_height"
-                    style="?android:attr/buttonBarPositiveButtonStyle"
-                    android:layout_height="wrap_content"
-                    android:enabled="false"
-                    android:text="@string/activity_resolver_use_always"
-                    android:onClick="onButtonClick" />
+
+            <Button
+                android:id="@+id/button_once"
+                android:layout_width="wrap_content"
+                android:layout_gravity="start"
+                android:maxLines="2"
+                style="?attr/buttonBarNegativeButtonStyle"
+                android:minHeight="@dimen/alert_dialog_button_bar_height"
+                android:layout_height="wrap_content"
+                android:enabled="false"
+                android:text="@string/activity_resolver_use_once"
+                android:onClick="onButtonClick" />
+
+            <Button
+                android:id="@+id/button_always"
+                android:layout_width="wrap_content"
+                android:layout_gravity="end"
+                android:maxLines="2"
+                android:minHeight="@dimen/alert_dialog_button_bar_height"
+                style="?attr/buttonBarPositiveButtonStyle"
+                android:layout_height="wrap_content"
+                android:enabled="false"
+                android:text="@string/activity_resolver_use_always"
+                android:onClick="onButtonClick" />
         </LinearLayout>
-        <View android:layout_width="match_parent"
-              android:layout_height="1dp"
-              android:background="?android:attr/dividerVertical" />
+
+        <View
+            android:layout_width="match_parent"
+            android:layout_height="1dp"
+            android:background="?attr/dividerVertical" />
     </LinearLayout>
 
     <ListView
@@ -140,6 +154,6 @@
         android:elevation="8dp"
         android:nestedScrollingEnabled="true"
         android:divider="@null"
-        />
+        android:scrollIndicators="top|bottom" />
 
 </com.android.internal.widget.ResolverDrawerLayout>
diff --git a/packages/Keyguard/res/drawable/ic_backspace_24dp.xml b/packages/Keyguard/res/drawable/ic_backspace_24dp.xml
index 47c8d14..1e4022e 100644
--- a/packages/Keyguard/res/drawable/ic_backspace_24dp.xml
+++ b/packages/Keyguard/res/drawable/ic_backspace_24dp.xml
@@ -15,6 +15,7 @@
 -->
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
         android:width="24dp"
+        android:autoMirrored="true"
         android:height="24dp"
         android:viewportWidth="48.0"
         android:viewportHeight="48.0">
diff --git a/packages/Keyguard/res/values/attrs.xml b/packages/Keyguard/res/values/attrs.xml
index 96a5bcc..7cfe631 100644
--- a/packages/Keyguard/res/values/attrs.xml
+++ b/packages/Keyguard/res/values/attrs.xml
@@ -32,6 +32,9 @@
 
     <declare-styleable name="PasswordTextView">
         <attr name="scaledTextSize" format="integer" />
+        <attr name="android:gravity" />
+        <attr name="dotSize" format="dimension" />
+        <attr name="charPadding" format="dimension" />
     </declare-styleable>
 
     <declare-styleable name="CarrierText">
diff --git a/packages/Keyguard/src/com/android/keyguard/NumPadKey.java b/packages/Keyguard/src/com/android/keyguard/NumPadKey.java
index ef8bb0b..2ff7e12 100644
--- a/packages/Keyguard/src/com/android/keyguard/NumPadKey.java
+++ b/packages/Keyguard/src/com/android/keyguard/NumPadKey.java
@@ -71,6 +71,10 @@
     }
 
     public NumPadKey(Context context, AttributeSet attrs, int defStyle) {
+        this(context, attrs, defStyle, R.layout.keyguard_num_pad_key);
+    }
+
+    protected NumPadKey(Context context, AttributeSet attrs, int defStyle, int contentResource) {
         super(context, attrs, defStyle);
         setFocusable(true);
 
@@ -92,7 +96,7 @@
         mPM = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
         LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(
                 Context.LAYOUT_INFLATER_SERVICE);
-        inflater.inflate(R.layout.keyguard_num_pad_key, this, true);
+        inflater.inflate(contentResource, this, true);
 
         mDigitText = (TextView) findViewById(R.id.digit_text);
         mDigitText.setText(Integer.toString(mDigit));
@@ -113,7 +117,11 @@
             }
         }
 
-        setBackground(mContext.getDrawable(R.drawable.ripple_drawable));
+        a = context.obtainStyledAttributes(attrs, android.R.styleable.View);
+        if (!a.hasValueOrEmpty(android.R.styleable.View_background)) {
+            setBackground(mContext.getDrawable(R.drawable.ripple_drawable));
+        }
+        a.recycle();
         setContentDescription(mDigitText.getText().toString());
     }
 
diff --git a/packages/Keyguard/src/com/android/keyguard/PasswordTextView.java b/packages/Keyguard/src/com/android/keyguard/PasswordTextView.java
index 50e7ecb..6eea81b 100644
--- a/packages/Keyguard/src/com/android/keyguard/PasswordTextView.java
+++ b/packages/Keyguard/src/com/android/keyguard/PasswordTextView.java
@@ -33,6 +33,7 @@
 import android.text.InputType;
 import android.text.TextUtils;
 import android.util.AttributeSet;
+import android.view.Gravity;
 import android.view.View;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityManager;
@@ -81,6 +82,7 @@
      * The raw text size, will be multiplied by the scaled density when drawn
      */
     private final int mTextHeightRaw;
+    private final int mGravity;
     private ArrayList<CharState> mTextChars = new ArrayList<>();
     private String mText = "";
     private Stack<CharState> mCharPool = new Stack<>();
@@ -118,6 +120,12 @@
         TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.PasswordTextView);
         try {
             mTextHeightRaw = a.getInt(R.styleable.PasswordTextView_scaledTextSize, 0);
+            mGravity = a.getInt(R.styleable.PasswordTextView_android_gravity, Gravity.CENTER);
+            mDotSize = a.getDimensionPixelSize(R.styleable.PasswordTextView_dotSize,
+                    getContext().getResources().getDimensionPixelSize(R.dimen.password_dot_size));
+            mCharPadding = a.getDimensionPixelSize(R.styleable.PasswordTextView_charPadding,
+                    getContext().getResources().getDimensionPixelSize(
+                            R.dimen.password_char_padding));
         } finally {
             a.recycle();
         }
@@ -125,9 +133,6 @@
         mDrawPaint.setTextAlign(Paint.Align.CENTER);
         mDrawPaint.setColor(0xffffffff);
         mDrawPaint.setTypeface(Typeface.create("sans-serif-light", 0));
-        mDotSize = getContext().getResources().getDimensionPixelSize(R.dimen.password_dot_size);
-        mCharPadding = getContext().getResources().getDimensionPixelSize(R.dimen
-                .password_char_padding);
         mShowPassword = Settings.System.getInt(mContext.getContentResolver(),
                 Settings.System.TEXT_SHOW_PASSWORD, 1) == 1;
         mAppearInterpolator = AnimationUtils.loadInterpolator(mContext,
@@ -142,11 +147,23 @@
     @Override
     protected void onDraw(Canvas canvas) {
         float totalDrawingWidth = getDrawingWidth();
-        float currentDrawPosition = getWidth() / 2 - totalDrawingWidth / 2;
+        float currentDrawPosition;
+        if ((mGravity & Gravity.START) != 0) {
+            if (getLayoutDirection() == LAYOUT_DIRECTION_RTL) {
+                currentDrawPosition = getWidth() - getPaddingRight() - totalDrawingWidth;
+            } else {
+                currentDrawPosition = getPaddingLeft();
+            }
+        } else {
+            currentDrawPosition = getWidth() / 2 - totalDrawingWidth / 2;
+        }
         int length = mTextChars.size();
         Rect bounds = getCharBounds();
         int charHeight = (bounds.bottom - bounds.top);
-        float yPosition = getHeight() / 2;
+        float yPosition =
+                (getHeight() - getPaddingBottom() - getPaddingTop()) / 2 + getPaddingTop();
+        canvas.clipRect(getPaddingLeft(), getPaddingTop(),
+                getWidth()-getPaddingRight(), getHeight()-getPaddingBottom());
         float charLength = bounds.right - bounds.left;
         for (int i = 0; i < length; i++) {
             CharState charState = mTextChars.get(i);
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index 3b0b79a..a111bf9 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -264,7 +264,12 @@
             return true;
         }
 
-        // Check if the caller is authorized.
+        // Stop an existing always-on VPN from being dethroned by other apps.
+        if (getAlwaysOnPackage() != null) {
+            return false;
+        }
+
+        // Check that the caller is authorized.
         enforceControlPermission();
 
         prepareInternal(newPackage);
diff --git a/services/core/java/com/android/server/content/ContentService.java b/services/core/java/com/android/server/content/ContentService.java
index e9d9628..6e7ea99 100644
--- a/services/core/java/com/android/server/content/ContentService.java
+++ b/services/core/java/com/android/server/content/ContentService.java
@@ -73,7 +73,8 @@
  * {@hide}
  */
 public final class ContentService extends IContentService.Stub {
-    private static final String TAG = "ContentService";
+    static final String TAG = "ContentService";
+    static final boolean DEBUG = false;
 
     public static class Lifecycle extends SystemService {
         private ContentService mContentService;
@@ -339,12 +340,10 @@
      */
     @Override
     public void notifyChange(Uri uri, IContentObserver observer,
-                             boolean observerWantsSelfNotifications, boolean syncToNetwork,
+                             boolean observerWantsSelfNotifications, int flags,
                              int userHandle) {
-        if (Log.isLoggable(TAG, Log.VERBOSE)) {
-            Log.v(TAG, "Notifying update of " + uri + " for user " + userHandle
-                    + " from observer " + observer + ", syncToNetwork " + syncToNetwork);
-        }
+        if (DEBUG) Slog.d(TAG, "Notifying update of " + uri + " for user " + userHandle
+                + " from observer " + observer + ", flags " + Integer.toHexString(flags));
 
         final int uid = Binder.getCallingUid();
         final int pid = Binder.getCallingPid();
@@ -373,16 +372,15 @@
             ArrayList<ObserverCall> calls = new ArrayList<ObserverCall>();
             synchronized (mRootNode) {
                 mRootNode.collectObserversLocked(uri, 0, observer, observerWantsSelfNotifications,
-                        userHandle, calls);
+                        flags, userHandle, calls);
             }
             final int numCalls = calls.size();
             for (int i=0; i<numCalls; i++) {
                 ObserverCall oc = calls.get(i);
                 try {
                     oc.mObserver.onChange(oc.mSelfChange, uri, userHandle);
-                    if (Log.isLoggable(TAG, Log.VERBOSE)) {
-                        Log.v(TAG, "Notified " + oc.mObserver + " of " + "update at " + uri);
-                    }
+                    if (DEBUG) Slog.d(TAG, "Notified " + oc.mObserver + " of " + "update at "
+                            + uri);
                 } catch (RemoteException ex) {
                     synchronized (mRootNode) {
                         Log.w(TAG, "Found dead observer, removing");
@@ -401,7 +399,7 @@
                     }
                 }
             }
-            if (syncToNetwork) {
+            if ((flags&ContentResolver.NOTIFY_SYNC_TO_NETWORK) != 0) {
                 SyncManager syncManager = getSyncManager();
                 if (syncManager != null) {
                     syncManager.scheduleLocalSync(null /* all accounts */, callingUserHandle, uid,
@@ -420,7 +418,8 @@
 
     public void notifyChange(Uri uri, IContentObserver observer,
                              boolean observerWantsSelfNotifications, boolean syncToNetwork) {
-        notifyChange(uri, observer, observerWantsSelfNotifications, syncToNetwork,
+        notifyChange(uri, observer, observerWantsSelfNotifications,
+                syncToNetwork ? ContentResolver.NOTIFY_SYNC_TO_NETWORK : 0,
                 UserHandle.getCallingUserId());
     }
 
@@ -1064,14 +1063,14 @@
             for (int i = 0; i < packageCache.size();) {
                 final Pair<String, Uri> key = packageCache.keyAt(i);
                 if (key.second != null && key.second.toString().startsWith(uri.toString())) {
-                    Slog.d(TAG, "Invalidating cache for key " + key);
+                    if (DEBUG) Slog.d(TAG, "Invalidating cache for key " + key);
                     packageCache.removeAt(i);
                 } else {
                     i++;
                 }
             }
         } else {
-            Slog.d(TAG, "Invalidating cache for package " + providerPackageName);
+            if (DEBUG) Slog.d(TAG, "Invalidating cache for package " + providerPackageName);
             packageCache.clear();
         }
     }
@@ -1310,8 +1309,8 @@
         }
 
         private void collectMyObserversLocked(boolean leaf, IContentObserver observer,
-                                              boolean observerWantsSelfNotifications, int targetUserHandle,
-                                              ArrayList<ObserverCall> calls) {
+                                              boolean observerWantsSelfNotifications, int flags,
+                                              int targetUserHandle, ArrayList<ObserverCall> calls) {
             int N = mObservers.size();
             IBinder observerBinder = observer == null ? null : observer.asBinder();
             for (int i = 0; i < N; i++) {
@@ -1329,9 +1328,29 @@
                         || entry.userHandle == UserHandle.USER_ALL
                         || targetUserHandle == entry.userHandle) {
                     // Make sure the observer is interested in the notification
-                    if (leaf || (!leaf && entry.notifyForDescendants)) {
-                        calls.add(new ObserverCall(this, entry.observer, selfChange));
+                    if (leaf) {
+                        // If we are at the leaf: we always report, unless the sender has asked
+                        // to skip observers that are notifying for descendants (since they will
+                        // be sending another more specific URI for them).
+                        if ((flags&ContentResolver.NOTIFY_SKIP_NOTIFY_FOR_DESCENDANTS) != 0
+                                && entry.notifyForDescendants) {
+                            if (DEBUG) Slog.d(TAG, "Skipping " + entry.observer
+                                    + ": skip notify for descendants");
+                            continue;
+                        }
+                    } else {
+                        // If we are not at the leaf: we report if the observer says it wants
+                        // to be notified for all descendants.
+                        if (!entry.notifyForDescendants) {
+                            if (DEBUG) Slog.d(TAG, "Skipping " + entry.observer
+                                    + ": not monitor descendants");
+                            continue;
+                        }
                     }
+                    if (DEBUG) Slog.d(TAG, "Reporting to " + entry.observer + ": leaf=" + leaf
+                            + " flags=" + Integer.toHexString(flags)
+                            + " desc=" + entry.notifyForDescendants);
+                    calls.add(new ObserverCall(this, entry.observer, selfChange));
                 }
             }
         }
@@ -1340,19 +1359,22 @@
          * targetUserHandle is either a hard user handle or is USER_ALL
          */
         public void collectObserversLocked(Uri uri, int index, IContentObserver observer,
-                                           boolean observerWantsSelfNotifications, int targetUserHandle,
-                                           ArrayList<ObserverCall> calls) {
+                                           boolean observerWantsSelfNotifications, int flags,
+                                           int targetUserHandle, ArrayList<ObserverCall> calls) {
             String segment = null;
             int segmentCount = countUriSegments(uri);
             if (index >= segmentCount) {
                 // This is the leaf node, notify all observers
+                if (DEBUG) Slog.d(TAG, "Collecting leaf observers @ #" + index + ", node " + mName);
                 collectMyObserversLocked(true, observer, observerWantsSelfNotifications,
-                        targetUserHandle, calls);
+                        flags, targetUserHandle, calls);
             } else if (index < segmentCount){
                 segment = getUriSegment(uri, index);
+                if (DEBUG) Slog.d(TAG, "Collecting non-leaf observers @ #" + index + " / "
+                        + segment);
                 // Notify any observers at this level who are interested in descendants
                 collectMyObserversLocked(false, observer, observerWantsSelfNotifications,
-                        targetUserHandle, calls);
+                        flags, targetUserHandle, calls);
             }
 
             int N = mChildren.size();
@@ -1360,8 +1382,8 @@
                 ObserverNode node = mChildren.get(i);
                 if (segment == null || node.mName.equals(segment)) {
                     // We found the child,
-                    node.collectObserversLocked(uri, index + 1,
-                            observer, observerWantsSelfNotifications, targetUserHandle, calls);
+                    node.collectObserversLocked(uri, index + 1, observer,
+                            observerWantsSelfNotifications, flags, targetUserHandle, calls);
                     if (segment != null) {
                         break;
                     }
diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java
index fa8620f..b235002 100644
--- a/services/core/java/com/android/server/job/JobSchedulerService.java
+++ b/services/core/java/com/android/server/job/JobSchedulerService.java
@@ -304,7 +304,7 @@
 
             toCancel = mJobs.getJobByUidAndJobId(uId, job.getId());
             if (toCancel != null) {
-                cancelJobImpl(toCancel);
+                cancelJobImpl(toCancel, jobStatus);
             }
             startTrackingJob(jobStatus, toCancel);
         }
@@ -331,7 +331,7 @@
         }
         for (int i=0; i<jobsForUser.size(); i++) {
             JobStatus toRemove = jobsForUser.get(i);
-            cancelJobImpl(toRemove);
+            cancelJobImpl(toRemove, null);
         }
     }
 
@@ -360,7 +360,7 @@
                 } catch (RemoteException e) {
                 }
             }
-            cancelJobImpl(toRemove);
+            cancelJobImpl(toRemove, null);
         }
     }
 
@@ -377,13 +377,13 @@
             toCancel = mJobs.getJobByUidAndJobId(uid, jobId);
         }
         if (toCancel != null) {
-            cancelJobImpl(toCancel);
+            cancelJobImpl(toCancel, null);
         }
     }
 
-    private void cancelJobImpl(JobStatus cancelled) {
+    private void cancelJobImpl(JobStatus cancelled, JobStatus incomingJob) {
         if (DEBUG) Slog.d(TAG, "CANCEL: " + cancelled.toShortString());
-        stopTrackingJob(cancelled, true /* writeBack */);
+        stopTrackingJob(cancelled, incomingJob, true /* writeBack */);
         synchronized (mLock) {
             // Remove from pending queue.
             mPendingJobs.remove(cancelled);
@@ -549,7 +549,7 @@
                 for (int i = 0; i < mControllers.size(); i++) {
                     StateController controller = mControllers.get(i);
                     if (update) {
-                        controller.maybeStopTrackingJobLocked(jobStatus, true);
+                        controller.maybeStopTrackingJobLocked(jobStatus, null, true);
                     }
                     controller.maybeStartTrackingJobLocked(jobStatus, lastJob);
                 }
@@ -561,14 +561,15 @@
      * Called when we want to remove a JobStatus object that we've finished executing. Returns the
      * object removed.
      */
-    private boolean stopTrackingJob(JobStatus jobStatus, boolean writeBack) {
+    private boolean stopTrackingJob(JobStatus jobStatus, JobStatus incomingJob,
+            boolean writeBack) {
         synchronized (mLock) {
             // Remove from store as well as controllers.
             final boolean removed = mJobs.remove(jobStatus, writeBack);
             if (removed && mReadyToRock) {
                 for (int i=0; i<mControllers.size(); i++) {
                     StateController controller = mControllers.get(i);
-                    controller.maybeStopTrackingJobLocked(jobStatus, false);
+                    controller.maybeStopTrackingJobLocked(jobStatus, incomingJob, false);
                 }
             }
             return removed;
@@ -696,7 +697,7 @@
         }
         // Do not write back immediately if this is a periodic job. The job may get lost if system
         // shuts down before it is added back.
-        if (!stopTrackingJob(jobStatus, !jobStatus.getJob().isPeriodic())) {
+        if (!stopTrackingJob(jobStatus, null, !jobStatus.getJob().isPeriodic())) {
             if (DEBUG) {
                 Slog.d(TAG, "Could not find job to remove. Was job removed while executing?");
             }
@@ -780,7 +781,7 @@
                     }
                     break;
                 case MSG_STOP_JOB:
-                    cancelJobImpl((JobStatus)message.obj);
+                    cancelJobImpl((JobStatus)message.obj, null);
                     break;
             }
             maybeRunPendingJobsH();
diff --git a/services/core/java/com/android/server/job/controllers/AppIdleController.java b/services/core/java/com/android/server/job/controllers/AppIdleController.java
index 2114fc3..d8490d4 100644
--- a/services/core/java/com/android/server/job/controllers/AppIdleController.java
+++ b/services/core/java/com/android/server/job/controllers/AppIdleController.java
@@ -77,7 +77,7 @@
     }
 
     @Override
-    public void maybeStopTrackingJobLocked(JobStatus jobStatus, boolean forUpdate) {
+    public void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob, boolean forUpdate) {
         mTrackedTasks.remove(jobStatus);
     }
 
diff --git a/services/core/java/com/android/server/job/controllers/BatteryController.java b/services/core/java/com/android/server/job/controllers/BatteryController.java
index ac9f425..0772364 100644
--- a/services/core/java/com/android/server/job/controllers/BatteryController.java
+++ b/services/core/java/com/android/server/job/controllers/BatteryController.java
@@ -87,7 +87,7 @@
     }
 
     @Override
-    public void maybeStopTrackingJobLocked(JobStatus taskStatus, boolean forUpdate) {
+    public void maybeStopTrackingJobLocked(JobStatus taskStatus, JobStatus incomingJob, boolean forUpdate) {
         if (taskStatus.hasChargingConstraint()) {
             mTrackedTasks.remove(taskStatus);
         }
diff --git a/services/core/java/com/android/server/job/controllers/ConnectivityController.java b/services/core/java/com/android/server/job/controllers/ConnectivityController.java
index 6ef425a..5ad8189 100644
--- a/services/core/java/com/android/server/job/controllers/ConnectivityController.java
+++ b/services/core/java/com/android/server/job/controllers/ConnectivityController.java
@@ -92,7 +92,7 @@
     }
 
     @Override
-    public void maybeStopTrackingJobLocked(JobStatus jobStatus, boolean forUpdate) {
+    public void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob, boolean forUpdate) {
         if (jobStatus.hasConnectivityConstraint() || jobStatus.hasUnmeteredConstraint()) {
             mTrackedJobs.remove(jobStatus);
         }
diff --git a/services/core/java/com/android/server/job/controllers/ContentObserverController.java b/services/core/java/com/android/server/job/controllers/ContentObserverController.java
index c5cf30f..b2f1958 100644
--- a/services/core/java/com/android/server/job/controllers/ContentObserverController.java
+++ b/services/core/java/com/android/server/job/controllers/ContentObserverController.java
@@ -84,17 +84,8 @@
             boolean havePendingUris = false;
             // If there is a previous job associated with the new job, propagate over
             // any pending content URI trigger reports.
-            if (lastJob != null && lastJob.contentObserverJobInstance != null
-                    && lastJob.contentObserverJobInstance
-                    != taskStatus.contentObserverJobInstance
-                    && lastJob.contentObserverJobInstance.mChangedAuthorities != null) {
+            if (taskStatus.contentObserverJobInstance.mChangedAuthorities != null) {
                 havePendingUris = true;
-                taskStatus.contentObserverJobInstance.mChangedAuthorities
-                        = lastJob.contentObserverJobInstance.mChangedAuthorities;
-                taskStatus.contentObserverJobInstance.mChangedUris
-                        = lastJob.contentObserverJobInstance.mChangedUris;
-                lastJob.contentObserverJobInstance.mChangedAuthorities = null;
-                lastJob.contentObserverJobInstance.mChangedUris = null;
             }
             // If we have previously reported changed authorities/uris, then we failed
             // to complete the job with them so will re-record them to report again.
@@ -138,15 +129,34 @@
     }
 
     @Override
-    public void maybeStopTrackingJobLocked(JobStatus taskStatus, boolean forUpdate) {
+    public void maybeStopTrackingJobLocked(JobStatus taskStatus, JobStatus incomingJob,
+            boolean forUpdate) {
         if (taskStatus.hasContentTriggerConstraint()) {
-            if (!forUpdate) {
-                // We won't do this reset if being called for an update, because
-                // we know it will be immediately followed by maybeStartTrackingJobLocked...
-                // and we don't want to lose any content changes in-between.
-                if (taskStatus.contentObserverJobInstance != null) {
-                    taskStatus.contentObserverJobInstance.detach();
-                    taskStatus.contentObserverJobInstance = null;
+            if (taskStatus.contentObserverJobInstance != null) {
+                if (incomingJob != null && taskStatus.contentObserverJobInstance != null
+                        && taskStatus.contentObserverJobInstance.mChangedAuthorities != null) {
+                    // We are stopping this job, but it is going to be replaced by this given
+                    // incoming job.  We want to propagate our state over to it, so we don't
+                    // lose any content changes that had happend since the last one started.
+                    // If there is a previous job associated with the new job, propagate over
+                    // any pending content URI trigger reports.
+                    if (incomingJob.contentObserverJobInstance == null) {
+                        incomingJob.contentObserverJobInstance = new JobInstance(incomingJob);
+                    }
+                    incomingJob.contentObserverJobInstance.mChangedAuthorities
+                            = taskStatus.contentObserverJobInstance.mChangedAuthorities;
+                    incomingJob.contentObserverJobInstance.mChangedUris
+                            = taskStatus.contentObserverJobInstance.mChangedUris;
+                    taskStatus.contentObserverJobInstance.mChangedAuthorities = null;
+                    taskStatus.contentObserverJobInstance.mChangedUris = null;
+                } else {
+                    // We won't do this reset if being called for an update, because
+                    // we know it will be immediately followed by maybeStartTrackingJobLocked...
+                    // and we don't want to lose any content changes in-between.
+                    if (taskStatus.contentObserverJobInstance != null) {
+                        taskStatus.contentObserverJobInstance.detach();
+                        taskStatus.contentObserverJobInstance = null;
+                    }
                 }
             }
             mTrackedTasks.remove(taskStatus);
diff --git a/services/core/java/com/android/server/job/controllers/DeviceIdleJobsController.java b/services/core/java/com/android/server/job/controllers/DeviceIdleJobsController.java
index d2ef6a2..64887e8 100644
--- a/services/core/java/com/android/server/job/controllers/DeviceIdleJobsController.java
+++ b/services/core/java/com/android/server/job/controllers/DeviceIdleJobsController.java
@@ -166,7 +166,7 @@
     }
 
     @Override
-    public void maybeStopTrackingJobLocked(JobStatus jobStatus, boolean forUpdate) {
+    public void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob, boolean forUpdate) {
         mTrackedTasks.remove(jobStatus);
     }
 
diff --git a/services/core/java/com/android/server/job/controllers/IdleController.java b/services/core/java/com/android/server/job/controllers/IdleController.java
index d9eb45c..50aa882 100644
--- a/services/core/java/com/android/server/job/controllers/IdleController.java
+++ b/services/core/java/com/android/server/job/controllers/IdleController.java
@@ -75,7 +75,7 @@
     }
 
     @Override
-    public void maybeStopTrackingJobLocked(JobStatus taskStatus, boolean forUpdate) {
+    public void maybeStopTrackingJobLocked(JobStatus taskStatus, JobStatus incomingJob, boolean forUpdate) {
         mTrackedTasks.remove(taskStatus);
     }
 
diff --git a/services/core/java/com/android/server/job/controllers/StateController.java b/services/core/java/com/android/server/job/controllers/StateController.java
index ac7f4c3f..0139039 100644
--- a/services/core/java/com/android/server/job/controllers/StateController.java
+++ b/services/core/java/com/android/server/job/controllers/StateController.java
@@ -56,7 +56,8 @@
     /**
      * Remove task - this will happen if the task is cancelled, completed, etc.
      */
-    public abstract void maybeStopTrackingJobLocked(JobStatus jobStatus, boolean forUpdate);
+    public abstract void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob,
+            boolean forUpdate);
     /**
      * Called when a new job is being created to reschedule an old failed job.
      */
diff --git a/services/core/java/com/android/server/job/controllers/TimeController.java b/services/core/java/com/android/server/job/controllers/TimeController.java
index 3543249..ab6768e 100644
--- a/services/core/java/com/android/server/job/controllers/TimeController.java
+++ b/services/core/java/com/android/server/job/controllers/TimeController.java
@@ -74,7 +74,7 @@
     @Override
     public void maybeStartTrackingJobLocked(JobStatus job, JobStatus lastJob) {
         if (job.hasTimingDelayConstraint() || job.hasDeadlineConstraint()) {
-            maybeStopTrackingJobLocked(job, false);
+            maybeStopTrackingJobLocked(job, null, false);
             boolean isInsert = false;
             ListIterator<JobStatus> it = mTrackedJobs.listIterator(mTrackedJobs.size());
             while (it.hasPrevious()) {
@@ -101,7 +101,7 @@
      * Really an == comparison should be enough, but why play with fate? We'll do <=.
      */
     @Override
-    public void maybeStopTrackingJobLocked(JobStatus job, boolean forUpdate) {
+    public void maybeStopTrackingJobLocked(JobStatus job, JobStatus incomingJob, boolean forUpdate) {
         if (mTrackedJobs.remove(job)) {
             checkExpiredDelaysAndResetAlarm();
             checkExpiredDeadlinesAndResetAlarm();
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 25da773..a632bdb 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -2586,7 +2586,7 @@
 
                     try {
 
-                        win.applyGravityAndUpdateFrame();
+                        win.applyGravityAndUpdateFrame(win.mContainingFrame, win.mDisplayFrame);
                         win.mWinAnimator.computeShownFrameLocked();
 
                         win.mWinAnimator.setSurfaceBoundariesLocked(false);
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index c62292d..1eca4d4 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -328,6 +328,8 @@
      */
     final Rect mInsetFrame = new Rect();
 
+    private static final Rect sTmpRect = new Rect();
+
     boolean mContentChanged;
 
     // If a window showing a wallpaper: the requested offset for the
@@ -629,6 +631,20 @@
         return mAttrs.packageName;
     }
 
+    /**
+     * Subtracts the insets calculated by intersecting {@param layoutFrame} with {@param insetFrame}
+     * from {@param frame}. In other words, it applies the insets that would result if
+     * {@param frame} would be shifted to {@param layoutFrame} and then applying the insets from
+     * {@param insetFrame}.
+     */
+    private void subtractInsets(Rect frame, Rect layoutFrame, Rect insetFrame) {
+        final int left = Math.max(0, insetFrame.left - layoutFrame.left);
+        final int top = Math.max(0, insetFrame.top - layoutFrame.top);
+        final int right = Math.max(0, layoutFrame.right - insetFrame.right);
+        final int bottom = Math.max(0, layoutFrame.bottom - insetFrame.bottom);
+        frame.inset(left, top, right, bottom);
+    }
+
     @Override
     public void computeFrameLw(Rect pf, Rect df, Rect of, Rect cf, Rect vf, Rect dcf, Rect sf,
             Rect osf) {
@@ -654,11 +670,25 @@
             task.getTempInsetBounds(mInsetFrame);
         }
 
+        // Denotes the actual frame used to calculate the insets and to perform the layout. When
+        // resizing in docked mode, we'd like to freeze the layout, so we also need to freeze the
+        // insets temporarily. By the notion of a task having a different layout frame, we can
+        // achieve that while still moving the task around.
+        final Rect layoutContainingFrame;
+        final Rect layoutDisplayFrame;
+
+        // The offset from the layout containing frame to the actual containing frame.
+        final int layoutXDiff;
+        final int layoutYDiff;
         if (mInsetFrame.isEmpty()  && (fullscreenTask
                 || layoutInParentFrame())) {
             // We use the parent frame as the containing frame for fullscreen and child windows
             mContainingFrame.set(pf);
             mDisplayFrame.set(df);
+            layoutDisplayFrame = df;
+            layoutContainingFrame = pf;
+            layoutXDiff = 0;
+            layoutYDiff = 0;
         } else {
             task.getBounds(mContainingFrame);
             if (mAppToken != null && !mAppToken.mFrozenBounds.isEmpty()) {
@@ -693,6 +723,14 @@
                 }
             }
             mDisplayFrame.set(mContainingFrame);
+            layoutXDiff = !mInsetFrame.isEmpty() ? mInsetFrame.left - mContainingFrame.left : 0;
+            layoutYDiff = !mInsetFrame.isEmpty() ? mInsetFrame.top - mContainingFrame.top : 0;
+            layoutContainingFrame = !mInsetFrame.isEmpty() ? mInsetFrame : mContainingFrame;
+            subtractInsets(mDisplayFrame, layoutContainingFrame, df);
+            subtractInsets(mContainingFrame, layoutContainingFrame, pf);
+            subtractInsets(mInsetFrame, layoutContainingFrame, pf);
+            layoutDisplayFrame = df;
+            layoutDisplayFrame.intersect(layoutContainingFrame);
         }
 
         final int pw = mContainingFrame.width();
@@ -723,7 +761,11 @@
         final int fw = mFrame.width();
         final int fh = mFrame.height();
 
-        applyGravityAndUpdateFrame();
+        applyGravityAndUpdateFrame(layoutContainingFrame, layoutDisplayFrame);
+
+        // Offset the actual frame by the amount layout frame is off.
+        mFrame.offset(-layoutXDiff, -layoutYDiff);
+        mCompatFrame.offset(-layoutXDiff, -layoutYDiff);
 
         // Calculate the outsets before the content frame gets shrinked to the window frame.
         if (hasOutsets) {
@@ -735,12 +777,6 @@
             mOutsets.set(0, 0, 0, 0);
         }
 
-        // Denotes the actual frame used to calculate the insets. When resizing in docked mode,
-        // we'd like to freeze the layout, so we also need to freeze the insets temporarily. By the
-        // notion of a task having a different inset frame, we can achieve that while still moving
-        // the task around.
-        final Rect frame = !mInsetFrame.isEmpty() ? mInsetFrame : mFrame;
-
         // Make sure the content and visible frames are inside of the
         // final window frame.
         if (windowsAreFloating && !mFrame.isEmpty()) {
@@ -769,29 +805,29 @@
                 mMovedByResize = true;
             }
         } else {
-            mContentFrame.set(Math.max(mContentFrame.left, frame.left),
-                    Math.max(mContentFrame.top, frame.top),
-                    Math.min(mContentFrame.right, frame.right),
-                    Math.min(mContentFrame.bottom, frame.bottom));
+            mContentFrame.set(Math.max(mContentFrame.left, layoutContainingFrame.left),
+                    Math.max(mContentFrame.top, layoutContainingFrame.top),
+                    Math.min(mContentFrame.right, layoutContainingFrame.right),
+                    Math.min(mContentFrame.bottom, layoutContainingFrame.bottom));
 
-            mVisibleFrame.set(Math.max(mVisibleFrame.left, frame.left),
-                    Math.max(mVisibleFrame.top, frame.top),
-                    Math.min(mVisibleFrame.right, frame.right),
-                    Math.min(mVisibleFrame.bottom, frame.bottom));
+            mVisibleFrame.set(Math.max(mVisibleFrame.left, layoutContainingFrame.left),
+                    Math.max(mVisibleFrame.top, layoutContainingFrame.top),
+                    Math.min(mVisibleFrame.right, layoutContainingFrame.right),
+                    Math.min(mVisibleFrame.bottom, layoutContainingFrame.bottom));
 
-            mStableFrame.set(Math.max(mStableFrame.left, frame.left),
-                    Math.max(mStableFrame.top, frame.top),
-                    Math.min(mStableFrame.right, frame.right),
-                    Math.min(mStableFrame.bottom, frame.bottom));
+            mStableFrame.set(Math.max(mStableFrame.left, layoutContainingFrame.left),
+                    Math.max(mStableFrame.top, layoutContainingFrame.top),
+                    Math.min(mStableFrame.right, layoutContainingFrame.right),
+                    Math.min(mStableFrame.bottom, layoutContainingFrame.bottom));
         }
 
         if (fullscreenTask && !windowsAreFloating) {
             // Windows that are not fullscreen can be positioned outside of the display frame,
             // but that is not a reason to provide them with overscan insets.
-            mOverscanInsets.set(Math.max(mOverscanFrame.left - frame.left, 0),
-                    Math.max(mOverscanFrame.top - frame.top, 0),
-                    Math.max(frame.right - mOverscanFrame.right, 0),
-                    Math.max(frame.bottom - mOverscanFrame.bottom, 0));
+            mOverscanInsets.set(Math.max(mOverscanFrame.left - layoutContainingFrame.left, 0),
+                    Math.max(mOverscanFrame.top - layoutContainingFrame.top, 0),
+                    Math.max(layoutContainingFrame.right - mOverscanFrame.right, 0),
+                    Math.max(layoutContainingFrame.bottom - mOverscanFrame.bottom, 0));
         }
 
         if (mAttrs.type == TYPE_DOCK_DIVIDER) {
@@ -812,45 +848,32 @@
             // non-fullscreen mode.
             boolean overrideRightInset = !fullscreenTask && mFrame.right > mTmpRect.right;
             boolean overrideBottomInset = !fullscreenTask && mFrame.bottom > mTmpRect.bottom;
-            mContentInsets.set(mContentFrame.left - frame.left,
-                    mContentFrame.top - frame.top,
+            mContentInsets.set(mContentFrame.left - layoutContainingFrame.left,
+                    mContentFrame.top - layoutContainingFrame.top,
                     overrideRightInset ? mTmpRect.right - mContentFrame.right
-                            : frame.right - mContentFrame.right,
+                            : layoutContainingFrame.right - mContentFrame.right,
                     overrideBottomInset ? mTmpRect.bottom - mContentFrame.bottom
-                            : frame.bottom - mContentFrame.bottom);
+                            : layoutContainingFrame.bottom - mContentFrame.bottom);
 
-            mVisibleInsets.set(mVisibleFrame.left - frame.left,
-                    mVisibleFrame.top - frame.top,
+            mVisibleInsets.set(mVisibleFrame.left - layoutContainingFrame.left,
+                    mVisibleFrame.top - layoutContainingFrame.top,
                     overrideRightInset ? mTmpRect.right - mVisibleFrame.right
-                            : frame.right - mVisibleFrame.right,
+                            : layoutContainingFrame.right - mVisibleFrame.right,
                     overrideBottomInset ? mTmpRect.bottom - mVisibleFrame.bottom
-                            : frame.bottom - mVisibleFrame.bottom);
+                            : layoutContainingFrame.bottom - mVisibleFrame.bottom);
 
-            mStableInsets.set(Math.max(mStableFrame.left - frame.left, 0),
-                    Math.max(mStableFrame.top - frame.top, 0),
+            mStableInsets.set(Math.max(mStableFrame.left - layoutContainingFrame.left, 0),
+                    Math.max(mStableFrame.top - layoutContainingFrame.top, 0),
                     overrideRightInset ? Math.max(mTmpRect.right - mStableFrame.right, 0)
-                            : Math.max(frame.right - mStableFrame.right, 0),
+                            : Math.max(layoutContainingFrame.right - mStableFrame.right, 0),
                     overrideBottomInset ? Math.max(mTmpRect.bottom - mStableFrame.bottom, 0)
-                            :  Math.max(frame.bottom - mStableFrame.bottom, 0));
+                            :  Math.max(layoutContainingFrame.bottom - mStableFrame.bottom, 0));
         }
 
-        if (!mInsetFrame.isEmpty()) {
-            mContentFrame.set(mFrame);
-            mContentFrame.top += mContentInsets.top;
-            mContentFrame.bottom -= mContentInsets.bottom;
-            mContentFrame.left += mContentInsets.left;
-            mContentFrame.right -= mContentInsets.right;
-            mVisibleFrame.set(mFrame);
-            mVisibleFrame.top += mVisibleInsets.top;
-            mVisibleFrame.bottom -= mVisibleInsets.bottom;
-            mVisibleFrame.left += mVisibleInsets.left;
-            mVisibleFrame.right -= mVisibleInsets.right;
-            mStableFrame.set(mFrame);
-            mStableFrame.top += mStableInsets.top;
-            mStableFrame.bottom -= mStableInsets.bottom;
-            mStableFrame.left += mStableInsets.left;
-            mStableFrame.right -= mStableInsets.right;
-        }
+        mContentFrame.offset(-layoutXDiff, -layoutYDiff);
+        mVisibleFrame.offset(-layoutXDiff, -layoutYDiff);
+        mStableFrame.offset(-layoutXDiff, -layoutYDiff);
+
         mCompatFrame.set(mFrame);
         if (mEnforceSizeCompat) {
             // If there is a size compatibility scale being applied to the
@@ -2549,9 +2572,9 @@
         }
     }
 
-    void applyGravityAndUpdateFrame() {
-        final int pw = mContainingFrame.width();
-        final int ph = mContainingFrame.height();
+    void applyGravityAndUpdateFrame(Rect containingFrame, Rect displayFrame) {
+        final int pw = containingFrame.width();
+        final int ph = containingFrame.height();
         final Task task = getTask();
         final boolean nonFullscreenTask = isInMultiWindowMode();
         final boolean fitToDisplay = task != null && !task.isFloating() && !layoutInParentFrame();
@@ -2606,13 +2629,13 @@
         }
 
         // Set mFrame
-        Gravity.apply(mAttrs.gravity, w, h, mContainingFrame,
+        Gravity.apply(mAttrs.gravity, w, h, containingFrame,
                 (int) (x + mAttrs.horizontalMargin * pw),
                 (int) (y + mAttrs.verticalMargin * ph), mFrame);
 
         // Now make sure the window fits in the overall display frame.
         if (fitToDisplay) {
-            Gravity.applyDisplay(mAttrs.gravity, mDisplayFrame, mFrame);
+            Gravity.applyDisplay(mAttrs.gravity, displayFrame, mFrame);
         }
 
         // We need to make sure we update the CompatFrame as it is used for
diff --git a/services/tests/servicestests/src/com/android/server/content/ObserverNodeTest.java b/services/tests/servicestests/src/com/android/server/content/ObserverNodeTest.java
index 5b70c17..07280bc 100644
--- a/services/tests/servicestests/src/com/android/server/content/ObserverNodeTest.java
+++ b/services/tests/servicestests/src/com/android/server/content/ObserverNodeTest.java
@@ -62,7 +62,7 @@
         ArrayList<ObserverCall> calls = new ArrayList<ObserverCall>();
 
         for (int i = nums.length - 1; i >=0; --i) {
-            root.collectObserversLocked(uris[i], 0, null, false, myUserHandle, calls);
+            root.collectObserversLocked(uris[i], 0, null, false, 0, myUserHandle, calls);
             assertEquals(nums[i], calls.size());
             calls.clear();
         }
@@ -92,7 +92,7 @@
         ArrayList<ObserverCall> calls = new ArrayList<ObserverCall>();
 
         for (int i = uris.length - 1; i >=0; --i) {
-            root.collectObserversLocked(uris[i], 0, null, false, myUserHandle, calls);
+            root.collectObserversLocked(uris[i], 0, null, false, 0, myUserHandle, calls);
             assertEquals(nums[i], calls.size());
             calls.clear();
         }
diff --git a/tools/layoutlib/bridge/src/android/view/Choreographer_Delegate.java b/tools/layoutlib/bridge/src/android/view/Choreographer_Delegate.java
index 01af669..381eb1f 100644
--- a/tools/layoutlib/bridge/src/android/view/Choreographer_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/view/Choreographer_Delegate.java
@@ -15,8 +15,11 @@
  */
 package android.view;
 
+import com.android.ide.common.rendering.api.LayoutLog;
+import com.android.layoutlib.bridge.Bridge;
 import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
 
+import java.lang.reflect.Field;
 import java.util.concurrent.atomic.AtomicReference;
 
 /**
@@ -64,4 +67,18 @@
 
         thisChoreographer.doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
     }
+
+    public static void dispose() {
+        try {
+            Field threadInstanceField = Choreographer.class.getDeclaredField("sThreadInstance");
+            threadInstanceField.setAccessible(true);
+            @SuppressWarnings("unchecked") ThreadLocal<Choreographer> threadInstance =
+                    (ThreadLocal<Choreographer>) threadInstanceField.get(null);
+            threadInstance.remove();
+        } catch (ReflectiveOperationException e) {
+            assert false;
+            Bridge.getLog().error(LayoutLog.TAG_BROKEN,
+                    "Unable to clear Choreographer memory.", e, null);
+        }
+    }
 }
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java
index ce7104e..866b248 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java
@@ -1453,6 +1453,7 @@
 
         if (createdLooper) {
             Bridge.cleanupThread();
+            Choreographer_Delegate.dispose();
         }
     }
 }