Merge changes I5c994de5,I6cb0dd84 into mnc-dev

* changes:
  Add a test for public bugs 2111 and 2136.
  Always check off-link connectivity in NetworkDiagnostics.
diff --git a/api/current.txt b/api/current.txt
index 7c5115f..16c1880 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -1807,6 +1807,7 @@
     field public static final int defaultVoiceMailAlphaTag = 17039364; // 0x1040004
     field public static final int dialog_alert_title = 17039380; // 0x1040014
     field public static final int emptyPhoneNumber = 17039366; // 0x1040006
+    field public static final int fingerprint_icon_content_description = 17039384; // 0x1040018
     field public static final int httpErrorBadUrl = 17039367; // 0x1040007
     field public static final int httpErrorUnsupportedScheme = 17039368; // 0x1040008
     field public static final int no = 17039369; // 0x1040009
diff --git a/api/system-current.txt b/api/system-current.txt
index a08a011..7c473a9 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -1905,6 +1905,7 @@
     field public static final int defaultVoiceMailAlphaTag = 17039364; // 0x1040004
     field public static final int dialog_alert_title = 17039380; // 0x1040014
     field public static final int emptyPhoneNumber = 17039366; // 0x1040006
+    field public static final int fingerprint_icon_content_description = 17039384; // 0x1040018
     field public static final int httpErrorBadUrl = 17039367; // 0x1040007
     field public static final int httpErrorUnsupportedScheme = 17039368; // 0x1040008
     field public static final int no = 17039369; // 0x1040009
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index b0d8541..fc71783 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -1862,6 +1862,9 @@
         } else {
             sb.append("null");
         }
+        if (this.tickerText != null) {
+            sb.append(" tick");
+        }
         sb.append(" defaults=0x");
         sb.append(Integer.toHexString(this.defaults));
         sb.append(" flags=0x");
diff --git a/core/java/android/text/Hyphenator.java b/core/java/android/text/Hyphenator.java
index 1ee3827..10a994a 100644
--- a/core/java/android/text/Hyphenator.java
+++ b/core/java/android/text/Hyphenator.java
@@ -45,6 +45,8 @@
     @GuardedBy("sLock")
     final static HashMap<Locale, Hyphenator> sMap = new HashMap<Locale, Hyphenator>();
 
+    final static Hyphenator sEmptyHyphenator = new Hyphenator(StaticLayout.nLoadHyphenator(""));
+
     final private long mNativePtr;
 
     private Hyphenator(long nativePtr) {
@@ -53,19 +55,19 @@
 
     public static long get(@Nullable Locale locale) {
         synchronized (sLock) {
-            if (sMap.containsKey(locale)) {
-                Hyphenator result = sMap.get(locale);
-                return (result == null) ? 0 : result.mNativePtr;
+            Hyphenator result = sMap.get(locale);
+            if (result != null) {
+                return result.mNativePtr;
             }
 
             // TODO: Convert this a proper locale-fallback system
 
             // Fall back to language-only, if available
             Locale languageOnlyLocale = new Locale(locale.getLanguage());
-            if (sMap.containsKey(languageOnlyLocale)) {
-                Hyphenator result = sMap.get(languageOnlyLocale);
+            result = sMap.get(languageOnlyLocale);
+            if (result != null) {
                 sMap.put(locale, result);
-                return (result == null) ? 0 : result.mNativePtr;
+                return result.mNativePtr;
             }
 
             // Fall back to script-only, if available
@@ -75,16 +77,16 @@
                         .setLanguage("und")
                         .setScript(script)
                         .build();
-                if (sMap.containsKey(scriptOnlyLocale)) {
-                    Hyphenator result = sMap.get(scriptOnlyLocale);
+                result = sMap.get(scriptOnlyLocale);
+                if (result != null) {
                     sMap.put(locale, result);
-                    return (result == null) ? 0 : result.mNativePtr;
+                    return result.mNativePtr;
                 }
             }
 
-            sMap.put(locale, null); // To remember we found nothing.
+            sMap.put(locale, sEmptyHyphenator);  // To remember we found nothing.
         }
-        return 0;
+        return sEmptyHyphenator.mNativePtr;
     }
 
     private static Hyphenator loadHyphenator(String languageTag) {
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 9b1db57..c22c0ef 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -555,11 +555,6 @@
                 mPendingContentInsets.set(mAttachInfo.mContentInsets);
                 mPendingStableInsets.set(mAttachInfo.mStableInsets);
                 mPendingVisibleInsets.set(0, 0, 0, 0);
-                try {
-                    relayoutWindow(attrs, getHostVisibility(), false);
-                } catch (RemoteException e) {
-                    if (DEBUG_LAYOUT) Log.e(TAG, "failed to relayoutWindow", e);
-                }
                 if (DEBUG_LAYOUT) Log.v(TAG, "Added window " + mWindow);
                 if (res < WindowManagerGlobal.ADD_OKAY) {
                     mAttachInfo.mRootView = null;
diff --git a/core/java/android/webkit/WebChromeClient.java b/core/java/android/webkit/WebChromeClient.java
index 0b18bb8..4737e9b 100644
--- a/core/java/android/webkit/WebChromeClient.java
+++ b/core/java/android/webkit/WebChromeClient.java
@@ -284,19 +284,13 @@
      * currently set for that origin. The host application should invoke the
      * specified callback with the desired permission state. See
      * {@link GeolocationPermissions} for details.
-     *
-     * If this method isn't overridden, the callback is invoked with permission
-     * denied state.
-     *
      * @param origin The origin of the web content attempting to use the
      *               Geolocation API.
      * @param callback The callback to use to set the permission state for the
      *                 origin.
      */
     public void onGeolocationPermissionsShowPrompt(String origin,
-            GeolocationPermissions.Callback callback) {
-        callback.invoke(origin, false, false);
-    }
+            GeolocationPermissions.Callback callback) {}
 
     /**
      * Notify the host application that a request for Geolocation permissions,
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index e169ceb..010cb27 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -4060,9 +4060,17 @@
         private float mPrevX;
         // Indicates if the handle has moved a boundary between LTR and RTL text.
         private boolean mLanguageDirectionChanged = false;
+        // Distance from edge of horizontally scrolling text view
+        // to use to switch to character mode.
+        private final float mTextViewEdgeSlop;
+        // Used to save text view location.
+        private final int[] mTextViewLocation = new int[2];
 
         public SelectionStartHandleView(Drawable drawableLtr, Drawable drawableRtl) {
             super(drawableLtr, drawableRtl);
+            ViewConfiguration viewConfiguration = ViewConfiguration.get(
+                    mTextView.getContext());
+            mTextViewEdgeSlop = viewConfiguration.getScaledTouchSlop() * 4;
         }
 
         @Override
@@ -4100,7 +4108,7 @@
             if (layout == null) {
                 // HandleView will deal appropriately in positionAtCursorOffset when
                 // layout is null.
-                positionAtCursorOffset(mTextView.getOffsetForPosition(x, y), false);
+                positionAndAdjustForCrossingHandles(mTextView.getOffsetForPosition(x, y));
                 return;
             }
 
@@ -4142,12 +4150,12 @@
                 // to the current position.
                 mLanguageDirectionChanged = true;
                 mTouchWordDelta = 0.0f;
-                positionAtCursorOffset(offset, false);
+                positionAndAdjustForCrossingHandles(offset);
                 return;
             } else if (mLanguageDirectionChanged && !isLvlBoundary) {
                 // We've just moved past the boundary so update the position. After this we can
                 // figure out if the user is expanding or shrinking to go by word or character.
-                positionAtCursorOffset(offset, false);
+                positionAndAdjustForCrossingHandles(offset);
                 mTouchWordDelta = 0.0f;
                 mLanguageDirectionChanged = false;
                 return;
@@ -4160,6 +4168,21 @@
                 }
             }
 
+            if (mTextView.getHorizontallyScrolling()) {
+                if (positionNearEdgeOfScrollingView(x, atRtl)
+                        && (mTextView.getScrollX() != 0)
+                        && ((isExpanding && offset < selectionStart) || !isExpanding)) {
+                    // If we're expanding ensure that the offset is smaller than the
+                    // selection start, if the handle snapped to the word, the finger position
+                    // may be out of sync and we don't want the selection to jump back.
+                    mTouchWordDelta = 0.0f;
+                    final int nextOffset = atRtl ? layout.getOffsetToRightOf(mPreviousOffset)
+                            : layout.getOffsetToLeftOf(mPreviousOffset);
+                    positionAndAdjustForCrossingHandles(nextOffset);
+                    return;
+                }
+            }
+
             if (isExpanding) {
                 // User is increasing the selection.
                 if (!mInWord || currLine < mPrevLine) {
@@ -4215,17 +4238,22 @@
             }
 
             if (positionCursor) {
-                // Handles can not cross and selection is at least one character.
-                if (offset >= selectionEnd) {
-                    offset = getNextCursorOffset(selectionEnd, false);
-                    mTouchWordDelta = 0.0f;
-                }
                 mPreviousLineTouched = currLine;
-                positionAtCursorOffset(offset, false);
+                positionAndAdjustForCrossingHandles(offset);
             }
             mPrevX = x;
         }
 
+        private void positionAndAdjustForCrossingHandles(int offset) {
+            final int selectionEnd = mTextView.getSelectionEnd();
+            if (offset >= selectionEnd) {
+                // Handles can not cross and selection is at least one character.
+                offset = getNextCursorOffset(selectionEnd, false);
+                mTouchWordDelta = 0.0f;
+            }
+            positionAtCursorOffset(offset, false);
+        }
+
         @Override
         protected void positionAtCursorOffset(int offset, boolean parentScrolled) {
             super.positionAtCursorOffset(offset, parentScrolled);
@@ -4243,6 +4271,20 @@
             }
             return superResult;
         }
+
+        private boolean positionNearEdgeOfScrollingView(float x, boolean atRtl) {
+            mTextView.getLocationOnScreen(mTextViewLocation);
+            boolean nearEdge;
+            if (atRtl) {
+                int rightEdge = mTextViewLocation[0] + mTextView.getWidth()
+                        - mTextView.getPaddingRight();
+                nearEdge = x > rightEdge - mTextViewEdgeSlop;
+            } else {
+                int leftEdge = mTextViewLocation[0] + mTextView.getPaddingLeft();
+                nearEdge = x < leftEdge + mTextViewEdgeSlop;
+            }
+            return nearEdge;
+        }
     }
 
     private class SelectionEndHandleView extends HandleView {
@@ -4254,9 +4296,17 @@
         private float mPrevX;
         // Indicates if the handle has moved a boundary between LTR and RTL text.
         private boolean mLanguageDirectionChanged = false;
+        // Distance from edge of horizontally scrolling text view
+        // to use to switch to character mode.
+        private final float mTextViewEdgeSlop;
+        // Used to save the text view location.
+        private final int[] mTextViewLocation = new int[2];
 
         public SelectionEndHandleView(Drawable drawableLtr, Drawable drawableRtl) {
             super(drawableLtr, drawableRtl);
+            ViewConfiguration viewConfiguration = ViewConfiguration.get(
+                    mTextView.getContext());
+            mTextViewEdgeSlop = viewConfiguration.getScaledTouchSlop() * 4;
         }
 
         @Override
@@ -4294,7 +4344,7 @@
             if (layout == null) {
                 // HandleView will deal appropriately in positionAtCursorOffset when
                 // layout is null.
-                positionAtCursorOffset(mTextView.getOffsetForPosition(x, y), false);
+                positionAndAdjustForCrossingHandles(mTextView.getOffsetForPosition(x, y));
                 return;
             }
 
@@ -4336,12 +4386,12 @@
                 // to the current position.
                 mLanguageDirectionChanged = true;
                 mTouchWordDelta = 0.0f;
-                positionAtCursorOffset(offset, false);
+                positionAndAdjustForCrossingHandles(offset);
                 return;
             } else if (mLanguageDirectionChanged && !isLvlBoundary) {
                 // We've just moved past the boundary so update the position. After this we can
                 // figure out if the user is expanding or shrinking to go by word or character.
-                positionAtCursorOffset(offset, false);
+                positionAndAdjustForCrossingHandles(offset);
                 mTouchWordDelta = 0.0f;
                 mLanguageDirectionChanged = false;
                 return;
@@ -4354,6 +4404,21 @@
                 }
             }
 
+            if (mTextView.getHorizontallyScrolling()) {
+                if (positionNearEdgeOfScrollingView(x, atRtl)
+                        && mTextView.canScrollHorizontally(atRtl ? -1 : 1)
+                        && ((isExpanding && offset > selectionEnd) || !isExpanding)) {
+                    // If we're expanding ensure that the offset is actually greater than the
+                    // selection end, if the handle snapped to the word, the finger position
+                    // may be out of sync and we don't want the selection to jump back.
+                    mTouchWordDelta = 0.0f;
+                    final int nextOffset = atRtl ? layout.getOffsetToLeftOf(mPreviousOffset)
+                            : layout.getOffsetToRightOf(mPreviousOffset);
+                    positionAndAdjustForCrossingHandles(nextOffset);
+                    return;
+                }
+            }
+
             if (isExpanding) {
                 // User is increasing the selection.
                 if (!mInWord || currLine > mPrevLine) {
@@ -4409,17 +4474,22 @@
             }
 
             if (positionCursor) {
-                // Handles can not cross and selection is at least one character.
-                if (offset <= selectionStart) {
-                    offset = getNextCursorOffset(selectionStart, true);
-                    mTouchWordDelta = 0.0f;
-                }
                 mPreviousLineTouched = currLine;
-                positionAtCursorOffset(offset, false);
+                positionAndAdjustForCrossingHandles(offset);
             }
             mPrevX = x;
         }
 
+        private void positionAndAdjustForCrossingHandles(int offset) {
+            final int selectionStart = mTextView.getSelectionStart();
+            if (offset <= selectionStart) {
+                // Handles can not cross and selection is at least one character.
+                offset = getNextCursorOffset(selectionStart, true);
+                mTouchWordDelta = 0.0f;
+            }
+            positionAtCursorOffset(offset, false);
+        }
+
         @Override
         protected void positionAtCursorOffset(int offset, boolean parentScrolled) {
             super.positionAtCursorOffset(offset, parentScrolled);
@@ -4437,6 +4507,20 @@
             }
             return superResult;
         }
+
+        private boolean positionNearEdgeOfScrollingView(float x, boolean atRtl) {
+            mTextView.getLocationOnScreen(mTextViewLocation);
+            boolean nearEdge;
+            if (atRtl) {
+                int leftEdge = mTextViewLocation[0] + mTextView.getPaddingLeft();
+                nearEdge = x < leftEdge + mTextViewEdgeSlop;
+            } else {
+                int rightEdge = mTextViewLocation[0] + mTextView.getWidth()
+                        - mTextView.getPaddingRight();
+                nearEdge = x > rightEdge - mTextViewEdgeSlop;
+            }
+            return nearEdge;
+        }
     }
 
     private int getCurrentLineAdjustedForSlop(Layout layout, int prevLine, float y) {
diff --git a/core/java/com/android/internal/midi/MidiConstants.java b/core/java/com/android/internal/midi/MidiConstants.java
index c13e5fc..b6b8bf0 100644
--- a/core/java/com/android/internal/midi/MidiConstants.java
+++ b/core/java/com/android/internal/midi/MidiConstants.java
@@ -55,18 +55,30 @@
     public final static int SYSTEM_BYTE_LENGTHS[] = { 1, 2, 3, 2, 1, 1, 1, 1, 1,
             1, 1, 1, 1, 1, 1, 1 };
 
-    /********************************************************************/
 
-    public static int getBytesPerMessage(int command) {
-        if ((command < 0x80) || (command > 0xFF)) {
-            return 0;
-        } else if (command >= 0xF0) {
-            return SYSTEM_BYTE_LENGTHS[command & 0x0F];
+    /**
+     * MIDI messages, except for SysEx, are 1,2 or 3 bytes long.
+     * You can tell how long a MIDI message is from the first status byte.
+     * Do not call this for SysEx, which has variable length.
+     * @param statusByte
+     * @return number of bytes in a complete message or zero if data byte passed
+     */
+    public static int getBytesPerMessage(byte statusByte) {
+        // Java bytes are signed so we need to mask off the high bits
+        // to get a value between 0 and 255.
+        int statusInt = statusByte & 0xFF;
+        if (statusInt >= 0xF0) {
+            // System messages use low nibble for size.
+            return SYSTEM_BYTE_LENGTHS[statusInt & 0x0F];
+        } else if(statusInt >= 0x80) {
+            // Channel voice messages use high nibble for size.
+            return CHANNEL_BYTE_LENGTHS[(statusInt >> 4) - 8];
         } else {
-            return CHANNEL_BYTE_LENGTHS[(command >> 4) - 8];
+            return 0; // data byte
         }
     }
 
+
     /**
      * @param msg
      * @param offset
diff --git a/core/java/com/android/internal/midi/MidiFramer.java b/core/java/com/android/internal/midi/MidiFramer.java
index 058f57c..62517fa 100644
--- a/core/java/com/android/internal/midi/MidiFramer.java
+++ b/core/java/com/android/internal/midi/MidiFramer.java
@@ -17,7 +17,7 @@
 package com.android.internal.midi;
 
 import android.media.midi.MidiReceiver;
-import android.util.Log;
+//import android.util.Log;
 
 import java.io.IOException;
 
@@ -37,7 +37,7 @@
     private MidiReceiver mReceiver;
     private byte[] mBuffer = new byte[3];
     private int mCount;
-    private int mRunningStatus;
+    private byte mRunningStatus;
     private int mNeeded;
     private boolean mInSysEx;
 
@@ -59,22 +59,22 @@
     @Override
     public void onSend(byte[] data, int offset, int count, long timestamp)
             throws IOException {
-        // Log.i(TAG, formatMidiData(data, offset, count));
         int sysExStartOffset = (mInSysEx ? offset : -1);
 
         for (int i = 0; i < count; i++) {
-            int b = data[offset] & 0xFF;
-            if (b >= 0x80) { // status byte?
-                if (b < 0xF0) { // channel message?
-                    mRunningStatus = (byte) b;
+            final byte currentByte = data[offset];
+            final int currentInt = currentByte & 0xFF;
+            if (currentInt >= 0x80) { // status byte?
+                if (currentInt < 0xF0) { // channel message?
+                    mRunningStatus = currentByte;
                     mCount = 1;
-                    mNeeded = MidiConstants.getBytesPerMessage(b) - 1;
-                } else if (b < 0xF8) { // system common?
-                    if (b == 0xF0 /* SysEx Start */) {
+                    mNeeded = MidiConstants.getBytesPerMessage(currentByte) - 1;
+                } else if (currentInt < 0xF8) { // system common?
+                    if (currentInt == 0xF0 /* SysEx Start */) {
                         // Log.i(TAG, "SysEx Start");
                         mInSysEx = true;
                         sysExStartOffset = offset;
-                    } else if (b == 0xF7 /* SysEx End */) {
+                    } else if (currentInt == 0xF7 /* SysEx End */) {
                         // Log.i(TAG, "SysEx End");
                         if (mInSysEx) {
                             mReceiver.send(data, sysExStartOffset,
@@ -83,10 +83,10 @@
                             sysExStartOffset = -1;
                         }
                     } else {
-                        mBuffer[0] = (byte) b;
+                        mBuffer[0] = currentByte;
                         mRunningStatus = 0;
                         mCount = 1;
-                        mNeeded = MidiConstants.getBytesPerMessage(b) - 1;
+                        mNeeded = MidiConstants.getBytesPerMessage(currentByte) - 1;
                     }
                 } else { // real-time?
                     // Single byte message interleaved with other data.
@@ -98,12 +98,11 @@
                     mReceiver.send(data, offset, 1, timestamp);
                 }
             } else { // data byte
-                // Save SysEx data for SysEx End marker or end of buffer.
                 if (!mInSysEx) {
-                    mBuffer[mCount++] = (byte) b;
+                    mBuffer[mCount++] = currentByte;
                     if (--mNeeded == 0) {
                         if (mRunningStatus != 0) {
-                            mBuffer[0] = (byte) mRunningStatus;
+                            mBuffer[0] = mRunningStatus;
                         }
                         mReceiver.send(mBuffer, 0, mCount, timestamp);
                         mNeeded = MidiConstants.getBytesPerMessage(mBuffer[0]) - 1;
diff --git a/core/java/com/android/internal/widget/LockPatternView.java b/core/java/com/android/internal/widget/LockPatternView.java
index 444f878..a709bb8e 100644
--- a/core/java/com/android/internal/widget/LockPatternView.java
+++ b/core/java/com/android/internal/widget/LockPatternView.java
@@ -23,10 +23,10 @@
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.graphics.Canvas;
+import android.graphics.CanvasProperty;
 import android.graphics.Paint;
 import android.graphics.Path;
 import android.graphics.Rect;
-import android.graphics.RectF;
 import android.media.AudioManager;
 import android.os.Bundle;
 import android.os.Debug;
@@ -38,8 +38,10 @@
 import android.util.AttributeSet;
 import android.util.IntArray;
 import android.util.Log;
+import android.view.DisplayListCanvas;
 import android.view.HapticFeedbackConstants;
 import android.view.MotionEvent;
+import android.view.RenderNodeAnimator;
 import android.view.View;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityManager;
@@ -200,10 +202,16 @@
     }
 
     public static class CellState {
-        public float scale = 1.0f;
-        public float translateY = 0.0f;
-        public float alpha = 1.0f;
-        public float size;
+        int row;
+        int col;
+        boolean hwAnimating;
+        CanvasProperty<Float> hwRadius;
+        CanvasProperty<Float> hwCenterX;
+        CanvasProperty<Float> hwCenterY;
+        CanvasProperty<Paint> hwPaint;
+        float radius;
+        float translationY;
+        float alpha = 1f;
         public float lineEndX = Float.MIN_VALUE;
         public float lineEndY = Float.MIN_VALUE;
         public ValueAnimator lineAnimator;
@@ -313,7 +321,9 @@
         for (int i = 0; i < 3; i++) {
             for (int j = 0; j < 3; j++) {
                 mCellStates[i][j] = new CellState();
-                mCellStates[i][j].size = mDotSize;
+                mCellStates[i][j].radius = mDotSize/2;
+                mCellStates[i][j].row = i;
+                mCellStates[i][j].col = j;
             }
         }
 
@@ -412,6 +422,112 @@
         invalidate();
     }
 
+    public void startCellStateAnimation(CellState cellState, float startAlpha, float endAlpha,
+            float startTranslationY, float endTranslationY, float startScale, float endScale,
+            long delay, long duration,
+            Interpolator interpolator, Runnable finishRunnable) {
+        if (isHardwareAccelerated()) {
+            startCellStateAnimationHw(cellState, startAlpha, endAlpha, startTranslationY,
+                    endTranslationY, startScale, endScale, delay, duration, interpolator,
+                    finishRunnable);
+        } else {
+            startCellStateAnimationSw(cellState, startAlpha, endAlpha, startTranslationY,
+                    endTranslationY, startScale, endScale, delay, duration, interpolator,
+                    finishRunnable);
+        }
+    }
+
+    private void startCellStateAnimationSw(final CellState cellState,
+            final float startAlpha, final float endAlpha,
+            final float startTranslationY, final float endTranslationY,
+            final float startScale, final float endScale,
+            long delay, long duration, Interpolator interpolator, final Runnable finishRunnable) {
+        cellState.alpha = startAlpha;
+        cellState.translationY = startTranslationY;
+        cellState.radius = mDotSize/2 * startScale;
+        ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f);
+        animator.setDuration(duration);
+        animator.setStartDelay(delay);
+        animator.setInterpolator(interpolator);
+        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+            @Override
+            public void onAnimationUpdate(ValueAnimator animation) {
+                float t = (float) animation.getAnimatedValue();
+                cellState.alpha = (1 - t) * startAlpha + t * endAlpha;
+                cellState.translationY = (1 - t) * startTranslationY + t * endTranslationY;
+                cellState.radius = mDotSize/2 * ((1 - t) * startScale + t * endScale);
+                invalidate();
+            }
+        });
+        animator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                if (finishRunnable != null) {
+                    finishRunnable.run();
+                }
+            }
+        });
+        animator.start();
+    }
+
+    private void startCellStateAnimationHw(final CellState cellState,
+            float startAlpha, float endAlpha,
+            float startTranslationY, float endTranslationY,
+            float startScale, float endScale,
+            long delay, long duration, Interpolator interpolator, final Runnable finishRunnable) {
+        cellState.alpha = endAlpha;
+        cellState.translationY = endTranslationY;
+        cellState.radius = mDotSize/2 * endScale;
+        cellState.hwAnimating = true;
+        cellState.hwCenterY = CanvasProperty.createFloat(
+                getCenterYForRow(cellState.row) + startTranslationY);
+        cellState.hwCenterX = CanvasProperty.createFloat(getCenterXForColumn(cellState.col));
+        cellState.hwRadius = CanvasProperty.createFloat(mDotSize/2 * startScale);
+        mPaint.setColor(getCurrentColor(false));
+        mPaint.setAlpha((int) (startAlpha * 255));
+        cellState.hwPaint = CanvasProperty.createPaint(new Paint(mPaint));
+
+        startRtFloatAnimation(cellState.hwCenterY,
+                getCenterYForRow(cellState.row) + endTranslationY, delay, duration, interpolator);
+        startRtFloatAnimation(cellState.hwRadius, mDotSize/2 * endScale, delay, duration,
+                interpolator);
+        startRtAlphaAnimation(cellState, endAlpha, delay, duration, interpolator,
+                new AnimatorListenerAdapter() {
+                    @Override
+                    public void onAnimationEnd(Animator animation) {
+                        cellState.hwAnimating = false;
+                        if (finishRunnable != null) {
+                            finishRunnable.run();
+                        }
+                    }
+                });
+
+        invalidate();
+    }
+
+    private void startRtAlphaAnimation(CellState cellState, float endAlpha,
+            long delay, long duration, Interpolator interpolator,
+            Animator.AnimatorListener listener) {
+        RenderNodeAnimator animator = new RenderNodeAnimator(cellState.hwPaint,
+                RenderNodeAnimator.PAINT_ALPHA, (int) (endAlpha * 255));
+        animator.setDuration(duration);
+        animator.setStartDelay(delay);
+        animator.setInterpolator(interpolator);
+        animator.setTarget(this);
+        animator.addListener(listener);
+        animator.start();
+    }
+
+    private void startRtFloatAnimation(CanvasProperty<Float> property, float endValue,
+            long delay, long duration, Interpolator interpolator) {
+        RenderNodeAnimator animator = new RenderNodeAnimator(property, endValue);
+        animator.setDuration(duration);
+        animator.setStartDelay(delay);
+        animator.setInterpolator(interpolator);
+        animator.setTarget(this);
+        animator.start();
+    }
+
     private void notifyCellAdded() {
         // sendAccessEvent(R.string.lockscreen_access_pattern_cell_added);
         if (mOnPatternListener != null) {
@@ -603,14 +719,15 @@
 
     private void startCellActivatedAnimation(Cell cell) {
         final CellState cellState = mCellStates[cell.row][cell.column];
-        startSizeAnimation(mDotSize, mDotSizeActivated, 96, mLinearOutSlowInInterpolator,
+        startRadiusAnimation(mDotSize/2, mDotSizeActivated/2, 96, mLinearOutSlowInInterpolator,
                 cellState, new Runnable() {
-            @Override
-            public void run() {
-                startSizeAnimation(mDotSizeActivated, mDotSize, 192, mFastOutSlowInInterpolator,
-                        cellState, null);
-            }
-        });
+                    @Override
+                    public void run() {
+                        startRadiusAnimation(mDotSizeActivated/2, mDotSize/2, 192,
+                                mFastOutSlowInInterpolator,
+                                cellState, null);
+                    }
+                });
         startLineEndAnimation(cellState, mInProgressX, mInProgressY,
                 getCenterXForColumn(cell.column), getCenterYForRow(cell.row));
     }
@@ -639,13 +756,13 @@
         state.lineAnimator = valueAnimator;
     }
 
-    private void startSizeAnimation(float start, float end, long duration, Interpolator interpolator,
-            final CellState state, final Runnable endRunnable) {
+    private void startRadiusAnimation(float start, float end, long duration,
+            Interpolator interpolator, final CellState state, final Runnable endRunnable) {
         ValueAnimator valueAnimator = ValueAnimator.ofFloat(start, end);
         valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
             @Override
             public void onAnimationUpdate(ValueAnimator animation) {
-                state.size = (float) animation.getAnimatedValue();
+                state.radius = (float) animation.getAnimatedValue();
                 invalidate();
             }
         });
@@ -969,10 +1086,16 @@
             for (int j = 0; j < 3; j++) {
                 CellState cellState = mCellStates[i][j];
                 float centerX = getCenterXForColumn(j);
-                float size = cellState.size * cellState.scale;
-                float translationY = cellState.translateY;
-                drawCircle(canvas, (int) centerX, (int) centerY + translationY,
-                        size, drawLookup[i][j], cellState.alpha);
+                float translationY = cellState.translationY;
+                if (isHardwareAccelerated() && cellState.hwAnimating) {
+                    DisplayListCanvas displayListCanvas = (DisplayListCanvas) canvas;
+                    displayListCanvas.drawCircle(cellState.hwCenterX, cellState.hwCenterY,
+                            cellState.hwRadius, cellState.hwPaint);
+                } else {
+                    drawCircle(canvas, (int) centerX, (int) centerY + translationY,
+                            cellState.radius, drawLookup[i][j], cellState.alpha);
+
+                }
             }
         }
 
@@ -1055,11 +1178,11 @@
     /**
      * @param partOfPattern Whether this circle is part of the pattern.
      */
-    private void drawCircle(Canvas canvas, float centerX, float centerY, float size,
+    private void drawCircle(Canvas canvas, float centerX, float centerY, float radius,
             boolean partOfPattern, float alpha) {
         mPaint.setColor(getCurrentColor(partOfPattern));
         mPaint.setAlpha((int) (alpha * 255));
-        canvas.drawCircle(centerX, centerY, size/2, mPaint);
+        canvas.drawCircle(centerX, centerY, radius, mPaint);
     }
 
     @Override
@@ -1290,7 +1413,6 @@
             float centerY = getCenterYForRow(row);
             float cellheight = mSquareHeight * mHitFactor * 0.5f;
             float cellwidth = mSquareWidth * mHitFactor * 0.5f;
-            float translationY = cell.translateY;
             bounds.left = (int) (centerX - cellwidth);
             bounds.right = (int) (centerX + cellwidth);
             bounds.top = (int) (centerY - cellheight);
diff --git a/core/jni/android/graphics/Bitmap.cpp b/core/jni/android/graphics/Bitmap.cpp
index 0d80a7f..670d3c0 100755
--- a/core/jni/android/graphics/Bitmap.cpp
+++ b/core/jni/android/graphics/Bitmap.cpp
@@ -475,6 +475,14 @@
     }
 }
 
+static void FromColor_DA8(void* dst, const SkColor src[], int width, int x, int y) {
+    uint8_t* d = (uint8_t*)dst;
+
+    for (int stop = x + width; x < stop; x++) {
+        *d++ = SkColorGetA(*src++);
+    }
+}
+
 // can return NULL
 static FromColorProc ChooseFromColorProc(const SkBitmap& bitmap) {
     switch (bitmap.colorType()) {
@@ -485,6 +493,8 @@
                     FromColor_D4444_Raw;
         case kRGB_565_SkColorType:
             return FromColor_D565;
+        case kAlpha_8_SkColorType:
+            return FromColor_DA8;
         default:
             break;
     }
@@ -632,6 +642,15 @@
     } while (--width != 0);
 }
 
+static void ToColor_SA8(SkColor dst[], const void* src, int width, SkColorTable*) {
+    SkASSERT(width > 0);
+    const uint8_t* s = (const uint8_t*)src;
+    do {
+        uint8_t c = *s++;
+        *dst++ = SkColorSetARGB(c, c, c, c);
+    } while (--width != 0);
+}
+
 // can return NULL
 static ToColorProc ChooseToColorProc(const SkBitmap& src) {
     switch (src.colorType()) {
@@ -673,6 +692,8 @@
                 default:
                     return NULL;
             }
+        case kAlpha_8_SkColorType:
+            return ToColor_SA8;
         default:
             break;
     }
diff --git a/core/jni/android_hardware_camera2_DngCreator.cpp b/core/jni/android_hardware_camera2_DngCreator.cpp
index 995d39f..ba08237 100644
--- a/core/jni/android_hardware_camera2_DngCreator.cpp
+++ b/core/jni/android_hardware_camera2_DngCreator.cpp
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-//#define LOG_NDEBUG 0
+#define LOG_NDEBUG 0
 #define LOG_TAG "DngCreator_JNI"
 #include <inttypes.h>
 #include <string.h>
@@ -26,6 +26,7 @@
 #include <utils/StrongPointer.h>
 #include <utils/RefBase.h>
 #include <utils/Vector.h>
+#include <utils/String8.h>
 #include <cutils/properties.h>
 #include <system/camera_metadata.h>
 #include <camera/CameraMetadata.h>
@@ -48,13 +49,22 @@
 using namespace android;
 using namespace img_utils;
 
-#define BAIL_IF_INVALID(expr, jnienv, tagId, writer) \
+#define BAIL_IF_INVALID_RET_BOOL(expr, jnienv, tagId, writer) \
     if ((expr) != OK) { \
         jniThrowExceptionFmt(jnienv, "java/lang/IllegalArgumentException", \
                 "Invalid metadata for tag %s (%x)", (writer)->getTagName(tagId), (tagId)); \
-        return; \
+        return false; \
     }
 
+
+#define BAIL_IF_INVALID_RET_NULL_SP(expr, jnienv, tagId, writer) \
+    if ((expr) != OK) { \
+        jniThrowExceptionFmt(jnienv, "java/lang/IllegalArgumentException", \
+                "Invalid metadata for tag %s (%x)", (writer)->getTagName(tagId), (tagId)); \
+        return nullptr; \
+    }
+
+
 #define BAIL_IF_INVALID_R(expr, jnienv, tagId, writer) \
     if ((expr) != OK) { \
         jniThrowExceptionFmt(jnienv, "java/lang/IllegalArgumentException", \
@@ -62,14 +72,14 @@
         return -1; \
     }
 
-
-#define BAIL_IF_EMPTY(entry, jnienv, tagId, writer) \
+#define BAIL_IF_EMPTY_RET_NULL_SP(entry, jnienv, tagId, writer) \
     if (entry.count == 0) { \
         jniThrowExceptionFmt(jnienv, "java/lang/IllegalArgumentException", \
                 "Missing metadata fields for tag %s (%x)", (writer)->getTagName(tagId), (tagId)); \
-        return; \
+        return nullptr; \
     }
 
+
 #define ANDROID_DNGCREATOR_CTX_JNI_ID     "mNativeContext"
 
 static struct {
@@ -102,6 +112,26 @@
     TIFF_IFD_GPSINFO = 2,
 };
 
+
+/**
+ * POD container class for GPS tag data.
+ */
+class GpsData {
+public:
+    enum {
+        GPS_VALUE_LENGTH = 6,
+        GPS_REF_LENGTH = 2,
+        GPS_DATE_LENGTH = 11,
+    };
+
+    uint32_t mLatitude[GPS_VALUE_LENGTH];
+    uint32_t mLongitude[GPS_VALUE_LENGTH];
+    uint32_t mTimestamp[GPS_VALUE_LENGTH];
+    uint8_t mLatitudeRef[GPS_REF_LENGTH];
+    uint8_t mLongitudeRef[GPS_REF_LENGTH];
+    uint8_t mDate[GPS_DATE_LENGTH];
+};
+
 // ----------------------------------------------------------------------------
 
 /**
@@ -109,8 +139,11 @@
  */
 
 class NativeContext : public LightRefBase<NativeContext> {
-
 public:
+    enum {
+        DATETIME_COUNT = 20,
+    };
+
     NativeContext(const CameraMetadata& characteristics, const CameraMetadata& result);
     virtual ~NativeContext();
 
@@ -119,12 +152,28 @@
     std::shared_ptr<const CameraMetadata> getCharacteristics() const;
     std::shared_ptr<const CameraMetadata> getResult() const;
 
-    uint32_t getThumbnailWidth();
-    uint32_t getThumbnailHeight();
-    const uint8_t* getThumbnail();
+    uint32_t getThumbnailWidth() const;
+    uint32_t getThumbnailHeight() const;
+    const uint8_t* getThumbnail() const;
+    bool hasThumbnail() const;
 
     bool setThumbnail(const uint8_t* buffer, uint32_t width, uint32_t height);
 
+    void setOrientation(uint16_t orientation);
+    uint16_t getOrientation() const;
+
+    void setDescription(const String8& desc);
+    String8 getDescription() const;
+    bool hasDescription() const;
+
+    void setGpsData(const GpsData& data);
+    GpsData getGpsData() const;
+    bool hasGpsData() const;
+
+    void setCaptureTime(const String8& formattedCaptureTime);
+    String8 getCaptureTime() const;
+    bool hasCaptureTime() const;
+
 private:
     Vector<uint8_t> mCurrentThumbnail;
     TiffWriter mWriter;
@@ -132,12 +181,21 @@
     std::shared_ptr<CameraMetadata> mResult;
     uint32_t mThumbnailWidth;
     uint32_t mThumbnailHeight;
+    uint16_t mOrientation;
+    bool mThumbnailSet;
+    bool mGpsSet;
+    bool mDescriptionSet;
+    bool mCaptureTimeSet;
+    String8 mDescription;
+    GpsData mGpsData;
+    String8 mFormattedCaptureTime;
 };
 
 NativeContext::NativeContext(const CameraMetadata& characteristics, const CameraMetadata& result) :
         mCharacteristics(std::make_shared<CameraMetadata>(characteristics)),
         mResult(std::make_shared<CameraMetadata>(result)), mThumbnailWidth(0),
-        mThumbnailHeight(0) {}
+        mThumbnailHeight(0), mOrientation(0), mThumbnailSet(false), mGpsSet(false),
+        mDescriptionSet(false), mCaptureTimeSet(false) {}
 
 NativeContext::~NativeContext() {}
 
@@ -153,18 +211,22 @@
     return mResult;
 }
 
-uint32_t NativeContext::getThumbnailWidth() {
+uint32_t NativeContext::getThumbnailWidth() const {
     return mThumbnailWidth;
 }
 
-uint32_t NativeContext::getThumbnailHeight() {
+uint32_t NativeContext::getThumbnailHeight() const {
     return mThumbnailHeight;
 }
 
-const uint8_t* NativeContext::getThumbnail() {
+const uint8_t* NativeContext::getThumbnail() const {
     return mCurrentThumbnail.array();
 }
 
+bool NativeContext::hasThumbnail() const {
+    return mThumbnailSet;
+}
+
 bool NativeContext::setThumbnail(const uint8_t* buffer, uint32_t width, uint32_t height) {
     mThumbnailWidth = width;
     mThumbnailHeight = height;
@@ -177,9 +239,57 @@
 
     uint8_t* thumb = mCurrentThumbnail.editArray();
     memcpy(thumb, buffer, size);
+    mThumbnailSet = true;
     return true;
 }
 
+void NativeContext::setOrientation(uint16_t orientation) {
+    mOrientation = orientation;
+}
+
+uint16_t NativeContext::getOrientation() const {
+    return mOrientation;
+}
+
+void NativeContext::setDescription(const String8& desc) {
+    mDescription = desc;
+    mDescriptionSet = true;
+}
+
+String8 NativeContext::getDescription() const {
+    return mDescription;
+}
+
+bool NativeContext::hasDescription() const {
+    return mDescriptionSet;
+}
+
+void NativeContext::setGpsData(const GpsData& data) {
+    mGpsData = data;
+    mGpsSet = true;
+}
+
+GpsData NativeContext::getGpsData() const {
+    return mGpsData;
+}
+
+bool NativeContext::hasGpsData() const {
+    return mGpsSet;
+}
+
+void NativeContext::setCaptureTime(const String8& formattedCaptureTime) {
+    mFormattedCaptureTime = formattedCaptureTime;
+    mCaptureTimeSet = true;
+}
+
+String8 NativeContext::getCaptureTime() const {
+    return mFormattedCaptureTime;
+}
+
+bool NativeContext::hasCaptureTime() const {
+    return mCaptureTimeSet;
+}
+
 // End of NativeContext
 // ----------------------------------------------------------------------------
 
@@ -211,7 +321,7 @@
 JniOutputStream::JniOutputStream(JNIEnv* env, jobject outStream) : mOutputStream(outStream),
         mEnv(env) {
     mByteArray = env->NewByteArray(BYTE_ARRAY_LENGTH);
-    if (mByteArray == NULL) {
+    if (mByteArray == nullptr) {
         jniThrowException(env, "java/lang/OutOfMemoryError", "Could not allocate byte array.");
     }
 }
@@ -286,7 +396,7 @@
 
 JniInputStream::JniInputStream(JNIEnv* env, jobject inStream) : mInStream(inStream), mEnv(env) {
     mByteArray = env->NewByteArray(BYTE_ARRAY_LENGTH);
-    if (mByteArray == NULL) {
+    if (mByteArray == nullptr) {
         jniThrowException(env, "java/lang/OutOfMemoryError", "Could not allocate byte array.");
     }
 }
@@ -372,7 +482,7 @@
 
 JniInputByteBuffer::JniInputByteBuffer(JNIEnv* env, jobject inBuf) : mInBuf(inBuf), mEnv(env) {
     mByteArray = env->NewByteArray(BYTE_ARRAY_LENGTH);
-    if (mByteArray == NULL) {
+    if (mByteArray == nullptr) {
         jniThrowException(env, "java/lang/OutOfMemoryError", "Could not allocate byte array.");
     }
 }
@@ -600,6 +710,7 @@
         return BAD_VALUE;
     }
 
+
     if (mPixStride == mBytesPerSample * mSamplesPerPixel
             && mRowStride == mWidth * mBytesPerSample * mSamplesPerPixel) {
         ALOGV("%s: Using direct single-pass write for strip.", __FUNCTION__);
@@ -643,37 +754,48 @@
 // ----------------------------------------------------------------------------
 
 /**
- * Given a buffer crop rectangle relative to the pixel array size, and the active array crop
- * rectangle for the camera characteristics, set the default crop rectangle in the TiffWriter
- * relative to the buffer crop rectangle origin.
+ * Given a buffer crop rectangle relative to the pixel array size, and the pre-correction active
+ * array crop rectangle for the camera characteristics, set the default crop rectangle in the
+ * TiffWriter relative to the buffer crop rectangle origin.
  */
 static status_t calculateAndSetCrop(JNIEnv* env, const CameraMetadata& characteristics,
-        uint32_t bufXMin, uint32_t bufYMin, uint32_t bufWidth, uint32_t bufHeight,
-        TiffWriter* writer) {
+        uint32_t bufWidth, uint32_t bufHeight, sp<TiffWriter> writer) {
 
     camera_metadata_ro_entry entry =
-            characteristics.find(ANDROID_SENSOR_INFO_ACTIVE_ARRAY_SIZE);
+            characteristics.find(ANDROID_SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE);
     uint32_t xmin = static_cast<uint32_t>(entry.data.i32[0]);
     uint32_t ymin = static_cast<uint32_t>(entry.data.i32[1]);
     uint32_t width = static_cast<uint32_t>(entry.data.i32[2]);
     uint32_t height = static_cast<uint32_t>(entry.data.i32[3]);
 
+    const uint32_t margin = 8; // Default margin recommended by Adobe for interpolation.
+
+    // Crop based on pre-correction array for pixel array
     uint32_t aLeft = xmin;
     uint32_t aTop = ymin;
     uint32_t aRight = xmin + width;
     uint32_t aBottom = ymin + height;
 
-    const uint32_t margin = 8; // Default margin recommended by Adobe for interpolation.
+    // 8 pixel border crop for pixel array dimens
+    uint32_t bLeft = margin;
+    uint32_t bTop = margin;
+    uint32_t bRight = bufWidth - margin;
+    uint32_t bBottom = bufHeight - margin;
 
-    uint32_t bLeft = bufXMin + margin;
-    uint32_t bTop = bufYMin + margin;
-    uint32_t bRight = bufXMin + bufWidth - margin;
-    uint32_t bBottom = bufYMin + bufHeight - margin;
-
+    // Set the crop to be the intersection of the two rectangles
     uint32_t defaultCropOrigin[] = {std::max(aLeft, bLeft), std::max(aTop, bTop)};
     uint32_t defaultCropSize[] = {std::min(aRight, bRight) - defaultCropOrigin[0],
             std::min(aBottom, bBottom) - defaultCropOrigin[1]};
 
+    // If using buffers with  pre-correction array dimens, switch to 8 pixel border crop
+    // relative to the pixel array dimens
+    if (bufWidth == width && bufHeight == height) {
+        defaultCropOrigin[0] = xmin + margin;
+        defaultCropOrigin[1] = ymin + margin;
+        defaultCropSize[0] = width - margin;
+        defaultCropSize[1] = height - margin;
+    }
+
     BAIL_IF_INVALID_R(writer->addEntry(TAG_DEFAULTCROPORIGIN, 2, defaultCropOrigin,
             TIFF_IFD_0), env, TAG_DEFAULTCROPORIGIN, writer);
     BAIL_IF_INVALID_R(writer->addEntry(TAG_DEFAULTCROPSIZE, 2, defaultCropSize,
@@ -682,9 +804,8 @@
     return OK;
 }
 
-static bool validateDngHeader(JNIEnv* env, TiffWriter* writer,
+static bool validateDngHeader(JNIEnv* env, sp<TiffWriter> writer,
         const CameraMetadata& characteristics, jint width, jint height) {
-    // TODO: handle lens shading map, etc. conversions for other raw buffer sizes.
     if (width <= 0) {
         jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException", \
                         "Image width %d is invalid", width);
@@ -710,20 +831,7 @@
     bool matchesPixelArray = (pWidth == width && pHeight == height);
     bool matchesPreCorrectionArray = (cWidth == width && cHeight == height);
 
-    if (matchesPixelArray) {
-        if (calculateAndSetCrop(env, characteristics, 0, 0, static_cast<uint32_t>(pWidth),
-                static_cast<uint32_t>(pHeight), writer) != OK) {
-            return false;
-        }
-    } else if (matchesPreCorrectionArray) {
-        if (calculateAndSetCrop(env, characteristics,
-                static_cast<uint32_t>(preCorrectionEntry.data.i32[0]),
-                static_cast<uint32_t>(preCorrectionEntry.data.i32[1]),
-                static_cast<uint32_t>(preCorrectionEntry.data.i32[2]),
-                static_cast<uint32_t>(preCorrectionEntry.data.i32[3]), writer) != OK) {
-            return false;
-        }
-    } else {
+    if (!(matchesPixelArray || matchesPreCorrectionArray)) {
         jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException", \
                         "Image dimensions (w=%d,h=%d) are invalid, must match either the pixel "
                         "array size (w=%d, h=%d) or the pre-correction array size (w=%d, h=%d)",
@@ -734,12 +842,12 @@
     return true;
 }
 
-static status_t moveEntries(TiffWriter* writer, uint32_t ifdFrom, uint32_t ifdTo,
+static status_t moveEntries(sp<TiffWriter> writer, uint32_t ifdFrom, uint32_t ifdTo,
         const Vector<uint16_t>& entries) {
     for (size_t i = 0; i < entries.size(); ++i) {
         uint16_t tagId = entries[i];
         sp<TiffEntry> entry = writer->getEntry(tagId, ifdFrom);
-        if (entry == NULL) {
+        if (entry.get() == nullptr) {
             ALOGE("%s: moveEntries failed, entry %u not found in IFD %u", __FUNCTION__, tagId,
                     ifdFrom);
             return BAD_VALUE;
@@ -881,7 +989,7 @@
     ALOGV("%s:", __FUNCTION__);
     NativeContext* current = DngCreator_getNativeContext(env, thiz);
 
-    if (context != NULL) {
+    if (context != nullptr) {
         context->incStrong((void*) DngCreator_setNativeContext);
     }
 
@@ -893,15 +1001,6 @@
             reinterpret_cast<jlong>(context.get()));
 }
 
-static TiffWriter* DngCreator_getCreator(JNIEnv* env, jobject thiz) {
-    ALOGV("%s:", __FUNCTION__);
-    NativeContext* current = DngCreator_getNativeContext(env, thiz);
-    if (current) {
-        return current->getWriter();
-    }
-    return NULL;
-}
-
 static void DngCreator_nativeClassInit(JNIEnv* env, jclass clazz) {
     ALOGV("%s:", __FUNCTION__);
 
@@ -938,7 +1037,62 @@
     }
 
     sp<NativeContext> nativeContext = new NativeContext(characteristics, results);
-    TiffWriter* writer = nativeContext->getWriter();
+
+    const char* captureTime = env->GetStringUTFChars(formattedCaptureTime, nullptr);
+
+    size_t len = strlen(captureTime) + 1;
+    if (len != NativeContext::DATETIME_COUNT) {
+        jniThrowException(env, "java/lang/IllegalArgumentException",
+                "Formatted capture time string length is not required 20 characters");
+        return;
+    }
+
+    nativeContext->setCaptureTime(String8(captureTime));
+
+    DngCreator_setNativeContext(env, thiz, nativeContext);
+}
+
+static sp<TiffWriter> DngCreator_setup(JNIEnv* env, jobject thiz, uint32_t imageWidth,
+        uint32_t imageHeight) {
+
+    NativeContext* nativeContext = DngCreator_getNativeContext(env, thiz);
+
+    if (nativeContext == nullptr) {
+        jniThrowException(env, "java/lang/AssertionError",
+                "No native context, must call init before other operations.");
+        return nullptr;
+    }
+
+    CameraMetadata characteristics = *(nativeContext->getCharacteristics());
+    CameraMetadata results = *(nativeContext->getResult());
+
+    sp<TiffWriter> writer = new TiffWriter();
+
+    uint32_t preWidth = 0;
+    uint32_t preHeight = 0;
+    {
+        // Check dimensions
+        camera_metadata_entry entry =
+                characteristics.find(ANDROID_SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE);
+        BAIL_IF_EMPTY_RET_NULL_SP(entry, env, TAG_IMAGEWIDTH, writer);
+        preWidth = static_cast<uint32_t>(entry.data.i32[2]);
+        preHeight = static_cast<uint32_t>(entry.data.i32[3]);
+
+        camera_metadata_entry pixelArrayEntry =
+                characteristics.find(ANDROID_SENSOR_INFO_PIXEL_ARRAY_SIZE);
+        uint32_t pixWidth = static_cast<uint32_t>(pixelArrayEntry.data.i32[0]);
+        uint32_t pixHeight = static_cast<uint32_t>(pixelArrayEntry.data.i32[1]);
+
+        if (!((imageWidth == preWidth && imageHeight == preHeight) ||
+            (imageWidth == pixWidth && imageHeight == pixHeight))) {
+            jniThrowException(env, "java/lang/AssertionError",
+                    "Height and width of imate buffer did not match height and width of"
+                    "either the preCorrectionActiveArraySize or the pixelArraySize.");
+            return nullptr;
+        }
+    }
+
+
 
     writer->addIfd(TIFF_IFD_0);
 
@@ -946,8 +1100,6 @@
 
     const uint32_t samplesPerPixel = 1;
     const uint32_t bitsPerSample = BITS_PER_SAMPLE;
-    uint32_t imageWidth = 0;
-    uint32_t imageHeight = 0;
 
     OpcodeListBuilder::CfaLayout opcodeCfaLayout = OpcodeListBuilder::CFA_RGGB;
     uint8_t cfaPlaneColor[3] = {0, 1, 2};
@@ -961,93 +1113,86 @@
     {
         // Set orientation
         uint16_t orientation = 1; // Normal
-        BAIL_IF_INVALID(writer->addEntry(TAG_ORIENTATION, 1, &orientation, TIFF_IFD_0), env,
-                TAG_ORIENTATION, writer);
+        BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_ORIENTATION, 1, &orientation, TIFF_IFD_0),
+                env, TAG_ORIENTATION, writer);
     }
 
     {
         // Set subfiletype
         uint32_t subfileType = 0; // Main image
-        BAIL_IF_INVALID(writer->addEntry(TAG_NEWSUBFILETYPE, 1, &subfileType, TIFF_IFD_0), env,
-                TAG_NEWSUBFILETYPE, writer);
+        BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_NEWSUBFILETYPE, 1, &subfileType,
+                TIFF_IFD_0), env, TAG_NEWSUBFILETYPE, writer);
     }
 
     {
         // Set bits per sample
         uint16_t bits = static_cast<uint16_t>(bitsPerSample);
-        BAIL_IF_INVALID(writer->addEntry(TAG_BITSPERSAMPLE, 1, &bits, TIFF_IFD_0), env,
+        BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_BITSPERSAMPLE, 1, &bits, TIFF_IFD_0), env,
                 TAG_BITSPERSAMPLE, writer);
     }
 
     {
         // Set compression
         uint16_t compression = 1; // None
-        BAIL_IF_INVALID(writer->addEntry(TAG_COMPRESSION, 1, &compression, TIFF_IFD_0), env,
-                TAG_COMPRESSION, writer);
+        BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_COMPRESSION, 1, &compression,
+                TIFF_IFD_0), env, TAG_COMPRESSION, writer);
     }
 
     {
         // Set dimensions
-        camera_metadata_entry entry =
-                characteristics.find(ANDROID_SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE);
-        BAIL_IF_EMPTY(entry, env, TAG_IMAGEWIDTH, writer);
-        uint32_t width = static_cast<uint32_t>(entry.data.i32[2]);
-        uint32_t height = static_cast<uint32_t>(entry.data.i32[3]);
-        BAIL_IF_INVALID(writer->addEntry(TAG_IMAGEWIDTH, 1, &width, TIFF_IFD_0), env,
-                TAG_IMAGEWIDTH, writer);
-        BAIL_IF_INVALID(writer->addEntry(TAG_IMAGELENGTH, 1, &height, TIFF_IFD_0), env,
-                TAG_IMAGELENGTH, writer);
-        imageWidth = width;
-        imageHeight = height;
+        BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_IMAGEWIDTH, 1, &imageWidth, TIFF_IFD_0),
+                env, TAG_IMAGEWIDTH, writer);
+        BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_IMAGELENGTH, 1, &imageHeight, TIFF_IFD_0),
+                env, TAG_IMAGELENGTH, writer);
     }
 
     {
         // Set photometric interpretation
         uint16_t interpretation = 32803; // CFA
-        BAIL_IF_INVALID(writer->addEntry(TAG_PHOTOMETRICINTERPRETATION, 1, &interpretation,
-                TIFF_IFD_0), env, TAG_PHOTOMETRICINTERPRETATION, writer);
+        BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_PHOTOMETRICINTERPRETATION, 1,
+                &interpretation, TIFF_IFD_0), env, TAG_PHOTOMETRICINTERPRETATION, writer);
     }
 
     {
         // Set blacklevel tags
         camera_metadata_entry entry =
                 characteristics.find(ANDROID_SENSOR_BLACK_LEVEL_PATTERN);
-        BAIL_IF_EMPTY(entry, env, TAG_BLACKLEVEL, writer);
+        BAIL_IF_EMPTY_RET_NULL_SP(entry, env, TAG_BLACKLEVEL, writer);
         const uint32_t* blackLevel = reinterpret_cast<const uint32_t*>(entry.data.i32);
-        BAIL_IF_INVALID(writer->addEntry(TAG_BLACKLEVEL, entry.count, blackLevel, TIFF_IFD_0), env,
-                TAG_BLACKLEVEL, writer);
+        BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_BLACKLEVEL, entry.count, blackLevel,
+                TIFF_IFD_0), env, TAG_BLACKLEVEL, writer);
 
         uint16_t repeatDim[2] = {2, 2};
-        BAIL_IF_INVALID(writer->addEntry(TAG_BLACKLEVELREPEATDIM, 2, repeatDim, TIFF_IFD_0), env,
-                TAG_BLACKLEVELREPEATDIM, writer);
+        BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_BLACKLEVELREPEATDIM, 2, repeatDim,
+                TIFF_IFD_0), env, TAG_BLACKLEVELREPEATDIM, writer);
     }
 
     {
         // Set samples per pixel
         uint16_t samples = static_cast<uint16_t>(samplesPerPixel);
-        BAIL_IF_INVALID(writer->addEntry(TAG_SAMPLESPERPIXEL, 1, &samples, TIFF_IFD_0),
+        BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_SAMPLESPERPIXEL, 1, &samples, TIFF_IFD_0),
                 env, TAG_SAMPLESPERPIXEL, writer);
     }
 
     {
         // Set planar configuration
         uint16_t config = 1; // Chunky
-        BAIL_IF_INVALID(writer->addEntry(TAG_PLANARCONFIGURATION, 1, &config, TIFF_IFD_0),
-                env, TAG_PLANARCONFIGURATION, writer);
+        BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_PLANARCONFIGURATION, 1, &config,
+                TIFF_IFD_0), env, TAG_PLANARCONFIGURATION, writer);
     }
 
     {
         // Set CFA pattern dimensions
         uint16_t repeatDim[2] = {2, 2};
-        BAIL_IF_INVALID(writer->addEntry(TAG_CFAREPEATPATTERNDIM, 2, repeatDim, TIFF_IFD_0),
-                env, TAG_CFAREPEATPATTERNDIM, writer);
+        BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_CFAREPEATPATTERNDIM, 2, repeatDim,
+                TIFF_IFD_0), env, TAG_CFAREPEATPATTERNDIM, writer);
     }
 
     {
         // Set CFA pattern
         camera_metadata_entry entry =
                         characteristics.find(ANDROID_SENSOR_INFO_COLOR_FILTER_ARRANGEMENT);
-        BAIL_IF_EMPTY(entry, env, TAG_CFAPATTERN, writer);
+        BAIL_IF_EMPTY_RET_NULL_SP(entry, env, TAG_CFAPATTERN, writer);
 
         const int cfaLength = 4;
         cfaEnum = entry.data.u8[0];
@@ -1057,30 +1202,30 @@
                         "Invalid metadata for tag %d", TAG_CFAPATTERN);
         }
 
-        BAIL_IF_INVALID(writer->addEntry(TAG_CFAPATTERN, cfaLength, cfa, TIFF_IFD_0), env,
-                TAG_CFAPATTERN, writer);
+        BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_CFAPATTERN, cfaLength, cfa, TIFF_IFD_0),
+                env, TAG_CFAPATTERN, writer);
 
         opcodeCfaLayout = convertCFAEnumToOpcodeLayout(cfaEnum);
     }
 
     {
         // Set CFA plane color
-        BAIL_IF_INVALID(writer->addEntry(TAG_CFAPLANECOLOR, 3, cfaPlaneColor, TIFF_IFD_0),
-                env, TAG_CFAPLANECOLOR, writer);
+        BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_CFAPLANECOLOR, 3, cfaPlaneColor,
+                TIFF_IFD_0), env, TAG_CFAPLANECOLOR, writer);
     }
 
     {
         // Set CFA layout
         uint16_t cfaLayout = 1;
-        BAIL_IF_INVALID(writer->addEntry(TAG_CFALAYOUT, 1, &cfaLayout, TIFF_IFD_0),
+        BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_CFALAYOUT, 1, &cfaLayout, TIFF_IFD_0),
                 env, TAG_CFALAYOUT, writer);
     }
 
     {
         // image description
         uint8_t imageDescription = '\0'; // empty
-        BAIL_IF_INVALID(writer->addEntry(TAG_IMAGEDESCRIPTION, 1, &imageDescription, TIFF_IFD_0),
-                env, TAG_IMAGEDESCRIPTION, writer);
+        BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_IMAGEDESCRIPTION, 1, &imageDescription,
+                TIFF_IFD_0), env, TAG_IMAGEDESCRIPTION, writer);
     }
 
     {
@@ -1091,8 +1236,8 @@
         property_get("ro.product.manufacturer", manufacturer, "");
         uint32_t count = static_cast<uint32_t>(strlen(manufacturer)) + 1;
 
-        BAIL_IF_INVALID(writer->addEntry(TAG_MAKE, count, reinterpret_cast<uint8_t*>(manufacturer),
-                TIFF_IFD_0), env, TAG_MAKE, writer);
+        BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_MAKE, count,
+                reinterpret_cast<uint8_t*>(manufacturer), TIFF_IFD_0), env, TAG_MAKE, writer);
     }
 
     {
@@ -1103,23 +1248,23 @@
         property_get("ro.product.model", model, "");
         uint32_t count = static_cast<uint32_t>(strlen(model)) + 1;
 
-        BAIL_IF_INVALID(writer->addEntry(TAG_MODEL, count, reinterpret_cast<uint8_t*>(model),
-                TIFF_IFD_0), env, TAG_MODEL, writer);
+        BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_MODEL, count,
+                reinterpret_cast<uint8_t*>(model), TIFF_IFD_0), env, TAG_MODEL, writer);
     }
 
     {
         // x resolution
         uint32_t xres[] = { 72, 1 }; // default 72 ppi
-        BAIL_IF_INVALID(writer->addEntry(TAG_XRESOLUTION, 1, xres, TIFF_IFD_0),
+        BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_XRESOLUTION, 1, xres, TIFF_IFD_0),
                 env, TAG_XRESOLUTION, writer);
 
         // y resolution
         uint32_t yres[] = { 72, 1 }; // default 72 ppi
-        BAIL_IF_INVALID(writer->addEntry(TAG_YRESOLUTION, 1, yres, TIFF_IFD_0),
+        BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_YRESOLUTION, 1, yres, TIFF_IFD_0),
                 env, TAG_YRESOLUTION, writer);
 
         uint16_t unit = 2; // inches
-        BAIL_IF_INVALID(writer->addEntry(TAG_RESOLUTIONUNIT, 1, &unit, TIFF_IFD_0),
+        BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_RESOLUTIONUNIT, 1, &unit, TIFF_IFD_0),
                 env, TAG_RESOLUTIONUNIT, writer);
     }
 
@@ -1128,52 +1273,41 @@
         char software[PROPERTY_VALUE_MAX];
         property_get("ro.build.fingerprint", software, "");
         uint32_t count = static_cast<uint32_t>(strlen(software)) + 1;
-        BAIL_IF_INVALID(writer->addEntry(TAG_SOFTWARE, count, reinterpret_cast<uint8_t*>(software),
-                TIFF_IFD_0), env, TAG_SOFTWARE, writer);
+        BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_SOFTWARE, count,
+                reinterpret_cast<uint8_t*>(software), TIFF_IFD_0), env, TAG_SOFTWARE, writer);
     }
 
-    {
+    if (nativeContext->hasCaptureTime()) {
         // datetime
-        const size_t DATETIME_COUNT = 20;
-        const char* captureTime = env->GetStringUTFChars(formattedCaptureTime, NULL);
+        String8 captureTime = nativeContext->getCaptureTime();
 
-        size_t len = strlen(captureTime) + 1;
-        if (len != DATETIME_COUNT) {
-            jniThrowException(env, "java/lang/IllegalArgumentException",
-                    "Timestamp string length is not required 20 characters");
-            return;
-        }
-
-        if (writer->addEntry(TAG_DATETIME, DATETIME_COUNT,
-                reinterpret_cast<const uint8_t*>(captureTime), TIFF_IFD_0) != OK) {
-            env->ReleaseStringUTFChars(formattedCaptureTime, captureTime);
+        if (writer->addEntry(TAG_DATETIME, NativeContext::DATETIME_COUNT,
+                reinterpret_cast<const uint8_t*>(captureTime.string()), TIFF_IFD_0) != OK) {
             jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException",
                     "Invalid metadata for tag %x", TAG_DATETIME);
-            return;
+            return nullptr;
         }
 
         // datetime original
-        if (writer->addEntry(TAG_DATETIMEORIGINAL, DATETIME_COUNT,
-                reinterpret_cast<const uint8_t*>(captureTime), TIFF_IFD_0) != OK) {
-            env->ReleaseStringUTFChars(formattedCaptureTime, captureTime);
+        if (writer->addEntry(TAG_DATETIMEORIGINAL, NativeContext::DATETIME_COUNT,
+                reinterpret_cast<const uint8_t*>(captureTime.string()), TIFF_IFD_0) != OK) {
             jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException",
                     "Invalid metadata for tag %x", TAG_DATETIMEORIGINAL);
-            return;
+            return nullptr;
         }
-        env->ReleaseStringUTFChars(formattedCaptureTime, captureTime);
     }
 
     {
         // TIFF/EP standard id
         uint8_t standardId[] = { 1, 0, 0, 0 };
-        BAIL_IF_INVALID(writer->addEntry(TAG_TIFFEPSTANDARDID, 4, standardId,
+        BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_TIFFEPSTANDARDID, 4, standardId,
                 TIFF_IFD_0), env, TAG_TIFFEPSTANDARDID, writer);
     }
 
     {
         // copyright
         uint8_t copyright = '\0'; // empty
-        BAIL_IF_INVALID(writer->addEntry(TAG_COPYRIGHT, 1, &copyright,
+        BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_COPYRIGHT, 1, &copyright,
                 TIFF_IFD_0), env, TAG_COPYRIGHT, writer);
     }
 
@@ -1181,7 +1315,7 @@
         // exposure time
         camera_metadata_entry entry =
             results.find(ANDROID_SENSOR_EXPOSURE_TIME);
-        BAIL_IF_EMPTY(entry, env, TAG_EXPOSURETIME, writer);
+        BAIL_IF_EMPTY_RET_NULL_SP(entry, env, TAG_EXPOSURETIME, writer);
 
         int64_t exposureTime = *(entry.data.i64);
 
@@ -1189,7 +1323,7 @@
             // Should be unreachable
             jniThrowException(env, "java/lang/IllegalArgumentException",
                     "Negative exposure time in metadata");
-            return;
+            return nullptr;
         }
 
         // Ensure exposure time doesn't overflow (for exposures > 4s)
@@ -1201,12 +1335,12 @@
                 // Should be unreachable
                 jniThrowException(env, "java/lang/IllegalArgumentException",
                         "Exposure time too long");
-                return;
+                return nullptr;
             }
         }
 
         uint32_t exposure[] = { static_cast<uint32_t>(exposureTime), denominator };
-        BAIL_IF_INVALID(writer->addEntry(TAG_EXPOSURETIME, 1, exposure,
+        BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_EXPOSURETIME, 1, exposure,
                 TIFF_IFD_0), env, TAG_EXPOSURETIME, writer);
 
     }
@@ -1215,13 +1349,13 @@
         // ISO speed ratings
         camera_metadata_entry entry =
             results.find(ANDROID_SENSOR_SENSITIVITY);
-        BAIL_IF_EMPTY(entry, env, TAG_ISOSPEEDRATINGS, writer);
+        BAIL_IF_EMPTY_RET_NULL_SP(entry, env, TAG_ISOSPEEDRATINGS, writer);
 
         int32_t tempIso = *(entry.data.i32);
         if (tempIso < 0) {
             jniThrowException(env, "java/lang/IllegalArgumentException",
                                     "Negative ISO value");
-            return;
+            return nullptr;
         }
 
         if (tempIso > UINT16_MAX) {
@@ -1230,7 +1364,7 @@
         }
 
         uint16_t iso = static_cast<uint16_t>(tempIso);
-        BAIL_IF_INVALID(writer->addEntry(TAG_ISOSPEEDRATINGS, 1, &iso,
+        BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_ISOSPEEDRATINGS, 1, &iso,
                 TIFF_IFD_0), env, TAG_ISOSPEEDRATINGS, writer);
     }
 
@@ -1238,10 +1372,10 @@
         // focal length
         camera_metadata_entry entry =
             results.find(ANDROID_LENS_FOCAL_LENGTH);
-        BAIL_IF_EMPTY(entry, env, TAG_FOCALLENGTH, writer);
+        BAIL_IF_EMPTY_RET_NULL_SP(entry, env, TAG_FOCALLENGTH, writer);
 
         uint32_t focalLength[] = { static_cast<uint32_t>(*(entry.data.f) * 100), 100 };
-        BAIL_IF_INVALID(writer->addEntry(TAG_FOCALLENGTH, 1, focalLength,
+        BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_FOCALLENGTH, 1, focalLength,
                 TIFF_IFD_0), env, TAG_FOCALLENGTH, writer);
     }
 
@@ -1249,39 +1383,39 @@
         // f number
         camera_metadata_entry entry =
             results.find(ANDROID_LENS_APERTURE);
-        BAIL_IF_EMPTY(entry, env, TAG_FNUMBER, writer);
+        BAIL_IF_EMPTY_RET_NULL_SP(entry, env, TAG_FNUMBER, writer);
 
         uint32_t fnum[] = { static_cast<uint32_t>(*(entry.data.f) * 100), 100 };
-        BAIL_IF_INVALID(writer->addEntry(TAG_FNUMBER, 1, fnum,
+        BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_FNUMBER, 1, fnum,
                 TIFF_IFD_0), env, TAG_FNUMBER, writer);
     }
 
     {
         // Set DNG version information
         uint8_t version[4] = {1, 4, 0, 0};
-        BAIL_IF_INVALID(writer->addEntry(TAG_DNGVERSION, 4, version, TIFF_IFD_0),
+        BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_DNGVERSION, 4, version, TIFF_IFD_0),
                 env, TAG_DNGVERSION, writer);
 
         uint8_t backwardVersion[4] = {1, 1, 0, 0};
-        BAIL_IF_INVALID(writer->addEntry(TAG_DNGBACKWARDVERSION, 4, backwardVersion, TIFF_IFD_0),
-                env, TAG_DNGBACKWARDVERSION, writer);
+        BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_DNGBACKWARDVERSION, 4, backwardVersion,
+                TIFF_IFD_0), env, TAG_DNGBACKWARDVERSION, writer);
     }
 
     {
         // Set whitelevel
         camera_metadata_entry entry =
                 characteristics.find(ANDROID_SENSOR_INFO_WHITE_LEVEL);
-        BAIL_IF_EMPTY(entry, env, TAG_WHITELEVEL, writer);
+        BAIL_IF_EMPTY_RET_NULL_SP(entry, env, TAG_WHITELEVEL, writer);
         uint32_t whiteLevel = static_cast<uint32_t>(entry.data.i32[0]);
-        BAIL_IF_INVALID(writer->addEntry(TAG_WHITELEVEL, 1, &whiteLevel, TIFF_IFD_0), env,
-                TAG_WHITELEVEL, writer);
+        BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_WHITELEVEL, 1, &whiteLevel, TIFF_IFD_0),
+                env, TAG_WHITELEVEL, writer);
     }
 
     {
         // Set default scale
         uint32_t defaultScale[4] = {1, 1, 1, 1};
-        BAIL_IF_INVALID(writer->addEntry(TAG_DEFAULTSCALE, 2, defaultScale, TIFF_IFD_0),
-                env, TAG_DEFAULTSCALE, writer);
+        BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_DEFAULTSCALE, 2, defaultScale,
+                TIFF_IFD_0), env, TAG_DEFAULTSCALE, writer);
     }
 
     bool singleIlluminant = false;
@@ -1289,7 +1423,7 @@
         // Set calibration illuminants
         camera_metadata_entry entry1 =
             characteristics.find(ANDROID_SENSOR_REFERENCE_ILLUMINANT1);
-        BAIL_IF_EMPTY(entry1, env, TAG_CALIBRATIONILLUMINANT1, writer);
+        BAIL_IF_EMPTY_RET_NULL_SP(entry1, env, TAG_CALIBRATIONILLUMINANT1, writer);
         camera_metadata_entry entry2 =
             characteristics.find(ANDROID_SENSOR_REFERENCE_ILLUMINANT2);
         if (entry2.count == 0) {
@@ -1297,12 +1431,12 @@
         }
         uint16_t ref1 = entry1.data.u8[0];
 
-        BAIL_IF_INVALID(writer->addEntry(TAG_CALIBRATIONILLUMINANT1, 1, &ref1,
+        BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_CALIBRATIONILLUMINANT1, 1, &ref1,
                 TIFF_IFD_0), env, TAG_CALIBRATIONILLUMINANT1, writer);
 
         if (!singleIlluminant) {
             uint16_t ref2 = entry2.data.u8[0];
-            BAIL_IF_INVALID(writer->addEntry(TAG_CALIBRATIONILLUMINANT2, 1, &ref2,
+            BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_CALIBRATIONILLUMINANT2, 1, &ref2,
                     TIFF_IFD_0), env, TAG_CALIBRATIONILLUMINANT2, writer);
         }
     }
@@ -1311,7 +1445,7 @@
         // Set color transforms
         camera_metadata_entry entry1 =
             characteristics.find(ANDROID_SENSOR_COLOR_TRANSFORM1);
-        BAIL_IF_EMPTY(entry1, env, TAG_COLORMATRIX1, writer);
+        BAIL_IF_EMPTY_RET_NULL_SP(entry1, env, TAG_COLORMATRIX1, writer);
 
         int32_t colorTransform1[entry1.count * 2];
 
@@ -1321,12 +1455,12 @@
             colorTransform1[ctr++] = entry1.data.r[i].denominator;
         }
 
-        BAIL_IF_INVALID(writer->addEntry(TAG_COLORMATRIX1, entry1.count, colorTransform1,
-                TIFF_IFD_0), env, TAG_COLORMATRIX1, writer);
+        BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_COLORMATRIX1, entry1.count,
+                colorTransform1, TIFF_IFD_0), env, TAG_COLORMATRIX1, writer);
 
         if (!singleIlluminant) {
             camera_metadata_entry entry2 = characteristics.find(ANDROID_SENSOR_COLOR_TRANSFORM2);
-            BAIL_IF_EMPTY(entry2, env, TAG_COLORMATRIX2, writer);
+            BAIL_IF_EMPTY_RET_NULL_SP(entry2, env, TAG_COLORMATRIX2, writer);
             int32_t colorTransform2[entry2.count * 2];
 
             ctr = 0;
@@ -1335,8 +1469,8 @@
                 colorTransform2[ctr++] = entry2.data.r[i].denominator;
             }
 
-            BAIL_IF_INVALID(writer->addEntry(TAG_COLORMATRIX2, entry2.count, colorTransform2,
-                    TIFF_IFD_0), env, TAG_COLORMATRIX2, writer);
+            BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_COLORMATRIX2, entry2.count,
+                    colorTransform2, TIFF_IFD_0), env, TAG_COLORMATRIX2, writer);
         }
     }
 
@@ -1344,7 +1478,7 @@
         // Set calibration transforms
         camera_metadata_entry entry1 =
             characteristics.find(ANDROID_SENSOR_CALIBRATION_TRANSFORM1);
-        BAIL_IF_EMPTY(entry1, env, TAG_CAMERACALIBRATION1, writer);
+        BAIL_IF_EMPTY_RET_NULL_SP(entry1, env, TAG_CAMERACALIBRATION1, writer);
 
         int32_t calibrationTransform1[entry1.count * 2];
 
@@ -1354,13 +1488,13 @@
             calibrationTransform1[ctr++] = entry1.data.r[i].denominator;
         }
 
-        BAIL_IF_INVALID(writer->addEntry(TAG_CAMERACALIBRATION1, entry1.count,
+        BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_CAMERACALIBRATION1, entry1.count,
                 calibrationTransform1, TIFF_IFD_0), env, TAG_CAMERACALIBRATION1, writer);
 
         if (!singleIlluminant) {
             camera_metadata_entry entry2 =
                 characteristics.find(ANDROID_SENSOR_CALIBRATION_TRANSFORM2);
-            BAIL_IF_EMPTY(entry2, env, TAG_CAMERACALIBRATION2, writer);
+            BAIL_IF_EMPTY_RET_NULL_SP(entry2, env, TAG_CAMERACALIBRATION2, writer);
             int32_t calibrationTransform2[entry2.count * 2];
 
             ctr = 0;
@@ -1369,7 +1503,7 @@
                 calibrationTransform2[ctr++] = entry2.data.r[i].denominator;
             }
 
-            BAIL_IF_INVALID(writer->addEntry(TAG_CAMERACALIBRATION2, entry2.count,
+            BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_CAMERACALIBRATION2, entry2.count,
                     calibrationTransform2, TIFF_IFD_0),  env, TAG_CAMERACALIBRATION2, writer);
         }
     }
@@ -1378,7 +1512,7 @@
         // Set forward transforms
         camera_metadata_entry entry1 =
             characteristics.find(ANDROID_SENSOR_FORWARD_MATRIX1);
-        BAIL_IF_EMPTY(entry1, env, TAG_FORWARDMATRIX1, writer);
+        BAIL_IF_EMPTY_RET_NULL_SP(entry1, env, TAG_FORWARDMATRIX1, writer);
 
         int32_t forwardTransform1[entry1.count * 2];
 
@@ -1388,13 +1522,13 @@
             forwardTransform1[ctr++] = entry1.data.r[i].denominator;
         }
 
-        BAIL_IF_INVALID(writer->addEntry(TAG_FORWARDMATRIX1, entry1.count, forwardTransform1,
-                TIFF_IFD_0), env, TAG_FORWARDMATRIX1, writer);
+        BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_FORWARDMATRIX1, entry1.count,
+                forwardTransform1, TIFF_IFD_0), env, TAG_FORWARDMATRIX1, writer);
 
         if (!singleIlluminant) {
             camera_metadata_entry entry2 =
                 characteristics.find(ANDROID_SENSOR_FORWARD_MATRIX2);
-            BAIL_IF_EMPTY(entry2, env, TAG_FORWARDMATRIX2, writer);
+            BAIL_IF_EMPTY_RET_NULL_SP(entry2, env, TAG_FORWARDMATRIX2, writer);
             int32_t forwardTransform2[entry2.count * 2];
 
             ctr = 0;
@@ -1403,8 +1537,8 @@
                 forwardTransform2[ctr++] = entry2.data.r[i].denominator;
             }
 
-            BAIL_IF_INVALID(writer->addEntry(TAG_FORWARDMATRIX2, entry2.count, forwardTransform2,
-                    TIFF_IFD_0),  env, TAG_FORWARDMATRIX2, writer);
+            BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_FORWARDMATRIX2, entry2.count,
+                    forwardTransform2, TIFF_IFD_0),  env, TAG_FORWARDMATRIX2, writer);
         }
     }
 
@@ -1412,7 +1546,7 @@
         // Set camera neutral
         camera_metadata_entry entry =
             results.find(ANDROID_SENSOR_NEUTRAL_COLOR_POINT);
-        BAIL_IF_EMPTY(entry, env, TAG_ASSHOTNEUTRAL, writer);
+        BAIL_IF_EMPTY_RET_NULL_SP(entry, env, TAG_ASSHOTNEUTRAL, writer);
         uint32_t cameraNeutral[entry.count * 2];
 
         size_t ctr = 0;
@@ -1423,33 +1557,27 @@
                     static_cast<uint32_t>(entry.data.r[i].denominator);
         }
 
-        BAIL_IF_INVALID(writer->addEntry(TAG_ASSHOTNEUTRAL, entry.count, cameraNeutral,
+        BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_ASSHOTNEUTRAL, entry.count, cameraNeutral,
                 TIFF_IFD_0), env, TAG_ASSHOTNEUTRAL, writer);
     }
 
-    {
-        // Setup data strips
-        // TODO: Switch to tiled implementation.
-        if (writer->addStrip(TIFF_IFD_0) != OK) {
-            ALOGE("%s: Could not setup strip tags.", __FUNCTION__);
-            jniThrowException(env, "java/lang/IllegalStateException",
-                    "Failed to setup strip tags.");
-            return;
-        }
-    }
 
     {
         // Set dimensions
+        if (calculateAndSetCrop(env, characteristics, imageWidth, imageHeight, writer) != OK) {
+            return nullptr;
+        }
         camera_metadata_entry entry =
                 characteristics.find(ANDROID_SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE);
-        BAIL_IF_EMPTY(entry, env, TAG_DEFAULTCROPSIZE, writer);
+        BAIL_IF_EMPTY_RET_NULL_SP(entry, env, TAG_DEFAULTCROPSIZE, writer);
         uint32_t xmin = static_cast<uint32_t>(entry.data.i32[0]);
         uint32_t ymin = static_cast<uint32_t>(entry.data.i32[1]);
         uint32_t width = static_cast<uint32_t>(entry.data.i32[2]);
         uint32_t height = static_cast<uint32_t>(entry.data.i32[3]);
-        if (calculateAndSetCrop(env, characteristics, xmin, ymin, width, height, writer) != OK) {
-            return;
-        }
+
+        uint32_t activeArea[] = {ymin, xmin, ymin + height, xmin + width};
+        BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_ACTIVEAREA, 4, activeArea, TIFF_IFD_0),
+                env, TAG_ACTIVEAREA, writer);
     }
 
     {
@@ -1469,7 +1597,7 @@
         cameraModel += "-";
         cameraModel += brand;
 
-        BAIL_IF_INVALID(writer->addEntry(TAG_UNIQUECAMERAMODEL, cameraModel.size() + 1,
+        BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_UNIQUECAMERAMODEL, cameraModel.size() + 1,
                 reinterpret_cast<const uint8_t*>(cameraModel.string()), TIFF_IFD_0), env,
                 TAG_UNIQUECAMERAMODEL, writer);
     }
@@ -1486,7 +1614,7 @@
         if ((err = convertCFA(cfaEnum, /*out*/cfaOut)) != OK) {
             jniThrowException(env, "java/lang/IllegalArgumentException",
                     "Invalid CFA from camera characteristics");
-            return;
+            return nullptr;
         }
 
         double noiseProfile[numPlaneColors * 2];
@@ -1500,8 +1628,9 @@
                 if ((err = generateNoiseProfile(entry.data.d, cfaOut, numCfaChannels,
                         cfaPlaneColor, numPlaneColors, /*out*/ noiseProfile)) == OK) {
 
-                    BAIL_IF_INVALID(writer->addEntry(TAG_NOISEPROFILE, numPlaneColors * 2,
-                            noiseProfile, TIFF_IFD_0), env, TAG_NOISEPROFILE, writer);
+                    BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_NOISEPROFILE,
+                            numPlaneColors * 2, noiseProfile, TIFF_IFD_0), env, TAG_NOISEPROFILE,
+                            writer);
                 } else {
                     ALOGW("%s: Error converting coefficients for noise profile, no noise profile"
                             " tag written...", __FUNCTION__);
@@ -1533,19 +1662,26 @@
         camera_metadata_entry entry2 =
                 results.find(ANDROID_STATISTICS_LENS_SHADING_MAP);
 
+        camera_metadata_entry entry =
+                characteristics.find(ANDROID_SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE);
+        BAIL_IF_EMPTY_RET_NULL_SP(entry, env, TAG_IMAGEWIDTH, writer);
+        uint32_t xmin = static_cast<uint32_t>(entry.data.i32[0]);
+        uint32_t ymin = static_cast<uint32_t>(entry.data.i32[1]);
+        uint32_t width = static_cast<uint32_t>(entry.data.i32[2]);
+        uint32_t height = static_cast<uint32_t>(entry.data.i32[3]);
         if (entry2.count > 0 && entry2.count == lsmWidth * lsmHeight * 4) {
             err = builder.addGainMapsForMetadata(lsmWidth,
                                                  lsmHeight,
-                                                 0,
-                                                 0,
-                                                 imageHeight,
-                                                 imageWidth,
+                                                 ymin,
+                                                 xmin,
+                                                 height,
+                                                 width,
                                                  opcodeCfaLayout,
                                                  entry2.data.f);
             if (err != OK) {
                 ALOGE("%s: Could not add Lens shading map.", __FUNCTION__);
                 jniThrowRuntimeException(env, "failed to add lens shading map.");
-                return;
+                return nullptr;
             }
         }
 
@@ -1553,14 +1689,14 @@
         uint8_t opcodeListBuf[listSize];
         err = builder.buildOpList(opcodeListBuf);
         if (err == OK) {
-            BAIL_IF_INVALID(writer->addEntry(TAG_OPCODELIST2, listSize, opcodeListBuf,
+            BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_OPCODELIST2, listSize, opcodeListBuf,
                     TIFF_IFD_0), env, TAG_OPCODELIST2, writer);
         } else {
             ALOGE("%s: Could not build list of opcodes for distortion correction and lens shading"
                     "map.", __FUNCTION__);
             jniThrowRuntimeException(env, "failed to construct opcode list for distortion"
                     " correction and lens shading map");
-            return;
+            return nullptr;
         }
     }
 
@@ -1578,12 +1714,12 @@
         if (entry3.count == 6 && entry4.count == 5) {
             float cx = entry4.data.f[/*c_x*/2];
             float cy = entry4.data.f[/*c_y*/3];
-            err = builder.addWarpRectilinearForMetadata(entry3.data.f, imageWidth, imageHeight, cx,
+            err = builder.addWarpRectilinearForMetadata(entry3.data.f, preWidth, preHeight, cx,
                     cy);
             if (err != OK) {
                 ALOGE("%s: Could not add distortion correction.", __FUNCTION__);
                 jniThrowRuntimeException(env, "failed to add distortion correction.");
-                return;
+                return nullptr;
             }
         }
 
@@ -1591,211 +1727,103 @@
         uint8_t opcodeListBuf[listSize];
         err = builder.buildOpList(opcodeListBuf);
         if (err == OK) {
-            BAIL_IF_INVALID(writer->addEntry(TAG_OPCODELIST3, listSize, opcodeListBuf,
+            BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_OPCODELIST3, listSize, opcodeListBuf,
                     TIFF_IFD_0), env, TAG_OPCODELIST3, writer);
         } else {
             ALOGE("%s: Could not build list of opcodes for distortion correction and lens shading"
                     "map.", __FUNCTION__);
             jniThrowRuntimeException(env, "failed to construct opcode list for distortion"
                     " correction and lens shading map");
-            return;
+            return nullptr;
         }
     }
 
-    DngCreator_setNativeContext(env, thiz, nativeContext);
-}
+    {
+        // Set up orientation tags.
+        uint16_t orientation = nativeContext->getOrientation();
+        BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_ORIENTATION, 1, &orientation, TIFF_IFD_0),
+                env, TAG_ORIENTATION, writer);
 
-static void DngCreator_destroy(JNIEnv* env, jobject thiz) {
-    ALOGV("%s:", __FUNCTION__);
-    DngCreator_setNativeContext(env, thiz, NULL);
-}
-
-static void DngCreator_nativeSetOrientation(JNIEnv* env, jobject thiz, jint orient) {
-    ALOGV("%s:", __FUNCTION__);
-
-    TiffWriter* writer = DngCreator_getCreator(env, thiz);
-    if (writer == NULL) {
-        ALOGE("%s: Failed to initialize DngCreator", __FUNCTION__);
-        jniThrowException(env, "java/lang/AssertionError",
-                "setOrientation called with uninitialized DngCreator");
-        return;
     }
 
-    uint16_t orientation = static_cast<uint16_t>(orient);
-    BAIL_IF_INVALID(writer->addEntry(TAG_ORIENTATION, 1, &orientation, TIFF_IFD_0), env,
-                TAG_ORIENTATION, writer);
-
-    // Set main image orientation also if in a separate IFD
-    if (writer->hasIfd(TIFF_IFD_SUB1)) {
-        BAIL_IF_INVALID(writer->addEntry(TAG_ORIENTATION, 1, &orientation, TIFF_IFD_SUB1), env,
-                    TAG_ORIENTATION, writer);
-    }
-}
-
-static void DngCreator_nativeSetDescription(JNIEnv* env, jobject thiz, jstring description) {
-    ALOGV("%s:", __FUNCTION__);
-
-    TiffWriter* writer = DngCreator_getCreator(env, thiz);
-    if (writer == NULL) {
-        ALOGE("%s: Failed to initialize DngCreator", __FUNCTION__);
-        jniThrowException(env, "java/lang/AssertionError",
-                "setDescription called with uninitialized DngCreator");
-        return;
-    }
-
-    const char* desc = env->GetStringUTFChars(description, NULL);
-    size_t len = strlen(desc) + 1;
-
-    if (writer->addEntry(TAG_IMAGEDESCRIPTION, len,
-            reinterpret_cast<const uint8_t*>(desc), TIFF_IFD_0) != OK) {
-        jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException",
-                "Invalid metadata for tag %x", TAG_IMAGEDESCRIPTION);
-    }
-
-    env->ReleaseStringUTFChars(description, desc);
-}
-
-static void DngCreator_nativeSetGpsTags(JNIEnv* env, jobject thiz, jintArray latTag, jstring latRef,
-        jintArray longTag, jstring longRef, jstring dateTag, jintArray timeTag) {
-    ALOGV("%s:", __FUNCTION__);
-
-    TiffWriter* writer = DngCreator_getCreator(env, thiz);
-    if (writer == NULL) {
-        ALOGE("%s: Failed to initialize DngCreator", __FUNCTION__);
-        jniThrowException(env, "java/lang/AssertionError",
-                "setGpsTags called with uninitialized DngCreator");
-        return;
-    }
-
-    if (!writer->hasIfd(TIFF_IFD_GPSINFO)) {
-        if (writer->addSubIfd(TIFF_IFD_0, TIFF_IFD_GPSINFO, TiffWriter::GPSINFO) != OK) {
-            ALOGE("%s: Failed to add GpsInfo IFD %u to IFD %u", __FUNCTION__, TIFF_IFD_GPSINFO,
-                    TIFF_IFD_0);
-            jniThrowException(env, "java/lang/IllegalStateException", "Failed to add GPSINFO");
-            return;
+    if (nativeContext->hasDescription()){
+        // Set Description
+        String8 description = nativeContext->getDescription();
+        size_t len = description.bytes() + 1;
+        if (writer->addEntry(TAG_IMAGEDESCRIPTION, len,
+                reinterpret_cast<const uint8_t*>(description.string()), TIFF_IFD_0) != OK) {
+            jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException",
+                    "Invalid metadata for tag %x", TAG_IMAGEDESCRIPTION);
         }
     }
 
-    const jsize GPS_VALUE_LENGTH = 6;
-    jsize latLen = env->GetArrayLength(latTag);
-    jsize longLen = env->GetArrayLength(longTag);
-    jsize timeLen = env->GetArrayLength(timeTag);
-    if (latLen != GPS_VALUE_LENGTH) {
-        jniThrowException(env, "java/lang/IllegalArgumentException",
-                "invalid latitude tag length");
-        return;
-    } else if (longLen != GPS_VALUE_LENGTH) {
-        jniThrowException(env, "java/lang/IllegalArgumentException",
-                "invalid longitude tag length");
-        return;
-    } else if (timeLen != GPS_VALUE_LENGTH) {
-        jniThrowException(env, "java/lang/IllegalArgumentException",
-                "invalid time tag length");
-        return;
+    if (nativeContext->hasGpsData()) {
+        // Set GPS tags
+        GpsData gpsData = nativeContext->getGpsData();
+        if (!writer->hasIfd(TIFF_IFD_GPSINFO)) {
+            if (writer->addSubIfd(TIFF_IFD_0, TIFF_IFD_GPSINFO, TiffWriter::GPSINFO) != OK) {
+                ALOGE("%s: Failed to add GpsInfo IFD %u to IFD %u", __FUNCTION__, TIFF_IFD_GPSINFO,
+                        TIFF_IFD_0);
+                jniThrowException(env, "java/lang/IllegalStateException", "Failed to add GPSINFO");
+                return nullptr;
+            }
+        }
+
+        {
+            uint8_t version[] = {2, 3, 0, 0};
+            BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_GPSVERSIONID, 4, version,
+                    TIFF_IFD_GPSINFO), env, TAG_GPSVERSIONID, writer);
+        }
+
+        {
+            BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_GPSLATITUDEREF,
+                    GpsData::GPS_REF_LENGTH, gpsData.mLatitudeRef, TIFF_IFD_GPSINFO), env,
+                    TAG_GPSLATITUDEREF, writer);
+        }
+
+        {
+            BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_GPSLONGITUDEREF,
+                    GpsData::GPS_REF_LENGTH, gpsData.mLongitudeRef, TIFF_IFD_GPSINFO), env,
+                    TAG_GPSLONGITUDEREF, writer);
+        }
+
+        {
+            BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_GPSLATITUDE, 3, gpsData.mLatitude,
+                    TIFF_IFD_GPSINFO), env, TAG_GPSLATITUDE, writer);
+        }
+
+        {
+            BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_GPSLONGITUDE, 3, gpsData.mLongitude,
+                    TIFF_IFD_GPSINFO), env, TAG_GPSLONGITUDE, writer);
+        }
+
+        {
+            BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_GPSTIMESTAMP, 3, gpsData.mTimestamp,
+                    TIFF_IFD_GPSINFO), env, TAG_GPSTIMESTAMP, writer);
+        }
+
+        {
+            BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_GPSDATESTAMP,
+                    GpsData::GPS_DATE_LENGTH, gpsData.mDate, TIFF_IFD_GPSINFO), env,
+                    TAG_GPSDATESTAMP, writer);
+        }
     }
 
-    uint32_t latitude[GPS_VALUE_LENGTH];
-    uint32_t longitude[GPS_VALUE_LENGTH];
-    uint32_t timestamp[GPS_VALUE_LENGTH];
 
-    env->GetIntArrayRegion(latTag, 0, static_cast<jsize>(GPS_VALUE_LENGTH),
-            reinterpret_cast<jint*>(&latitude));
-    env->GetIntArrayRegion(longTag, 0, static_cast<jsize>(GPS_VALUE_LENGTH),
-            reinterpret_cast<jint*>(&longitude));
-    env->GetIntArrayRegion(timeTag, 0, static_cast<jsize>(GPS_VALUE_LENGTH),
-            reinterpret_cast<jint*>(&timestamp));
-
-    const jsize GPS_REF_LENGTH = 2;
-    const jsize GPS_DATE_LENGTH = 11;
-    uint8_t latitudeRef[GPS_REF_LENGTH];
-    uint8_t longitudeRef[GPS_REF_LENGTH];
-    uint8_t date[GPS_DATE_LENGTH];
-
-    env->GetStringUTFRegion(latRef, 0, 1, reinterpret_cast<char*>(&latitudeRef));
-    latitudeRef[GPS_REF_LENGTH - 1] = '\0';
-    env->GetStringUTFRegion(longRef, 0, 1, reinterpret_cast<char*>(&longitudeRef));
-    longitudeRef[GPS_REF_LENGTH - 1] = '\0';
-
-    env->GetStringUTFRegion(dateTag, 0, GPS_DATE_LENGTH - 1, reinterpret_cast<char*>(&date));
-    date[GPS_DATE_LENGTH - 1] = '\0';
-
-    {
-        uint8_t version[] = {2, 3, 0, 0};
-        BAIL_IF_INVALID(writer->addEntry(TAG_GPSVERSIONID, 4, version,
-                TIFF_IFD_GPSINFO), env, TAG_GPSVERSIONID, writer);
-    }
-
-    {
-        BAIL_IF_INVALID(writer->addEntry(TAG_GPSLATITUDEREF, GPS_REF_LENGTH, latitudeRef,
-                TIFF_IFD_GPSINFO), env, TAG_GPSLATITUDEREF, writer);
-    }
-
-    {
-        BAIL_IF_INVALID(writer->addEntry(TAG_GPSLONGITUDEREF, GPS_REF_LENGTH, longitudeRef,
-                TIFF_IFD_GPSINFO), env, TAG_GPSLONGITUDEREF, writer);
-    }
-
-    {
-        BAIL_IF_INVALID(writer->addEntry(TAG_GPSLATITUDE, 3, latitude,
-                TIFF_IFD_GPSINFO), env, TAG_GPSLATITUDE, writer);
-    }
-
-    {
-        BAIL_IF_INVALID(writer->addEntry(TAG_GPSLONGITUDE, 3, longitude,
-                TIFF_IFD_GPSINFO), env, TAG_GPSLONGITUDE, writer);
-    }
-
-    {
-        BAIL_IF_INVALID(writer->addEntry(TAG_GPSTIMESTAMP, 3, timestamp,
-                TIFF_IFD_GPSINFO), env, TAG_GPSTIMESTAMP, writer);
-    }
-
-    {
-        BAIL_IF_INVALID(writer->addEntry(TAG_GPSDATESTAMP, GPS_DATE_LENGTH, date,
-                TIFF_IFD_GPSINFO), env, TAG_GPSDATESTAMP, writer);
-    }
-}
-
-static void DngCreator_nativeSetThumbnail(JNIEnv* env, jobject thiz, jobject buffer, jint width,
-        jint height) {
-    ALOGV("%s:", __FUNCTION__);
-
-    NativeContext* context = DngCreator_getNativeContext(env, thiz);
-    TiffWriter* writer = DngCreator_getCreator(env, thiz);
-    if (writer == NULL || context == NULL) {
-        ALOGE("%s: Failed to initialize DngCreator", __FUNCTION__);
-        jniThrowException(env, "java/lang/AssertionError",
-                "setThumbnail called with uninitialized DngCreator");
-        return;
-    }
-
-    size_t fullSize = width * height * BYTES_PER_RGB_PIXEL;
-    jlong capacity = env->GetDirectBufferCapacity(buffer);
-    if (static_cast<uint64_t>(capacity) != static_cast<uint64_t>(fullSize)) {
-        jniThrowExceptionFmt(env, "java/lang/AssertionError",
-                "Invalid size %d for thumbnail, expected size was %d",
-                capacity, fullSize);
-        return;
-    }
-
-    uint8_t* pixelBytes = reinterpret_cast<uint8_t*>(env->GetDirectBufferAddress(buffer));
-    if (pixelBytes == NULL) {
-        ALOGE("%s: Could not get native ByteBuffer", __FUNCTION__);
-        jniThrowException(env, "java/lang/IllegalArgumentException", "Invalid ByteBuffer");
-        return;
-    }
-
-    if (!writer->hasIfd(TIFF_IFD_SUB1)) {
-        if (writer->addSubIfd(TIFF_IFD_0, TIFF_IFD_SUB1) != OK) {
-            ALOGE("%s: Failed to add SubIFD %u to IFD %u", __FUNCTION__, TIFF_IFD_SUB1,
-                    TIFF_IFD_0);
-            jniThrowException(env, "java/lang/IllegalStateException", "Failed to add SubIFD");
-            return;
+    if (nativeContext->hasThumbnail()) {
+        if (!writer->hasIfd(TIFF_IFD_SUB1)) {
+            if (writer->addSubIfd(TIFF_IFD_0, TIFF_IFD_SUB1) != OK) {
+                ALOGE("%s: Failed to add SubIFD %u to IFD %u", __FUNCTION__, TIFF_IFD_SUB1,
+                        TIFF_IFD_0);
+                jniThrowException(env, "java/lang/IllegalStateException", "Failed to add SubIFD");
+                return nullptr;
+            }
         }
 
         Vector<uint16_t> tagsToMove;
         tagsToMove.add(TAG_ORIENTATION);
         tagsToMove.add(TAG_NEWSUBFILETYPE);
+        tagsToMove.add(TAG_ACTIVEAREA);
         tagsToMove.add(TAG_BITSPERSAMPLE);
         tagsToMove.add(TAG_COMPRESSION);
         tagsToMove.add(TAG_IMAGEWIDTH);
@@ -1814,9 +1842,6 @@
         tagsToMove.add(TAG_RESOLUTIONUNIT);
         tagsToMove.add(TAG_WHITELEVEL);
         tagsToMove.add(TAG_DEFAULTSCALE);
-        tagsToMove.add(TAG_ROWSPERSTRIP);
-        tagsToMove.add(TAG_STRIPBYTECOUNTS);
-        tagsToMove.add(TAG_STRIPOFFSETS);
         tagsToMove.add(TAG_DEFAULTCROPORIGIN);
         tagsToMove.add(TAG_DEFAULTCROPSIZE);
         tagsToMove.add(TAG_OPCODELIST2);
@@ -1824,101 +1849,217 @@
 
         if (moveEntries(writer, TIFF_IFD_0, TIFF_IFD_SUB1, tagsToMove) != OK) {
             jniThrowException(env, "java/lang/IllegalStateException", "Failed to move entries");
-            return;
+            return nullptr;
         }
 
         // Make sure both IFDs get the same orientation tag
         sp<TiffEntry> orientEntry = writer->getEntry(TAG_ORIENTATION, TIFF_IFD_SUB1);
-        if (orientEntry != NULL) {
+        if (orientEntry.get() != nullptr) {
             writer->addEntry(orientEntry, TIFF_IFD_0);
         }
-    }
 
-    // Setup thumbnail tags
+        // Setup thumbnail tags
 
-    {
-        // Set photometric interpretation
-        uint16_t interpretation = 2; // RGB
-        BAIL_IF_INVALID(writer->addEntry(TAG_PHOTOMETRICINTERPRETATION, 1, &interpretation,
-                TIFF_IFD_0), env, TAG_PHOTOMETRICINTERPRETATION, writer);
-    }
-
-    {
-        // Set planar configuration
-        uint16_t config = 1; // Chunky
-        BAIL_IF_INVALID(writer->addEntry(TAG_PLANARCONFIGURATION, 1, &config, TIFF_IFD_0),
-                env, TAG_PLANARCONFIGURATION, writer);
-    }
-
-    {
-        // Set samples per pixel
-        uint16_t samples = SAMPLES_PER_RGB_PIXEL;
-        BAIL_IF_INVALID(writer->addEntry(TAG_SAMPLESPERPIXEL, 1, &samples, TIFF_IFD_0),
-                env, TAG_SAMPLESPERPIXEL, writer);
-    }
-
-    {
-        // Set bits per sample
-        uint16_t bits = BITS_PER_RGB_SAMPLE;
-        BAIL_IF_INVALID(writer->addEntry(TAG_BITSPERSAMPLE, 1, &bits, TIFF_IFD_0), env,
-                TAG_BITSPERSAMPLE, writer);
-    }
-
-    {
-        // Set subfiletype
-        uint32_t subfileType = 1; // Thumbnail image
-        BAIL_IF_INVALID(writer->addEntry(TAG_NEWSUBFILETYPE, 1, &subfileType, TIFF_IFD_0), env,
-                TAG_NEWSUBFILETYPE, writer);
-    }
-
-    {
-        // Set compression
-        uint16_t compression = 1; // None
-        BAIL_IF_INVALID(writer->addEntry(TAG_COMPRESSION, 1, &compression, TIFF_IFD_0), env,
-                TAG_COMPRESSION, writer);
-    }
-
-    {
-        // Set dimensions
-        uint32_t uWidth = static_cast<uint32_t>(width);
-        uint32_t uHeight = static_cast<uint32_t>(height);
-        BAIL_IF_INVALID(writer->addEntry(TAG_IMAGEWIDTH, 1, &uWidth, TIFF_IFD_0), env,
-                TAG_IMAGEWIDTH, writer);
-        BAIL_IF_INVALID(writer->addEntry(TAG_IMAGELENGTH, 1, &uHeight, TIFF_IFD_0), env,
-                TAG_IMAGELENGTH, writer);
-    }
-
-    {
-        // x resolution
-        uint32_t xres[] = { 72, 1 }; // default 72 ppi
-        BAIL_IF_INVALID(writer->addEntry(TAG_XRESOLUTION, 1, xres, TIFF_IFD_0),
-                env, TAG_XRESOLUTION, writer);
-
-        // y resolution
-        uint32_t yres[] = { 72, 1 }; // default 72 ppi
-        BAIL_IF_INVALID(writer->addEntry(TAG_YRESOLUTION, 1, yres, TIFF_IFD_0),
-                env, TAG_YRESOLUTION, writer);
-
-        uint16_t unit = 2; // inches
-        BAIL_IF_INVALID(writer->addEntry(TAG_RESOLUTIONUNIT, 1, &unit, TIFF_IFD_0),
-                env, TAG_RESOLUTIONUNIT, writer);
-    }
-
-    {
-        // Setup data strips
-        if (writer->addStrip(TIFF_IFD_0) != OK) {
-            ALOGE("%s: Could not setup thumbnail strip tags.", __FUNCTION__);
-            jniThrowException(env, "java/lang/IllegalStateException",
-                    "Failed to setup thumbnail strip tags.");
-            return;
+        {
+            // Set photometric interpretation
+            uint16_t interpretation = 2; // RGB
+            BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_PHOTOMETRICINTERPRETATION, 1,
+                    &interpretation, TIFF_IFD_0), env, TAG_PHOTOMETRICINTERPRETATION, writer);
         }
+
+        {
+            // Set planar configuration
+            uint16_t config = 1; // Chunky
+            BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_PLANARCONFIGURATION, 1, &config,
+                    TIFF_IFD_0), env, TAG_PLANARCONFIGURATION, writer);
+        }
+
+        {
+            // Set samples per pixel
+            uint16_t samples = SAMPLES_PER_RGB_PIXEL;
+            BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_SAMPLESPERPIXEL, 1, &samples,
+                    TIFF_IFD_0), env, TAG_SAMPLESPERPIXEL, writer);
+        }
+
+        {
+            // Set bits per sample
+            uint16_t bits = BITS_PER_RGB_SAMPLE;
+            BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_BITSPERSAMPLE, 1, &bits, TIFF_IFD_0),
+                    env, TAG_BITSPERSAMPLE, writer);
+        }
+
+        {
+            // Set subfiletype
+            uint32_t subfileType = 1; // Thumbnail image
+            BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_NEWSUBFILETYPE, 1, &subfileType,
+                    TIFF_IFD_0), env, TAG_NEWSUBFILETYPE, writer);
+        }
+
+        {
+            // Set compression
+            uint16_t compression = 1; // None
+            BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_COMPRESSION, 1, &compression,
+                    TIFF_IFD_0), env, TAG_COMPRESSION, writer);
+        }
+
+        {
+            // Set dimensions
+            uint32_t uWidth = nativeContext->getThumbnailWidth();
+            uint32_t uHeight = nativeContext->getThumbnailHeight();
+            BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_IMAGEWIDTH, 1, &uWidth, TIFF_IFD_0),
+                    env, TAG_IMAGEWIDTH, writer);
+            BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_IMAGELENGTH, 1, &uHeight, TIFF_IFD_0),
+                    env, TAG_IMAGELENGTH, writer);
+        }
+
+        {
+            // x resolution
+            uint32_t xres[] = { 72, 1 }; // default 72 ppi
+            BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_XRESOLUTION, 1, xres, TIFF_IFD_0),
+                    env, TAG_XRESOLUTION, writer);
+
+            // y resolution
+            uint32_t yres[] = { 72, 1 }; // default 72 ppi
+            BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_YRESOLUTION, 1, yres, TIFF_IFD_0),
+                    env, TAG_YRESOLUTION, writer);
+
+            uint16_t unit = 2; // inches
+            BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_RESOLUTIONUNIT, 1, &unit, TIFF_IFD_0),
+                    env, TAG_RESOLUTIONUNIT, writer);
+        }
+    }
+
+    if (writer->addStrip(TIFF_IFD_0) != OK) {
+        ALOGE("%s: Could not setup thumbnail strip tags.", __FUNCTION__);
+        jniThrowException(env, "java/lang/IllegalStateException",
+                "Failed to setup thumbnail strip tags.");
+        return nullptr;
+    }
+
+    if (writer->hasIfd(TIFF_IFD_SUB1)) {
         if (writer->addStrip(TIFF_IFD_SUB1) != OK) {
             ALOGE("%s: Could not main image strip tags.", __FUNCTION__);
             jniThrowException(env, "java/lang/IllegalStateException",
                     "Failed to setup main image strip tags.");
-            return;
+            return nullptr;
         }
     }
+    return writer;
+}
+
+static void DngCreator_destroy(JNIEnv* env, jobject thiz) {
+    ALOGV("%s:", __FUNCTION__);
+    DngCreator_setNativeContext(env, thiz, nullptr);
+}
+
+static void DngCreator_nativeSetOrientation(JNIEnv* env, jobject thiz, jint orient) {
+    ALOGV("%s:", __FUNCTION__);
+
+    NativeContext* context = DngCreator_getNativeContext(env, thiz);
+    if (context == nullptr) {
+        ALOGE("%s: Failed to initialize DngCreator", __FUNCTION__);
+        jniThrowException(env, "java/lang/AssertionError",
+                "setOrientation called with uninitialized DngCreator");
+        return;
+    }
+
+    uint16_t orientation = static_cast<uint16_t>(orient);
+    context->setOrientation(orientation);
+}
+
+static void DngCreator_nativeSetDescription(JNIEnv* env, jobject thiz, jstring description) {
+    ALOGV("%s:", __FUNCTION__);
+
+    NativeContext* context = DngCreator_getNativeContext(env, thiz);
+    if (context == nullptr) {
+        ALOGE("%s: Failed to initialize DngCreator", __FUNCTION__);
+        jniThrowException(env, "java/lang/AssertionError",
+                "setDescription called with uninitialized DngCreator");
+        return;
+    }
+
+    const char* desc = env->GetStringUTFChars(description, nullptr);
+    context->setDescription(String8(desc));
+    env->ReleaseStringUTFChars(description, desc);
+}
+
+static void DngCreator_nativeSetGpsTags(JNIEnv* env, jobject thiz, jintArray latTag,
+        jstring latRef, jintArray longTag, jstring longRef, jstring dateTag, jintArray timeTag) {
+    ALOGV("%s:", __FUNCTION__);
+
+    NativeContext* context = DngCreator_getNativeContext(env, thiz);
+    if (context == nullptr) {
+        ALOGE("%s: Failed to initialize DngCreator", __FUNCTION__);
+        jniThrowException(env, "java/lang/AssertionError",
+                "setGpsTags called with uninitialized DngCreator");
+        return;
+    }
+
+    GpsData data;
+
+    jsize latLen = env->GetArrayLength(latTag);
+    jsize longLen = env->GetArrayLength(longTag);
+    jsize timeLen = env->GetArrayLength(timeTag);
+    if (latLen != GpsData::GPS_VALUE_LENGTH) {
+        jniThrowException(env, "java/lang/IllegalArgumentException",
+                "invalid latitude tag length");
+        return;
+    } else if (longLen != GpsData::GPS_VALUE_LENGTH) {
+        jniThrowException(env, "java/lang/IllegalArgumentException",
+                "invalid longitude tag length");
+        return;
+    } else if (timeLen != GpsData::GPS_VALUE_LENGTH) {
+        jniThrowException(env, "java/lang/IllegalArgumentException",
+                "invalid time tag length");
+        return;
+    }
+
+    env->GetIntArrayRegion(latTag, 0, static_cast<jsize>(GpsData::GPS_VALUE_LENGTH),
+            reinterpret_cast<jint*>(&data.mLatitude));
+    env->GetIntArrayRegion(longTag, 0, static_cast<jsize>(GpsData::GPS_VALUE_LENGTH),
+            reinterpret_cast<jint*>(&data.mLongitude));
+    env->GetIntArrayRegion(timeTag, 0, static_cast<jsize>(GpsData::GPS_VALUE_LENGTH),
+            reinterpret_cast<jint*>(&data.mTimestamp));
+
+
+    env->GetStringUTFRegion(latRef, 0, 1, reinterpret_cast<char*>(&data.mLatitudeRef));
+    data.mLatitudeRef[GpsData::GPS_REF_LENGTH - 1] = '\0';
+    env->GetStringUTFRegion(longRef, 0, 1, reinterpret_cast<char*>(&data.mLongitudeRef));
+    data.mLongitudeRef[GpsData::GPS_REF_LENGTH - 1] = '\0';
+    env->GetStringUTFRegion(dateTag, 0, GpsData::GPS_DATE_LENGTH - 1,
+            reinterpret_cast<char*>(&data.mDate));
+    data.mDate[GpsData::GPS_DATE_LENGTH - 1] = '\0';
+
+    context->setGpsData(data);
+}
+
+static void DngCreator_nativeSetThumbnail(JNIEnv* env, jobject thiz, jobject buffer, jint width,
+        jint height) {
+    ALOGV("%s:", __FUNCTION__);
+
+    NativeContext* context = DngCreator_getNativeContext(env, thiz);
+    if (context == nullptr) {
+        ALOGE("%s: Failed to initialize DngCreator", __FUNCTION__);
+        jniThrowException(env, "java/lang/AssertionError",
+                "setThumbnail called with uninitialized DngCreator");
+        return;
+    }
+
+    size_t fullSize = width * height * BYTES_PER_RGB_PIXEL;
+    jlong capacity = env->GetDirectBufferCapacity(buffer);
+    if (static_cast<uint64_t>(capacity) != static_cast<uint64_t>(fullSize)) {
+        jniThrowExceptionFmt(env, "java/lang/AssertionError",
+                "Invalid size %d for thumbnail, expected size was %d",
+                capacity, fullSize);
+        return;
+    }
+
+    uint8_t* pixelBytes = reinterpret_cast<uint8_t*>(env->GetDirectBufferAddress(buffer));
+    if (pixelBytes == nullptr) {
+        ALOGE("%s: Could not get native ByteBuffer", __FUNCTION__);
+        jniThrowException(env, "java/lang/IllegalArgumentException", "Invalid ByteBuffer");
+        return;
+    }
 
     if (!context->setThumbnail(pixelBytes, width, height)) {
         jniThrowException(env, "java/lang/IllegalStateException",
@@ -1947,16 +2088,20 @@
         return;
     }
 
-    TiffWriter* writer = DngCreator_getCreator(env, thiz);
     NativeContext* context = DngCreator_getNativeContext(env, thiz);
-    if (writer == NULL || context == NULL) {
+    if (context == nullptr) {
         ALOGE("%s: Failed to initialize DngCreator", __FUNCTION__);
         jniThrowException(env, "java/lang/AssertionError",
                 "Write called with uninitialized DngCreator");
         return;
     }
+    sp<TiffWriter> writer = DngCreator_setup(env, thiz, uWidth, uHeight);
 
-    // Validate DNG header
+    if (writer.get() == nullptr) {
+        return;
+    }
+
+    // Validate DNG size
     if (!validateDngHeader(env, writer, *(context->getCharacteristics()), width, height)) {
         return;
     }
@@ -1991,7 +2136,7 @@
         }
 
         uint8_t* pixelBytes = reinterpret_cast<uint8_t*>(env->GetDirectBufferAddress(inBuffer));
-        if (pixelBytes == NULL) {
+        if (pixelBytes == nullptr) {
             ALOGE("%s: Could not get native ByteBuffer", __FUNCTION__);
             jniThrowException(env, "java/lang/IllegalArgumentException", "Invalid ByteBuffer");
             return;
@@ -2051,16 +2196,20 @@
         return;
     }
 
-    TiffWriter* writer = DngCreator_getCreator(env, thiz);
     NativeContext* context = DngCreator_getNativeContext(env, thiz);
-    if (writer == NULL || context == NULL) {
+    if (context == nullptr) {
         ALOGE("%s: Failed to initialize DngCreator", __FUNCTION__);
         jniThrowException(env, "java/lang/AssertionError",
                 "Write called with uninitialized DngCreator");
         return;
     }
+    sp<TiffWriter> writer = DngCreator_setup(env, thiz, uWidth, uHeight);
 
-    // Validate DNG header
+    if (writer.get() == nullptr) {
+        return;
+    }
+
+    // Validate DNG size
     if (!validateDngHeader(env, writer, *(context->getCharacteristics()), width, height)) {
         return;
     }
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 568e61e..85b6c12 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2652,4 +2652,6 @@
     <public type="id" name="accessibilityActionScrollDown" id="0x0102003a" />
     <public type="id" name="accessibilityActionScrollRight" id="0x0102003b" />
     <public type="id" name="accessibilityActionContextClick" id="0x0102003c" />
+
+    <public type="string" name="fingerprint_icon_content_description" />
 </resources>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 35c1f0e..a45a0fa 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1221,6 +1221,9 @@
     <string-array name="fingerprint_error_vendor">
     </string-array>
 
+    <!-- Content description which should be used for the fingerprint icon. -->
+    <string name="fingerprint_icon_content_description">Fingerprint icon</string>
+
     <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permlab_readSyncSettings">read sync settings</string>
     <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
diff --git a/docs/html/tools/data-binding/guide.jd b/docs/html/tools/data-binding/guide.jd
index 7c4c15a..2de5bc2 100644
--- a/docs/html/tools/data-binding/guide.jd
+++ b/docs/html/tools/data-binding/guide.jd
@@ -28,6 +28,10 @@
           <li>
             <a href="#binding_data">Binding Data</a>
           </li>
+
+          <li>
+            <a href="#binding_events">Binding Events</a>
+          </li>
         </ol>
       </li>
 
@@ -141,7 +145,7 @@
 &mdash; it's a support library, so you can use it with all Android platform
 versions back to <strong>Android 2.1</strong> (API level 7+).</p>
 
-<p>To use data binding, Android Plugin for Gradle <strong>1.3.0-beta1</strong>
+<p>To use data binding, Android Plugin for Gradle <strong>1.3.0-beta4</strong>
 or higher is required.</p>
 
 <h4>Beta release</h4>
@@ -187,9 +191,9 @@
 <p>To get started with Data Binding, download the library from the Support
 repository in the Android SDK manager. </p>
 
-<p>The Data Binding plugin requires Android Plugin for Gradle <strong>1.3.0-beta1
-or higher</strong>, so update your build dependencies (in
-<code>build.gradle</code>) as needed.</p>
+<p>The Data Binding plugin requires Android Plugin for Gradle <strong>1.3.0-beta4
+or higher</strong>, so update your build dependencies (in the top-level
+<code>build.gradle</code> file) as needed.</p>
 
 <p>Also, make sure you are using a compatible version of Android Studio.
 <strong>Android Studio 1.3</strong> adds the code-completion and layout-preview
@@ -201,18 +205,18 @@
 
 <p>
   To set up your application to use data binding, add data binding to the class
-  path of your <code>build.gradle</code> file, right below "android".
+  path of your top-level <code>build.gradle</code> file, right below "android".
 </p>
 
 <pre>
    dependencies {
-       classpath <strong>"com.android.tools.build:gradle:1.3.0-beta1"
-       </strong>classpath <strong>"com.android.databinding:dataBinder:</strong>1.0-rc0"
+       classpath <strong>"com.android.tools.build:gradle:1.3.0-beta4"</strong>
+       classpath <strong>"com.android.databinding:dataBinder:1.0-rc1"</strong>
    }
-}
 </pre>
 <p>
-  Then make sure jcenter is in the repositories list for your sub projects.
+  Then make sure jcenter is in the repositories list for your projects in the top-level
+  <code>build.gradle</code> file.
 </p>
 
 <pre>
@@ -228,8 +232,8 @@
 </p>
 
 <pre>
-apply plugin: ‘com.android.application&apos;
-apply plugin: &apos;<strong>com.android.databinding</strong>&apos;
+apply plugin: &apos;com.android.application&apos;
+apply plugin: &apos;com.android.databinding&apos;
 </pre>
 <p>
   The data binding plugin is going to add necessary <strong>provided</strong>
@@ -252,23 +256,23 @@
 </p>
 
 <pre>
-<em>&lt;?</em><strong>xml version="1.0" encoding="utf-8"</strong><em>?&gt;
-</em>&lt;<strong>layout xmlns:android="http://schemas.android.com/apk/res/android"</strong>&gt;
-   &lt;<strong>data</strong>&gt;
-       &lt;<strong>variable name="user" type="com.example.User"</strong>/&gt;
-   &lt;/<strong>data</strong>&gt;
-   &lt;<strong>LinearLayout
+&lt;?xml version="1.0" encoding="utf-8"?&gt;
+&lt;layout xmlns:android="http://schemas.android.com/apk/res/android"&gt;
+   &lt;data&gt;
+       &lt;variable name="user" type="com.example.User"/&gt;
+   &lt;/data&gt;
+   &lt;LinearLayout
        android:orientation="vertical"
        android:layout_width="match_parent"
-       android:layout_height="match_parent"</strong>&gt;
-       &lt;<strong>TextView android:layout_width="wrap_content"
+       android:layout_height="match_parent"&gt;
+       &lt;TextView android:layout_width="wrap_content"
            android:layout_height="wrap_content"
-           android:text="&commat;{user.firstName}"</strong>/&gt;
-       &lt;<strong>TextView android:layout_width="wrap_content"
+           android:text="&commat;{user.firstName}"/&gt;
+       &lt;TextView android:layout_width="wrap_content"
            android:layout_height="wrap_content"
-           android:text="&commat;{user.lastName}"</strong>/&gt;
-   &lt;/<strong>LinearLayout</strong>&gt;
-&lt;/<strong>layout</strong>&gt;
+           android:text="&commat;{user.lastName}"/&gt;
+   &lt;/LinearLayout&gt;
+&lt;/layout&gt;
 </pre>
 <p>
   The user <strong>variable</strong> within <strong>data</strong> describes a
@@ -285,9 +289,9 @@
 </p>
 
 <pre>
-&lt;<strong>TextView android:layout_width="wrap_content"
+&lt;TextView android:layout_width="wrap_content"
           android:layout_height="wrap_content"
-          android:text="&commat;{user.firstName}"</strong>/&gt;
+          android:text="&commat;{user.firstName}"/&gt;
 </pre>
 <h3 id="data_object">
   Data Object
@@ -298,12 +302,12 @@
 </p>
 
 <pre>
-<strong>public class </strong>User {
-   <strong>public final </strong>String <strong>firstName</strong>;
-   <strong>public final </strong>String <strong>lastName</strong>;
-   <strong>public </strong>User(String firstName, String lastName) {
-       <strong>this</strong>.<strong>firstName </strong>= firstName;
-       <strong>this</strong>.<strong>lastName </strong>= lastName;
+public class User {
+   public final String firstName;
+   public final String lastName;
+   public User(String firstName, String lastName) {
+       this.firstName = firstName;
+       this.lastName = lastName;
    }
 }
 </pre>
@@ -314,18 +318,18 @@
 </p>
 
 <pre>
-<strong>public class </strong>User {
-   <strong>private final </strong>String <strong>firstName</strong>;
-   <strong>private final </strong>String <strong>lastName</strong>;
-   <strong>public </strong>User(String firstName, String lastName) {
-       <strong>this</strong>.<strong>firstName </strong>= firstName;
-       <strong>this</strong>.<strong>lastName </strong>= lastName;
+public class User {
+   private final String firstName;
+   private final String lastName;
+   public User(String firstName, String lastName) {
+       this.firstName = firstName;
+       this.lastName = lastName;
    }
-   <strong>public </strong>String getFirstName() {
-       <strong>return this</strong>.<strong>firstName</strong>;
+   public String getFirstName() {
+       return this.firstName;
    }
-   <strong>public </strong>String getLastName() {
-       <strong>return this</strong>.<strong>lastName</strong>;
+   public String getLastName() {
+       return this.lastName;
    }
 }
 </pre>
@@ -334,7 +338,8 @@
   expression <strong><code>&commat;{user.firstName}</code></strong> used for
   the TextView’s <strong><code>android:text</code></strong> attribute will
   access the <strong><code>firstName</code></strong> field in the former class
-  and the <code>getFirstName()</code> method in the latter class.
+  and the <code>getFirstName()</code> method in the latter class. Alternatively, it
+  will also be resolved to <code>firstName()</code> if that method exists.
 </p>
 
 <h3 id="binding_data">
@@ -344,8 +349,8 @@
 <p>
   By default, a Binding class will be generated based on the name of the layout
   file, converting it to Pascal case and suffixing “Binding” to it. The above
-  layout file was <code>activity_main.xml</code> so the generate class was
-  <code>ActivityMainBinding</code>. This class holds all the bindings from the
+  layout file was <code>main_activity.xml</code> so the generate class was
+  <code>MainActivityBinding</code>. This class holds all the bindings from the
   layout properties (e.g. the <code>user</code> variable) to the layout’s Views
   and knows how to assign values for the binding expressions.The easiest means
   for creating the bindings is to do it while inflating:
@@ -353,10 +358,10 @@
 
 <pre>
 &commat;Override
-<strong>protected void </strong>onCreate(Bundle savedInstanceState) {
-   <strong>super</strong>.onCreate(savedInstanceState);
-   ActivityMainBinding binding = DataBindingUtil.<em>setContentView</em>(<strong>this</strong>, R.layout.<em><strong>main_activity</strong></em>);
-   User user = <strong>new </strong>User(<strong>"Test"</strong>, <strong>"User"</strong>);
+protected void onCreate(Bundle savedInstanceState) {
+   super.onCreate(savedInstanceState);
+   MainActivityBinding binding = DataBindingUtil.setContentView(this, R.layout.main_activity);
+   User user = new User("Test", "User");
    binding.setUser(user);
 }
 </pre>
@@ -374,11 +379,55 @@
 </p>
 
 <pre>
-ListItemBinding binding = ListItemBinding.inflate(layoutInflater, viewGroup,
-false);
+ListItemBinding binding = ListItemBinding.inflate(layoutInflater, viewGroup, false);
 //or
 ListItemBinding binding = DataBindingUtil.<em>inflate</em>(layoutInflater, R.layout.<em><strong>list_item</strong></em>, viewGroup, <strong>false</strong>);
 </pre>
+
+<h3 id="binding_events">
+  Binding Events
+</h3>
+<p>
+  Events may be bound to handler methods directly, similar to the way
+  <strong><code>android:onClick</code></strong> can be assigned to a method in the Activity.
+  Event attribute names are governed by the name of the listener method with a few exceptions.
+  For example, {@link android.view.View.OnLongClickListener} has a method {@link android.view.View.OnLongClickListener#onLongClick onLongClick()},
+  so the attribute for this event is <code>android:onLongClick</code>.
+</p>
+<p>
+  To assign an event to its handler, use a normal binding expression, with the value
+  being the method name to call. For example, if your data object has two methods:
+</p>
+<pre>public class MyHandlers {
+    public void onClickFriend(View view) { ... }
+    public void onClickEnemy(View view) { ... }
+}
+</pre>
+<p>
+  The binding expression can assign the click listener for a View:
+</p>
+<pre>
+&lt;?xml version="1.0" encoding="utf-8"?&gt;
+&lt;layout xmlns:android="http://schemas.android.com/apk/res/android"&gt;
+   &lt;data&gt;
+       &lt;variable name="handlers" type="com.example.Handlers"/&gt;
+       &lt;variable name="user" type="com.example.User"/&gt;
+   &lt;/data&gt;
+   &lt;LinearLayout
+       android:orientation="vertical"
+       android:layout_width="match_parent"
+       android:layout_height="match_parent"&gt;
+       &lt;TextView android:layout_width="wrap_content"
+           android:layout_height="wrap_content"
+           android:text="&commat;{user.firstName}"
+           android:onClick="&commat;{user.isFriend ? handlers.onClickFriend : handlers.onClickEnemy}"/&gt;
+       &lt;TextView android:layout_width="wrap_content"
+           android:layout_height="wrap_content"
+           android:text="&commat;{user.lastName}"
+           android:onClick="&commat;{user.isFriend ? handlers.onClickFriend : handlers.onClickEnemy}"/&gt;
+   &lt;/LinearLayout&gt;
+&lt;/layout&gt;
+</pre>
 <h2 id="layout_details">
   Layout Details
 </h2>
@@ -394,20 +443,20 @@
 </p>
 
 <pre>
-&lt;<strong>data</strong>&gt;
-    &lt;<strong>import type="android.view.View"</strong>/&gt;
-&lt;/<strong>data</strong>&gt;
+&lt;data&gt;
+    &lt;import type="android.view.View"/&gt;
+&lt;/data&gt;
 </pre>
 <p>
   Now, View may be used within your binding expression:
 </p>
 
 <pre>
-&lt;<strong>TextView
+&lt;TextView
    android:text="&commat;{user.lastName}"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
-   android:visibility="&commat;{user.isAdult ? View.VISIBLE : View.GONE}"</strong>/&gt;
+   android:visibility="&commat;{user.isAdult ? View.VISIBLE : View.GONE}"/&gt;
 </pre>
 <p>
   When there are class name conflicts, one of the classes may be renamed to an
@@ -415,9 +464,9 @@
 </p>
 
 <pre>
-&lt;<strong>import type="android.view.View"</strong>/&gt;
-&lt;<strong>import type="com.example.real.estate.View"
-        alias="Vista"</strong>/&gt;
+&lt;import type="android.view.View"/&gt;
+&lt;import type="com.example.real.estate.View"
+        alias="Vista"/&gt;
 </pre>
 <p>
   Now, <strong><code>Vista</code></strong> may be used to reference the
@@ -428,12 +477,12 @@
 </p>
 
 <pre>
-&lt;<strong>data</strong>&gt;
-    &lt;<strong>import type="com.example.User"</strong>/&gt;
-    &lt;<strong>import type="java.util.List"</strong>/&gt;
-    &lt;<strong>variable name="user" type="User"</strong>/&gt;
-    &lt;<strong>variable name="userList" type="List&amp;lt;User&gt;"</strong>/&gt;
-    &lt;/<strong>data</strong>&gt;
+&lt;data&gt;
+    &lt;import type="com.example.User"/&gt;
+    &lt;import type="java.util.List"/&gt;
+    &lt;variable name="user" type="User"/&gt;
+    &lt;variable name="userList" type="List&amp;lt;User&gt;"/&gt;
+&lt;/data&gt;
 </pre>
 <p class="caution">
   <strong>Note</strong>: Android Studio does not yet handle imports so the
@@ -443,10 +492,10 @@
 </p>
 
 <pre>
-&lt;<strong>TextView
+&lt;TextView
    android:text="&commat;{((User)(user.connection)).lastName}"
    android:layout_width="wrap_content"
-   android:layout_height="wrap_content"</strong>/&gt;
+   android:layout_height="wrap_content"/&gt;
 </pre>
 <p>
   Imported types may also be used when referencing static fields and methods in
@@ -454,15 +503,15 @@
 </p>
 
 <pre>
-&lt;<strong>data</strong>&gt;
-    &lt;<strong>import type="com.example.MyStringUtils"</strong>/&gt;
-    &lt;<strong>variable name="user" type="com.example.User"</strong>/&gt;
-&lt;/<strong>data</strong>&gt;
+&lt;data&gt;
+    &lt;import type="com.example.MyStringUtils"/&gt;
+    &lt;variable name="user" type="com.example.User"/&gt;
+&lt;/data&gt;

-&lt;<strong>TextView
+&lt;TextView
    android:text="&commat;{MyStringUtils.capitalize(user.lastName)}"
    android:layout_width="wrap_content"
-   android:layout_height="wrap_content"</strong>/&gt;
+   android:layout_height="wrap_content"/&gt;
 </pre>
 <p>
   Just as in Java, <code>java.lang.*</code> is imported automatically.
@@ -481,16 +530,16 @@
 </p>
 
 <pre>
-&lt;<strong>data</strong>&gt;
-    &lt;<strong>import type="android.graphics.drawable.Drawable"</strong>/&gt;
-    &lt;<strong>variable name="user"  type="com.example.User"</strong>/&gt;
-    &lt;<strong>variable name="image" type="Drawable"</strong>/&gt;
-    &lt;<strong>variable name="note"  type="String"</strong>/&gt;
-&lt;/<strong>data</strong>&gt;
+&lt;data&gt;
+    &lt;import type="android.graphics.drawable.Drawable"/&gt;
+    &lt;variable name="user"  type="com.example.User"/&gt;
+    &lt;variable name="image" type="Drawable"/&gt;
+    &lt;variable name="note"  type="String"/&gt;
+&lt;/data&gt;
 </pre>
 <p>
   The variable types are inspected at compile time, so if a variable implements
-  <a href="#observable_objects">Observable</a> or is an <a href=
+  {@link android.databinding.Observable} or is an <a href=
   "#observable_collections">observable collection</a>, that should be reflected
   in the type. If the variable is a base class or interface that does not
   implement the Observable* interface, the variables will <strong>not
@@ -533,9 +582,9 @@
 </p>
 
 <pre>
-&lt;<strong>data class="ContactItem"</strong>&gt;
+&lt;data class="ContactItem"&gt;
     ...
-&lt;/<strong>data</strong>&gt;
+&lt;/data&gt;
 </pre>
 <p>
   This generates the binding class as <code>ContactItem</code> in the
@@ -545,9 +594,9 @@
 </p>
 
 <pre>
-&lt;<strong>data class=".ContactItem"</strong>&gt;
+&lt;data class=".ContactItem"&gt;
     ...
-&lt;/<strong>data</strong>&gt;
+&lt;/data&gt;
 </pre>
 <p>
   In this case, <code>ContactItem</code> is generated in the module package
@@ -555,9 +604,9 @@
 </p>
 
 <pre>
-&lt;<strong>data class="com.example.ContactItem"</strong>&gt;
+&lt;data class="com.example.ContactItem"&gt;
     ...
-&lt;/<strong>data</strong>&gt;
+&lt;/data&gt;
 </pre>
 <h3 id="includes">
   Includes
@@ -570,28 +619,46 @@
 </p>
 
 <pre>
-<em>&lt;?</em><strong>xml version="1.0" encoding="utf-8"</strong><em>?&gt;
-</em>&lt;<strong>layout xmlns:android="http://schemas.android.com/apk/res/android"
-</strong>       <strong> xmlns:bind="http://schemas.android.com/apk/res-auto"</strong>&gt;
-   &lt;<strong>data</strong>&gt;
-       &lt;<strong>variable name="user" type="com.example.User"</strong>/&gt;
-   &lt;/<strong>data</strong>&gt;
-   &lt;<strong>LinearLayout
+&lt;?xml version="1.0" encoding="utf-8"?&gt;
+&lt;layout xmlns:android="http://schemas.android.com/apk/res/android"
+        xmlns:bind="http://schemas.android.com/apk/res-auto"&gt;
+   &lt;data&gt;
+       &lt;variable name="user" type="com.example.User"/&gt;
+   &lt;/data&gt;
+   &lt;LinearLayout
        android:orientation="vertical"
        android:layout_width="match_parent"
-       android:layout_height="match_parent"</strong>&gt;
-       &lt;<strong>include layout="&commat;layout/name"
-           bind:user="&commat;{user}"</strong>/&gt;
-       &lt;<strong>include layout="&commat;layout/contact"
-           bind:user="&commat;{user}"</strong>/&gt;
-   &lt;/<strong>LinearLayout</strong>&gt;
-&lt;/<strong>layout</strong>&gt;
+       android:layout_height="match_parent"&gt;
+       &lt;include layout="&commat;layout/name"
+           bind:user="&commat;{user}"/&gt;
+       &lt;include layout="&commat;layout/contact"
+           bind:user="&commat;{user}"/&gt;
+   &lt;/LinearLayout&gt;
+&lt;/layout&gt;
 </pre>
 <p>
   Here, there must be a <code>user</code> variable in both the
   <code>name.xml</code> and <code>contact.xml</code> layout files.
 </p>
-
+<p>
+  Data binding does not support include as a direct child of a merge element. For example,
+  <strong>the following layout is not supported:</strong>
+</p>
+<pre>
+&lt;?xml version="1.0" encoding="utf-8"?&gt;
+&lt;layout xmlns:android="http://schemas.android.com/apk/res/android"
+        xmlns:bind="http://schemas.android.com/apk/res-auto"&gt;
+   &lt;data&gt;
+       &lt;variable name="user" type="com.example.User"/&gt;
+   &lt;/data&gt;
+   &lt;merge&gt;
+       &lt;include layout="&commat;layout/name"
+           bind:user="&commat;{user}"/&gt;
+       &lt;include layout="&commat;layout/contact"
+           bind:user="&commat;{user}"/&gt;
+   &lt;/merge&gt;
+&lt;/layout&gt;
+</pre>
 <h3 id="expression_language">
   Expression Language
 </h3>
@@ -613,10 +680,10 @@
   </li>
 
   <li>
-    <code>L</code>ogical <strong><code>&& ||</code></strong>
+    Logical <strong><code>&& ||</code></strong>
   </li>
 
-  <li>Binary <strong><code>&</code> <code>|</code> <code>^</code></strong>
+  <li>Binary <strong><code>& | ^</code></strong>
   </li>
 
   <li>Unary <strong><code>+ - ! ~</code></strong>
@@ -659,9 +726,9 @@
 </p>
 
 <pre>
-<strong>android:text="&commat;{String.valueOf(index + 1)}"
+android:text="&commat;{String.valueOf(index + 1)}"
 android:visibility="&commat;{age &amp;lt; 13 ? View.GONE : View.VISIBLE}"
-android:transitionName=&apos;&commat;{"image_" + id}&apos;</strong>
+android:transitionName=&apos;&commat;{"image_" + id}&apos;
 </pre>
 <h4 id="missing_operations">
   Missing Operations
@@ -746,23 +813,23 @@
 </p>
 
 <pre>
-&lt;<strong>data</strong>&gt;
-    &lt;<strong>import type="android.util.SparseArray"</strong>/&gt;
-    &lt;<strong>import type="java.util.Map"</strong>/&gt;
-    &lt;<strong>import type="java.util.List"</strong>/&gt;
-    &lt;<strong>variable name="list" type="List&lt;String&gt;"</strong>/&gt;
-    &lt;<strong>variable name="sparse" type="SparseArray&amp;lt;String&gt;"</strong>/&gt;
-    &lt;<strong>variable name="map" type="Map&amp;lt;String, String&gt;"</strong>/&gt;
-    &lt;<strong>variable name="index" type="int"</strong>/&gt;
-    &lt;<strong>variable name="key" type="String"</strong>/&gt;
-&lt;/<strong>data</strong>&gt;
+&lt;data&gt;
+    &lt;import type="android.util.SparseArray"/&gt;
+    &lt;import type="java.util.Map"/&gt;
+    &lt;import type="java.util.List"/&gt;
+    &lt;variable name="list" type="List&amp;lt;String&gt;"/&gt;
+    &lt;variable name="sparse" type="SparseArray&amp;lt;String&gt;"/&gt;
+    &lt;variable name="map" type="Map&amp;lt;String, String&gt;"/&gt;
+    &lt;variable name="index" type="int"/&gt;
+    &lt;variable name="key" type="String"/&gt;
+&lt;/data&gt;

-<strong>android:text="&commat;{list[index]}"
-</strong>…
-<strong>android:text="&commat;{sparse[index]}"
-</strong>…
-<strong>android:text="&commat;{map[key]}"
-</strong>
+android:text="&commat;{list[index]}"
+…
+android:text="&commat;{sparse[index]}"
+…
+android:text="&commat;{map[key]}"
+
 </pre>
 <h4 id="string_literals">
   String Literals
@@ -774,7 +841,7 @@
 </p>
 
 <pre>
-<strong>android:text=&apos;&commat;{map["firstName"]}&apos;</strong>
+android:text=&apos;&commat;{map["firstName"]}&apos;
 </pre>
 <p>
   It is also possible to use double quotes to surround the attribute value.
@@ -783,8 +850,8 @@
 </p>
 
 <pre>
-<strong>android:text="&commat;{map[`firstName`}"
-android:text="&commat;{map[&amp;quot;firstName&amp;quot;]}"</strong>
+android:text="&commat;{map[`firstName`}"
+android:text="&commat;{map[&amp;quot;firstName&amp;quot;]}"
 </pre>
 <h4 id="resources">
   Resources
@@ -796,15 +863,15 @@
 </p>
 
 <pre>
-<strong>android:padding="&commat;{large? &commat;dimen/largePadding : &commat;dimen/smallPadding}"</strong>
+android:padding="&commat;{large? &commat;dimen/largePadding : &commat;dimen/smallPadding}"
 </pre>
 <p>
   Format strings and plurals may be evaluated by providing parameters:
 </p>
 
 <pre>
-<strong>android:text="&commat;{&commat;string/nameFormat(firstName, lastName)}"
-android:text="&commat;{&commat;plurals/banana(bananaCount)}"</strong>
+android:text="&commat;{&commat;string/nameFormat(firstName, lastName)}"
+android:text="&commat;{&commat;plurals/banana(bananaCount)}"
 </pre>
 <p>
   When a plural takes multiple parameters, all parameters should be passed:
@@ -815,7 +882,7 @@
   Have an orange
   Have %d oranges
 
-android:text="<strong>&commat;{&commat;plurals/orange(orangeCount, orangeCount)}</strong>"
+android:text="&commat;{&commat;plurals/orange(orangeCount, orangeCount)}"
 </pre>
 <p>
   Some resources require explicit type evaluation.
@@ -836,9 +903,7 @@
 
   <tr>
     <td>
-      <pre>
-String[]
-</pre>
+      String[]
     </td>
     <td>
       &commat;array
@@ -901,9 +966,7 @@
       color <code>int</code>
     </td>
     <td>
-      <pre>
-&commat;color
-</pre>
+      &commat;color
     </td>
     <td>
       &commat;color
@@ -932,8 +995,9 @@
   a POJO will not cause the UI to update. The real power of data binding can be
   used by giving your data objects the ability to notify when data changes.
   There are three different data change notification mechanisms,
-  <code>Observable</code> objects, <code>ObservableField</code>s, and
-  <code>observable collections</code>.
+  <a href="#observable_objects">Observable objects</a>,
+  <a href="#observablefields">observable fields</a>, and
+  <a href="#observable_collections">observable collection</a>s.
 </p>
 
 <p>
@@ -946,49 +1010,49 @@
 </h3>
 
 <p>
-  A class implementing <code>android.databinding.Observable</code> interface
+  A class implementing the {@link android.databinding.Observable} interface
   will allow the binding to attach a single listener to a bound object to
   listen for changes of all properties on that object.
 </p>
 
 <p>
-  The <code>Observable</code> interface has a mechanism to add and remove
+  The {@link android.databinding.Observable} interface has a mechanism to add and remove
   listeners, but notifying is up to the developer. To make development easier,
-  a base class, <code>BaseObservable,</code> was created to implement the
+  a base class, {@link android.databinding.BaseObservable}, was created to implement the
   listener registration mechanism. The data class implementer is still
   responsible for notifying when the properties change. This is done by
-  assigning a <code>Bindable</code> annotation to the getter and notifying in
+  assigning a {@link android.databinding.Bindable} annotation to the getter and notifying in
   the setter.
 </p>
 
 <pre>
-<strong>private static class </strong>User <strong>extends </strong>BaseObservable {
-   <strong>private </strong>String <strong>firstName</strong>;
-   <strong>private </strong>String <strong>lastName</strong>;
+private static class User extends BaseObservable {
+   private String firstName;
+   private String lastName;
    &commat;Bindable
-   <strong>public </strong>String getFirstName() {
-       <strong>return this</strong>.<strong>firstName</strong>;
+   public String getFirstName() {
+       return this.firstName;
    }
    &commat;Bindable
-   <strong>public </strong>String getFirstName() {
-       <strong>return this</strong>.<strong>lastName</strong>;
+   public String getLastName() {
+       return this.lastName;
    }
-   <strong>public void </strong>setFirstName(String firstName) {
-       <strong>this</strong>.<strong>firstName </strong>= firstName;
+   public void setFirstName(String firstName) {
+       this.firstName = firstName;
        notifyPropertyChanged(BR.firstName);
    }
-   <strong>public void </strong>setLastName(String lastName) {
-       <strong>this</strong>.<strong>lastName </strong>= lastName;
+   public void setLastName(String lastName) {
+       this.lastName = lastName;
        notifyPropertyChanged(BR.lastName);
    }
 }
 </pre>
 <p>
-  The <code>Bindable</code> annotation generates an entry in the BR class file
+  The {@link android.databinding.Bindable} annotation generates an entry in the BR class file
   during compilation. The BR class file will be generated in the module
-  package.If the base class for data classes cannot be changed, the
-  <code>Observable</code> interface may be implemented using the convenient
-  <code>PropertyChangeRegistry</code> to store and notify listeners
+  package. If the base class for data classes cannot be changed, the
+  {@link android.databinding.Observable} interface may be implemented using the convenient
+  {@link android.databinding.PropertyChangeRegistry} to store and notify listeners
   efficiently.
 </p>
 
@@ -997,20 +1061,30 @@
 </h3>
 
 <p>
-  A little work is involved in creating Observable classes, so developers who
-  want to save time or have few properties may use ObservableFields.
-  ObservableFields are self-contained observable objects that have a single
-  field. There are versions for all primitive types and one for reference
-  types. To use, create a public final field in the data class:
+  A little work is involved in creating {@link android.databinding.Observable} classes, so
+  developers who want to save time or have few properties may use
+  {@link android.databinding.ObservableField} and its siblings
+  {@link android.databinding.ObservableBoolean},
+  {@link android.databinding.ObservableByte},
+  {@link android.databinding.ObservableChar},
+  {@link android.databinding.ObservableShort},
+  {@link android.databinding.ObservableInt},
+  {@link android.databinding.ObservableLong},
+  {@link android.databinding.ObservableFloat},
+  {@link android.databinding.ObservableDouble}, and
+  {@link android.databinding.ObservableParcelable}.
+  <code>ObservableFields</code> are self-contained observable objects that have a single
+  field. The primitive versions avoid boxing and unboxing during access operations.
+  To use, create a public final field in the data class:
 </p>
 
 <pre>
-<strong>private static class </strong>User <strong>extends </strong>BaseObservable {
-   <strong>public final </strong>ObservableField&lt;String&gt; <strong>firstName </strong>=
-       <strong>new </strong>ObservableField&lt;&gt;();
-   <strong>public final </strong>ObservableField&lt;String&gt; <strong>lastName </strong>=
-       <strong>new </strong>ObservableField&lt;&gt;();
-   <strong>public final </strong>ObservableInt <strong>age </strong>= <strong>new </strong>ObservableInt();
+private static class User {
+   public final ObservableField&lt;String&gt; firstName =
+       new ObservableField&lt;&gt;();
+   public final ObservableField&lt;String&gt; lastName =
+       new ObservableField&lt;&gt;();
+   public final ObservableInt age = new ObservableInt();
 }
 </pre>
 <p>
@@ -1018,8 +1092,8 @@
 </p>
 
 <pre>
-user.<strong>firstName</strong>.set(<strong>"Google"</strong>);
-<strong>int </strong>age = user.<strong>age</strong>.get();
+user.firstName.set("Google");
+int age = user.age.get();
 </pre>
 <h3 id="observable_collections">
   Observable Collections
@@ -1027,43 +1101,44 @@
 
 <p>
   Some applications use more dynamic structures to hold data. Observable
-  collections allow keyed access to these data objects.ObservableArrayMap is
+  collections allow keyed access to these data objects.
+  {@link android.databinding.ObservableArrayMap} is
   useful when the key is a reference type, such as String.
 </p>
 
 <pre>
-ObservableArrayMap&lt;String, Object&gt; user = <strong>new </strong>ObservableArrayMap&lt;&gt;();
-user.put(<strong>"firstName"</strong>, <strong>"Google"</strong>);
-user.put(<strong>"lastName"</strong>, <strong>"Inc."</strong>);
-user.put(<strong>"age"</strong>, 17);
+ObservableArrayMap&lt;String, Object&gt; user = new ObservableArrayMap&lt;&gt;();
+user.put("firstName", "Google");
+user.put("lastName", "Inc.");
+user.put("age", 17);
 </pre>
 <p>
   In the layout, the map may be accessed through the String keys:
 </p>
 
 <pre>
-&lt;<strong>data</strong>&gt;
-    &lt;<strong>import type="android.databinding.ObservableMap"</strong>/&gt;
-    &lt;<strong>variable name="user" type="ObservableMap&amp;lt;String, Object&gt;"</strong>/&gt;
-&lt;/<strong>data</strong>&gt;
+&lt;data&gt;
+    &lt;import type="android.databinding.ObservableMap"/&gt;
+    &lt;variable name="user" type="ObservableMap&amp;lt;String, Object&gt;"/&gt;
+&lt;/data&gt;

-&lt;<strong>TextView
+&lt;TextView
    android:text=&apos;&commat;{user["lastName"]}&apos;
    android:layout_width="wrap_content"
-   android:layout_height="wrap_content"</strong>/&gt;
-&lt;<strong>TextView
+   android:layout_height="wrap_content"/&gt;
+&lt;TextView
    android:text=&apos;&commat;{String.valueOf(1 + (Integer)user["age"])}&apos;
    android:layout_width="wrap_content"
-   android:layout_height="wrap_content"</strong>/&gt;
+   android:layout_height="wrap_content"/&gt;
 </pre>
 <p>
-  ObservableArrayList is useful when the key is an integer:
+  {@link android.databinding.ObservableArrayList} is useful when the key is an integer:
 </p>
 
 <pre>
-ObservableArrayList&lt;Object&gt; user = <strong>new </strong>ObservableArrayList&lt;&gt;();
-user.add(<strong>"Google"</strong>);
-user.add(<strong>"Inc."</strong>);
+ObservableArrayList&lt;Object&gt; user = new ObservableArrayList&lt;&gt;();
+user.add("Google");
+user.add("Inc.");
 user.add(17);
 </pre>
 <p>
@@ -1071,20 +1146,20 @@
 </p>
 
 <pre>
-&lt;<strong>data</strong>&gt;
-    &lt;<strong>import type="android.databinding.ObservableList"</strong>/&gt;
-    &lt;<strong>import type="com.example.my.app.Fields"</strong>/&gt;
-    &lt;<strong>variable name="user" type="ObservableList&amp;lt;Object&gt;"</strong>/&gt;
-&lt;/<strong>data</strong>&gt;
+&lt;data&gt;
+    &lt;import type="android.databinding.ObservableList"/&gt;
+    &lt;import type="com.example.my.app.Fields"/&gt;
+    &lt;variable name="user" type="ObservableList&amp;lt;Object&gt;"/&gt;
+&lt;/data&gt;

-&lt;<strong>TextView
+&lt;TextView
    android:text=&apos;&commat;{user[Fields.LAST_NAME]}&apos;
    android:layout_width="wrap_content"
-   android:layout_height="wrap_content"</strong>/&gt;
-&lt;<strong>TextView
+   android:layout_height="wrap_content"/&gt;
+&lt;TextView
    android:text=&apos;&commat;{String.valueOf(1 + (Integer)user[Fields.AGE])}&apos;
    android:layout_width="wrap_content"
-   android:layout_height="wrap_content"</strong>/&gt;
+   android:layout_height="wrap_content"/&gt;
 </pre>
 <h2 id="generated_binding">
   Generated Binding
@@ -1094,7 +1169,7 @@
   The generated binding class links the layout variables with the Views within
   the layout. As discussed earlier, the name and package of the Binding may be
   <a href="#custom_binding_class_names">customized</a>. The Generated binding
-  classes all extend <code>android.databinding.ViewDataBinding</code>.
+  classes all extend {@link android.databinding.ViewDataBinding}.
 </p>
 
 <h3 id="creating">
@@ -1107,13 +1182,13 @@
   within the layout. There are a few ways to bind to a layout. The most common
   is to use the static methods on the Binding class.The inflate method inflates
   the View hierarchy and binds to it all it one step. There is a simpler
-  version that only takes a <code>LayoutInflater</code> and one that takes a
-  <code>ViewGroup</code> as well:
+  version that only takes a {@link android.view.LayoutInflater} and one that takes a
+  {@link android.view.ViewGroup} as well:
 </p>
 
 <pre>
-MyLayoutBinding binding = MyLayoutBinding.<em>inflate</em>(<strong>layoutInflater</strong>);
-MyLayoutBinding binding = MyLayoutBinding.<em>inflate</em>(LayoutInflater, viewGroup, false);
+MyLayoutBinding binding = MyLayoutBinding.inflate(layoutInflater);
+MyLayoutBinding binding = MyLayoutBinding.inflate(layoutInflater, viewGroup, false);
 </pre>
 <p>
   If the layout was inflated using a different mechanism, it may be bound
@@ -1121,17 +1196,17 @@
 </p>
 
 <pre>
-MyLayoutBinding binding = MyLayoutBinding.<em>bind</em>(viewRoot);
+MyLayoutBinding binding = MyLayoutBinding.bind(viewRoot);
 </pre>
 <p>
   Sometimes the binding cannot be known in advance. In such cases, the binding
-  can be created using the DataBindingUtil class:
+  can be created using the {@link android.databinding.DataBindingUtil} class:
 </p>
 
 <pre>
-ViewDataBinding binding = DataBindingUtil.<em>inflate</em>(LayoutInflater, layoutId,
+ViewDataBinding binding = DataBindingUtil.inflate(LayoutInflater, layoutId,
     parent, attachToParent);
-ViewDataBinding binding = DataBindingUtil.<em>bindTo</em>(viewRoot, layoutId);
+ViewDataBinding binding = DataBindingUtil.bindTo(viewRoot, layoutId);
 </pre>
 <h3 id="views_with_ids">
   Views With IDs
@@ -1145,32 +1220,32 @@
 </p>
 
 <pre>
-&lt;<strong>layout xmlns:android="http://schemas.android.com/apk/res/android"</strong>&gt;
-   &lt;<strong>data</strong>&gt;
-       &lt;<strong>variable name="user" type="com.example.User"</strong>/&gt;
-   &lt;/<strong>data</strong>&gt;
-   &lt;<strong>LinearLayout
+&lt;layout xmlns:android="http://schemas.android.com/apk/res/android"&gt;
+   &lt;data&gt;
+       &lt;variable name="user" type="com.example.User"/&gt;
+   &lt;/data&gt;
+   &lt;LinearLayout
        android:orientation="vertical"
        android:layout_width="match_parent"
-       android:layout_height="match_parent"</strong>&gt;
-       &lt;<strong>TextView android:layout_width="wrap_content"
+       android:layout_height="match_parent"&gt;
+       &lt;TextView android:layout_width="wrap_content"
            android:layout_height="wrap_content"
-           android:text="&commat;{user.firstName}"</strong>
-   <strong>android:id="&commat;+id/firstName"</strong>/&gt;
-       &lt;<strong>TextView android:layout_width="wrap_content"
+           android:text="&commat;{user.firstName}"
+   android:id="&commat;+id/firstName"/&gt;
+       &lt;TextView android:layout_width="wrap_content"
            android:layout_height="wrap_content"
-           android:text="&commat;{user.lastName}"</strong>
-  <strong>android:id="&commat;+id/lastName"</strong>/&gt;
-   &lt;/<strong>LinearLayout</strong>&gt;
-&lt;/<strong>layout</strong>&gt;
+           android:text="&commat;{user.lastName}"
+  android:id="&commat;+id/lastName"/&gt;
+   &lt;/LinearLayout&gt;
+&lt;/layout&gt;
 </pre>
 <p>
   Will generate a binding class with:
 </p>
 
 <pre>
-<strong>public final </strong>TextView <strong>firstName</strong>;
-<strong>public final </strong>TextView <strong>lastName</strong>;
+public final TextView firstName;
+public final TextView lastName;
 </pre>
 <p>
   IDs are not nearly as necessary as without data binding, but there are still
@@ -1186,49 +1261,49 @@
 </p>
 
 <pre>
-&lt;<strong>data</strong>&gt;
-    &lt;<strong>import type="android.graphics.drawable.Drawable"</strong>/&gt;
-    &lt;<strong>variable name="user"  type="com.example.User"</strong>/&gt;
-    &lt;<strong>variable name="image" type="Drawable"</strong>/&gt;
-    &lt;<strong>variable name="note"  type="String"</strong>/&gt;
-&lt;/<strong>data</strong>&gt;
+&lt;data&gt;
+    &lt;import type="android.graphics.drawable.Drawable"/&gt;
+    &lt;variable name="user"  type="com.example.User"/&gt;
+    &lt;variable name="image" type="Drawable"/&gt;
+    &lt;variable name="note"  type="String"/&gt;
+&lt;/data&gt;
 </pre>
 <p>
   will generate setters and getters in the binding:
 </p>
 
 <pre>
-<strong>public abstract </strong>com.example.User getUser();
-<strong>public abstract void </strong>setUser(com.example.User user);
-<strong>public abstract </strong>Drawable getImage();
-<strong>public abstract void </strong>setImage(Drawable image);
-<strong>public abstract </strong>String getNote();
-<strong>public abstract void </strong>setNote(String note);
+public abstract com.example.User getUser();
+public abstract void setUser(com.example.User user);
+public abstract Drawable getImage();
+public abstract void setImage(Drawable image);
+public abstract String getNote();
+public abstract void setNote(String note);
 </pre>
 <h3 id="viewstubs">
   ViewStubs
 </h3>
 
 <p>
-  ViewStubs are a little different from normal Views. They start off invisible
+  {@link android.view.ViewStub}s are a little different from normal Views. They start off invisible
   and when they either are made visible or are explicitly told to inflate, they
   replace themselves in the layout by inflating another layout.
 </p>
 
 <p>
-  Because the ViewStub essentially disappears from the View hierarchy, the View
+  Because the <code>ViewStub</code> essentially disappears from the View hierarchy, the View
   in the binding object must also disappear to allow collection. Because the
-  Views are final, a ViewStubProxy object takes the place of the ViewStub,
-  giving the developer access to the ViewStub when it exists and also access to
-  the inflated View hierarchy when the ViewStub has been inflated.
+  Views are final, a {@link android.databinding.ViewStubProxy} object takes the place of the
+  <code>ViewStub</code>, giving the developer access to the ViewStub when it exists and also
+  access to the inflated View hierarchy when the <code>ViewStub</code> has been inflated.
 </p>
 
 <p>
   When inflating another layout, a binding must be established for the new
-  layout. Therefore, the ViewStubProxy must listen to the ViewStub&apos;s
-  OnInflateListener and establish the binding at that time. Since only one can
-  exist, the ViewStubProxy allows the developer to set an OnInflateListener on
-  it that it will call after establishing the binding.
+  layout. Therefore, the <code>ViewStubProxy</code> must listen to the <code>ViewStub</code>&apos;s
+  {@link android.view.ViewStub.OnInflateListener} and establish the binding at that time. Since
+  only one can exist, the <code>ViewStubProxy</code> allows the developer to set an
+  <code>OnInflateListener</code> on it that it will call after establishing the binding.
 </p>
 
 <h3 id="advanced_binding">
@@ -1241,20 +1316,20 @@
 
 <p>
   At times, the specific binding class won&apos;t be known. For example, a
-  RecyclerView Adapter operating against arbitrary layouts won&apos;t know the
-  specific binding class. It still must assign the binding value during the
-  onBindViewHolder.
+  {@link android.support.v7.widget.RecyclerView.Adapter} operating against arbitrary layouts
+  won&apos;t know the specific binding class. It still must assign the binding value during the
+  {@link android.support.v7.widget.RecyclerView.Adapter#onBindViewHolder}.
 </p>
 
 <p>
   In this example, all layouts that the RecyclerView binds to have an "item"
-  variable. The BindingHolder has a getBinding method returning the
-  <code>ViewDataBinding</code> base.
+  variable. The <code>BindingHolder</code> has a <code>getBinding</code> method returning the
+  {@link android.databinding.ViewDataBinding} base.
 </p>
 
 <pre>
-<strong>public void </strong>onBindViewHolder(BindingHolder holder, <strong>int </strong>position) {
-   <strong>final </strong>T item = <strong>mItems</strong>.get(position);
+public void onBindViewHolder(BindingHolder holder, int position) {
+   final T item = mItems.get(position);
    holder.getBinding().setVariable(BR.item, item);
    holder.getBinding().executePendingBindings();
 }
@@ -1267,7 +1342,7 @@
   When a variable or observable changes, the binding will be scheduled to
   change before the next frame. There are times, however, when binding must be
   executed immediately. To force execution, use the
-  <code>executePendingBindings()</code> method.
+  {@link android.databinding.ViewDataBinding#executePendingBindings()} method.
 </p>
 
 <h4>
@@ -1321,17 +1396,18 @@
 <p>
   Some attributes have setters that don&apos;t match by name. For these
   methods, an attribute may be associated with the setter through
-  BindingMethods annotation. This must be associated with a class and contains
-  BindingMethod annotations, one for each renamed method. For example, the
-  <strong><code>android:tint</code></strong> attribute is really associated
-  with setImageTintList, not setTint.
+  {@link android.databinding.BindingMethods} annotation. This must be associated with
+  a class and contains {@link android.databinding.BindingMethod} annotations, one for
+  each renamed method. For example, the <strong><code>android:tint</code></strong> attribute
+  is really associated with {@link android.widget.ImageView#setImageTintList}, not
+  <code>setTint</code>.
 </p>
 
 <pre>
 &commat;BindingMethods({
-       &commat;BindingMethod(type = <strong>"android.widget.ImageView"</strong>,
-                      attribute = <strong>"android:tint"</strong>,
-                      method = <strong>"setImageTintList"</strong>),
+       &commat;BindingMethod(type = "android.widget.ImageView",
+                      attribute = "android:tint",
+                      method = "setImageTintList"),
 })
 </pre>
 <p>
@@ -1347,7 +1423,7 @@
   Some attributes need custom binding logic. For example, there is no
   associated setter for the <strong><code>android:paddingLeft</code></strong>
   attribute. Instead, <code>setPadding(left, top, right, bottom)</code> exists.
-  A static binding adapter method with the <code>BindingAdapter</code>
+  A static binding adapter method with the {@link android.databinding.BindingAdapter}
   annotation allows the developer to customize how a setter for an attribute is
   called.
 </p>
@@ -1358,9 +1434,8 @@
 </p>
 
 <pre>
-
-&commat;BindingAdapter(<strong>"android:paddingLeft"</strong>)
-<strong>public static void </strong>setPaddingLeft(View view, <strong>int </strong>padding) {
+&commat;BindingAdapter("android:paddingLeft")
+public static void setPaddingLeft(View view, int padding) {
    view.setPadding(padding,
                    view.getPaddingTop(),
                    view.getPaddingRight(),
@@ -1382,9 +1457,9 @@
 </p>
 
 <pre>
-&commat;BindingAdapter({<strong>"bind:imageUrl"</strong>, <strong>"bind:error"</strong>})
-<strong>public static void </strong>loadImage(ImageView view, String url, Drawable error) {
-   Picasso.<em>with</em>(view.getContext()).load(url).error(error).into(view);
+&commat;BindingAdapter({"bind:imageUrl", "bind:error"})
+public static void loadImage(ImageView view, String url, Drawable error) {
+   Picasso.with(view.getContext()).load(url).error(error).into(view);
 }
 </pre>
 <pre>
@@ -1406,6 +1481,123 @@
   </li>
 </ul>
 
+<p>
+  Binding adapter methods may optionally take the old values in their handlers. A method
+  taking old and new values should have all old values for the attributes come first, followed
+  by the new values:
+</p>
+<pre>
+&commat;BindingAdapter("android:paddingLeft")
+public static void setPaddingLeft(View view, int oldPadding, int newPadding) {
+   if (oldPadding != newPadding) {
+       view.setPadding(newPadding,
+                       view.getPaddingTop(),
+                       view.getPaddingRight(),
+                       view.getPaddingBottom());
+   }
+}
+</pre>
+<p>
+  Event handlers may only be used with interfaces or abstract classes with one abstract method.
+  For example:
+</p>
+<pre>
+&commat;BindingAdapter("android:onLayoutChange")
+public static void setOnLayoutChangeListener(View view, View.OnLayoutChangeListener oldValue,
+       View.OnLayoutChangeListener newValue) {
+    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
+        if (oldValue != null) {
+            view.removeOnLayoutChangeListener(oldValue);
+        }
+        if (newValue != null) {
+            view.addOnLayoutChangeListener(newValue);
+        }
+    }
+}
+</pre>
+<p>
+  When a listener has multiple methods, it must be split into multiple listeners. For example,
+  {@link android.view.View.OnAttachStateChangeListener} has two methods:
+  {@link android.view.View.OnAttachStateChangeListener#onViewAttachedToWindow onViewAttachedToWindow()} and
+  {@link android.view.View.OnAttachStateChangeListener#onViewDetachedFromWindow onViewDetachedFromWindow()}.
+  We must then create two interfaces to differentiate the attributes and handlers for them.
+</p>
+
+<pre>
+&commat;TargetApi(VERSION_CODES.HONEYCOMB_MR1)
+public interface OnViewDetachedFromWindow {
+    void onViewDetachedFromWindow(View v);
+}
+
+&commat;TargetApi(VERSION_CODES.HONEYCOMB_MR1)
+public interface OnViewAttachedToWindow {
+    void onViewAttachedToWindow(View v);
+}
+</pre>
+<p>
+  Because changing one listener will also affect the other, we must have three different
+  binding adapters, one for each attribute and one for both, should they both be set.
+</p>
+<pre>
+&commat;BindingAdapter("android:onViewAttachedToWindow")
+public static void setListener(View view, OnViewAttachedToWindow attached) {
+    setListener(view, null, attached);
+}
+
+&commat;BindingAdapter("android:onViewDetachedFromWindow")
+public static void setListener(View view, OnViewDetachedFromWindow detached) {
+    setListener(view, detached, null);
+}
+
+&commat;BindingAdapter({"android:onViewDetachedFromWindow", "android:onViewAttachedToWindow"})
+public static void setListener(View view, final OnViewDetachedFromWindow detach,
+        final OnViewAttachedToWindow attach) {
+    if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB_MR1) {
+        final OnAttachStateChangeListener newListener;
+        if (detach == null && attach == null) {
+            newListener = null;
+        } else {
+            newListener = new OnAttachStateChangeListener() {
+                &commat;Override
+                public void onViewAttachedToWindow(View v) {
+                    if (attach != null) {
+                        attach.onViewAttachedToWindow(v);
+                    }
+                }
+
+                &commat;Override
+                public void onViewDetachedFromWindow(View v) {
+                    if (detach != null) {
+                        detach.onViewDetachedFromWindow(v);
+                    }
+                }
+            };
+        }
+        final OnAttachStateChangeListener oldListener = ListenerUtil.trackListener(view,
+                newListener, R.id.onAttachStateChangeListener);
+        if (oldListener != null) {
+            view.removeOnAttachStateChangeListener(oldListener);
+        }
+        if (newListener != null) {
+            view.addOnAttachStateChangeListener(newListener);
+        }
+    }
+}
+</pre>
+<p>
+  The above example is slightly more complicated than normal because View uses add and remove
+  for the listener instead of a set method for {@link android.view.View.OnAttachStateChangeListener}.
+  The <code>android.databinding.adapters.ListenerUtil</code> class helps keep track of the previous
+  listeners so that they may be removed in the Binding Adaper.
+</p>
+<p>
+  By annotating the interfaces <code>OnViewDetachedFromWindow</code> and
+  <code>OnViewAttachedToWindow</code> with
+  <code>&commat;TargetApi(VERSION_CODES.HONEYCOMB_MR1)</code>, the data binding code
+  generator knows that the listener should only be generated when running on Honeycomb MR1
+  and new devices, the same version supported by
+  {@link android.view.View#addOnAttachStateChangeListener}.
+</p>
 <h2 id="converters">
   Converters
 </h2>
@@ -1426,10 +1618,10 @@
 </p>
 
 <pre>
-&lt;<strong>TextView
+&lt;TextView
    android:text=&apos;&commat;{userMap["lastName"]}&apos;
    android:layout_width="wrap_content"
-   android:layout_height="wrap_content"</strong>/&gt;
+   android:layout_height="wrap_content"/&gt;
 </pre>
 
 <p>
@@ -1447,10 +1639,10 @@
 </p>
 
 <pre>
-&lt;<strong>View
+&lt;View
    android:background="&commat;{isError ? &commat;color/red : &commat;color/white}"
    android:layout_width="wrap_content"
-   android:layout_height="wrap_content"</strong>/&gt;
+   android:layout_height="wrap_content"/&gt;
 </pre>
 <p>
   Here, the background takes a <code>Drawable</code>, but the color is an
@@ -1462,8 +1654,8 @@
 
 <pre>
 &commat;BindingConversion
-<strong>public static </strong>ColorDrawable convertColorToDrawable(<strong>int </strong>color) {
-   <strong>return new </strong>ColorDrawable(color);
+public static ColorDrawable convertColorToDrawable(int color) {
+   return new ColorDrawable(color);
 }
 </pre>
 <p>
@@ -1472,8 +1664,8 @@
 </p>
 
 <pre>
-&lt;<strong>View
+&lt;View
    android:background="&commat;{isError ? &commat;drawable/error : &commat;color/white}"
    android:layout_width="wrap_content"
-   android:layout_height="wrap_content"</strong>/&gt;
+   android:layout_height="wrap_content"/&gt;
 </pre>
diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp
index a95db9f..62aabb1 100644
--- a/libs/androidfw/ResourceTypes.cpp
+++ b/libs/androidfw/ResourceTypes.cpp
@@ -6758,7 +6758,13 @@
                     printf("      NON-INTEGER ResTable_type ADDRESS: %p\n", type);
                     continue;
                 }
-                String8 configStr = type->config.toString();
+
+                // Always copy the config, as fields get added and we need to
+                // set the defaults.
+                ResTable_config thisConfig;
+                thisConfig.copyFromDtoH(type->config);
+
+                String8 configStr = thisConfig.toString();
                 printf("      config %s:\n", configStr.size() > 0
                         ? configStr.string() : "(default)");
                 size_t entryCount = dtohl(type->entryCount);
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardPINView.java b/packages/Keyguard/src/com/android/keyguard/KeyguardPINView.java
index d265e0d..4abb795 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardPINView.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardPINView.java
@@ -16,8 +16,12 @@
 
 package com.android.keyguard;
 
+import android.animation.Animator;
+import android.animation.ObjectAnimator;
 import android.content.Context;
 import android.util.AttributeSet;
+import android.view.RenderNode;
+import android.view.RenderNodeAnimator;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.animation.AnimationUtils;
@@ -114,10 +118,8 @@
         enableClipping(false);
         setAlpha(1f);
         setTranslationY(mAppearAnimationUtils.getStartTranslation());
-        animate()
-                .setDuration(500)
-                .setInterpolator(mAppearAnimationUtils.getInterpolator())
-                .translationY(0);
+        AppearAnimationUtils.startTranslationYAnimation(this, 0 /* delay */, 500 /* duration */,
+                0, mAppearAnimationUtils.getInterpolator());
         mAppearAnimationUtils.startAnimation2d(mViews,
                 new Runnable() {
                     @Override
@@ -131,10 +133,8 @@
     public boolean startDisappearAnimation(final Runnable finishRunnable) {
         enableClipping(false);
         setTranslationY(0);
-        animate()
-                .setDuration(280)
-                .setInterpolator(mDisappearAnimationUtils.getInterpolator())
-                .translationY(mDisappearYTranslation);
+        AppearAnimationUtils.startTranslationYAnimation(this, 0 /* delay */, 280 /* duration */,
+                mDisappearYTranslation, mDisappearAnimationUtils.getInterpolator());
         mDisappearAnimationUtils.startAnimation2d(mViews,
                 new Runnable() {
                     @Override
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardPatternView.java b/packages/Keyguard/src/com/android/keyguard/KeyguardPatternView.java
index 3568429..b000e26 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardPatternView.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardPatternView.java
@@ -17,6 +17,7 @@
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
 import android.animation.ValueAnimator;
 import android.content.Context;
 import android.graphics.Rect;
@@ -27,6 +28,7 @@
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.MotionEvent;
+import android.view.RenderNodeAnimator;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.animation.AnimationUtils;
@@ -334,10 +336,8 @@
         enableClipping(false);
         setAlpha(1f);
         setTranslationY(mAppearAnimationUtils.getStartTranslation());
-        animate()
-                .setDuration(500)
-                .setInterpolator(mAppearAnimationUtils.getInterpolator())
-                .translationY(0);
+        AppearAnimationUtils.startTranslationYAnimation(this, 0 /* delay */, 500 /* duration */,
+                0, mAppearAnimationUtils.getInterpolator());
         mAppearAnimationUtils.startAnimation2d(
                 mLockPatternView.getCellStates(),
                 new Runnable() {
@@ -362,10 +362,9 @@
         mLockPatternView.clearPattern();
         enableClipping(false);
         setTranslationY(0);
-        animate()
-                .setDuration(300)
-                .setInterpolator(mDisappearAnimationUtils.getInterpolator())
-                .translationY(-mDisappearAnimationUtils.getStartTranslation());
+        AppearAnimationUtils.startTranslationYAnimation(this, 0 /* delay */, 300 /* duration */,
+                -mDisappearAnimationUtils.getStartTranslation(),
+                mDisappearAnimationUtils.getInterpolator());
         mDisappearAnimationUtils.startAnimation2d(mLockPatternView.getCellStates(),
                 new Runnable() {
                     @Override
@@ -398,43 +397,16 @@
             long duration, float translationY, final boolean appearing,
             Interpolator interpolator,
             final Runnable finishListener) {
-        if (appearing) {
-            animatedCell.scale = 0.0f;
-            animatedCell.alpha = 1.0f;
-        }
-        animatedCell.translateY = appearing ? translationY : 0;
-        ValueAnimator animator = ValueAnimator.ofFloat(animatedCell.translateY,
-                appearing ? 0 : translationY);
-        animator.setInterpolator(interpolator);
-        animator.setDuration(duration);
-        animator.setStartDelay(delay);
-        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
-            @Override
-            public void onAnimationUpdate(ValueAnimator animation) {
-                float animatedFraction = animation.getAnimatedFraction();
-                if (appearing) {
-                    animatedCell.scale = animatedFraction;
-                } else {
-                    animatedCell.alpha = 1 - animatedFraction;
-                }
-                animatedCell.translateY = (float) animation.getAnimatedValue();
-                mLockPatternView.invalidate();
-            }
-        });
+        mLockPatternView.startCellStateAnimation(animatedCell,
+                1f, appearing ? 1f : 0f, /* alpha */
+                appearing ? translationY : 0f, appearing ? 0f : translationY, /* translation */
+                appearing ? 0f : 1f, 1f /* scale */,
+                delay, duration, interpolator, finishListener);
         if (finishListener != null) {
-            animator.addListener(new AnimatorListenerAdapter() {
-                @Override
-                public void onAnimationEnd(Animator animation) {
-                    finishListener.run();
-                }
-            });
-
             // Also animate the Emergency call
             mAppearAnimationUtils.createAnimation(mEcaView, delay, duration, translationY,
                     appearing, interpolator, null);
         }
-        animator.start();
-        mLockPatternView.invalidate();
     }
 
     @Override
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
index 3c5dae3..6baa4b3 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
@@ -679,7 +679,7 @@
         if (resolvedActivities.get(0).activityInfo.exported) {
             intent.putExtra(PrintService.EXTRA_PRINT_JOB_INFO, mPrintJob);
             intent.putExtra(PrintService.EXTRA_PRINTER_INFO, printer);
-            intent.putExtra(PrintService.EXTRA_PRINT_JOB_INFO,
+            intent.putExtra(PrintService.EXTRA_PRINT_DOCUMENT_INFO,
                     mPrintedDocument.getDocumentInfo().info);
 
             // This is external activity and may not be there.
diff --git a/packages/SettingsLib/src/com/android/settingslib/animation/AppearAnimationUtils.java b/packages/SettingsLib/src/com/android/settingslib/animation/AppearAnimationUtils.java
index 441474d..df76125 100644
--- a/packages/SettingsLib/src/com/android/settingslib/animation/AppearAnimationUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/animation/AppearAnimationUtils.java
@@ -16,11 +16,18 @@
 
 package com.android.settingslib.animation;
 
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
 import android.content.Context;
+import android.view.RenderNodeAnimator;
 import android.view.View;
+import android.view.ViewPropertyAnimator;
 import android.view.animation.AnimationUtils;
 import android.view.animation.Interpolator;
 
+import com.android.internal.widget.LockPatternView;
 import com.android.settingslib.R;
 
 /**
@@ -174,26 +181,65 @@
     }
 
     @Override
-    public void createAnimation(View view, long delay, long duration, float translationY,
-            boolean appearing, Interpolator interpolator, Runnable endRunnable) {
+    public void createAnimation(final View view, long delay, long duration, float translationY,
+            boolean appearing, Interpolator interpolator, final Runnable endRunnable) {
         if (view != null) {
             view.setAlpha(appearing ? 0f : 1.0f);
             view.setTranslationY(appearing ? translationY : 0);
-            view.animate()
-                    .alpha(appearing ? 1f : 0f)
-                    .translationY(appearing ? 0 : translationY)
-                    .setInterpolator(interpolator)
-                    .setDuration(duration)
-                    .setStartDelay(delay);
+            Animator alphaAnim;
+            float targetAlpha =  appearing ? 1f : 0f;
+            if (view.isHardwareAccelerated()) {
+                RenderNodeAnimator alphaAnimRt = new RenderNodeAnimator(RenderNodeAnimator.ALPHA,
+                        targetAlpha);
+                alphaAnimRt.setTarget(view);
+                alphaAnim = alphaAnimRt;
+            } else {
+                alphaAnim = ObjectAnimator.ofFloat(view, View.ALPHA, view.getAlpha(), targetAlpha);
+            }
+            alphaAnim.setInterpolator(interpolator);
+            alphaAnim.setDuration(duration);
+            alphaAnim.setStartDelay(delay);
             if (view.hasOverlappingRendering()) {
-                view.animate().withLayer();
+                view.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+                alphaAnim.addListener(new AnimatorListenerAdapter() {
+                    @Override
+                    public void onAnimationEnd(Animator animation) {
+                        view.setLayerType(View.LAYER_TYPE_NONE, null);
+                    }
+                });
             }
             if (endRunnable != null) {
-                view.animate().withEndAction(endRunnable);
+                alphaAnim.addListener(new AnimatorListenerAdapter() {
+                    @Override
+                    public void onAnimationEnd(Animator animation) {
+                        endRunnable.run();
+                    }
+                });
             }
+            alphaAnim.start();
+            startTranslationYAnimation(view, delay, duration, appearing ? 0 : translationY,
+                    interpolator);
         }
     }
 
+    public static void startTranslationYAnimation(View view, long delay, long duration,
+            float endTranslationY, Interpolator interpolator) {
+        Animator translationAnim;
+        if (view.isHardwareAccelerated()) {
+            RenderNodeAnimator translationAnimRt = new RenderNodeAnimator(
+                    RenderNodeAnimator.TRANSLATION_Y, endTranslationY);
+            translationAnimRt.setTarget(view);
+            translationAnim = translationAnimRt;
+        } else {
+            translationAnim = ObjectAnimator.ofFloat(view, View.TRANSLATION_Y,
+                    view.getTranslationY(), endTranslationY);
+        }
+        translationAnim.setInterpolator(interpolator);
+        translationAnim.setDuration(duration);
+        translationAnim.setStartDelay(delay);
+        translationAnim.start();
+    }
+
     public class AppearAnimationProperties {
         public long[][] delays;
         public int maxDelayRowIndex;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
index a2e6632..41b37b0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
@@ -1837,7 +1837,7 @@
             logUpdate(entry, n);
         }
         boolean applyInPlace = shouldApplyInPlace(entry, n);
-        boolean shouldInterrupt = shouldInterrupt(entry);
+        boolean shouldInterrupt = shouldInterrupt(entry, notification);
         boolean alertAgain = alertAgain(entry, n);
 
         entry.notification = notification;
@@ -2009,7 +2009,10 @@
     }
 
     protected boolean shouldInterrupt(Entry entry) {
-        StatusBarNotification sbn = entry.notification;
+        return shouldInterrupt(entry, entry.notification);
+    }
+
+    protected boolean shouldInterrupt(Entry entry, StatusBarNotification sbn) {
         if (mNotificationData.shouldFilterOut(sbn)) {
             if (DEBUG) {
                 Log.d(TAG, "Skipping HUN check for " + sbn.getKey() + " since it's filtered out.");
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
index 03bdf97..56e3032 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -26,11 +26,13 @@
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.Paint;
+import android.graphics.Rect;
 import android.util.AttributeSet;
 import android.util.MathUtils;
 import android.view.MotionEvent;
 import android.view.VelocityTracker;
 import android.view.View;
+import android.view.ViewRootImpl;
 import android.view.ViewTreeObserver;
 import android.view.WindowInsets;
 import android.view.accessibility.AccessibilityEvent;
@@ -42,6 +44,7 @@
 
 import com.android.internal.logging.MetricsLogger;
 import com.android.keyguard.KeyguardStatusView;
+import com.android.systemui.DejankUtils;
 import com.android.systemui.EventLogConstants;
 import com.android.systemui.EventLogTags;
 import com.android.systemui.R;
@@ -79,6 +82,8 @@
     private static final String COUNTER_PANEL_OPEN_QS = "panel_open_qs";
     private static final String COUNTER_PANEL_OPEN_PEEK = "panel_open_peek";
 
+    private static final Rect mDummyDirtyRect = new Rect(0, 0, 1, 1);
+
     public static final long DOZE_ANIMATION_DURATION = 700;
 
     private KeyguardAffordanceHelper mAfforanceHelper;
@@ -1777,7 +1782,22 @@
         mIsExpanding = false;
         mScrollYOverride = -1;
         if (isFullyCollapsed()) {
-            setListening(false);
+            DejankUtils.postAfterTraversal(new Runnable() {
+                @Override
+                public void run() {
+                    setListening(false);
+                }
+            });
+
+            // Workaround b/22639032: Make sure we invalidate something because else RenderThread
+            // thinks we are actually drawing a frame put in reality we don't, so RT doesn't go
+            // ahead with rendering and we jank.
+            postOnAnimation(new Runnable() {
+                @Override
+                public void run() {
+                    getParent().invalidateChild(NotificationPanelView.this, mDummyDirtyRect);
+                }
+            });
         } else {
             setListening(true);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
index fb940d2..dfce170 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -3163,9 +3163,7 @@
 
     @Override
     public boolean shouldDisableNavbarGestures() {
-        return !isDeviceProvisioned()
-                || mExpandedVisible
-                || (mDisabled1 & StatusBarManager.DISABLE_SEARCH) != 0;
+        return !isDeviceProvisioned() || (mDisabled1 & StatusBarManager.DISABLE_SEARCH) != 0;
     }
 
     public void postStartActivityDismissingKeyguard(final Intent intent, int delay) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java
index 5d58cd0..7ee47df 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java
@@ -789,7 +789,9 @@
                     mQsDetailHeader.setOnClickListener(new OnClickListener() {
                         @Override
                         public void onClick(View v) {
-                            detail.setToggleState(!mQsDetailHeaderSwitch.isChecked());
+                            boolean checked = !mQsDetailHeaderSwitch.isChecked();
+                            mQsDetailHeaderSwitch.setChecked(checked);
+                            detail.setToggleState(checked);
                         }
                     });
                 }
diff --git a/services/core/java/com/android/server/MountService.java b/services/core/java/com/android/server/MountService.java
index ed136e9..857394f 100644
--- a/services/core/java/com/android/server/MountService.java
+++ b/services/core/java/com/android/server/MountService.java
@@ -146,8 +146,6 @@
 class MountService extends IMountService.Stub
         implements INativeDaemonConnectorCallbacks, Watchdog.Monitor {
 
-    // TODO: finish enforcing UserManager.DISALLOW_MOUNT_PHYSICAL_MEDIA
-
     // Static direct instance pointer for the tightly-coupled idle service to use
     static MountService sSelf = null;
 
@@ -631,6 +629,10 @@
                 }
                 case H_VOLUME_MOUNT: {
                     final VolumeInfo vol = (VolumeInfo) msg.obj;
+                    if (isMountDisallowed(vol)) {
+                        Slog.i(TAG, "Ignoring mount " + vol.getId() + " due to policy");
+                        break;
+                    }
                     try {
                         mConnector.execute("volume", "mount", vol.id, vol.mountFlags,
                                 vol.mountUserId);
@@ -1305,10 +1307,16 @@
         mContext.enforceCallingOrSelfPermission(perm, perm);
     }
 
-    private void enforceUserRestriction(String restriction) {
-        UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
-        if (um.hasUserRestriction(restriction, Binder.getCallingUserHandle())) {
-            throw new SecurityException("User has restriction " + restriction);
+    /**
+     * Decide if volume is mountable per device policies.
+     */
+    private boolean isMountDisallowed(VolumeInfo vol) {
+        if (vol.type == VolumeInfo.TYPE_PUBLIC || vol.type == VolumeInfo.TYPE_PRIVATE) {
+            final UserManager userManager = mContext.getSystemService(UserManager.class);
+            return userManager.hasUserRestriction(UserManager.DISALLOW_MOUNT_PHYSICAL_MEDIA,
+                    Binder.getCallingUserHandle());
+        } else {
+            return false;
         }
     }
 
@@ -1586,8 +1594,8 @@
         waitForReady();
 
         final VolumeInfo vol = findVolumeByIdOrThrow(volId);
-        if (vol.type == VolumeInfo.TYPE_PUBLIC || vol.type == VolumeInfo.TYPE_PRIVATE) {
-            enforceUserRestriction(UserManager.DISALLOW_MOUNT_PHYSICAL_MEDIA);
+        if (isMountDisallowed(vol)) {
+            throw new SecurityException("Mounting " + volId + " restricted by policy");
         }
         try {
             mConnector.execute("volume", "mount", vol.id, vol.mountFlags, vol.mountUserId);
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index 4102f37..447fe87 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -859,26 +859,35 @@
     public boolean isOnBattery() {
         return mStats.isOnBattery();
     }
-    
-    public void setBatteryState(int status, int health, int plugType, int level,
-            int temp, int volt) {
-        enforceCallingPermission();
-        synchronized (mStats) {
-            final boolean onBattery = plugType == BatteryStatsImpl.BATTERY_PLUGGED_NONE;
-            if (mStats.isOnBattery() == onBattery) {
-                // The battery state has not changed, so we don't need to sync external
-                // stats immediately.
-                mStats.setBatteryStateLocked(status, health, plugType, level, temp, volt);
-                return;
-            }
-        }
 
-        // Sync external stats first as the battery has changed states. If we don't sync
-        // immediately here, we may not collect the relevant data later.
-        updateExternalStats("battery-state", UPDATE_ALL);
-        synchronized (mStats) {
-            mStats.setBatteryStateLocked(status, health, plugType, level, temp, volt);
-        }
+    @Override
+    public void setBatteryState(final int status, final int health, final int plugType,
+                                final int level, final int temp, final int volt) {
+        enforceCallingPermission();
+
+        // BatteryService calls us here and we may update external state. It would be wrong
+        // to block such a low level service like BatteryService on external stats like WiFi.
+        mHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                synchronized (mStats) {
+                    final boolean onBattery = plugType == BatteryStatsImpl.BATTERY_PLUGGED_NONE;
+                    if (mStats.isOnBattery() == onBattery) {
+                        // The battery state has not changed, so we don't need to sync external
+                        // stats immediately.
+                        mStats.setBatteryStateLocked(status, health, plugType, level, temp, volt);
+                        return;
+                    }
+                }
+
+                // Sync external stats first as the battery has changed states. If we don't sync
+                // immediately here, we may not collect the relevant data later.
+                updateExternalStats("battery-state", UPDATE_ALL);
+                synchronized (mStats) {
+                    mStats.setBatteryStateLocked(status, health, plugType, level, temp, volt);
+                }
+            }
+        });
     }
     
     public long getAwakeTimeBattery() {
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 395aa27..cd982d3 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -1520,9 +1520,14 @@
         }
 
         synchronized (mRulesLock) {
-            final int oldPolicy = mUidPolicy.get(uid, POLICY_NONE);
-            if (oldPolicy != policy) {
-                setUidPolicyUncheckedLocked(uid, policy, true);
+            final long token = Binder.clearCallingIdentity();
+            try {
+                final int oldPolicy = mUidPolicy.get(uid, POLICY_NONE);
+                if (oldPolicy != policy) {
+                    setUidPolicyUncheckedLocked(uid, policy, true);
+                }
+            } finally {
+                Binder.restoreCallingIdentity(token);
             }
         }
     }
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 61ed1a7..b719b16 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -3119,7 +3119,13 @@
             final PackageParser.Package p = mPackages.get(pkgName);
             if (p != null && p.mExtras != null) {
                 final PackageSetting ps = (PackageSetting) p.mExtras;
-                if (ps.getPermissionsState().hasPermission(permName, userId)) {
+                final PermissionsState permissionsState = ps.getPermissionsState();
+                if (permissionsState.hasPermission(permName, userId)) {
+                    return PackageManager.PERMISSION_GRANTED;
+                }
+                // Special case: ACCESS_FINE_LOCATION permission includes ACCESS_COARSE_LOCATION
+                if (Manifest.permission.ACCESS_COARSE_LOCATION.equals(permName) && permissionsState
+                        .hasPermission(Manifest.permission.ACCESS_FINE_LOCATION, userId)) {
                     return PackageManager.PERMISSION_GRANTED;
                 }
             }
@@ -3140,13 +3146,25 @@
             Object obj = mSettings.getUserIdLPr(UserHandle.getAppId(uid));
             if (obj != null) {
                 final SettingBase ps = (SettingBase) obj;
-                if (ps.getPermissionsState().hasPermission(permName, userId)) {
+                final PermissionsState permissionsState = ps.getPermissionsState();
+                if (permissionsState.hasPermission(permName, userId)) {
+                    return PackageManager.PERMISSION_GRANTED;
+                }
+                // Special case: ACCESS_FINE_LOCATION permission includes ACCESS_COARSE_LOCATION
+                if (Manifest.permission.ACCESS_COARSE_LOCATION.equals(permName) && permissionsState
+                        .hasPermission(Manifest.permission.ACCESS_FINE_LOCATION, userId)) {
                     return PackageManager.PERMISSION_GRANTED;
                 }
             } else {
                 ArraySet<String> perms = mSystemPermissions.get(uid);
-                if (perms != null && perms.contains(permName)) {
-                    return PackageManager.PERMISSION_GRANTED;
+                if (perms != null) {
+                    if (perms.contains(permName)) {
+                        return PackageManager.PERMISSION_GRANTED;
+                    }
+                    if (Manifest.permission.ACCESS_COARSE_LOCATION.equals(permName) && perms
+                            .contains(Manifest.permission.ACCESS_FINE_LOCATION)) {
+                        return PackageManager.PERMISSION_GRANTED;
+                    }
                 }
             }
         }
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 23cb767..6707562 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -529,6 +529,7 @@
 
     @Override
     public void setUserRestriction(String key, boolean value, int userId) {
+        checkManageUsersPermission("setUserRestriction");
         synchronized (mPackagesLock) {
             if (!SYSTEM_CONTROLLED_RESTRICTIONS.contains(key)) {
                 Bundle restrictions = getUserRestrictions(userId);