Merge "AAPT2: Fix tokenizer to deal with the separator as last char"
diff --git a/core/java/android/app/TimePickerDialog.java b/core/java/android/app/TimePickerDialog.java
index a3b3022..aca0763 100644
--- a/core/java/android/app/TimePickerDialog.java
+++ b/core/java/android/app/TimePickerDialog.java
@@ -23,10 +23,8 @@
 import android.util.TypedValue;
 import android.view.LayoutInflater;
 import android.view.View;
-import android.widget.Button;
 import android.widget.TimePicker;
 import android.widget.TimePicker.OnTimeChangedListener;
-import android.widget.TimePicker.ValidationCallback;
 
 import com.android.internal.R;
 
@@ -64,7 +62,7 @@
          * @param hourOfDay the hour that was set
          * @param minute the minute that was set
          */
-        public void onTimeSet(TimePicker view, int hourOfDay, int minute);
+        void onTimeSet(TimePicker view, int hourOfDay, int minute);
     }
 
     /**
@@ -115,7 +113,6 @@
 
         final TypedValue outValue = new TypedValue();
         context.getTheme().resolveAttribute(R.attr.timePickerDialogTheme, outValue, true);
-        final int layoutResId = outValue.resourceId;
 
         final LayoutInflater inflater = LayoutInflater.from(themeContext);
         final View view = inflater.inflate(R.layout.time_picker_dialog, null);
@@ -129,7 +126,6 @@
         mTimePicker.setCurrentHour(mInitialHourOfDay);
         mTimePicker.setCurrentMinute(mInitialMinute);
         mTimePicker.setOnTimeChangedListener(this);
-        mTimePicker.setValidationCallback(mValidationCallback);
     }
 
     @Override
@@ -181,14 +177,4 @@
         mTimePicker.setCurrentHour(hour);
         mTimePicker.setCurrentMinute(minute);
     }
-
-    private final ValidationCallback mValidationCallback = new ValidationCallback() {
-        @Override
-        public void onValidationChanged(boolean valid) {
-            final Button positive = getButton(BUTTON_POSITIVE);
-            if (positive != null) {
-                positive.setEnabled(valid);
-            }
-        }
-    };
 }
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index 52ec4cc..eda4136 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -484,7 +484,7 @@
      *
      * {@hide}
      */
-    public static final int PRIVATE_FLAG_AUTOPLAY = 1<<6;
+    public static final int PRIVATE_FLAG_AUTOPLAY = 1 << 7;
 
     /**
      * Private/hidden flags. See {@code PRIVATE_FLAG_...} constants.
diff --git a/core/java/android/widget/PopupMenu.java b/core/java/android/widget/PopupMenu.java
index 34a8439..a53df88 100644
--- a/core/java/android/widget/PopupMenu.java
+++ b/core/java/android/widget/PopupMenu.java
@@ -21,7 +21,6 @@
 import com.android.internal.view.menu.MenuPopupHelper;
 import com.android.internal.view.menu.MenuPresenter;
 import com.android.internal.view.menu.ShowableListMenu;
-import com.android.internal.view.menu.SubMenuBuilder;
 
 import android.annotation.MenuRes;
 import android.content.Context;
@@ -33,35 +32,23 @@
 import android.view.View.OnTouchListener;
 
 /**
- * A PopupMenu displays a {@link Menu} in a modal popup window anchored to a {@link View}.
- * The popup will appear below the anchor view if there is room, or above it if there is not.
- * If the IME is visible the popup will not overlap it until it is touched. Touching outside
- * of the popup will dismiss it.
+ * A PopupMenu displays a {@link Menu} in a modal popup window anchored to a
+ * {@link View}. The popup will appear below the anchor view if there is room,
+ * or above it if there is not. If the IME is visible the popup will not
+ * overlap it until it is touched. Touching outside of the popup will dismiss
+ * it.
  */
-public class PopupMenu implements MenuBuilder.Callback, MenuPresenter.Callback {
+public class PopupMenu {
     private final Context mContext;
     private final MenuBuilder mMenu;
     private final View mAnchor;
     private final MenuPopupHelper mPopup;
-    private final boolean mShowCascadingMenus;
 
     private OnMenuItemClickListener mMenuItemClickListener;
     private OnDismissListener mDismissListener;
     private OnTouchListener mDragListener;
 
     /**
-     * Callback interface used to notify the application that the menu has closed.
-     */
-    public interface OnDismissListener {
-        /**
-         * Called when the associated menu has been dismissed.
-         *
-         * @param menu The PopupMenu that was dismissed.
-         */
-        public void onDismiss(PopupMenu menu);
-    }
-
-    /**
      * Constructor to create a new popup menu with an anchor view.
      *
      * @param context Context the popup menu is running in, through which it
@@ -108,14 +95,40 @@
     public PopupMenu(Context context, View anchor, int gravity, int popupStyleAttr,
             int popupStyleRes) {
         mContext = context;
-        mShowCascadingMenus = context.getResources().getBoolean(
-                com.android.internal.R.bool.config_enableCascadingSubmenus);
-        mMenu = new MenuBuilder(context);
-        mMenu.setCallback(this);
         mAnchor = anchor;
+
+        mMenu = new MenuBuilder(context);
+        mMenu.setCallback(new MenuBuilder.Callback() {
+            @Override
+            public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) {
+                if (mMenuItemClickListener != null) {
+                    return mMenuItemClickListener.onMenuItemClick(item);
+                }
+                return false;
+            }
+
+            @Override
+            public void onMenuModeChange(MenuBuilder menu) {
+            }
+        });
+
         mPopup = new MenuPopupHelper(context, mMenu, anchor, false, popupStyleAttr, popupStyleRes);
         mPopup.setGravity(gravity);
-        mPopup.setCallback(this);
+        mPopup.setCallback(new MenuPresenter.Callback() {
+            @Override
+            public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
+                if (mDismissListener != null) {
+                    mDismissListener.onDismiss(PopupMenu.this);
+                }
+            }
+
+            @Override
+            public boolean onOpenSubMenu(MenuBuilder subMenu) {
+                // The menu presenter will handle opening the submenu itself.
+                // Nothing to do here.
+                return false;
+            }
+        });
     }
 
     /**
@@ -125,7 +138,6 @@
      * the next time the popup is shown.
      *
      * @param gravity the gravity used to align the popup window
-     *
      * @see #getGravity()
      */
     public void setGravity(int gravity) {
@@ -134,7 +146,6 @@
 
     /**
      * @return the gravity used to align the popup window to its anchor view
-     *
      * @see #setGravity(int)
      */
     public int getGravity() {
@@ -146,8 +157,8 @@
      * to implement drag-to-open behavior.
      * <p>
      * When the listener is set on a view, touching that view and dragging
-     * outside of its bounds will open the popup window. Lifting will select the
-     * currently touched list item.
+     * outside of its bounds will open the popup window. Lifting will select
+     * the currently touched list item.
      * <p>
      * Example usage:
      * <pre>
@@ -184,9 +195,10 @@
     }
 
     /**
-     * @return the {@link Menu} associated with this popup. Populate the returned Menu with
-     * items before calling {@link #show()}.
+     * Returns the {@link Menu} associated with this popup. Populate the
+     * returned Menu with items before calling {@link #show()}.
      *
+     * @return the {@link Menu} associated with this popup
      * @see #show()
      * @see #getMenuInflater()
      */
@@ -195,9 +207,8 @@
     }
 
     /**
-     * @return a {@link MenuInflater} that can be used to inflate menu items from XML into the
-     * menu returned by {@link #getMenu()}.
-     *
+     * @return a {@link MenuInflater} that can be used to inflate menu items
+     *         from XML into the menu returned by {@link #getMenu()}
      * @see #getMenu()
      */
     public MenuInflater getMenuInflater() {
@@ -205,8 +216,9 @@
     }
 
     /**
-     * Inflate a menu resource into this PopupMenu. This is equivalent to calling
-     * popupMenu.getMenuInflater().inflate(menuRes, popupMenu.getMenu()).
+     * Inflate a menu resource into this PopupMenu. This is equivalent to
+     * calling {@code popupMenu.getMenuInflater().inflate(menuRes, popupMenu.getMenu())}.
+     *
      * @param menuRes Menu resource to inflate
      */
     public void inflate(@MenuRes int menuRes) {
@@ -215,6 +227,7 @@
 
     /**
      * Show the menu popup anchored to the view specified during construction.
+     *
      * @see #dismiss()
      */
     public void show() {
@@ -223,6 +236,7 @@
 
     /**
      * Dismiss the menu popup.
+     *
      * @see #show()
      */
     public void dismiss() {
@@ -230,68 +244,49 @@
     }
 
     /**
-     * Set a listener that will be notified when the user selects an item from the menu.
+     * Sets a listener that will be notified when the user selects an item from
+     * the menu.
      *
-     * @param listener Listener to notify
+     * @param listener the listener to notify
      */
     public void setOnMenuItemClickListener(OnMenuItemClickListener listener) {
         mMenuItemClickListener = listener;
     }
 
     /**
-     * Set a listener that will be notified when this menu is dismissed.
+     * Sets a listener that will be notified when this menu is dismissed.
      *
-     * @param listener Listener to notify
+     * @param listener the listener to notify
      */
     public void setOnDismissListener(OnDismissListener listener) {
         mDismissListener = listener;
     }
 
     /**
-     * @hide
-     */
-    public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) {
-        if (mMenuItemClickListener != null) {
-            return mMenuItemClickListener.onMenuItemClick(item);
-        }
-        return false;
-    }
-
-    /**
-     * @hide
-     */
-    public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
-        if (mDismissListener != null) {
-            mDismissListener.onDismiss(this);
-        }
-    }
-
-    /**
-     * @hide
-     */
-    public boolean onOpenSubMenu(MenuBuilder subMenu) {
-        // The menu presenter will handle opening the submenu itself. Nothing to do here.
-        return false;
-    }
-
-    /**
-     * @hide
-     */
-    public void onMenuModeChange(MenuBuilder menu) {
-    }
-
-    /**
-     * Interface responsible for receiving menu item click events if the items themselves
-     * do not have individual item click listeners.
+     * Interface responsible for receiving menu item click events if the items
+     * themselves do not have individual item click listeners.
      */
     public interface OnMenuItemClickListener {
         /**
-         * This method will be invoked when a menu item is clicked if the item itself did
-         * not already handle the event.
+         * This method will be invoked when a menu item is clicked if the item
+         * itself did not already handle the event.
          *
-         * @param item {@link MenuItem} that was clicked
-         * @return <code>true</code> if the event was handled, <code>false</code> otherwise.
+         * @param item the menu item that was clicked
+         * @return {@code true} if the event was handled, {@code false}
+         *         otherwise
          */
-        public boolean onMenuItemClick(MenuItem item);
+        boolean onMenuItemClick(MenuItem item);
+    }
+
+    /**
+     * Callback interface used to notify the application that the menu has closed.
+     */
+    public interface OnDismissListener {
+        /**
+         * Called when the associated menu has been dismissed.
+         *
+         * @param menu the popup menu that was dismissed
+         */
+        void onDismiss(PopupMenu menu);
     }
 }
diff --git a/core/java/android/widget/TimePicker.java b/core/java/android/widget/TimePicker.java
index 8e5af79..a24d37f 100644
--- a/core/java/android/widget/TimePicker.java
+++ b/core/java/android/widget/TimePicker.java
@@ -100,7 +100,7 @@
      * @see #getHour()
      */
     public void setHour(int hour) {
-        mDelegate.setCurrentHour(hour);
+        mDelegate.setHour(hour);
     }
 
     /**
@@ -110,7 +110,7 @@
      * @see #setHour(int)
      */
     public int getHour() {
-        return mDelegate.getCurrentHour();
+        return mDelegate.getHour();
     }
 
     /**
@@ -120,7 +120,7 @@
      * @see #getMinute()
      */
     public void setMinute(int minute) {
-        mDelegate.setCurrentMinute(minute);
+        mDelegate.setMinute(minute);
     }
 
     /**
@@ -130,7 +130,7 @@
      * @see #setMinute(int)
      */
     public int getMinute() {
-        return mDelegate.getCurrentMinute();
+        return mDelegate.getMinute();
     }
 
     /**
@@ -150,7 +150,7 @@
     @NonNull
     @Deprecated
     public Integer getCurrentHour() {
-        return mDelegate.getCurrentHour();
+        return mDelegate.getHour();
     }
 
     /**
@@ -160,7 +160,7 @@
      */
     @Deprecated
     public void setCurrentMinute(@NonNull Integer currentMinute) {
-        mDelegate.setCurrentMinute(currentMinute);
+        mDelegate.setMinute(currentMinute);
     }
 
     /**
@@ -170,7 +170,7 @@
     @NonNull
     @Deprecated
     public Integer getCurrentMinute() {
-        return mDelegate.getCurrentMinute();
+        return mDelegate.getMinute();
     }
 
     /**
@@ -186,7 +186,7 @@
             return;
         }
 
-        mDelegate.setIs24HourView(is24HourView);
+        mDelegate.setIs24Hour(is24HourView);
     }
 
     /**
@@ -195,7 +195,7 @@
      * @see #setIs24HourView(Boolean)
      */
     public boolean is24HourView() {
-        return mDelegate.is24HourView();
+        return mDelegate.is24Hour();
     }
 
     /**
@@ -207,16 +207,6 @@
         mDelegate.setOnTimeChangedListener(onTimeChangedListener);
     }
 
-    /**
-     * Sets the callback that indicates the current time is valid.
-     *
-     * @param callback the callback, may be null
-     * @hide
-     */
-    public void setValidationCallback(@Nullable ValidationCallback callback) {
-        mDelegate.setValidationCallback(callback);
-    }
-
     @Override
     public void setEnabled(boolean enabled) {
         super.setEnabled(enabled);
@@ -234,12 +224,6 @@
     }
 
     @Override
-    protected void onConfigurationChanged(Configuration newConfig) {
-        super.onConfigurationChanged(newConfig);
-        mDelegate.onConfigurationChanged(newConfig);
-    }
-
-    @Override
     protected Parcelable onSaveInstanceState() {
         Parcelable superState = super.onSaveInstanceState();
         return mDelegate.onSaveInstanceState(superState);
@@ -269,25 +253,22 @@
      * for the real behavior.
      */
     interface TimePickerDelegate {
-        void setCurrentHour(int currentHour);
-        int getCurrentHour();
+        void setHour(int hour);
+        int getHour();
 
-        void setCurrentMinute(int currentMinute);
-        int getCurrentMinute();
+        void setMinute(int minute);
+        int getMinute();
 
-        void setIs24HourView(boolean is24HourView);
-        boolean is24HourView();
+        void setIs24Hour(boolean is24Hour);
+        boolean is24Hour();
 
         void setOnTimeChangedListener(OnTimeChangedListener onTimeChangedListener);
-        void setValidationCallback(ValidationCallback callback);
 
         void setEnabled(boolean enabled);
         boolean isEnabled();
 
         int getBaseline();
 
-        void onConfigurationChanged(Configuration newConfig);
-
         Parcelable onSaveInstanceState(Parcelable superState);
         void onRestoreInstanceState(Parcelable state);
 
@@ -295,16 +276,6 @@
         void onPopulateAccessibilityEvent(AccessibilityEvent event);
     }
 
-    /**
-     * A callback interface for updating input validity when the TimePicker
-     * when included into a Dialog.
-     *
-     * @hide
-     */
-    public static interface ValidationCallback {
-        void onValidationChanged(boolean valid);
-    }
-
     static String[] getAmPmStrings(Context context) {
         final Locale locale = context.getResources().getConfiguration().locale;
         final LocaleData d = LocaleData.get(locale);
@@ -319,43 +290,16 @@
      * An abstract class which can be used as a start for TimePicker implementations
      */
     abstract static class AbstractTimePickerDelegate implements TimePickerDelegate {
-        // The delegator
-        protected TimePicker mDelegator;
+        protected final TimePicker mDelegator;
+        protected final Context mContext;
+        protected final Locale mLocale;
 
-        // The context
-        protected Context mContext;
-
-        // The current locale
-        protected Locale mCurrentLocale;
-
-        // Callbacks
         protected OnTimeChangedListener mOnTimeChangedListener;
-        protected ValidationCallback mValidationCallback;
 
-        public AbstractTimePickerDelegate(TimePicker delegator, Context context) {
+        public AbstractTimePickerDelegate(@NonNull TimePicker delegator, @NonNull Context context) {
             mDelegator = delegator;
             mContext = context;
-
-            // initialization based on locale
-            setCurrentLocale(Locale.getDefault());
-        }
-
-        public void setCurrentLocale(Locale locale) {
-            if (locale.equals(mCurrentLocale)) {
-                return;
-            }
-            mCurrentLocale = locale;
-        }
-
-        @Override
-        public void setValidationCallback(ValidationCallback callback) {
-            mValidationCallback = callback;
-        }
-
-        protected void onValidationChanged(boolean valid) {
-            if (mValidationCallback != null) {
-                mValidationCallback.onValidationChanged(valid);
-            }
+            mLocale = context.getResources().getConfiguration().locale;
         }
     }
 }
diff --git a/core/java/android/widget/TimePickerClockDelegate.java b/core/java/android/widget/TimePickerClockDelegate.java
index 4dc5fd3e..38ce033 100644
--- a/core/java/android/widget/TimePickerClockDelegate.java
+++ b/core/java/android/widget/TimePickerClockDelegate.java
@@ -19,7 +19,6 @@
 import android.annotation.Nullable;
 import android.content.Context;
 import android.content.res.ColorStateList;
-import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.os.Parcel;
@@ -89,7 +88,7 @@
     private boolean mAllowAutoAdvance;
     private int mInitialHourOfDay;
     private int mInitialMinute;
-    private boolean mIs24HourView;
+    private boolean mIs24Hour;
     private boolean mIsAmPmAtStart;
 
     // Accessibility strings.
@@ -200,19 +199,19 @@
         mAllowAutoAdvance = true;
 
         // Updates mHourFormat variables used below.
-        updateHourFormat(mCurrentLocale, mIs24HourView);
+        updateHourFormat(mLocale, mIs24Hour);
 
         // Update hour text field.
         final int minHour = mHourFormatStartsAtZero ? 0 : 1;
-        final int maxHour = (mIs24HourView ? 23 : 11) + minHour;
+        final int maxHour = (mIs24Hour ? 23 : 11) + minHour;
         mHourView.setRange(minHour, maxHour);
         mHourView.setShowLeadingZeroes(mHourFormatShowLeadingZero);
 
         // Initialize with current time.
-        mTempCalendar = Calendar.getInstance(mCurrentLocale);
+        mTempCalendar = Calendar.getInstance(mLocale);
         final int currentHour = mTempCalendar.get(Calendar.HOUR_OF_DAY);
         final int currentMinute = mTempCalendar.get(Calendar.MINUTE);
-        initialize(currentHour, currentMinute, mIs24HourView, HOUR_INDEX);
+        initialize(currentHour, currentMinute, mIs24Hour, HOUR_INDEX);
     }
 
     /**
@@ -333,7 +332,7 @@
     private void initialize(int hourOfDay, int minute, boolean is24HourView, int index) {
         mInitialHourOfDay = hourOfDay;
         mInitialMinute = minute;
-        mIs24HourView = is24HourView;
+        mIs24Hour = is24HourView;
         updateUI(index);
     }
 
@@ -352,17 +351,16 @@
     }
 
     private void updateRadialPicker(int index) {
-        mRadialTimePickerView.initialize(mInitialHourOfDay, mInitialMinute, mIs24HourView);
+        mRadialTimePickerView.initialize(mInitialHourOfDay, mInitialMinute, mIs24Hour);
         setCurrentItemShowing(index, false, true);
     }
 
     private void updateHeaderAmPm() {
-
-        if (mIs24HourView) {
+        if (mIs24Hour) {
             mAmPmLayout.setVisibility(View.GONE);
         } else {
             // Ensure that AM/PM layout is in the correct position.
-            final String dateTimePattern = DateFormat.getBestDateTimePattern(mCurrentLocale, "hm");
+            final String dateTimePattern = DateFormat.getBestDateTimePattern(mLocale, "hm");
             final boolean isAmPmAtStart = dateTimePattern.startsWith("a");
             setAmPmAtStart(isAmPmAtStart);
 
@@ -395,35 +393,32 @@
      * Set the current hour.
      */
     @Override
-    public void setCurrentHour(int currentHour) {
-        if (mInitialHourOfDay == currentHour) {
-            return;
+    public void setHour(int hour) {
+        if (mInitialHourOfDay != hour) {
+            mInitialHourOfDay = hour;
+            updateHeaderHour(hour, true);
+            updateHeaderAmPm();
+            mRadialTimePickerView.setCurrentHour(hour);
+            mRadialTimePickerView.setAmOrPm(mInitialHourOfDay < 12 ? AM : PM);
+            mDelegator.invalidate();
+            onTimeChanged();
         }
-        mInitialHourOfDay = currentHour;
-        updateHeaderHour(currentHour, true);
-        updateHeaderAmPm();
-        mRadialTimePickerView.setCurrentHour(currentHour);
-        mRadialTimePickerView.setAmOrPm(mInitialHourOfDay < 12 ? AM : PM);
-        mDelegator.invalidate();
-        onTimeChanged();
     }
 
     /**
-     * @return The current hour in the range (0-23).
+     * @return the current hour in the range (0-23)
      */
     @Override
-    public int getCurrentHour() {
-        int currentHour = mRadialTimePickerView.getCurrentHour();
-        if (mIs24HourView) {
+    public int getHour() {
+        final int currentHour = mRadialTimePickerView.getCurrentHour();
+        if (mIs24Hour) {
             return currentHour;
+        }
+
+        if (mRadialTimePickerView.getAmOrPm() == PM) {
+            return (currentHour % HOURS_IN_HALF_DAY) + HOURS_IN_HALF_DAY;
         } else {
-            switch(mRadialTimePickerView.getAmOrPm()) {
-                case PM:
-                    return (currentHour % HOURS_IN_HALF_DAY) + HOURS_IN_HALF_DAY;
-                case AM:
-                default:
-                    return currentHour % HOURS_IN_HALF_DAY;
-            }
+            return currentHour % HOURS_IN_HALF_DAY;
         }
     }
 
@@ -431,48 +426,48 @@
      * Set the current minute (0-59).
      */
     @Override
-    public void setCurrentMinute(int currentMinute) {
-        if (mInitialMinute == currentMinute) {
-            return;
+    public void setMinute(int minute) {
+        if (mInitialMinute != minute) {
+            mInitialMinute = minute;
+            updateHeaderMinute(minute, true);
+            mRadialTimePickerView.setCurrentMinute(minute);
+            mDelegator.invalidate();
+            onTimeChanged();
         }
-        mInitialMinute = currentMinute;
-        updateHeaderMinute(currentMinute, true);
-        mRadialTimePickerView.setCurrentMinute(currentMinute);
-        mDelegator.invalidate();
-        onTimeChanged();
     }
 
     /**
      * @return The current minute.
      */
     @Override
-    public int getCurrentMinute() {
+    public int getMinute() {
         return mRadialTimePickerView.getCurrentMinute();
     }
 
     /**
-     * Set whether in 24 hour or AM/PM mode.
+     * Sets whether time is displayed in 24-hour mode or 12-hour mode with
+     * AM/PM indicators.
      *
-     * @param is24HourView True = 24 hour mode. False = AM/PM.
+     * @param is24Hour {@code true} to display time in 24-hour mode or
+     *        {@code false} for 12-hour mode with AM/PM
      */
-    @Override
-    public void setIs24HourView(boolean is24HourView) {
-        if (is24HourView == mIs24HourView) {
-            return;
+    public void setIs24Hour(boolean is24Hour) {
+        if (mIs24Hour != is24Hour) {
+            mIs24Hour = is24Hour;
+            mInitialHourOfDay = getHour();
+
+            updateUI(mRadialTimePickerView.getCurrentItemShowing());
         }
-
-        mIs24HourView = is24HourView;
-        mInitialHourOfDay = getCurrentHour();
-
-        updateUI(mRadialTimePickerView.getCurrentItemShowing());
     }
 
     /**
-     * @return true if this is in 24 hour view else false.
+     * @return {@code true} if time is displayed in 24-hour mode, or
+     *         {@code false} if time is displayed in 12-hour mode with AM/PM
+     *         indicators
      */
     @Override
-    public boolean is24HourView() {
-        return mIs24HourView;
+    public boolean is24Hour() {
+        return mIs24Hour;
     }
 
     @Override
@@ -502,14 +497,9 @@
     }
 
     @Override
-    public void onConfigurationChanged(Configuration newConfig) {
-        updateUI(mRadialTimePickerView.getCurrentItemShowing());
-    }
-
-    @Override
     public Parcelable onSaveInstanceState(Parcelable superState) {
-        return new SavedState(superState, getCurrentHour(), getCurrentMinute(),
-                is24HourView(), getCurrentItemShowing());
+        return new SavedState(superState, getHour(), getMinute(),
+                is24Hour(), getCurrentItemShowing());
     }
 
     @Override
@@ -520,12 +510,6 @@
     }
 
     @Override
-    public void setCurrentLocale(Locale locale) {
-        super.setCurrentLocale(locale);
-        mTempCalendar = Calendar.getInstance(locale);
-    }
-
-    @Override
     public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
         onPopulateAccessibilityEvent(event);
         return true;
@@ -534,13 +518,13 @@
     @Override
     public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
         int flags = DateUtils.FORMAT_SHOW_TIME;
-        if (mIs24HourView) {
+        if (mIs24Hour) {
             flags |= DateUtils.FORMAT_24HOUR;
         } else {
             flags |= DateUtils.FORMAT_12HOUR;
         }
-        mTempCalendar.set(Calendar.HOUR_OF_DAY, getCurrentHour());
-        mTempCalendar.set(Calendar.MINUTE, getCurrentMinute());
+        mTempCalendar.set(Calendar.HOUR_OF_DAY, getHour());
+        mTempCalendar.set(Calendar.MINUTE, getMinute());
         String selectedDate = DateUtils.formatDateTime(mContext,
                 mTempCalendar.getTimeInMillis(), flags);
         event.getText().add(selectedDate);
@@ -559,8 +543,7 @@
     private void onTimeChanged() {
         mDelegator.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
         if (mOnTimeChangedListener != null) {
-            mOnTimeChangedListener.onTimeChanged(mDelegator,
-                    getCurrentHour(), getCurrentMinute());
+            mOnTimeChangedListener.onTimeChanged(mDelegator, getHour(), getMinute());
         }
     }
 
@@ -666,7 +649,7 @@
         }
 
         if (mOnTimeChangedListener != null) {
-            mOnTimeChangedListener.onTimeChanged(mDelegator, getCurrentHour(), getCurrentMinute());
+            mOnTimeChangedListener.onTimeChanged(mDelegator, getHour(), getMinute());
         }
     }
 
@@ -677,14 +660,14 @@
      * @return a localized hour number
      */
     private int getLocalizedHour(int hourOfDay) {
-        if (!mIs24HourView) {
+        if (!mIs24Hour) {
             // Convert to hour-of-am-pm.
             hourOfDay %= 12;
         }
 
         if (!mHourFormatStartsAtZero && hourOfDay == 0) {
             // Convert to clock-hour (either of-day or of-am-pm).
-            hourOfDay = mIs24HourView ? 24 : 12;
+            hourOfDay = mIs24Hour ? 24 : 12;
         }
 
         return hourOfDay;
@@ -716,8 +699,8 @@
      * separator as the character which is just after the hour marker in the returned pattern.
      */
     private void updateHeaderSeparator() {
-        final String bestDateTimePattern = DateFormat.getBestDateTimePattern(mCurrentLocale,
-                (mIs24HourView) ? "Hm" : "hm");
+        final String bestDateTimePattern = DateFormat.getBestDateTimePattern(mLocale,
+                (mIs24Hour) ? "Hm" : "hm");
         final String separatorText;
         // See http://www.unicode.org/reports/tr35/tr35-dates.html for hour formats
         final char[] hourFormats = {'H', 'h', 'K', 'k'};
@@ -819,14 +802,14 @@
     private final Runnable mCommitHour = new Runnable() {
         @Override
         public void run() {
-            setCurrentHour(mHourView.getValue());
+            setHour(mHourView.getValue());
         }
     };
 
     private final Runnable mCommitMinute = new Runnable() {
         @Override
         public void run() {
-            setCurrentMinute(mMinuteView.getValue());
+            setMinute(mMinuteView.getValue());
         }
     };
 
diff --git a/core/java/android/widget/TimePickerSpinnerDelegate.java b/core/java/android/widget/TimePickerSpinnerDelegate.java
index 8741cc3..2ed230b 100644
--- a/core/java/android/widget/TimePickerSpinnerDelegate.java
+++ b/core/java/android/widget/TimePickerSpinnerDelegate.java
@@ -17,7 +17,6 @@
 package android.widget;
 
 import android.content.Context;
-import android.content.res.Configuration;
 import android.content.res.TypedArray;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -33,7 +32,6 @@
 import com.android.internal.R;
 
 import java.util.Calendar;
-import java.util.Locale;
 
 import libcore.icu.LocaleData;
 
@@ -92,7 +90,7 @@
         mHourSpinner.setOnValueChangedListener(new NumberPicker.OnValueChangeListener() {
             public void onValueChange(NumberPicker spinner, int oldVal, int newVal) {
                 updateInputState();
-                if (!is24HourView()) {
+                if (!is24Hour()) {
                     if ((oldVal == HOURS_IN_HALF_DAY - 1 && newVal == HOURS_IN_HALF_DAY) ||
                             (oldVal == HOURS_IN_HALF_DAY && newVal == HOURS_IN_HALF_DAY - 1)) {
                         mIsAm = !mIsAm;
@@ -124,14 +122,14 @@
                 int maxValue = mMinuteSpinner.getMaxValue();
                 if (oldVal == maxValue && newVal == minValue) {
                     int newHour = mHourSpinner.getValue() + 1;
-                    if (!is24HourView() && newHour == HOURS_IN_HALF_DAY) {
+                    if (!is24Hour() && newHour == HOURS_IN_HALF_DAY) {
                         mIsAm = !mIsAm;
                         updateAmPmControl();
                     }
                     mHourSpinner.setValue(newHour);
                 } else if (oldVal == minValue && newVal == maxValue) {
                     int newHour = mHourSpinner.getValue() - 1;
-                    if (!is24HourView() && newHour == HOURS_IN_HALF_DAY - 1) {
+                    if (!is24Hour() && newHour == HOURS_IN_HALF_DAY - 1) {
                         mIsAm = !mIsAm;
                         updateAmPmControl();
                     }
@@ -204,8 +202,8 @@
         updateAmPmControl();
 
         // set to current time
-        setCurrentHour(mTempCalendar.get(Calendar.HOUR_OF_DAY));
-        setCurrentMinute(mTempCalendar.get(Calendar.MINUTE));
+        setHour(mTempCalendar.get(Calendar.HOUR_OF_DAY));
+        setMinute(mTempCalendar.get(Calendar.MINUTE));
 
         if (!isEnabled()) {
             setEnabled(false);
@@ -221,7 +219,7 @@
     }
 
     private void getHourFormatData() {
-        final String bestDateTimePattern = DateFormat.getBestDateTimePattern(mCurrentLocale,
+        final String bestDateTimePattern = DateFormat.getBestDateTimePattern(mLocale,
                 (mIs24HourView) ? "Hm" : "hm");
         final int lengthPattern = bestDateTimePattern.length();
         mHourWithTwoDigit = false;
@@ -241,7 +239,7 @@
     }
 
     private boolean isAmPmAtStart() {
-        final String bestDateTimePattern = DateFormat.getBestDateTimePattern(mCurrentLocale,
+        final String bestDateTimePattern = DateFormat.getBestDateTimePattern(mLocale,
                 "hm" /* skeleton */);
 
         return bestDateTimePattern.startsWith("a");
@@ -257,7 +255,7 @@
      */
     private void setDividerText() {
         final String skeleton = (mIs24HourView) ? "Hm" : "hm";
-        final String bestDateTimePattern = DateFormat.getBestDateTimePattern(mCurrentLocale,
+        final String bestDateTimePattern = DateFormat.getBestDateTimePattern(mLocale,
                 skeleton);
         final String separatorText;
         int hourIndex = bestDateTimePattern.lastIndexOf('H');
@@ -279,16 +277,16 @@
     }
 
     @Override
-    public void setCurrentHour(int currentHour) {
-        setCurrentHour(currentHour, true);
+    public void setHour(int hour) {
+        setCurrentHour(hour, true);
     }
 
     private void setCurrentHour(int currentHour, boolean notifyTimeChanged) {
         // why was Integer used in the first place?
-        if (currentHour == getCurrentHour()) {
+        if (currentHour == getHour()) {
             return;
         }
-        if (!is24HourView()) {
+        if (!is24Hour()) {
             // convert [0,23] ordinal to wall clock display
             if (currentHour >= HOURS_IN_HALF_DAY) {
                 mIsAm = false;
@@ -310,9 +308,9 @@
     }
 
     @Override
-    public int getCurrentHour() {
+    public int getHour() {
         int currentHour = mHourSpinner.getValue();
-        if (is24HourView()) {
+        if (is24Hour()) {
             return currentHour;
         } else if (mIsAm) {
             return currentHour % HOURS_IN_HALF_DAY;
@@ -322,28 +320,27 @@
     }
 
     @Override
-    public void setCurrentMinute(int currentMinute) {
-        if (currentMinute == getCurrentMinute()) {
+    public void setMinute(int minute) {
+        if (minute == getMinute()) {
             return;
         }
-        mMinuteSpinner.setValue(currentMinute);
+        mMinuteSpinner.setValue(minute);
         onTimeChanged();
     }
 
     @Override
-    public int getCurrentMinute() {
+    public int getMinute() {
         return mMinuteSpinner.getValue();
     }
 
-    @Override
-    public void setIs24HourView(boolean is24HourView) {
-        if (mIs24HourView == is24HourView) {
+    public void setIs24Hour(boolean is24Hour) {
+        if (mIs24HourView == is24Hour) {
             return;
         }
         // cache the current hour since spinner range changes and BEFORE changing mIs24HourView!!
-        int currentHour = getCurrentHour();
+        int currentHour = getHour();
         // Order is important here.
-        mIs24HourView = is24HourView;
+        mIs24HourView = is24Hour;
         getHourFormatData();
         updateHourControl();
         // set value after spinner range is updated
@@ -353,7 +350,7 @@
     }
 
     @Override
-    public boolean is24HourView() {
+    public boolean is24Hour() {
         return mIs24HourView;
     }
 
@@ -388,20 +385,15 @@
     }
 
     @Override
-    public void onConfigurationChanged(Configuration newConfig) {
-        setCurrentLocale(newConfig.locale);
-    }
-
-    @Override
     public Parcelable onSaveInstanceState(Parcelable superState) {
-        return new SavedState(superState, getCurrentHour(), getCurrentMinute());
+        return new SavedState(superState, getHour(), getMinute());
     }
 
     @Override
     public void onRestoreInstanceState(Parcelable state) {
         SavedState ss = (SavedState) state;
-        setCurrentHour(ss.getHour());
-        setCurrentMinute(ss.getMinute());
+        setHour(ss.getHour());
+        setMinute(ss.getMinute());
     }
 
     @Override
@@ -418,8 +410,8 @@
         } else {
             flags |= DateUtils.FORMAT_12HOUR;
         }
-        mTempCalendar.set(Calendar.HOUR_OF_DAY, getCurrentHour());
-        mTempCalendar.set(Calendar.MINUTE, getCurrentMinute());
+        mTempCalendar.set(Calendar.HOUR_OF_DAY, getHour());
+        mTempCalendar.set(Calendar.MINUTE, getMinute());
         String selectedDateUtterance = DateUtils.formatDateTime(mContext,
                 mTempCalendar.getTimeInMillis(), flags);
         event.getText().add(selectedDateUtterance);
@@ -447,7 +439,7 @@
     }
 
     private void updateAmPmControl() {
-        if (is24HourView()) {
+        if (is24Hour()) {
             if (mAmPmSpinner != null) {
                 mAmPmSpinner.setVisibility(View.GONE);
             } else {
@@ -466,27 +458,16 @@
         mDelegator.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
     }
 
-    /**
-     * Sets the current locale.
-     *
-     * @param locale The current locale.
-     */
-    @Override
-    public void setCurrentLocale(Locale locale) {
-        super.setCurrentLocale(locale);
-        mTempCalendar = Calendar.getInstance(locale);
-    }
-
     private void onTimeChanged() {
         mDelegator.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
         if (mOnTimeChangedListener != null) {
-            mOnTimeChangedListener.onTimeChanged(mDelegator, getCurrentHour(),
-                    getCurrentMinute());
+            mOnTimeChangedListener.onTimeChanged(mDelegator, getHour(),
+                    getMinute());
         }
     }
 
     private void updateHourControl() {
-        if (is24HourView()) {
+        if (is24Hour()) {
             // 'k' means 1-24 hour
             if (mHourFormat == 'k') {
                 mHourSpinner.setMinValue(1);
@@ -509,7 +490,7 @@
     }
 
     private void updateMinuteControl() {
-        if (is24HourView()) {
+        if (is24Hour()) {
             mMinuteSpinnerInput.setImeOptions(EditorInfo.IME_ACTION_DONE);
         } else {
             mMinuteSpinnerInput.setImeOptions(EditorInfo.IME_ACTION_NEXT);
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk
index 4acad67..8565372 100644
--- a/libs/hwui/Android.mk
+++ b/libs/hwui/Android.mk
@@ -206,7 +206,9 @@
 LOCAL_MODULE_TAGS := tests
 LOCAL_SHARED_LIBRARIES := $(hwui_shared_libraries)
 LOCAL_STATIC_LIBRARIES := libhwui_static_null_gpu
-LOCAL_CFLAGS := $(hwui_cflags)
+LOCAL_CFLAGS := \
+        $(hwui_cflags) \
+        -DHWUI_NULL_GPU
 
 LOCAL_SRC_FILES += \
     unit_tests/CanvasStateTests.cpp \
diff --git a/libs/hwui/BakedOpRenderer.cpp b/libs/hwui/BakedOpRenderer.cpp
index 1aa291f..d2d3285 100644
--- a/libs/hwui/BakedOpRenderer.cpp
+++ b/libs/hwui/BakedOpRenderer.cpp
@@ -35,11 +35,11 @@
     LOG_ALWAYS_FATAL_IF(mRenderTarget.offscreenBuffer, "already has layer...");
 
     OffscreenBuffer* buffer = mRenderState.layerPool().get(mRenderState, width, height);
-    startRepaintLayer(buffer);
+    startRepaintLayer(buffer, Rect(width, height));
     return buffer;
 }
 
-void BakedOpRenderer::startRepaintLayer(OffscreenBuffer* offscreenBuffer) {
+void BakedOpRenderer::startRepaintLayer(OffscreenBuffer* offscreenBuffer, const Rect& repaintRect) {
     LOG_ALWAYS_FATAL_IF(mRenderTarget.offscreenBuffer, "already has layer...");
 
     mRenderTarget.offscreenBuffer = offscreenBuffer;
@@ -55,12 +55,10 @@
     LOG_ALWAYS_FATAL_IF(glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE,
             "framebuffer incomplete!");
 
-    // Clear the FBO
-    mRenderState.scissor().setEnabled(false);
-    glClear(GL_COLOR_BUFFER_BIT);
-
     // Change the viewport & ortho projection
     setViewport(offscreenBuffer->viewportWidth, offscreenBuffer->viewportHeight);
+
+    clearColorBuffer(repaintRect);
 }
 
 void BakedOpRenderer::endLayer() {
@@ -74,16 +72,13 @@
     mRenderTarget.frameBufferId = -1;
 }
 
-void BakedOpRenderer::startFrame(uint32_t width, uint32_t height) {
+void BakedOpRenderer::startFrame(uint32_t width, uint32_t height, const Rect& repaintRect) {
     mRenderState.bindFramebuffer(0);
     setViewport(width, height);
     mCaches.clearGarbage();
 
     if (!mOpaque) {
-        // TODO: partial invalidate!
-        mRenderState.scissor().setEnabled(false);
-        glClear(GL_COLOR_BUFFER_BIT);
-        mHasDrawn = true;
+        clearColorBuffer(repaintRect);
     }
 }
 
@@ -113,6 +108,20 @@
     mRenderState.blend().syncEnabled();
 }
 
+void BakedOpRenderer::clearColorBuffer(const Rect& rect) {
+    if (Rect(mRenderTarget.viewportWidth, mRenderTarget.viewportHeight).contains(rect)) {
+        // Full viewport is being cleared - disable scissor
+        mRenderState.scissor().setEnabled(false);
+    } else {
+        // Requested rect is subset of viewport - scissor to it to avoid over-clearing
+        mRenderState.scissor().setEnabled(true);
+        mRenderState.scissor().set(rect.left, mRenderTarget.viewportHeight - rect.bottom,
+                rect.getWidth(), rect.getHeight());
+    }
+    glClear(GL_COLOR_BUFFER_BIT);
+    if (!mRenderTarget.frameBufferId) mHasDrawn = true;
+}
+
 Texture* BakedOpRenderer::getTexture(const SkBitmap* bitmap) {
     Texture* texture = mRenderState.assetAtlas().getEntryTexture(bitmap);
     if (!texture) {
@@ -136,7 +145,7 @@
         mRenderTarget.offscreenBuffer->region.orSelf(dirty);
     }
     mRenderState.render(glop, mRenderTarget.orthoMatrix);
-    mHasDrawn = true;
+    if (!mRenderTarget.frameBufferId) mHasDrawn = true;
 }
 
 ////////////////////////////////////////////////////////////////////////////////
@@ -217,7 +226,7 @@
     paint.setAntiAlias(true); // want to use AlphaVertex
 
     // The caller has made sure casterAlpha > 0.
-    uint8_t ambientShadowAlpha = 128u; //TODO: mAmbientShadowAlpha;
+    uint8_t ambientShadowAlpha = renderer.getLightInfo().ambientShadowAlpha;
     if (CC_UNLIKELY(Properties::overrideAmbientShadowStrength >= 0)) {
         ambientShadowAlpha = Properties::overrideAmbientShadowStrength;
     }
@@ -227,7 +236,7 @@
                 paint, VertexBufferRenderFlags::ShadowInterp);
     }
 
-    uint8_t spotShadowAlpha = 128u; //TODO: mSpotShadowAlpha;
+    uint8_t spotShadowAlpha = renderer.getLightInfo().spotShadowAlpha;
     if (CC_UNLIKELY(Properties::overrideSpotShadowStrength >= 0)) {
         spotShadowAlpha = Properties::overrideSpotShadowStrength;
     }
@@ -240,12 +249,10 @@
 
 void BakedOpDispatcher::onShadowOp(BakedOpRenderer& renderer, const ShadowOp& op, const BakedOpState& state) {
     TessellationCache::vertexBuffer_pair_t buffers;
-    Vector3 lightCenter = { 300, 300, 300 }; // TODO!
-    float lightRadius = 150; // TODO!
-
     renderer.caches().tessellationCache.getShadowBuffers(&state.computedState.transform,
             op.localClipRect, op.casterAlpha >= 1.0f, op.casterPath,
-            &op.shadowMatrixXY, &op.shadowMatrixZ, lightCenter, lightRadius,
+            &op.shadowMatrixXY, &op.shadowMatrixZ,
+            op.lightCenter, renderer.getLightInfo().lightRadius,
             buffers);
 
     renderShadow(renderer, state, op.casterAlpha, buffers.first, buffers.second);
diff --git a/libs/hwui/BakedOpRenderer.h b/libs/hwui/BakedOpRenderer.h
index d6d9cb1..29f9a6f 100644
--- a/libs/hwui/BakedOpRenderer.h
+++ b/libs/hwui/BakedOpRenderer.h
@@ -39,27 +39,39 @@
  */
 class BakedOpRenderer {
 public:
-    BakedOpRenderer(Caches& caches, RenderState& renderState, bool opaque)
+    /**
+     * Position agnostic shadow lighting info. Used with all shadow ops in scene.
+     */
+    struct LightInfo {
+        float lightRadius = 0;
+        uint8_t ambientShadowAlpha = 0;
+        uint8_t spotShadowAlpha = 0;
+    };
+
+    BakedOpRenderer(Caches& caches, RenderState& renderState, bool opaque, const LightInfo& lightInfo)
             : mRenderState(renderState)
             , mCaches(caches)
-            , mOpaque(opaque) {
+            , mOpaque(opaque)
+            , mLightInfo(lightInfo) {
     }
 
     RenderState& renderState() { return mRenderState; }
     Caches& caches() { return mCaches; }
 
-    void startFrame(uint32_t width, uint32_t height);
+    void startFrame(uint32_t width, uint32_t height, const Rect& repaintRect);
     void endFrame();
     OffscreenBuffer* startTemporaryLayer(uint32_t width, uint32_t height);
-    void startRepaintLayer(OffscreenBuffer* offscreenBuffer);
+    void startRepaintLayer(OffscreenBuffer* offscreenBuffer, const Rect& repaintRect);
     void endLayer();
 
     Texture* getTexture(const SkBitmap* bitmap);
+    const LightInfo& getLightInfo() { return mLightInfo; }
 
     void renderGlop(const BakedOpState& state, const Glop& glop);
     bool didDraw() { return mHasDrawn; }
 private:
     void setViewport(uint32_t width, uint32_t height);
+    void clearColorBuffer(const Rect& clearRect);
 
     RenderState& mRenderState;
     Caches& mCaches;
@@ -75,6 +87,8 @@
         uint32_t viewportHeight = 0;
         Matrix4 orthoMatrix;
     } mRenderTarget;
+
+    const LightInfo mLightInfo;
 };
 
 /**
diff --git a/libs/hwui/OpReorderer.cpp b/libs/hwui/OpReorderer.cpp
index 80efaed..b04f16f 100644
--- a/libs/hwui/OpReorderer.cpp
+++ b/libs/hwui/OpReorderer.cpp
@@ -18,6 +18,7 @@
 
 #include "LayerUpdateQueue.h"
 #include "RenderNode.h"
+#include "renderstate/OffscreenBufferPool.h"
 #include "utils/FatVector.h"
 #include "utils/PaintUtils.h"
 
@@ -33,8 +34,8 @@
 
 public:
     BatchBase(batchid_t batchId, BakedOpState* op, bool merging)
-        : mBatchId(batchId)
-        , mMerging(merging) {
+            : mBatchId(batchId)
+            , mMerging(merging) {
         mBounds = op->computedState.clippedBounds;
         mOps.push_back(op);
     }
@@ -207,9 +208,10 @@
 };
 
 OpReorderer::LayerReorderer::LayerReorderer(uint32_t width, uint32_t height,
-        const BeginLayerOp* beginLayerOp, RenderNode* renderNode)
+        const Rect& repaintRect, const BeginLayerOp* beginLayerOp, RenderNode* renderNode)
         : width(width)
         , height(height)
+        , repaintRect(repaintRect)
         , offscreenBuffer(renderNode ? renderNode->getLayer() : nullptr)
         , beginLayerOp(beginLayerOp)
         , renderNode(renderNode) {}
@@ -309,15 +311,19 @@
 
 OpReorderer::OpReorderer(const LayerUpdateQueue& layers, const SkRect& clip,
         uint32_t viewportWidth, uint32_t viewportHeight,
-        const std::vector< sp<RenderNode> >& nodes)
+        const std::vector< sp<RenderNode> >& nodes, const Vector3& lightCenter)
         : mCanvasState(*this) {
     ATRACE_NAME("prepare drawing commands");
-    mLayerReorderers.emplace_back(viewportWidth, viewportHeight);
-    mLayerStack.push_back(0);
 
+    mLayerReorderers.reserve(layers.entries().size());
+    mLayerStack.reserve(layers.entries().size());
+
+    // Prepare to defer Fbo0
+    mLayerReorderers.emplace_back(viewportWidth, viewportHeight, Rect(clip));
+    mLayerStack.push_back(0);
     mCanvasState.initializeSaveStack(viewportWidth, viewportHeight,
             clip.fLeft, clip.fTop, clip.fRight, clip.fBottom,
-            Vector3());
+            lightCenter);
 
     // Render all layers to be updated, in order. Defer in reverse order, so that they'll be
     // updated in the order they're passed in (mLayerReorderers are issued to Renderer in reverse)
@@ -325,7 +331,8 @@
         RenderNode* layerNode = layers.entries()[i].renderNode;
         const Rect& layerDamage = layers.entries()[i].damage;
 
-        saveForLayer(layerNode->getWidth(), layerNode->getHeight(), nullptr, layerNode);
+        saveForLayer(layerNode->getWidth(), layerNode->getHeight(),
+                layerDamage, nullptr, layerNode);
         mCanvasState.writableSnapshot()->setClip(
                 layerDamage.left, layerDamage.top, layerDamage.right, layerDamage.bottom);
 
@@ -345,14 +352,17 @@
     }
 }
 
-OpReorderer::OpReorderer(int viewportWidth, int viewportHeight, const DisplayList& displayList)
+OpReorderer::OpReorderer(int viewportWidth, int viewportHeight, const DisplayList& displayList,
+        const Vector3& lightCenter)
         : mCanvasState(*this) {
     ATRACE_NAME("prepare drawing commands");
-    mLayerReorderers.emplace_back(viewportWidth, viewportHeight);
+    // Prepare to defer Fbo0
+    mLayerReorderers.emplace_back(viewportWidth, viewportHeight,
+            Rect(viewportWidth, viewportHeight));
     mLayerStack.push_back(0);
-
     mCanvasState.initializeSaveStack(viewportWidth, viewportHeight,
-            0, 0, viewportWidth, viewportHeight, Vector3());
+            0, 0, viewportWidth, viewportHeight, lightCenter);
+
     deferImpl(displayList);
 }
 
@@ -508,7 +518,8 @@
     }
 
     ShadowOp* shadowOp = new (mAllocator) ShadowOp(casterNodeOp, casterAlpha, casterPath,
-            mCanvasState.getLocalClipBounds());
+            mCanvasState.getLocalClipBounds(),
+            mCanvasState.currentSnapshot()->getRelativeLightCenter());
     BakedOpState* bakedOpState = BakedOpState::tryShadowOpConstruct(
             mAllocator, *mCanvasState.currentSnapshot(), shadowOp);
     if (CC_LIKELY(bakedOpState)) {
@@ -589,17 +600,36 @@
     currentLayer().deferUnmergeableOp(mAllocator, bakedStateOp, OpBatchType::Vertices);
 }
 
-void OpReorderer::saveForLayer(uint32_t layerWidth, uint32_t layerHeight,
+void OpReorderer::saveForLayer(uint32_t layerWidth, uint32_t layerHeight, const Rect& repaintRect,
         const BeginLayerOp* beginLayerOp, RenderNode* renderNode) {
 
+    auto previous = mCanvasState.currentSnapshot();
     mCanvasState.save(SkCanvas::kClip_SaveFlag | SkCanvas::kMatrix_SaveFlag);
     mCanvasState.writableSnapshot()->transform->loadIdentity();
     mCanvasState.writableSnapshot()->initializeViewport(layerWidth, layerHeight);
     mCanvasState.writableSnapshot()->roundRectClipState = nullptr;
 
+    Vector3 lightCenter = previous->getRelativeLightCenter();
+    if (renderNode) {
+        Matrix4& inverse = renderNode->getLayer()->inverseTransformInWindow;
+        inverse.mapPoint3d(lightCenter);
+    } else {
+        // Combine all transforms used to present saveLayer content:
+        // parent content transform * canvas transform * bounds offset
+        Matrix4 contentTransform(*previous->transform);
+        contentTransform.multiply(beginLayerOp->localMatrix);
+        contentTransform.translate(beginLayerOp->unmappedBounds.left, beginLayerOp->unmappedBounds.top);
+
+        // inverse the total transform, to map light center into layer-relative space
+        Matrix4 inverse;
+        inverse.loadInverse(contentTransform);
+        inverse.mapPoint3d(lightCenter);
+    }
+    mCanvasState.writableSnapshot()->setRelativeLightCenter(lightCenter);
+
     // create a new layer, and push its index on the stack
     mLayerStack.push_back(mLayerReorderers.size());
-    mLayerReorderers.emplace_back(layerWidth, layerHeight, beginLayerOp, renderNode);
+    mLayerReorderers.emplace_back(layerWidth, layerHeight, repaintRect, beginLayerOp, renderNode);
 }
 
 void OpReorderer::restoreForLayer() {
@@ -612,7 +642,7 @@
 void OpReorderer::onBeginLayerOp(const BeginLayerOp& op) {
     const uint32_t layerWidth = (uint32_t) op.unmappedBounds.getWidth();
     const uint32_t layerHeight = (uint32_t) op.unmappedBounds.getHeight();
-    saveForLayer(layerWidth, layerHeight, &op, nullptr);
+    saveForLayer(layerWidth, layerHeight, Rect(layerWidth, layerHeight), &op, nullptr);
 }
 
 void OpReorderer::onEndLayerOp(const EndLayerOp& /* ignored */) {
diff --git a/libs/hwui/OpReorderer.h b/libs/hwui/OpReorderer.h
index 2c30f0d..09d5cbc 100644
--- a/libs/hwui/OpReorderer.h
+++ b/libs/hwui/OpReorderer.h
@@ -67,13 +67,13 @@
     class LayerReorderer {
     public:
         // Create LayerReorderer for Fbo0
-        LayerReorderer(uint32_t width, uint32_t height)
-                : LayerReorderer(width, height, nullptr, nullptr) {};
+        LayerReorderer(uint32_t width, uint32_t height, const Rect& repaintRect)
+                : LayerReorderer(width, height, repaintRect, nullptr, nullptr) {};
 
         // Create LayerReorderer for an offscreen layer, where beginLayerOp is present for a
         // saveLayer, renderNode is present for a HW layer.
         LayerReorderer(uint32_t width, uint32_t height,
-                const BeginLayerOp* beginLayerOp, RenderNode* renderNode);
+                const Rect& repaintRect, const BeginLayerOp* beginLayerOp, RenderNode* renderNode);
 
         // iterate back toward target to see if anything drawn since should overlap the new op
         // if no target, merging ops still iterate to find similar batch to insert after
@@ -101,6 +101,7 @@
 
         const uint32_t width;
         const uint32_t height;
+        const Rect repaintRect;
         OffscreenBuffer* offscreenBuffer;
         const BeginLayerOp* beginLayerOp;
         const RenderNode* renderNode;
@@ -116,14 +117,15 @@
 
         // Maps batch ids to the most recent *non-merging* batch of that id
         OpBatch* mBatchLookup[OpBatchType::Count] = { nullptr };
-
     };
+
 public:
     OpReorderer(const LayerUpdateQueue& layers, const SkRect& clip,
             uint32_t viewportWidth, uint32_t viewportHeight,
-            const std::vector< sp<RenderNode> >& nodes);
+            const std::vector< sp<RenderNode> >& nodes, const Vector3& lightCenter);
 
-    OpReorderer(int viewportWidth, int viewportHeight, const DisplayList& displayList);
+    OpReorderer(int viewportWidth, int viewportHeight, const DisplayList& displayList,
+            const Vector3& lightCenter);
 
     virtual ~OpReorderer() {}
 
@@ -153,7 +155,7 @@
             LayerReorderer& layer = mLayerReorderers[i];
             if (layer.renderNode) {
                 // cached HW layer - can't skip layer if empty
-                renderer.startRepaintLayer(layer.offscreenBuffer);
+                renderer.startRepaintLayer(layer.offscreenBuffer, layer.repaintRect);
                 layer.replayBakedOpsImpl((void*)&renderer, receivers);
                 renderer.endLayer();
             } else if (!layer.empty()) { // save layer - skip entire layer if empty
@@ -164,7 +166,7 @@
         }
 
         const LayerReorderer& fbo0 = mLayerReorderers[0];
-        renderer.startFrame(fbo0.width, fbo0.height);
+        renderer.startFrame(fbo0.width, fbo0.height, fbo0.repaintRect);
         fbo0.replayBakedOpsImpl((void*)&renderer, receivers);
         renderer.endFrame();
     }
@@ -188,7 +190,7 @@
         Positive
     };
     void saveForLayer(uint32_t layerWidth, uint32_t layerHeight,
-            const BeginLayerOp* beginLayerOp, RenderNode* renderNode);
+            const Rect& repaintRect, const BeginLayerOp* beginLayerOp, RenderNode* renderNode);
     void restoreForLayer();
 
     LayerReorderer& currentLayer() { return mLayerReorderers[mLayerStack.back()]; }
diff --git a/libs/hwui/RecordedOp.h b/libs/hwui/RecordedOp.h
index bb7a0a7c..ef05367 100644
--- a/libs/hwui/RecordedOp.h
+++ b/libs/hwui/RecordedOp.h
@@ -17,10 +17,11 @@
 #ifndef ANDROID_HWUI_RECORDED_OP_H
 #define ANDROID_HWUI_RECORDED_OP_H
 
-#include "utils/LinearAllocator.h"
-#include "Rect.h"
 #include "Matrix.h"
+#include "Rect.h"
 #include "RenderNode.h"
+#include "utils/LinearAllocator.h"
+#include "Vector.h"
 
 #include "SkXfermode.h"
 
@@ -116,15 +117,17 @@
  * Uses invalid/empty bounds and matrix since ShadowOp bounds aren't known at defer time,
  * and are resolved dynamically, and transform isn't needed.
  *
- * State construction handles these properties specially.
+ * State construction handles these properties specially, ignoring matrix/bounds.
  */
 struct ShadowOp : RecordedOp {
-    ShadowOp(const RenderNodeOp& casterOp, float casterAlpha, const SkPath* casterPath, const Rect& clipRect)
+    ShadowOp(const RenderNodeOp& casterOp, float casterAlpha, const SkPath* casterPath,
+            const Rect& clipRect, const Vector3& lightCenter)
             : RecordedOp(RecordedOpId::ShadowOp, Rect(), Matrix4::identity(), clipRect, nullptr)
             , shadowMatrixXY(casterOp.localMatrix)
             , shadowMatrixZ(casterOp.localMatrix)
             , casterAlpha(casterAlpha)
-            , casterPath(casterPath) {
+            , casterPath(casterPath)
+            , lightCenter(lightCenter) {
         const RenderNode& node = *casterOp.renderNode;
         node.applyViewPropertyTransforms(shadowMatrixXY, false);
         node.applyViewPropertyTransforms(shadowMatrixZ, true);
@@ -133,6 +136,7 @@
     Matrix4 shadowMatrixZ;
     const float casterAlpha;
     const SkPath* casterPath;
+    const Vector3 lightCenter;
 };
 
 struct SimpleRectsOp : RecordedOp { // Filled, no AA (TODO: better name?)
diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp
index e988555..6ab253c 100644
--- a/libs/hwui/RecordingCanvas.cpp
+++ b/libs/hwui/RecordingCanvas.cpp
@@ -156,7 +156,7 @@
 
     snapshot.flags |= Snapshot::kFlagFboTarget | Snapshot::kFlagIsFboLayer;
     snapshot.initializeViewport(untransformedBounds.getWidth(), untransformedBounds.getHeight());
-    snapshot.resetTransform(-untransformedBounds.left, -untransformedBounds.top, 0.0f);
+    snapshot.transform->loadTranslate(-untransformedBounds.left, -untransformedBounds.top, 0.0f);
 
     Rect clip = layerBounds;
     clip.translate(-untransformedBounds.left, -untransformedBounds.top);
diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp
index e177f9a..2713f46 100644
--- a/libs/hwui/RenderNode.cpp
+++ b/libs/hwui/RenderNode.cpp
@@ -326,15 +326,11 @@
         return;
     }
 
-    if (transformUpdateNeeded) {
+    if (transformUpdateNeeded && mLayer) {
         // update the transform in window of the layer to reset its origin wrt light source position
         Matrix4 windowTransform;
         info.damageAccumulator->computeCurrentTransform(&windowTransform);
-#if HWUI_NEW_OPS
-        // TODO: update layer transform (perhaps as part of enqueueLayerWithDamage)
-#else
         mLayer->setWindowTransform(windowTransform);
-#endif
     }
 
 #if HWUI_NEW_OPS
diff --git a/libs/hwui/Snapshot.cpp b/libs/hwui/Snapshot.cpp
index 0a58f4b..2f535bb 100644
--- a/libs/hwui/Snapshot.cpp
+++ b/libs/hwui/Snapshot.cpp
@@ -130,6 +130,9 @@
 ///////////////////////////////////////////////////////////////////////////////
 
 void Snapshot::resetTransform(float x, float y, float z) {
+#if HWUI_NEW_OPS
+    LOG_ALWAYS_FATAL("not supported - light center managed differently");
+#else
     // before resetting, map current light pos with inverse of current transform
     Vector3 center = mRelativeLightCenter;
     mat4 inverse;
@@ -139,6 +142,7 @@
 
     transform = &mTransformRoot;
     transform->loadTranslate(x, y, z);
+#endif
 }
 
 void Snapshot::buildScreenSpaceTransform(Matrix4* outTransform) const {
diff --git a/libs/hwui/microbench/OpReordererBench.cpp b/libs/hwui/microbench/OpReordererBench.cpp
index 7b8d0e5..b24858e 100644
--- a/libs/hwui/microbench/OpReordererBench.cpp
+++ b/libs/hwui/microbench/OpReordererBench.cpp
@@ -48,7 +48,7 @@
 void BM_OpReorderer_defer::Run(int iters) {
     StartBenchmarkTiming();
     for (int i = 0; i < iters; i++) {
-        OpReorderer reorderer(200, 200, *sReorderingDisplayList);
+        OpReorderer reorderer(200, 200, *sReorderingDisplayList, (Vector3) { 100, 100, 100 });
         MicroBench::DoNotOptimize(&reorderer);
     }
     StopBenchmarkTiming();
@@ -59,11 +59,13 @@
     TestUtils::runOnRenderThread([this, iters](renderthread::RenderThread& thread) {
         RenderState& renderState = thread.renderState();
         Caches& caches = Caches::getInstance();
+        BakedOpRenderer::LightInfo lightInfo = { 50.0f, 128, 128 };
+
         StartBenchmarkTiming();
         for (int i = 0; i < iters; i++) {
-            OpReorderer reorderer(200, 200, *sReorderingDisplayList);
+            OpReorderer reorderer(200, 200, *sReorderingDisplayList, (Vector3) { 100, 100, 100 });
 
-            BakedOpRenderer renderer(caches, renderState, true);
+            BakedOpRenderer renderer(caches, renderState, true, lightInfo);
             reorderer.replayBakedOps<BakedOpDispatcher>(renderer);
             MicroBench::DoNotOptimize(&renderer);
         }
diff --git a/libs/hwui/renderstate/OffscreenBufferPool.h b/libs/hwui/renderstate/OffscreenBufferPool.h
index f0fd82d..fac6c35 100644
--- a/libs/hwui/renderstate/OffscreenBufferPool.h
+++ b/libs/hwui/renderstate/OffscreenBufferPool.h
@@ -34,6 +34,11 @@
  * Lightweight alternative to Layer. Owns the persistent state of an offscreen render target, and
  * encompasses enough information to draw it back on screen (minus paint properties, which are held
  * by LayerOp).
+ *
+ * Has two distinct sizes - viewportWidth/viewportHeight describe content area,
+ * texture.width/.height are actual allocated texture size. Texture will tend to be larger than the
+ * viewport bounds, since textures are always allocated with width / height as a multiple of 64, for
+ * the purpose of improving reuse.
  */
 class OffscreenBuffer {
 public:
@@ -44,11 +49,17 @@
     // must be called prior to rendering, to construct/update vertex buffer
     void updateMeshFromRegion();
 
+    // Set by RenderNode for HW layers, TODO for clipped saveLayers
+    void setWindowTransform(const Matrix4& transform) {
+        inverseTransformInWindow.loadInverse(transform);
+    }
+
     static uint32_t computeIdealDimension(uint32_t dimension);
 
     uint32_t getSizeInBytes() { return texture.width * texture.height * 4; }
 
     RenderState& renderState;
+
     uint32_t viewportWidth;
     uint32_t viewportHeight;
     Texture texture;
@@ -56,6 +67,10 @@
     // Portion of layer that has been drawn to. Used to minimize drawing area when
     // drawing back to screen / parent FBO.
     Region region;
+
+    Matrix4 inverseTransformInWindow;
+
+    // vbo / size of mesh
     GLsizei elementCount = 0;
     GLuint vbo = 0;
 };
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index f094b2d..89cadea 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -31,7 +31,6 @@
 #include "utils/TimeUtils.h"
 
 #if HWUI_NEW_OPS
-#include "BakedOpRenderer.h"
 #include "OpReorderer.h"
 #endif
 
@@ -150,13 +149,23 @@
 // TODO: don't pass viewport size, it's automatic via EGL
 void CanvasContext::setup(int width, int height, float lightRadius,
         uint8_t ambientShadowAlpha, uint8_t spotShadowAlpha) {
+#if HWUI_NEW_OPS
+    mLightInfo.lightRadius = lightRadius;
+    mLightInfo.ambientShadowAlpha = ambientShadowAlpha;
+    mLightInfo.spotShadowAlpha = spotShadowAlpha;
+#else
     if (!mCanvas) return;
     mCanvas->initLight(lightRadius, ambientShadowAlpha, spotShadowAlpha);
+#endif
 }
 
 void CanvasContext::setLightCenter(const Vector3& lightCenter) {
+#if HWUI_NEW_OPS
+    mLightCenter = lightCenter;
+#else
     if (!mCanvas) return;
     mCanvas->setLightCenter(lightCenter);
+#endif
 }
 
 void CanvasContext::setOpaque(bool opaque) {
@@ -340,9 +349,11 @@
     mEglManager.damageFrame(frame, dirty);
 
 #if HWUI_NEW_OPS
-    OpReorderer reorderer(mLayerUpdateQueue, dirty, frame.width(), frame.height(), mRenderNodes);
+    OpReorderer reorderer(mLayerUpdateQueue, dirty, frame.width(), frame.height(),
+            mRenderNodes, mLightCenter);
     mLayerUpdateQueue.clear();
-    BakedOpRenderer renderer(Caches::getInstance(), mRenderThread.renderState(), mOpaque);
+    BakedOpRenderer renderer(Caches::getInstance(), mRenderThread.renderState(),
+            mOpaque, mLightInfo);
     // TODO: profiler().draw(mCanvas);
     reorderer.replayBakedOps<BakedOpDispatcher>(renderer);
 
@@ -576,8 +587,12 @@
     // purposes when the frame is actually drawn
     node->setPropertyFieldsDirty(RenderNode::GENERIC);
 
+#if HWUI_NEW_OPS
+    LOG_ALWAYS_FATAL("unsupported");
+#else
     mCanvas->markLayersAsBuildLayers();
     mCanvas->flushLayerUpdates();
+#endif
 
     node->incStrong(nullptr);
     mPrefetechedLayers.insert(node);
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index d656014..c3cfc94 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -27,6 +27,10 @@
 #include "renderthread/RenderTask.h"
 #include "renderthread/RenderThread.h"
 
+#if HWUI_NEW_OPS
+#include "BakedOpRenderer.h"
+#endif
+
 #include <cutils/compiler.h>
 #include <EGL/egl.h>
 #include <SkBitmap.h>
@@ -165,6 +169,11 @@
 
     bool mOpaque;
     OpenGLRenderer* mCanvas = nullptr;
+#if HWUI_NEW_OPS
+    BakedOpRenderer::LightInfo mLightInfo;
+    Vector3 mLightCenter = { 0, 0, 0 };
+#endif
+
     bool mHaveNewSurface = false;
     DamageAccumulator mDamageAccumulator;
     LayerUpdateQueue mLayerUpdateQueue;
diff --git a/libs/hwui/unit_tests/OpReordererTests.cpp b/libs/hwui/unit_tests/OpReordererTests.cpp
index 07080a2..a8c9bba 100644
--- a/libs/hwui/unit_tests/OpReordererTests.cpp
+++ b/libs/hwui/unit_tests/OpReordererTests.cpp
@@ -29,6 +29,14 @@
 namespace uirenderer {
 
 LayerUpdateQueue sEmptyLayerUpdateQueue;
+Vector3 sLightCenter = {100, 100, 100};
+
+static std::vector<sp<RenderNode>> createSyncedNodeList(sp<RenderNode>& node) {
+    TestUtils::syncHierarchyPropertiesAndDisplayList(node);
+    std::vector<sp<RenderNode>> vec;
+    vec.emplace_back(node);
+    return vec;
+}
 
 /**
  * Virtual class implemented by each test to redirect static operation / state transitions to
@@ -48,13 +56,13 @@
         ADD_FAILURE() << "Layer creation not expected in this test";
         return nullptr;
     }
-    virtual void startRepaintLayer(OffscreenBuffer*) {
+    virtual void startRepaintLayer(OffscreenBuffer*, const Rect& repaintRect) {
         ADD_FAILURE() << "Layer repaint not expected in this test";
     }
     virtual void endLayer() {
         ADD_FAILURE() << "Layer updates not expected in this test";
     }
-    virtual void startFrame(uint32_t width, uint32_t height) {}
+    virtual void startFrame(uint32_t width, uint32_t height, const Rect& repaintRect) {}
     virtual void endFrame() {}
 
     // define virtual defaults for direct
@@ -87,7 +95,7 @@
 TEST(OpReorderer, simple) {
     class SimpleTestRenderer : public TestRendererBase {
     public:
-        void startFrame(uint32_t width, uint32_t height) override {
+        void startFrame(uint32_t width, uint32_t height, const Rect& repaintRect) override {
             EXPECT_EQ(0, mIndex++);
             EXPECT_EQ(100u, width);
             EXPECT_EQ(200u, height);
@@ -108,7 +116,7 @@
         canvas.drawRect(0, 0, 100, 200, SkPaint());
         canvas.drawBitmap(bitmap, 10, 10, nullptr);
     });
-    OpReorderer reorderer(100, 200, *dl);
+    OpReorderer reorderer(100, 200, *dl, sLightCenter);
 
     SimpleTestRenderer renderer;
     reorderer.replayBakedOps<TestDispatcher>(renderer);
@@ -122,7 +130,7 @@
         canvas.drawRect(0, 0, 400, 400, SkPaint());
         canvas.restore();
     });
-    OpReorderer reorderer(200, 200, *dl);
+    OpReorderer reorderer(200, 200, *dl, sLightCenter);
 
     FailRenderer renderer;
     reorderer.replayBakedOps<TestDispatcher>(renderer);
@@ -154,7 +162,7 @@
         canvas.restore();
     });
 
-    OpReorderer reorderer(200, 200, *dl);
+    OpReorderer reorderer(200, 200, *dl, sLightCenter);
 
     SimpleBatchingTestRenderer renderer;
     reorderer.replayBakedOps<TestDispatcher>(renderer);
@@ -198,13 +206,8 @@
         canvas.restore();
     });
 
-    TestUtils::syncHierarchyPropertiesAndDisplayList(parent);
-
-    std::vector< sp<RenderNode> > nodes;
-    nodes.push_back(parent.get());
-
-    OpReorderer reorderer(sEmptyLayerUpdateQueue,
-            SkRect::MakeWH(200, 200), 200, 200, nodes);
+    OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
+            createSyncedNodeList(parent), sLightCenter);
 
     RenderNodeTestRenderer renderer;
     reorderer.replayBakedOps<TestDispatcher>(renderer);
@@ -225,13 +228,10 @@
         SkBitmap bitmap = TestUtils::createSkBitmap(200, 200);
         canvas.drawBitmap(bitmap, 0, 0, nullptr);
     });
-    TestUtils::syncHierarchyPropertiesAndDisplayList(node);
-    std::vector< sp<RenderNode> > nodes;
-    nodes.push_back(node.get());
 
     OpReorderer reorderer(sEmptyLayerUpdateQueue,
             SkRect::MakeLTRB(10, 20, 30, 40), // clip to small area, should see in receiver
-            200, 200, nodes);
+            200, 200, createSyncedNodeList(node), sLightCenter);
 
     ClippedTestRenderer renderer;
     reorderer.replayBakedOps<TestDispatcher>(renderer);
@@ -273,7 +273,7 @@
         canvas.restore();
     });
 
-    OpReorderer reorderer(200, 200, *dl);
+    OpReorderer reorderer(200, 200, *dl, sLightCenter);
 
     SaveLayerSimpleTestRenderer renderer;
     reorderer.replayBakedOps<TestDispatcher>(renderer);
@@ -305,7 +305,7 @@
             int index = mIndex++;
             EXPECT_TRUE(index == 2 || index == 6);
         }
-        void startFrame(uint32_t width, uint32_t height) override {
+        void startFrame(uint32_t width, uint32_t height, const Rect& repaintRect) override {
             EXPECT_EQ(7, mIndex++);
         }
         void endFrame() override {
@@ -344,7 +344,7 @@
         canvas.restore();
     });
 
-    OpReorderer reorderer(800, 800, *dl);
+    OpReorderer reorderer(800, 800, *dl, sLightCenter);
 
     SaveLayerNestedTestRenderer renderer;
     reorderer.replayBakedOps<TestDispatcher>(renderer);
@@ -363,19 +363,21 @@
         canvas.restore();
         canvas.restore();
     });
-    OpReorderer reorderer(200, 200, *dl);
+    OpReorderer reorderer(200, 200, *dl, sLightCenter);
 
     FailRenderer renderer;
     // should see no ops, even within the layer, since the layer should be rejected
     reorderer.replayBakedOps<TestDispatcher>(renderer);
 }
 
-TEST(OpReorderer, hwLayerSimple) {
+RENDERTHREAD_TEST(OpReorderer, hwLayerSimple) {
     class HwLayerSimpleTestRenderer : public TestRendererBase {
     public:
-        void startRepaintLayer(OffscreenBuffer* offscreenBuffer) override {
+        void startRepaintLayer(OffscreenBuffer* offscreenBuffer, const Rect& repaintRect) override {
             EXPECT_EQ(0, mIndex++);
-            EXPECT_EQ(offscreenBuffer, (OffscreenBuffer*) 0x0124);
+            EXPECT_EQ(100u, offscreenBuffer->viewportWidth);
+            EXPECT_EQ(100u, offscreenBuffer->viewportHeight);
+            EXPECT_EQ(Rect(25, 25, 75, 75), repaintRect);
         }
         void onRectOp(const RectOp& op, const BakedOpState& state) override {
             EXPECT_EQ(1, mIndex++);
@@ -389,7 +391,7 @@
         void endLayer() override {
             EXPECT_EQ(2, mIndex++);
         }
-        void startFrame(uint32_t width, uint32_t height) override {
+        void startFrame(uint32_t width, uint32_t height, const Rect& repaintRect) override {
             EXPECT_EQ(3, mIndex++);
         }
         void onLayerOp(const LayerOp& op, const BakedOpState& state) override {
@@ -405,29 +407,29 @@
         paint.setColor(SK_ColorWHITE);
         canvas.drawRect(0, 0, 100, 100, paint);
     }, TestUtils::getHwLayerSetupCallback());
-    OffscreenBuffer** bufferHandle = node->getLayerHandle();
-    *bufferHandle = (OffscreenBuffer*) 0x0124;
+    OffscreenBuffer** layerHandle = node->getLayerHandle();
 
-    TestUtils::syncHierarchyPropertiesAndDisplayList(node);
+    // create RenderNode's layer here in same way prepareTree would
+    OffscreenBuffer layer(renderThread.renderState(), Caches::getInstance(), 100, 100);
+    *layerHandle = &layer;
 
-    std::vector< sp<RenderNode> > nodes;
-    nodes.push_back(node.get());
+    auto syncedNodeList = createSyncedNodeList(node);
 
     // only enqueue partial damage
-    LayerUpdateQueue layerUpdateQueue;
+    LayerUpdateQueue layerUpdateQueue; // Note: enqueue damage post-sync, so bounds are valid
     layerUpdateQueue.enqueueLayerWithDamage(node.get(), Rect(25, 25, 75, 75));
 
-    OpReorderer reorderer(layerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200, nodes);
-
+    OpReorderer reorderer(layerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
+            syncedNodeList, sLightCenter);
     HwLayerSimpleTestRenderer renderer;
     reorderer.replayBakedOps<TestDispatcher>(renderer);
     EXPECT_EQ(6, renderer.getIndex());
 
     // clean up layer pointer, so we can safely destruct RenderNode
-    *bufferHandle = nullptr;
+    *layerHandle = nullptr;
 }
 
-TEST(OpReorderer, hwLayerComplex) {
+RENDERTHREAD_TEST(OpReorderer, hwLayerComplex) {
     /* parentLayer { greyRect, saveLayer { childLayer { whiteRect } } } will play back as:
      * - startRepaintLayer(child), rect(grey), endLayer
      * - startTemporaryLayer, drawLayer(child), endLayer
@@ -440,14 +442,16 @@
             EXPECT_EQ(3, mIndex++); // savelayer first
             return (OffscreenBuffer*)0xabcd;
         }
-        void startRepaintLayer(OffscreenBuffer* offscreenBuffer) override {
+        void startRepaintLayer(OffscreenBuffer* offscreenBuffer, const Rect& repaintRect) override {
             int index = mIndex++;
             if (index == 0) {
                 // starting inner layer
-                EXPECT_EQ((OffscreenBuffer*)0x4567, offscreenBuffer);
+                EXPECT_EQ(100u, offscreenBuffer->viewportWidth);
+                EXPECT_EQ(100u, offscreenBuffer->viewportHeight);
             } else if (index == 6) {
                 // starting outer layer
-                EXPECT_EQ((OffscreenBuffer*)0x0123, offscreenBuffer);
+                EXPECT_EQ(200u, offscreenBuffer->viewportWidth);
+                EXPECT_EQ(200u, offscreenBuffer->viewportHeight);
             } else { ADD_FAILURE(); }
         }
         void onRectOp(const RectOp& op, const BakedOpState& state) override {
@@ -464,17 +468,20 @@
             int index = mIndex++;
             EXPECT_TRUE(index == 2 || index == 5 || index == 9);
         }
-        void startFrame(uint32_t width, uint32_t height) override {
+        void startFrame(uint32_t width, uint32_t height, const Rect& repaintRect) override {
             EXPECT_EQ(10, mIndex++);
         }
         void onLayerOp(const LayerOp& op, const BakedOpState& state) override {
+            OffscreenBuffer* layer = *op.layerHandle;
             int index = mIndex++;
             if (index == 4) {
-                EXPECT_EQ((OffscreenBuffer*)0x4567, *op.layerHandle);
+                EXPECT_EQ(100u, layer->viewportWidth);
+                EXPECT_EQ(100u, layer->viewportHeight);
             } else if (index == 8) {
                 EXPECT_EQ((OffscreenBuffer*)0xabcd, *op.layerHandle);
             } else if (index == 11) {
-                EXPECT_EQ((OffscreenBuffer*)0x0123, *op.layerHandle);
+                EXPECT_EQ(200u, layer->viewportWidth);
+                EXPECT_EQ(200u, layer->viewportHeight);
             } else { ADD_FAILURE(); }
         }
         void endFrame() override {
@@ -488,7 +495,8 @@
         paint.setColor(SK_ColorWHITE);
         canvas.drawRect(0, 0, 100, 100, paint);
     }, TestUtils::getHwLayerSetupCallback());
-    *(child->getLayerHandle()) = (OffscreenBuffer*) 0x4567;
+    OffscreenBuffer childLayer(renderThread.renderState(), Caches::getInstance(), 100, 100);
+    *(child->getLayerHandle()) = &childLayer;
 
     RenderNode* childPtr = child.get();
     auto parent = TestUtils::createNode<RecordingCanvas>(0, 0, 200, 200,
@@ -501,18 +509,17 @@
         canvas.drawRenderNode(childPtr);
         canvas.restore();
     }, TestUtils::getHwLayerSetupCallback());
-    *(parent->getLayerHandle()) = (OffscreenBuffer*) 0x0123;
+    OffscreenBuffer parentLayer(renderThread.renderState(), Caches::getInstance(), 200, 200);
+    *(parent->getLayerHandle()) = &parentLayer;
 
-    TestUtils::syncHierarchyPropertiesAndDisplayList(parent);
+    auto syncedList = createSyncedNodeList(parent);
 
-    std::vector< sp<RenderNode> > nodes;
-    nodes.push_back(parent.get());
-
-    LayerUpdateQueue layerUpdateQueue;
+    LayerUpdateQueue layerUpdateQueue; // Note: enqueue damage post-sync, so bounds are valid
     layerUpdateQueue.enqueueLayerWithDamage(child.get(), Rect(100, 100));
     layerUpdateQueue.enqueueLayerWithDamage(parent.get(), Rect(200, 200));
 
-    OpReorderer reorderer(layerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200, nodes);
+    OpReorderer reorderer(layerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
+            syncedList, sLightCenter);
 
     HwLayerComplexTestRenderer renderer;
     reorderer.replayBakedOps<TestDispatcher>(renderer);
@@ -561,58 +568,184 @@
         drawOrderedRect(&canvas, 8);
         drawOrderedNode(&canvas, 9, -10.0f); // in reorder=false at this point, so played inorder
     });
-    TestUtils::syncHierarchyPropertiesAndDisplayList(parent);
-
-    std::vector< sp<RenderNode> > nodes;
-    nodes.push_back(parent.get());
-    OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 100), 100, 100, nodes);
+    OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 100), 100, 100,
+            createSyncedNodeList(parent), sLightCenter);
 
     ZReorderTestRenderer renderer;
     reorderer.replayBakedOps<TestDispatcher>(renderer);
     EXPECT_EQ(10, renderer.getIndex());
 };
 
+// creates a 100x100 shadow casting node with provided translationZ
+static sp<RenderNode> createWhiteRectShadowCaster(float translationZ) {
+    return TestUtils::createNode<RecordingCanvas>(0, 0, 100, 100,
+            [](RecordingCanvas& canvas) {
+        SkPaint paint;
+        paint.setColor(SK_ColorWHITE);
+        canvas.drawRect(0, 0, 100, 100, paint);
+    }, [translationZ] (RenderProperties& properties) {
+        properties.setTranslationZ(translationZ);
+        properties.mutableOutline().setRoundRect(0, 0, 100, 100, 0.0f, 1.0f);
+        return RenderNode::GENERIC | RenderNode::TRANSLATION_Z;
+    });
+}
+
 TEST(OpReorderer, shadow) {
     class ShadowTestRenderer : public TestRendererBase {
     public:
         void onShadowOp(const ShadowOp& op, const BakedOpState& state) override {
             EXPECT_EQ(0, mIndex++);
+            EXPECT_FLOAT_EQ(1.0f, op.casterAlpha);
+            EXPECT_TRUE(op.casterPath->isRect(nullptr));
+            EXPECT_MATRIX_APPROX_EQ(Matrix4::identity(), op.shadowMatrixXY);
+
+            Matrix4 expectedZ;
+            expectedZ.loadTranslate(0, 0, 5);
+            EXPECT_MATRIX_APPROX_EQ(expectedZ, op.shadowMatrixZ);
         }
         void onRectOp(const RectOp& op, const BakedOpState& state) override {
             EXPECT_EQ(1, mIndex++);
         }
     };
 
-    sp<RenderNode> caster = TestUtils::createNode<RecordingCanvas>(0, 0, 100, 100,
-            [](RecordingCanvas& canvas) {
-        SkPaint paint;
-        paint.setColor(SK_ColorWHITE);
-        canvas.drawRect(0, 0, 100, 100, paint);
-    }, [] (RenderProperties& properties) {
-        properties.setTranslationZ(5.0f);
-        properties.mutableOutline().setRoundRect(0, 0, 100, 100, 5, 1.0f);
-        return RenderNode::GENERIC | RenderNode::TRANSLATION_Z;
-    });
     sp<RenderNode> parent = TestUtils::createNode<RecordingCanvas>(0, 0, 200, 200,
-            [&caster] (RecordingCanvas& canvas) {
+            [] (RecordingCanvas& canvas) {
         canvas.insertReorderBarrier(true);
-        canvas.drawRenderNode(caster.get());
+        canvas.drawRenderNode(createWhiteRectShadowCaster(5.0f).get());
     });
 
-    TestUtils::syncHierarchyPropertiesAndDisplayList(parent);
-
-    std::vector< sp<RenderNode> > nodes;
-    nodes.push_back(parent.get());
-
-    OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200, nodes);
+    OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
+            createSyncedNodeList(parent), sLightCenter);
 
     ShadowTestRenderer renderer;
     reorderer.replayBakedOps<TestDispatcher>(renderer);
     EXPECT_EQ(2, renderer.getIndex());
 }
 
-static void testProperty(
-        TestUtils::PropSetupCallback propSetupCallback,
+TEST(OpReorderer, shadowSaveLayer) {
+    class ShadowSaveLayerTestRenderer : public TestRendererBase {
+    public:
+        OffscreenBuffer* startTemporaryLayer(uint32_t width, uint32_t height) override {
+            EXPECT_EQ(0, mIndex++);
+            return nullptr;
+        }
+        void onShadowOp(const ShadowOp& op, const BakedOpState& state) override {
+            EXPECT_EQ(1, mIndex++);
+            EXPECT_FLOAT_EQ(50, op.lightCenter.x);
+            EXPECT_FLOAT_EQ(40, op.lightCenter.y);
+        }
+        void onRectOp(const RectOp& op, const BakedOpState& state) override {
+            EXPECT_EQ(2, mIndex++);
+        }
+        void endLayer() override {
+            EXPECT_EQ(3, mIndex++);
+        }
+        void onLayerOp(const LayerOp& op, const BakedOpState& state) override {
+            EXPECT_EQ(4, mIndex++);
+        }
+    };
+
+    sp<RenderNode> parent = TestUtils::createNode<RecordingCanvas>(0, 0, 200, 200,
+            [] (RecordingCanvas& canvas) {
+        // save/restore outside of reorderBarrier, so they don't get moved out of place
+        canvas.translate(20, 10);
+        int count = canvas.saveLayerAlpha(30, 50, 130, 150, 128, SkCanvas::kClipToLayer_SaveFlag);
+        canvas.insertReorderBarrier(true);
+        canvas.drawRenderNode(createWhiteRectShadowCaster(5.0f).get());
+        canvas.insertReorderBarrier(false);
+        canvas.restoreToCount(count);
+    });
+
+    OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
+            createSyncedNodeList(parent), (Vector3) { 100, 100, 100 });
+
+    ShadowSaveLayerTestRenderer renderer;
+    reorderer.replayBakedOps<TestDispatcher>(renderer);
+    EXPECT_EQ(5, renderer.getIndex());
+}
+
+RENDERTHREAD_TEST(OpReorderer, shadowHwLayer) {
+    class ShadowHwLayerTestRenderer : public TestRendererBase {
+    public:
+        void startRepaintLayer(OffscreenBuffer* offscreenBuffer, const Rect& repaintRect) override {
+            EXPECT_EQ(0, mIndex++);
+        }
+        void onShadowOp(const ShadowOp& op, const BakedOpState& state) override {
+            EXPECT_EQ(1, mIndex++);
+            EXPECT_FLOAT_EQ(50, op.lightCenter.x);
+            EXPECT_FLOAT_EQ(40, op.lightCenter.y);
+        }
+        void onRectOp(const RectOp& op, const BakedOpState& state) override {
+            EXPECT_EQ(2, mIndex++);
+        }
+        void endLayer() override {
+            EXPECT_EQ(3, mIndex++);
+        }
+        void onLayerOp(const LayerOp& op, const BakedOpState& state) override {
+            EXPECT_EQ(4, mIndex++);
+        }
+    };
+
+    sp<RenderNode> parent = TestUtils::createNode<RecordingCanvas>(50, 60, 150, 160,
+            [] (RecordingCanvas& canvas) {
+        canvas.insertReorderBarrier(true);
+        canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
+        canvas.translate(20, 10);
+        canvas.drawRenderNode(createWhiteRectShadowCaster(5.0f).get());
+        canvas.restore();
+    }, TestUtils::getHwLayerSetupCallback());
+    OffscreenBuffer** layerHandle = parent->getLayerHandle();
+
+    // create RenderNode's layer here in same way prepareTree would, setting windowTransform
+    OffscreenBuffer layer(renderThread.renderState(), Caches::getInstance(), 100, 100);
+    Matrix4 windowTransform;
+    windowTransform.loadTranslate(50, 60, 0); // total transform of layer's origin
+    layer.setWindowTransform(windowTransform);
+    *layerHandle = &layer;
+
+    auto syncedList = createSyncedNodeList(parent);
+    LayerUpdateQueue layerUpdateQueue; // Note: enqueue damage post-sync, so bounds are valid
+    layerUpdateQueue.enqueueLayerWithDamage(parent.get(), Rect(100, 100));
+    OpReorderer reorderer(layerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
+            syncedList, (Vector3) { 100, 100, 100 });
+
+    ShadowHwLayerTestRenderer renderer;
+    reorderer.replayBakedOps<TestDispatcher>(renderer);
+    EXPECT_EQ(5, renderer.getIndex());
+
+    // clean up layer pointer, so we can safely destruct RenderNode
+    *layerHandle = nullptr;
+}
+
+TEST(OpReorderer, shadowLayering) {
+    class ShadowLayeringTestRenderer : public TestRendererBase {
+    public:
+        void onShadowOp(const ShadowOp& op, const BakedOpState& state) override {
+            int index = mIndex++;
+            EXPECT_TRUE(index == 0 || index == 1);
+        }
+        void onRectOp(const RectOp& op, const BakedOpState& state) override {
+            int index = mIndex++;
+            EXPECT_TRUE(index == 2 || index == 3);
+        }
+    };
+    sp<RenderNode> parent = TestUtils::createNode<RecordingCanvas>(0, 0, 200, 200,
+            [] (RecordingCanvas& canvas) {
+        canvas.insertReorderBarrier(true);
+        canvas.drawRenderNode(createWhiteRectShadowCaster(5.0f).get());
+        canvas.drawRenderNode(createWhiteRectShadowCaster(5.0001f).get());
+    });
+
+    OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
+            createSyncedNodeList(parent), sLightCenter);
+
+    ShadowLayeringTestRenderer renderer;
+    reorderer.replayBakedOps<TestDispatcher>(renderer);
+    EXPECT_EQ(4, renderer.getIndex());
+}
+
+
+static void testProperty(TestUtils::PropSetupCallback propSetupCallback,
         std::function<void(const RectOp&, const BakedOpState&)> opValidateCallback) {
     class PropertyTestRenderer : public TestRendererBase {
     public:
@@ -630,13 +763,9 @@
         paint.setColor(SK_ColorWHITE);
         canvas.drawRect(0, 0, 100, 100, paint);
     }, propSetupCallback);
-    TestUtils::syncHierarchyPropertiesAndDisplayList(node);
 
-    std::vector< sp<RenderNode> > nodes;
-    nodes.push_back(node.get());
-
-    OpReorderer reorderer(sEmptyLayerUpdateQueue,
-            SkRect::MakeWH(100, 100), 200, 200, nodes);
+    OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 100), 200, 200,
+            createSyncedNodeList(node), sLightCenter);
 
     PropertyTestRenderer renderer(opValidateCallback);
     reorderer.replayBakedOps<TestDispatcher>(renderer);
diff --git a/libs/hwui/unit_tests/TestUtils.h b/libs/hwui/unit_tests/TestUtils.h
index efa28ae..38bafd5 100644
--- a/libs/hwui/unit_tests/TestUtils.h
+++ b/libs/hwui/unit_tests/TestUtils.h
@@ -45,6 +45,20 @@
             && MathUtils::areEqual(a.right, b.right) \
             && MathUtils::areEqual(a.bottom, b.bottom));
 
+/**
+ * Like gtest's TEST, but runs on the RenderThread, and 'renderThread' is passed, in top level scope
+ * (for e.g. accessing its RenderState)
+ */
+#define RENDERTHREAD_TEST(test_case_name, test_name) \
+    class test_case_name##_##test_name##_RenderThreadTest { \
+    public: \
+        static void doTheThing(renderthread::RenderThread& renderThread); \
+    }; \
+    TEST(test_case_name, test_name) { \
+        TestUtils::runOnRenderThread(test_case_name##_##test_name##_RenderThreadTest::doTheThing); \
+    }; \
+    void test_case_name##_##test_name##_RenderThreadTest::doTheThing(renderthread::RenderThread& renderThread)
+
 class TestUtils {
 public:
     class SignalingDtor {
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java
index 3151ccb..874eeb3 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java
@@ -79,8 +79,8 @@
     private final Map<String, Integer> mMappingMode = new HashMap<>();
 
     @VisibleForTesting
-    MtpDatabase(Context context) {
-        mDatabase = new MtpDatabaseInternal(context);
+    MtpDatabase(Context context, boolean inMemory) {
+        mDatabase = new MtpDatabaseInternal(context, inMemory);
     }
 
     /**
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabaseInternal.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabaseInternal.java
index 7328f05..df2875e 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabaseInternal.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabaseInternal.java
@@ -35,8 +35,8 @@
  */
 class MtpDatabaseInternal {
     private static class OpenHelper extends SQLiteOpenHelper {
-        public OpenHelper(Context context) {
-            super(context, DATABASE_NAME, null, DATABASE_VERSION);
+        public OpenHelper(Context context, boolean inMemory) {
+            super(context, inMemory ? null : DATABASE_NAME, null, DATABASE_VERSION);
         }
 
         @Override
@@ -54,8 +54,8 @@
 
     private final SQLiteDatabase mDatabase;
 
-    MtpDatabaseInternal(Context context) {
-        final OpenHelper helper = new OpenHelper(context);
+    MtpDatabaseInternal(Context context, boolean inMemory) {
+        final OpenHelper helper = new OpenHelper(context, inMemory);
         mDatabase = helper.getWritableDatabase();
     }
 
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java
index 0931445..f7e9463 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java
@@ -78,7 +78,7 @@
         mMtpManager = new MtpManager(getContext());
         mResolver = getContext().getContentResolver();
         mDeviceToolkits = new HashMap<Integer, DeviceToolkit>();
-        mDatabase = new MtpDatabase(getContext());
+        mDatabase = new MtpDatabase(getContext(), false);
         mRootScanner = new RootScanner(mResolver, mResources, mMtpManager, mDatabase);
         return true;
     }
diff --git a/packages/MtpDocumentsProvider/tests/AndroidManifest.xml b/packages/MtpDocumentsProvider/tests/AndroidManifest.xml
index 28ad3f4..e1307e9 100644
--- a/packages/MtpDocumentsProvider/tests/AndroidManifest.xml
+++ b/packages/MtpDocumentsProvider/tests/AndroidManifest.xml
@@ -18,7 +18,4 @@
     <instrumentation android:name="com.android.mtp.TestResultInstrumentation"
         android:targetPackage="com.android.mtp"
         android:label="Tests for MtpDocumentsProvider with the UI for output." />
-    <instrumentation android:name="android.test.InstrumentationTestRunner"
-        android:targetPackage="com.android.mtp"
-        android:label="Tests for MtpDocumentsProvider." />
 </manifest>
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/DocumentLoaderTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/DocumentLoaderTest.java
index a012d7f..f6d6d44 100644
--- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/DocumentLoaderTest.java
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/DocumentLoaderTest.java
@@ -22,15 +22,14 @@
 import android.net.Uri;
 import android.provider.DocumentsContract;
 import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.SmallTest;
+import android.test.suitebuilder.annotation.MediumTest;
 
 import java.io.IOException;
-import java.util.Date;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.concurrent.CountDownLatch;
 
-@SmallTest
+@MediumTest
 public class DocumentLoaderTest extends AndroidTestCase {
     private BlockableTestMtpManager mManager;
     private TestContentResolver mResolver;
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java
index ce4cf14..7641e3a 100644
--- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java
@@ -41,18 +41,29 @@
     };
 
     private final TestResources resources = new TestResources();
+    MtpDatabase mDatabase;
+
+    @Override
+    public void setUp() {
+        mDatabase = new MtpDatabase(getContext(), true);
+    }
+
+    @Override
+    public void tearDown() {
+        mDatabase.close();
+        mDatabase = null;
+    }
 
     public void testPutRootDocuments() throws Exception {
-        final MtpDatabase database = new MtpDatabase(getContext());
-        database.startAddingRootDocuments(0);
-        database.putRootDocuments(0, resources, new MtpRoot[] {
+        mDatabase.startAddingRootDocuments(0);
+        mDatabase.putRootDocuments(0, resources, new MtpRoot[] {
                 new MtpRoot(0, 1, "Device", "Storage", 1000, 2000, ""),
                 new MtpRoot(0, 2, "Device", "Storage", 2000, 4000, ""),
                 new MtpRoot(0, 3, "Device", "/@#%&<>Storage", 3000, 6000,"")
         });
 
         {
-            final Cursor cursor = database.queryRootDocuments(COLUMN_NAMES);
+            final Cursor cursor = mDatabase.queryRootDocuments(COLUMN_NAMES);
             assertEquals(3, cursor.getCount());
 
             cursor.moveToNext();
@@ -80,7 +91,7 @@
         }
 
         {
-            final Cursor cursor = database.queryRoots(new String [] {
+            final Cursor cursor = mDatabase.queryRoots(new String [] {
                     Root.COLUMN_ROOT_ID,
                     Root.COLUMN_FLAGS,
                     Root.COLUMN_ICON,
@@ -136,15 +147,14 @@
     }
 
     public void testPutChildDocuments() throws Exception {
-        final MtpDatabase database = new MtpDatabase(getContext());
-        database.startAddingChildDocuments("parentId");
-        database.putChildDocuments(0, "parentId", new MtpObjectInfo[] {
+        mDatabase.startAddingChildDocuments("parentId");
+        mDatabase.putChildDocuments(0, "parentId", new MtpObjectInfo[] {
                 createDocument(100, "note.txt", MtpConstants.FORMAT_TEXT, 1024),
                 createDocument(101, "image.jpg", MtpConstants.FORMAT_EXIF_JPEG, 2 * 1024 * 1024),
                 createDocument(102, "music.mp3", MtpConstants.FORMAT_MP3, 3 * 1024 * 1024)
         });
 
-        final Cursor cursor = database.queryChildDocuments(COLUMN_NAMES, "parentId");
+        final Cursor cursor = mDatabase.queryChildDocuments(COLUMN_NAMES, "parentId");
         assertEquals(3, cursor.getCount());
 
         cursor.moveToNext();
@@ -202,7 +212,6 @@
     }
 
     public void testRestoreIdForRootDocuments() throws Exception {
-        final MtpDatabase database = new MtpDatabase(getContext());
         final String[] columns = new String[] {
                 DocumentsContract.Document.COLUMN_DOCUMENT_ID,
                 MtpDatabaseConstants.COLUMN_STORAGE_ID,
@@ -212,14 +221,15 @@
                 Root.COLUMN_ROOT_ID,
                 Root.COLUMN_AVAILABLE_BYTES
         };
-        database.startAddingRootDocuments(0);
-        database.putRootDocuments(0, resources, new MtpRoot[] {
+
+        mDatabase.startAddingRootDocuments(0);
+        mDatabase.putRootDocuments(0, resources, new MtpRoot[] {
                 new MtpRoot(0, 100, "Device", "Storage A", 1000, 0, ""),
                 new MtpRoot(0, 101, "Device", "Storage B", 1001, 0, "")
         });
 
         {
-            final Cursor cursor = database.queryRootDocuments(columns);
+            final Cursor cursor = mDatabase.queryRootDocuments(columns);
             assertEquals(2, cursor.getCount());
             cursor.moveToNext();
             assertEquals("documentId", 1, cursor.getInt(0));
@@ -233,7 +243,7 @@
         }
 
         {
-            final Cursor cursor = database.queryRoots(rootColumns);
+            final Cursor cursor = mDatabase.queryRoots(rootColumns);
             assertEquals(2, cursor.getCount());
             cursor.moveToNext();
             assertEquals("rootId", 1, cursor.getInt(0));
@@ -244,10 +254,10 @@
             cursor.close();
         }
 
-        database.clearMapping();
+        mDatabase.clearMapping();
 
         {
-            final Cursor cursor = database.queryRootDocuments(columns);
+            final Cursor cursor = mDatabase.queryRootDocuments(columns);
             assertEquals(2, cursor.getCount());
             cursor.moveToNext();
             assertEquals("documentId", 1, cursor.getInt(0));
@@ -261,7 +271,7 @@
         }
 
         {
-            final Cursor cursor = database.queryRoots(rootColumns);
+            final Cursor cursor = mDatabase.queryRoots(rootColumns);
             assertEquals(2, cursor.getCount());
             cursor.moveToNext();
             assertEquals("rootId", 1, cursor.getInt(0));
@@ -272,14 +282,14 @@
             cursor.close();
         }
 
-        database.startAddingRootDocuments(0);
-        database.putRootDocuments(0, resources, new MtpRoot[] {
+        mDatabase.startAddingRootDocuments(0);
+        mDatabase.putRootDocuments(0, resources, new MtpRoot[] {
                 new MtpRoot(0, 200, "Device", "Storage A", 2000, 0, ""),
                 new MtpRoot(0, 202, "Device", "Storage C", 2002, 0, "")
         });
 
         {
-            final Cursor cursor = database.queryRootDocuments(columns);
+            final Cursor cursor = mDatabase.queryRootDocuments(columns);
             assertEquals(3, cursor.getCount());
             cursor.moveToNext();
             assertEquals("documentId", 1, cursor.getInt(0));
@@ -297,7 +307,7 @@
         }
 
         {
-            final Cursor cursor = database.queryRoots(rootColumns);
+            final Cursor cursor = mDatabase.queryRoots(rootColumns);
             assertEquals(3, cursor.getCount());
             cursor.moveToNext();
             assertEquals("rootId", 1, cursor.getInt(0));
@@ -311,10 +321,10 @@
             cursor.close();
         }
 
-        database.stopAddingRootDocuments(0);
+        mDatabase.stopAddingRootDocuments(0);
 
         {
-            final Cursor cursor = database.queryRootDocuments(columns);
+            final Cursor cursor = mDatabase.queryRootDocuments(columns);
             assertEquals(2, cursor.getCount());
             cursor.moveToNext();
             assertEquals("documentId", 1, cursor.getInt(0));
@@ -328,7 +338,7 @@
         }
 
         {
-            final Cursor cursor = database.queryRoots(rootColumns);
+            final Cursor cursor = mDatabase.queryRoots(rootColumns);
             assertEquals(2, cursor.getCount());
             cursor.moveToNext();
             assertEquals("rootId", 1, cursor.getInt(0));
@@ -341,22 +351,21 @@
     }
 
     public void testRestoreIdForChildDocuments() throws Exception {
-        final MtpDatabase database = new MtpDatabase(getContext());
         final String[] columns = new String[] {
                 DocumentsContract.Document.COLUMN_DOCUMENT_ID,
                 MtpDatabaseConstants.COLUMN_OBJECT_HANDLE,
                 DocumentsContract.Document.COLUMN_DISPLAY_NAME
         };
-        database.startAddingChildDocuments("parentId");
-        database.putChildDocuments(0, "parentId", new MtpObjectInfo[] {
+        mDatabase.startAddingChildDocuments("parentId");
+        mDatabase.putChildDocuments(0, "parentId", new MtpObjectInfo[] {
                 createDocument(100, "note.txt", MtpConstants.FORMAT_TEXT, 1024),
                 createDocument(101, "image.jpg", MtpConstants.FORMAT_EXIF_JPEG, 2 * 1024 * 1024),
                 createDocument(102, "music.mp3", MtpConstants.FORMAT_MP3, 3 * 1024 * 1024)
         });
-        database.clearMapping();
+        mDatabase.clearMapping();
 
         {
-            final Cursor cursor = database.queryChildDocuments(columns, "parentId");
+            final Cursor cursor = mDatabase.queryChildDocuments(columns, "parentId");
             assertEquals(3, cursor.getCount());
 
             cursor.moveToNext();
@@ -377,14 +386,14 @@
             cursor.close();
         }
 
-        database.startAddingChildDocuments("parentId");
-        database.putChildDocuments(0, "parentId", new MtpObjectInfo[] {
+        mDatabase.startAddingChildDocuments("parentId");
+        mDatabase.putChildDocuments(0, "parentId", new MtpObjectInfo[] {
                 createDocument(200, "note.txt", MtpConstants.FORMAT_TEXT, 1024),
                 createDocument(203, "video.mp4", MtpConstants.FORMAT_MP4_CONTAINER, 1024),
         });
 
         {
-            final Cursor cursor = database.queryChildDocuments(columns, "parentId");
+            final Cursor cursor = mDatabase.queryChildDocuments(columns, "parentId");
             assertEquals(4, cursor.getCount());
 
             cursor.moveToPosition(3);
@@ -395,10 +404,10 @@
             cursor.close();
         }
 
-        database.stopAddingChildDocuments("parentId");
+        mDatabase.stopAddingChildDocuments("parentId");
 
         {
-            final Cursor cursor = database.queryChildDocuments(columns, "parentId");
+            final Cursor cursor = mDatabase.queryChildDocuments(columns, "parentId");
             assertEquals(2, cursor.getCount());
 
             cursor.moveToNext();
@@ -415,7 +424,6 @@
     }
 
     public void testRestoreIdForDifferentDevices() throws Exception {
-        final MtpDatabase database = new MtpDatabase(getContext());
         final String[] columns = new String[] {
                 DocumentsContract.Document.COLUMN_DOCUMENT_ID,
                 MtpDatabaseConstants.COLUMN_STORAGE_ID,
@@ -425,17 +433,17 @@
                 Root.COLUMN_ROOT_ID,
                 Root.COLUMN_AVAILABLE_BYTES
         };
-        database.startAddingRootDocuments(0);
-        database.startAddingRootDocuments(1);
-        database.putRootDocuments(0, resources, new MtpRoot[] {
+        mDatabase.startAddingRootDocuments(0);
+        mDatabase.startAddingRootDocuments(1);
+        mDatabase.putRootDocuments(0, resources, new MtpRoot[] {
                 new MtpRoot(0, 100, "Device", "Storage", 0, 0, "")
         });
-        database.putRootDocuments(1, resources, new MtpRoot[] {
+        mDatabase.putRootDocuments(1, resources, new MtpRoot[] {
                 new MtpRoot(1, 100, "Device", "Storage", 0, 0, "")
         });
 
         {
-            final Cursor cursor = database.queryRootDocuments(columns);
+            final Cursor cursor = mDatabase.queryRootDocuments(columns);
             assertEquals(2, cursor.getCount());
             cursor.moveToNext();
             assertEquals("documentId", 1, cursor.getInt(0));
@@ -449,7 +457,7 @@
         }
 
         {
-            final Cursor cursor = database.queryRoots(rootColumns);
+            final Cursor cursor = mDatabase.queryRoots(rootColumns);
             assertEquals(2, cursor.getCount());
             cursor.moveToNext();
             assertEquals("rootId", 1, cursor.getInt(0));
@@ -460,21 +468,21 @@
             cursor.close();
         }
 
-        database.clearMapping();
+        mDatabase.clearMapping();
 
-        database.startAddingRootDocuments(0);
-        database.startAddingRootDocuments(1);
-        database.putRootDocuments(0, resources, new MtpRoot[] {
+        mDatabase.startAddingRootDocuments(0);
+        mDatabase.startAddingRootDocuments(1);
+        mDatabase.putRootDocuments(0, resources, new MtpRoot[] {
                 new MtpRoot(0, 200, "Device", "Storage", 2000, 0, "")
         });
-        database.putRootDocuments(1, resources, new MtpRoot[] {
+        mDatabase.putRootDocuments(1, resources, new MtpRoot[] {
                 new MtpRoot(1, 300, "Device", "Storage", 3000, 0, "")
         });
-        database.stopAddingRootDocuments(0);
-        database.stopAddingRootDocuments(1);
+        mDatabase.stopAddingRootDocuments(0);
+        mDatabase.stopAddingRootDocuments(1);
 
         {
-            final Cursor cursor = database.queryRootDocuments(columns);
+            final Cursor cursor = mDatabase.queryRootDocuments(columns);
             assertEquals(2, cursor.getCount());
             cursor.moveToNext();
             assertEquals("documentId", 1, cursor.getInt(0));
@@ -488,7 +496,7 @@
         }
 
         {
-            final Cursor cursor = database.queryRoots(rootColumns);
+            final Cursor cursor = mDatabase.queryRoots(rootColumns);
             assertEquals(2, cursor.getCount());
             cursor.moveToNext();
             assertEquals("rootId", 1, cursor.getInt(0));
@@ -501,34 +509,33 @@
     }
 
     public void testRestoreIdForDifferentParents() throws Exception {
-        final MtpDatabase database = new MtpDatabase(getContext());
         final String[] columns = new String[] {
                 DocumentsContract.Document.COLUMN_DOCUMENT_ID,
                 MtpDatabaseConstants.COLUMN_OBJECT_HANDLE
         };
 
-        database.startAddingChildDocuments("parentId1");
-        database.startAddingChildDocuments("parentId2");
-        database.putChildDocuments(0, "parentId1", new MtpObjectInfo[] {
+        mDatabase.startAddingChildDocuments("parentId1");
+        mDatabase.startAddingChildDocuments("parentId2");
+        mDatabase.putChildDocuments(0, "parentId1", new MtpObjectInfo[] {
                 createDocument(100, "note.txt", MtpConstants.FORMAT_TEXT, 1024),
         });
-        database.putChildDocuments(0, "parentId2", new MtpObjectInfo[] {
+        mDatabase.putChildDocuments(0, "parentId2", new MtpObjectInfo[] {
                 createDocument(101, "note.txt", MtpConstants.FORMAT_TEXT, 1024),
         });
-        database.clearMapping();
+        mDatabase.clearMapping();
 
-        database.startAddingChildDocuments("parentId1");
-        database.startAddingChildDocuments("parentId2");
-        database.putChildDocuments(0, "parentId1", new MtpObjectInfo[] {
+        mDatabase.startAddingChildDocuments("parentId1");
+        mDatabase.startAddingChildDocuments("parentId2");
+        mDatabase.putChildDocuments(0, "parentId1", new MtpObjectInfo[] {
                 createDocument(200, "note.txt", MtpConstants.FORMAT_TEXT, 1024),
         });
-        database.putChildDocuments(0, "parentId2", new MtpObjectInfo[] {
+        mDatabase.putChildDocuments(0, "parentId2", new MtpObjectInfo[] {
                 createDocument(201, "note.txt", MtpConstants.FORMAT_TEXT, 1024),
         });
-        database.stopAddingChildDocuments("parentId1");
+        mDatabase.stopAddingChildDocuments("parentId1");
 
         {
-            final Cursor cursor = database.queryChildDocuments(columns, "parentId1");
+            final Cursor cursor = mDatabase.queryChildDocuments(columns, "parentId1");
             assertEquals(1, cursor.getCount());
             cursor.moveToNext();
             assertEquals("documentId", 1, cursor.getInt(0));
@@ -536,7 +543,7 @@
             cursor.close();
         }
         {
-            final Cursor cursor = database.queryChildDocuments(columns, "parentId2");
+            final Cursor cursor = mDatabase.queryChildDocuments(columns, "parentId2");
             assertEquals(1, cursor.getCount());
             cursor.moveToNext();
             assertEquals("documentId", 2, cursor.getInt(0));
@@ -546,7 +553,6 @@
     }
 
     public void testClearMtpIdentifierBeforeResolveRootDocuments() {
-        final MtpDatabase database = new MtpDatabase(getContext());
         final String[] columns = new String[] {
                 DocumentsContract.Document.COLUMN_DOCUMENT_ID,
                 MtpDatabaseConstants.COLUMN_STORAGE_ID,
@@ -557,26 +563,26 @@
                 Root.COLUMN_AVAILABLE_BYTES
         };
 
-        database.startAddingRootDocuments(0);
-        database.putRootDocuments(0, resources, new MtpRoot[] {
+        mDatabase.startAddingRootDocuments(0);
+        mDatabase.putRootDocuments(0, resources, new MtpRoot[] {
                 new MtpRoot(0, 100, "Device", "Storage", 0, 0, ""),
         });
-        database.clearMapping();
+        mDatabase.clearMapping();
 
-        database.startAddingRootDocuments(0);
-        database.putRootDocuments(0, resources, new MtpRoot[] {
+        mDatabase.startAddingRootDocuments(0);
+        mDatabase.putRootDocuments(0, resources, new MtpRoot[] {
                 new MtpRoot(0, 200, "Device", "Storage", 2000, 0, ""),
         });
-        database.clearMapping();
+        mDatabase.clearMapping();
 
-        database.startAddingRootDocuments(0);
-        database.putRootDocuments(0, resources, new MtpRoot[] {
+        mDatabase.startAddingRootDocuments(0);
+        mDatabase.putRootDocuments(0, resources, new MtpRoot[] {
                 new MtpRoot(0, 300, "Device", "Storage", 3000, 0, ""),
         });
-        database.stopAddingRootDocuments(0);
+        mDatabase.stopAddingRootDocuments(0);
 
         {
-            final Cursor cursor = database.queryRootDocuments(columns);
+            final Cursor cursor = mDatabase.queryRootDocuments(columns);
             assertEquals(1, cursor.getCount());
             cursor.moveToNext();
             assertEquals("documentId", 1, cursor.getInt(0));
@@ -585,7 +591,7 @@
             cursor.close();
         }
         {
-            final Cursor cursor = database.queryRoots(rootColumns);
+            final Cursor cursor = mDatabase.queryRoots(rootColumns);
             assertEquals(1, cursor.getCount());
             cursor.moveToNext();
             assertEquals("rootId", 1, cursor.getInt(0));
@@ -595,7 +601,6 @@
     }
 
     public void testPutSameNameRootsAfterClearing() throws Exception {
-        final MtpDatabase database = new MtpDatabase(getContext());
         final String[] columns = new String[] {
                 DocumentsContract.Document.COLUMN_DOCUMENT_ID,
                 MtpDatabaseConstants.COLUMN_STORAGE_ID,
@@ -606,21 +611,21 @@
                 Root.COLUMN_AVAILABLE_BYTES
         };
 
-        database.startAddingRootDocuments(0);
-        database.putRootDocuments(0, resources, new MtpRoot[] {
+        mDatabase.startAddingRootDocuments(0);
+        mDatabase.putRootDocuments(0, resources, new MtpRoot[] {
                 new MtpRoot(0, 100, "Device", "Storage", 0, 0, ""),
         });
-        database.clearMapping();
+        mDatabase.clearMapping();
 
-        database.startAddingRootDocuments(0);
-        database.putRootDocuments(0, resources, new MtpRoot[] {
+        mDatabase.startAddingRootDocuments(0);
+        mDatabase.putRootDocuments(0, resources, new MtpRoot[] {
                 new MtpRoot(0, 200, "Device", "Storage", 2000, 0, ""),
                 new MtpRoot(0, 201, "Device", "Storage", 2001, 0, ""),
         });
-        database.stopAddingRootDocuments(0);
+        mDatabase.stopAddingRootDocuments(0);
 
         {
-            final Cursor cursor = database.queryRootDocuments(columns);
+            final Cursor cursor = mDatabase.queryRootDocuments(columns);
             assertEquals(2, cursor.getCount());
             cursor.moveToNext();
             assertEquals("documentId", 2, cursor.getInt(0));
@@ -633,7 +638,7 @@
             cursor.close();
         }
         {
-            final Cursor cursor = database.queryRoots(rootColumns);
+            final Cursor cursor = mDatabase.queryRoots(rootColumns);
             assertEquals(2, cursor.getCount());
             cursor.moveToNext();
             assertEquals("rootId", 2, cursor.getInt(0));
@@ -646,27 +651,26 @@
     }
 
     public void testReplaceExistingRoots() {
-        // The client code should be able to replace exisitng rows with new information.
-        final MtpDatabase database = new MtpDatabase(getContext());
+        // The client code should be able to replace existing rows with new information.
         // Add one.
-        database.startAddingRootDocuments(0);
-        database.putRootDocuments(0, resources, new MtpRoot[] {
+        mDatabase.startAddingRootDocuments(0);
+        mDatabase.putRootDocuments(0, resources, new MtpRoot[] {
                 new MtpRoot(0, 100, "Device", "Storage A", 0, 0, ""),
         });
-        database.stopAddingRootDocuments(0);
+        mDatabase.stopAddingRootDocuments(0);
         // Replace it.
-        database.startAddingRootDocuments(0);
-        database.putRootDocuments(0, resources, new MtpRoot[] {
+        mDatabase.startAddingRootDocuments(0);
+        mDatabase.putRootDocuments(0, resources, new MtpRoot[] {
                 new MtpRoot(0, 100, "Device", "Storage B", 1000, 1000, ""),
         });
-        database.stopAddingRootDocuments(0);
+        mDatabase.stopAddingRootDocuments(0);
         {
             final String[] columns = new String[] {
                     DocumentsContract.Document.COLUMN_DOCUMENT_ID,
                     MtpDatabaseConstants.COLUMN_STORAGE_ID,
                     DocumentsContract.Document.COLUMN_DISPLAY_NAME
             };
-            final Cursor cursor = database.queryRootDocuments(columns);
+            final Cursor cursor = mDatabase.queryRootDocuments(columns);
             assertEquals(1, cursor.getCount());
             cursor.moveToNext();
             assertEquals("documentId", 1, cursor.getInt(0));
@@ -679,7 +683,7 @@
                     Root.COLUMN_ROOT_ID,
                     Root.COLUMN_AVAILABLE_BYTES
             };
-            final Cursor cursor = database.queryRoots(columns);
+            final Cursor cursor = mDatabase.queryRoots(columns);
             assertEquals(1, cursor.getCount());
             cursor.moveToNext();
             assertEquals("rootId", 1, cursor.getInt(0));
@@ -690,20 +694,19 @@
 
     public void _testFailToReplaceExisitingUnmappedRoots() {
         // The client code should not be able to replace rows before resolving 'unmapped' rows.
-        final MtpDatabase database = new MtpDatabase(getContext());
         // Add one.
-        database.startAddingRootDocuments(0);
-        database.putRootDocuments(0, resources, new MtpRoot[] {
+        mDatabase.startAddingRootDocuments(0);
+        mDatabase.putRootDocuments(0, resources, new MtpRoot[] {
                 new MtpRoot(0, 100, "Device", "Storage A", 0, 0, ""),
         });
-        database.clearMapping();
+        mDatabase.clearMapping();
         // Add one.
-        database.putRootDocuments(0, resources, new MtpRoot[] {
+        mDatabase.putRootDocuments(0, resources, new MtpRoot[] {
                 new MtpRoot(0, 100, "Device", "Storage B", 1000, 1000, ""),
         });
         // Add one more before resolving unmapped documents.
         try {
-            database.putRootDocuments(0, resources, new MtpRoot[] {
+            mDatabase.putRootDocuments(0, resources, new MtpRoot[] {
                     new MtpRoot(0, 100, "Device", "Storage B", 1000, 1000, ""),
             });
             fail();
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java
index bc7f28c..70923c0 100644
--- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java
@@ -43,7 +43,7 @@
         mResolver = new TestContentResolver();
         mMtpManager = new TestMtpManager(getContext());
         mProvider = new MtpDocumentsProvider();
-        mDatabase = new MtpDatabase(getContext());
+        mDatabase = new MtpDatabase(getContext(), true);
         mProvider.onCreateForTesting(mResources, mMtpManager, mResolver, mDatabase);
     }
 
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/PipeManagerTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/PipeManagerTest.java
index 53018cc..7c947f5 100644
--- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/PipeManagerTest.java
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/PipeManagerTest.java
@@ -19,15 +19,14 @@
 import android.mtp.MtpObjectInfo;
 import android.os.ParcelFileDescriptor;
 import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.SmallTest;
+import android.test.suitebuilder.annotation.MediumTest;
 
 import java.io.IOException;
-import java.util.Date;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.concurrent.TimeUnit;
 
-@SmallTest
+@MediumTest
 public class PipeManagerTest extends AndroidTestCase {
     private static final byte[] HELLO_BYTES = new byte[] { 'h', 'e', 'l', 'l', 'o' };
 
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestResultInstrumentation.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestResultInstrumentation.java
index a243375..0fb0f34 100644
--- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestResultInstrumentation.java
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestResultInstrumentation.java
@@ -12,8 +12,20 @@
 
     @Override
     public void onCreate(Bundle arguments) {
+        if (arguments == null) {
+            arguments = new Bundle();
+        }
+        final boolean includeRealDeviceTest =
+                Boolean.parseBoolean(arguments.getString("realDeviceTest", "false"));
+        if (!includeRealDeviceTest) {
+            arguments.putString("notAnnotation", "com.android.mtp.RealDeviceTest");
+        }
         super.onCreate(arguments);
-        addTestListener(this);
+        if (includeRealDeviceTest) {
+            // Show the test result by using activity because we need to disconnect USB cable
+            // from adb host while testing with real MTP device.
+            addTestListener(this);
+        }
     }
 
     @Override
diff --git a/packages/PrintSpooler/res/layout/printer_dropdown_prompt.xml b/packages/PrintSpooler/res/layout/printer_dropdown_prompt.xml
new file mode 100644
index 0000000..11fef2d
--- /dev/null
+++ b/packages/PrintSpooler/res/layout/printer_dropdown_prompt.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+      android:layout_width="fill_parent"
+      android:layout_height="wrap_content"
+      android:textAppearance="?android:attr/textAppearanceMedium"
+      android:textIsSelectable="false"
+      android:textColor="?android:attr/textColorPrimary"
+      android:paddingStart="20dip"
+      android:paddingEnd="8dip"
+      android:minHeight="56dip"
+      android:orientation="horizontal"
+      android:text="@string/destination_default_text"
+      android:gravity="start|center_vertical" />
diff --git a/packages/PrintSpooler/res/values/strings.xml b/packages/PrintSpooler/res/values/strings.xml
index 70abdf4..6d81788 100644
--- a/packages/PrintSpooler/res/values/strings.xml
+++ b/packages/PrintSpooler/res/values/strings.xml
@@ -49,6 +49,9 @@
     <!-- Label of the page selection widget. [CHAR LIMIT=20] -->
     <string name="label_pages">Pages</string>
 
+    <!-- Label of the destination widget. [CHAR LIMIT=20] -->
+    <string name="destination_default_text">Select a printer</string>
+
     <!-- Template for the all pages option in the page selection widget. [CHAR LIMIT=20] -->
     <string name="template_all_pages">All <xliff:g id="page_count" example="100">%1$s</xliff:g></string>
 
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
index f409fd4..e758835 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
@@ -64,6 +64,7 @@
 import android.util.ArrayMap;
 import android.util.Log;
 import android.view.KeyEvent;
+import android.view.MotionEvent;
 import android.view.View;
 import android.view.View.OnClickListener;
 import android.view.View.OnFocusChangeListener;
@@ -125,6 +126,8 @@
 
     private static final String FRAGMENT_TAG = "FRAGMENT_TAG";
 
+    private static final String HAS_PRINTED_PREF = "has_printed";
+
     private static final int ORIENTATION_PORTRAIT = 0;
     private static final int ORIENTATION_LANDSCAPE = 1;
 
@@ -187,6 +190,7 @@
 
     private Spinner mDestinationSpinner;
     private DestinationAdapter mDestinationSpinnerAdapter;
+    private boolean mShowDestinationPrompt;
 
     private Spinner mMediaSizeSpinner;
     private ArrayAdapter<SpinnerItem<MediaSize>> mMediaSizeSpinnerAdapter;
@@ -1093,6 +1097,7 @@
 
         updateOptionsUi();
         addCurrentPrinterToHistory();
+        setUserPrinted();
 
         PageRange[] selectedPages = computeSelectedPages();
         if (!Arrays.equals(mSelectedPages, selectedPages)) {
@@ -1195,6 +1200,29 @@
         // Print button
         mPrintButton = (ImageView) findViewById(R.id.print_button);
         mPrintButton.setOnClickListener(clickListener);
+
+        // Special prompt instead of destination spinner for the first time the user printed
+        if (!hasUserEverPrinted()) {
+            mShowDestinationPrompt = true;
+
+            mSummaryCopies.setEnabled(false);
+            mSummaryPaperSize.setEnabled(false);
+
+            mDestinationSpinner.setOnTouchListener(new View.OnTouchListener() {
+                @Override
+                public boolean onTouch(View v, MotionEvent event) {
+                    mShowDestinationPrompt = false;
+                    mSummaryCopies.setEnabled(true);
+                    mSummaryPaperSize.setEnabled(true);
+                    updateOptionsUi();
+
+                    mDestinationSpinner.setOnTouchListener(null);
+                    mDestinationSpinnerAdapter.notifyDataSetChanged();
+
+                    return false;
+                }
+            });
+        }
     }
 
     /**
@@ -1332,6 +1360,22 @@
                 && printer.getStatus() != PrinterInfo.STATUS_UNAVAILABLE;
     }
 
+    /**
+     * Disable all options UI elements, beside the {@link #mDestinationSpinner}
+     */
+    private void disableOptionsUi() {
+        mCopiesEditText.setEnabled(false);
+        mCopiesEditText.setFocusable(false);
+        mMediaSizeSpinner.setEnabled(false);
+        mColorModeSpinner.setEnabled(false);
+        mDuplexModeSpinner.setEnabled(false);
+        mOrientationSpinner.setEnabled(false);
+        mRangeOptionsSpinner.setEnabled(false);
+        mPageRangeEditText.setEnabled(false);
+        mPrintButton.setVisibility(View.GONE);
+        mMoreOptionsButton.setEnabled(false);
+    }
+
     void updateOptionsUi() {
         // Always update the summary.
         updateSummary();
@@ -1346,32 +1390,14 @@
             if (mState != STATE_PRINTER_UNAVAILABLE) {
                 mDestinationSpinner.setEnabled(false);
             }
-            mCopiesEditText.setEnabled(false);
-            mCopiesEditText.setFocusable(false);
-            mMediaSizeSpinner.setEnabled(false);
-            mColorModeSpinner.setEnabled(false);
-            mDuplexModeSpinner.setEnabled(false);
-            mOrientationSpinner.setEnabled(false);
-            mRangeOptionsSpinner.setEnabled(false);
-            mPageRangeEditText.setEnabled(false);
-            mPrintButton.setVisibility(View.GONE);
-            mMoreOptionsButton.setEnabled(false);
+            disableOptionsUi();
             return;
         }
 
         // If no current printer, or it has no capabilities, or it is not
         // available, we disable all print options except the destination.
         if (mCurrentPrinter == null || !canPrint(mCurrentPrinter)) {
-            mCopiesEditText.setEnabled(false);
-            mCopiesEditText.setFocusable(false);
-            mMediaSizeSpinner.setEnabled(false);
-            mColorModeSpinner.setEnabled(false);
-            mDuplexModeSpinner.setEnabled(false);
-            mOrientationSpinner.setEnabled(false);
-            mRangeOptionsSpinner.setEnabled(false);
-            mPageRangeEditText.setEnabled(false);
-            mPrintButton.setVisibility(View.GONE);
-            mMoreOptionsButton.setEnabled(false);
+            disableOptionsUi();
             return;
         }
 
@@ -1679,6 +1705,10 @@
             mCopiesEditText.setText(MIN_COPIES_STRING);
             mCopiesEditText.requestFocus();
         }
+
+        if (mShowDestinationPrompt) {
+            disableOptionsUi();
+        }
     }
 
     private void updateSummary() {
@@ -1980,6 +2010,32 @@
         }
     }
 
+
+    /**
+     * Check if the user has ever printed a document
+     *
+     * @return true iff the user has ever printed a document
+     */
+    private boolean hasUserEverPrinted() {
+        SharedPreferences preferences = getSharedPreferences(HAS_PRINTED_PREF, MODE_PRIVATE);
+
+        return preferences.getBoolean(HAS_PRINTED_PREF, false);
+    }
+
+    /**
+     * Remember that the user printed a document
+     */
+    private void setUserPrinted() {
+        SharedPreferences preferences = getSharedPreferences(HAS_PRINTED_PREF, MODE_PRIVATE);
+
+        if (!preferences.getBoolean(HAS_PRINTED_PREF, false)) {
+            SharedPreferences.Editor edit = preferences.edit();
+
+            edit.putBoolean(HAS_PRINTED_PREF, true);
+            edit.apply();
+        }
+    }
+
     private final class DestinationAdapter extends BaseAdapter
             implements PrinterRegistry.OnPrintersChangeListener {
         private final List<PrinterHolder> mPrinterHolders = new ArrayList<>();
@@ -1988,6 +2044,11 @@
 
         private boolean mHistoricalPrintersLoaded;
 
+        /**
+         * Has the {@link #mDestinationSpinner} ever used a view from printer_dropdown_prompt
+         */
+        private boolean hadPromptView;
+
         public DestinationAdapter() {
             mHistoricalPrintersLoaded = mPrinterRegistry.areHistoricalPrintersLoaded();
             if (mHistoricalPrintersLoaded) {
@@ -2098,9 +2159,20 @@
 
         @Override
         public View getView(int position, View convertView, ViewGroup parent) {
-            if (convertView == null) {
-                convertView = getLayoutInflater().inflate(
-                        R.layout.printer_dropdown_item, parent, false);
+            if (mShowDestinationPrompt) {
+                if (convertView == null) {
+                    convertView = getLayoutInflater().inflate(
+                            R.layout.printer_dropdown_prompt, parent, false);
+                    hadPromptView = true;
+                }
+
+                return convertView;
+            } else {
+                // We don't know if we got an recyled printer_dropdown_prompt, hence do not use it
+                if (hadPromptView || convertView == null) {
+                    convertView = getLayoutInflater().inflate(
+                            R.layout.printer_dropdown_item, parent, false);
+                }
             }
 
             CharSequence title = null;
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 13d3ee1..0ec4b18 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -3354,11 +3354,10 @@
             // preserve the old window until the new one is drawn. This prevents having a gap
             // between the removal and addition, in which no window is visible. We also want the
             // entrance of the new window to be properly animated.
-            mWindowManager.setReplacingWindow(topActivity.appToken, true /* animate */);
+            mWindowManager.setReplacingWindow(topActivity.appToken);
         }
-        final ActivityStack stack =
-                moveTaskToStackUncheckedLocked(task, stackId, toTop, forceFocus,
-                        "moveTaskToStack:" + reason);
+        final ActivityStack stack = moveTaskToStackUncheckedLocked(
+                task, stackId, toTop, forceFocus, "moveTaskToStack:" + reason);
 
         // Make sure the task has the appropriate bounds/size for the stack it is in.
         if (stackId == FULLSCREEN_WORKSPACE_STACK_ID && task.mBounds != null) {
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index 425ff9b..3f4eaac 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -128,8 +128,6 @@
     boolean mWillReplaceWindow;
     // If true, the replaced window was already requested to be removed.
     boolean mReplacingRemoveRequested;
-    // Whether the replacement of the window should trigger app transition animation.
-    boolean mAnimateReplacingWindow;
     // If not null, the window that will be used to replace the old one. This is being set when
     // the window is added and unset when this window reports its first draw.
     WindowState mReplacingWindow;
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 4293f0a..957cb0d 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -2077,7 +2077,7 @@
     }
 
     private void prepareWindowReplacementTransition(AppWindowToken atoken) {
-        if (atoken == null || !atoken.mWillReplaceWindow || !atoken.mAnimateReplacingWindow) {
+        if (atoken == null || !atoken.mWillReplaceWindow) {
             return;
         }
         atoken.allDrawn = false;
@@ -8556,8 +8556,7 @@
             } else if (wtoken != null) {
                 winAnimator.mAnimLayer =
                         w.mLayer + wtoken.mAppAnimator.animLayerAdjustment;
-                if (wtoken.mWillReplaceWindow && wtoken.mAnimateReplacingWindow &&
-                        wtoken.mReplacingWindow != w) {
+                if (wtoken.mWillReplaceWindow && wtoken.mReplacingWindow != w) {
                     // We know that we will be animating a relaunching window in the near future,
                     // which will receive a z-order increase. We want the replaced window to
                     // immediately receive the same treatment, e.g. to be above the dock divider.
@@ -10094,9 +10093,8 @@
      * Hint to a token that its activity will relaunch, which will trigger removal and addition of
      * a window.
      * @param token Application token for which the activity will be relaunched.
-     * @param animate Whether to animate the addition of the new window.
      */
-    public void setReplacingWindow(IBinder token, boolean animate) {
+    public void setReplacingWindow(IBinder token) {
         synchronized (mWindowMap) {
             AppWindowToken appWindowToken = findAppWindowToken(token);
             if (appWindowToken == null) {
@@ -10107,7 +10105,13 @@
                     + " as replacing window.");
             appWindowToken.mWillReplaceWindow = true;
             appWindowToken.mHasReplacedWindow = false;
-            appWindowToken.mAnimateReplacingWindow = animate;
+
+            // Set-up dummy animation so we can start treating windows associated with this token
+            // like they are in transition before the new app window is ready for us to run the
+            // real transition animation.
+            if (DEBUG_APP_TRANSITIONS) Slog.v(TAG,
+                    "setReplacingWindow() Setting dummy animation on: " + appWindowToken);
+            appWindowToken.mAppAnimator.setDummyAnimation();
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 673c21f..b2d6a88 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -1388,7 +1388,6 @@
                 && token.mHasReplacedWindow) {
             if (DEBUG_ADD_REMOVE) Slog.d(TAG, "Removing replacing window: " + this);
             token.mWillReplaceWindow = false;
-            token.mAnimateReplacingWindow = false;
             token.mReplacingRemoveRequested = false;
             token.mReplacingWindow = null;
             token.mHasReplacedWindow = false;