Merge "Add accessibility action to open power long-press dialog"
diff --git a/api/current.txt b/api/current.txt
index 1816736..d55cac6 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -244,6 +244,7 @@
     field public static final int action = 16842797; // 0x101002d
     field public static final int actionBarDivider = 16843675; // 0x101039b
     field public static final int actionBarItemBackground = 16843676; // 0x101039c
+    field public static final int actionBarPopupTheme = 16843919; // 0x101048f
     field public static final int actionBarSize = 16843499; // 0x10102eb
     field public static final int actionBarSplitStyle = 16843656; // 0x1010388
     field public static final int actionBarStyle = 16843470; // 0x10102ce
@@ -852,7 +853,7 @@
     field public static final int mirrorForRtl = 16843726; // 0x10103ce
     field public static final int mode = 16843134; // 0x101017e
     field public static final int moreIcon = 16843061; // 0x1010135
-    field public static final int multiArch = 16843919; // 0x101048f
+    field public static final int multiArch = 16843920; // 0x1010490
     field public static final int multiprocess = 16842771; // 0x1010013
     field public static final int name = 16842755; // 0x1010003
     field public static final int navigationBarColor = 16843860; // 0x1010454
@@ -3159,6 +3160,7 @@
     method public abstract deprecated void addTab(android.app.ActionBar.Tab, int, boolean);
     method public abstract android.view.View getCustomView();
     method public abstract int getDisplayOptions();
+    method public float getElevation();
     method public abstract int getHeight();
     method public int getHideOffset();
     method public abstract deprecated int getNavigationItemCount();
@@ -3190,6 +3192,7 @@
     method public abstract void setDisplayShowHomeEnabled(boolean);
     method public abstract void setDisplayShowTitleEnabled(boolean);
     method public abstract void setDisplayUseLogoEnabled(boolean);
+    method public void setElevation(float);
     method public void setHideOffset(int);
     method public void setHideOnContentScrollEnabled(boolean);
     method public void setHomeActionContentDescription(java.lang.CharSequence);
@@ -5344,6 +5347,7 @@
     method public void setRestrictionsProvider(android.content.ComponentName, android.content.ComponentName);
     method public void setSecureSetting(android.content.ComponentName, java.lang.String, java.lang.String);
     method public int setStorageEncryption(android.content.ComponentName, boolean);
+    method public boolean switchUser(android.content.ComponentName, android.os.UserHandle);
     method public void uninstallCaCert(android.content.ComponentName, byte[]);
     method public void wipeData(int);
     field public static final java.lang.String ACTION_ADD_DEVICE_ADMIN = "android.app.action.ADD_DEVICE_ADMIN";
@@ -5492,6 +5496,7 @@
     method public int getNetworkCapabilities();
     method public android.content.ComponentName getService();
     method public boolean isPeriodic();
+    method public boolean isPersisted();
     method public boolean isRequireCharging();
     method public boolean isRequireDeviceIdle();
     method public void writeToParcel(android.os.Parcel, int);
@@ -5508,6 +5513,7 @@
     method public android.app.job.JobInfo build();
     method public android.app.job.JobInfo.Builder setBackoffCriteria(long, int);
     method public android.app.job.JobInfo.Builder setExtras(android.os.PersistableBundle);
+    method public android.app.job.JobInfo.Builder setIsPersisted(boolean);
     method public android.app.job.JobInfo.Builder setMinimumLatency(long);
     method public android.app.job.JobInfo.Builder setOverrideDeadline(long);
     method public android.app.job.JobInfo.Builder setPeriodic(long);
@@ -12823,7 +12829,15 @@
   public final class DngCreator implements java.lang.AutoCloseable {
     ctor public DngCreator(android.hardware.camera2.CameraCharacteristics, android.hardware.camera2.CaptureResult);
     method public void close();
+    method public android.hardware.camera2.DngCreator setDescription(java.lang.String);
+    method public android.hardware.camera2.DngCreator setLocation(android.location.Location);
+    method public android.hardware.camera2.DngCreator setOrientation(int);
+    method public android.hardware.camera2.DngCreator setThumbnail(android.graphics.Bitmap);
+    method public android.hardware.camera2.DngCreator setThumbnail(android.media.Image);
+    method public void writeByteBuffer(java.io.OutputStream, android.util.Size, java.nio.ByteBuffer, long) throws java.io.IOException;
     method public void writeImage(java.io.OutputStream, android.media.Image) throws java.io.IOException;
+    method public void writeInputStream(java.io.OutputStream, android.util.Size, java.io.InputStream, long) throws java.io.IOException;
+    field public static final int MAX_THUMBNAIL_DIMENSION = 256; // 0x100
   }
 
   public final class TotalCaptureResult extends android.hardware.camera2.CaptureResult {
@@ -36102,11 +36116,13 @@
     ctor public ActionMenuView(android.content.Context, android.util.AttributeSet);
     method public void dismissPopupMenus();
     method public android.view.Menu getMenu();
+    method public int getPopupTheme();
     method public boolean hideOverflowMenu();
     method public boolean isOverflowMenuShowing();
     method public void onConfigurationChanged(android.content.res.Configuration);
     method public void onDetachedFromWindow();
     method public void setOnMenuItemClickListener(android.widget.ActionMenuView.OnMenuItemClickListener);
+    method public void setPopupTheme(int);
     method public boolean showOverflowMenu();
   }
 
@@ -38334,6 +38350,7 @@
     method public android.view.Menu getMenu();
     method public java.lang.CharSequence getNavigationContentDescription();
     method public android.graphics.drawable.Drawable getNavigationIcon();
+    method public int getPopupTheme();
     method public java.lang.CharSequence getSubtitle();
     method public java.lang.CharSequence getTitle();
     method public boolean hasExpandedActionView();
@@ -38355,6 +38372,7 @@
     method public void setNavigationIcon(android.graphics.drawable.Drawable);
     method public void setNavigationOnClickListener(android.view.View.OnClickListener);
     method public void setOnMenuItemClickListener(android.widget.Toolbar.OnMenuItemClickListener);
+    method public void setPopupTheme(int);
     method public void setSubtitle(int);
     method public void setSubtitle(java.lang.CharSequence);
     method public void setSubtitleTextAppearance(android.content.Context, int);
diff --git a/core/java/android/app/ActionBar.java b/core/java/android/app/ActionBar.java
index 5c98180..b74c824 100644
--- a/core/java/android/app/ActionBar.java
+++ b/core/java/android/app/ActionBar.java
@@ -993,6 +993,33 @@
         }
     }
 
+    /**
+     * Set the Z-axis elevation of the action bar in pixels.
+     *
+     * <p>The action bar's elevation is the distance it is placed from its parent surface. Higher
+     * values are closer to the user.</p>
+     *
+     * @param elevation Elevation value in pixels
+     */
+    public void setElevation(float elevation) {
+        if (elevation != 0) {
+            throw new UnsupportedOperationException("Setting a non-zero elevation is " +
+                    "not supported in this action bar configuration.");
+        }
+    }
+
+    /**
+     * Get the Z-axis elevation of the action bar in pixels.
+     *
+     * <p>The action bar's elevation is the distance it is placed from its parent surface. Higher
+     * values are closer to the user.</p>
+     *
+     * @return Elevation value in pixels
+     */
+    public float getElevation() {
+        return 0;
+    }
+
     /** @hide */
     public void setDefaultDisplayHomeAsUpEnabled(boolean enabled) {
     }
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 2feec1b..6ea6b4b 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -2375,6 +2375,24 @@
     }
 
     /**
+     * Called by a device owner to switch the specified user to the foreground.
+     *
+     * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+     * @param userHandle the user to switch to; null will switch to primary.
+     * @return {@code true} if the switch was successful, {@code false} otherwise.
+     *
+     * @see Intent#ACTION_USER_FOREGROUND
+     */
+    public boolean switchUser(ComponentName admin, UserHandle userHandle) {
+        try {
+            return mService.switchUser(admin, userHandle);
+        } catch (RemoteException re) {
+            Log.w(TAG, "Could not switch user ", re);
+            return false;
+        }
+    }
+
+    /**
      * Called by a profile or device owner to get the application restrictions for a given target
      * application running in the managed profile.
      *
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 40bd7d1..c27d1cc 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -139,6 +139,7 @@
     UserHandle createUser(in ComponentName who, in String name);
     UserHandle createAndInitializeUser(in ComponentName who, in String name, in String profileOwnerName, in ComponentName profileOwnerComponent, in Bundle adminExtras);
     boolean removeUser(in ComponentName who, in UserHandle userHandle);
+    boolean switchUser(in ComponentName who, in UserHandle userHandle);
 
     void setAccountManagementDisabled(in ComponentName who, in String accountType, in boolean disabled);
     String[] getAccountTypesWithManagementDisabled();
diff --git a/core/java/android/app/job/JobInfo.java b/core/java/android/app/job/JobInfo.java
index a22e4cd..70f6966 100644
--- a/core/java/android/app/job/JobInfo.java
+++ b/core/java/android/app/job/JobInfo.java
@@ -64,7 +64,6 @@
     }
 
     private final int jobId;
-    // TODO: Change this to use PersistableBundle when that lands in master.
     private final PersistableBundle extras;
     private final ComponentName service;
     private final boolean requireCharging;
@@ -75,6 +74,7 @@
     private final long minLatencyMillis;
     private final long maxExecutionDelayMillis;
     private final boolean isPeriodic;
+    private final boolean isPersisted;
     private final long intervalMillis;
     private final long initialBackoffMillis;
     private final int backoffPolicy;
@@ -145,6 +145,13 @@
     }
 
     /**
+     * @return Whether or not this job should be persisted across device reboots.
+     */
+    public boolean isPersisted() {
+        return isPersisted;
+    }
+
+    /**
      * Set to the interval between occurrences of this job. This value is <b>not</b> set if the
      * job does not recur periodically.
      */
@@ -197,6 +204,7 @@
         minLatencyMillis = in.readLong();
         maxExecutionDelayMillis = in.readLong();
         isPeriodic = in.readInt() == 1;
+        isPersisted = in.readInt() == 1;
         intervalMillis = in.readLong();
         initialBackoffMillis = in.readLong();
         backoffPolicy = in.readInt();
@@ -214,6 +222,7 @@
         minLatencyMillis = b.mMinLatencyMillis;
         maxExecutionDelayMillis = b.mMaxExecutionDelayMillis;
         isPeriodic = b.mIsPeriodic;
+        isPersisted = b.mIsPersisted;
         intervalMillis = b.mIntervalMillis;
         initialBackoffMillis = b.mInitialBackoffMillis;
         backoffPolicy = b.mBackoffPolicy;
@@ -237,6 +246,7 @@
         out.writeLong(minLatencyMillis);
         out.writeLong(maxExecutionDelayMillis);
         out.writeInt(isPeriodic ? 1 : 0);
+        out.writeInt(isPersisted ? 1 : 0);
         out.writeLong(intervalMillis);
         out.writeLong(initialBackoffMillis);
         out.writeInt(backoffPolicy);
@@ -265,6 +275,7 @@
         private boolean mRequiresCharging;
         private boolean mRequiresDeviceIdle;
         private int mNetworkCapabilities;
+        private boolean mIsPersisted;
         // One-off parameters.
         private long mMinLatencyMillis;
         private long mMaxExecutionDelayMillis;
@@ -342,11 +353,6 @@
          * Specify that this job should recur with the provided interval, not more than once per
          * period. You have no control over when within this interval this job will be executed,
          * only the guarantee that it will be executed at most once within this interval.
-         * A periodic job will be repeated until the phone is turned off, however it will only be
-         * persisted beyond boot if the client app has declared the
-         * {@link android.Manifest.permission#RECEIVE_BOOT_COMPLETED} permission. You can schedule
-         * periodic jobs without this permission, they simply will cease to exist after the phone
-         * restarts.
          * Setting this function on the builder with {@link #setMinimumLatency(long)} or
          * {@link #setOverrideDeadline(long)} will result in an error.
          * @param intervalMillis Millisecond interval for which this job will repeat.
@@ -407,6 +413,19 @@
         }
 
         /**
+         * Set whether or not to persist this job across device reboots. This will only have an
+         * effect if your application holds the permission
+         * {@link android.Manifest.permission#RECEIVE_BOOT_COMPLETED}. Otherwise an exception will
+         * be thrown.
+         * @param isPersisted True to indicate that the job will be written to disk and loaded at
+         *                    boot.
+         */
+        public Builder setIsPersisted(boolean isPersisted) {
+            mIsPersisted = isPersisted;
+            return this;
+        }
+
+        /**
          * @return The job object to hand to the JobScheduler. This object is immutable.
          */
         public JobInfo build() {
diff --git a/core/java/android/hardware/camera2/DngCreator.java b/core/java/android/hardware/camera2/DngCreator.java
index 3e3303c..6fc99ac 100644
--- a/core/java/android/hardware/camera2/DngCreator.java
+++ b/core/java/android/hardware/camera2/DngCreator.java
@@ -17,6 +17,7 @@
 package android.hardware.camera2;
 
 import android.graphics.Bitmap;
+import android.graphics.Color;
 import android.graphics.ImageFormat;
 import android.hardware.camera2.impl.CameraMetadataNative;
 import android.location.Location;
@@ -31,6 +32,7 @@
 import java.nio.ByteBuffer;
 import java.text.DateFormat;
 import java.text.SimpleDateFormat;
+import java.util.Calendar;
 import java.util.TimeZone;
 
 /**
@@ -68,17 +70,19 @@
      * </p>
      * <p>
      * DNG metadata tags will be generated from the corresponding parameters in the
-     * {@link android.hardware.camera2.CaptureResult} object.  This removes or overrides
-     * all previous tags set.
+     * {@link android.hardware.camera2.CaptureResult} object.
      * </p>
-     *
+     * <p>
+     * For best quality DNG files, it is strongly recommended that lens shading map output is
+     * enabled if supported. See {@link CaptureRequest#STATISTICS_LENS_SHADING_MAP_MODE}.
+     * </p>
      * @param characteristics an object containing the static
      *          {@link android.hardware.camera2.CameraCharacteristics}.
      * @param metadata a metadata object to generate tags from.
      */
     public DngCreator(CameraCharacteristics characteristics, CaptureResult metadata) {
         if (characteristics == null || metadata == null) {
-            throw new NullPointerException("Null argument to DngCreator constructor");
+            throw new IllegalArgumentException("Null argument to DngCreator constructor");
         }
 
         // Find current time
@@ -121,10 +125,8 @@
      *                      <li>{@link android.media.ExifInterface#ORIENTATION_ROTATE_270}</li>
      *                    </ul>
      * @return this {@link #DngCreator} object.
-     * @hide
      */
     public DngCreator setOrientation(int orientation) {
-
         if (orientation < ExifInterface.ORIENTATION_UNDEFINED ||
                 orientation > ExifInterface.ORIENTATION_ROTATE_270) {
             throw new IllegalArgumentException("Orientation " + orientation +
@@ -139,32 +141,32 @@
      *
      * <p>
      * Pixel data will be converted to a Baseline TIFF RGB image, with 8 bits per color channel.
-     * The alpha channel will be discarded.
-     * </p>
-     *
-     * <p>
-     * The given bitmap should not be altered while this object is in use.
+     * The alpha channel will be discarded.  Thumbnail images with a dimension larger than
+     * {@link #MAX_THUMBNAIL_DIMENSION} will be rejected.
      * </p>
      *
      * @param pixels a {@link android.graphics.Bitmap} of pixel data.
      * @return this {@link #DngCreator} object.
-     * @hide
+     * @throws java.lang.IllegalArgumentException if the given thumbnail image has a dimension
+     *      larger than {@link #MAX_THUMBNAIL_DIMENSION}.
      */
     public DngCreator setThumbnail(Bitmap pixels) {
         if (pixels == null) {
-            throw new NullPointerException("Null argument to setThumbnail");
+            throw new IllegalArgumentException("Null argument to setThumbnail");
         }
 
-        Bitmap.Config config = pixels.getConfig();
+        int width = pixels.getWidth();
+        int height = pixels.getHeight();
 
-        if (config != Bitmap.Config.ARGB_8888) {
-            pixels = pixels.copy(Bitmap.Config.ARGB_8888, false);
-            if (pixels == null) {
-                throw new IllegalArgumentException("Unsupported Bitmap format " + config);
-            }
-            nativeSetThumbnailBitmap(pixels);
+        if (width > MAX_THUMBNAIL_DIMENSION || height > MAX_THUMBNAIL_DIMENSION) {
+            throw new IllegalArgumentException("Thumbnail dimensions width,height (" + width +
+                    "," + height + ") too large, dimensions must be smaller than " +
+                    MAX_THUMBNAIL_DIMENSION);
         }
 
+        ByteBuffer rgbBuffer = convertToRGB(pixels);
+        nativeSetThumbnail(rgbBuffer, width, height);
+
         return this;
     }
 
@@ -173,37 +175,41 @@
      *
      * <p>
      * Pixel data is interpreted as a {@link android.graphics.ImageFormat#YUV_420_888} image.
-     * </p>
-     *
-     * <p>
-     * The given image should not be altered while this object is in use.
+     * Thumbnail images with a dimension larger than {@link #MAX_THUMBNAIL_DIMENSION} will be
+     * rejected.
      * </p>
      *
      * @param pixels an {@link android.media.Image} object with the format
      *               {@link android.graphics.ImageFormat#YUV_420_888}.
      * @return this {@link #DngCreator} object.
-     * @hide
+     * @throws java.lang.IllegalArgumentException if the given thumbnail image has a dimension
+     *      larger than {@link #MAX_THUMBNAIL_DIMENSION}.
      */
     public DngCreator setThumbnail(Image pixels) {
         if (pixels == null) {
-            throw new NullPointerException("Null argument to setThumbnail");
+            throw new IllegalArgumentException("Null argument to setThumbnail");
         }
 
         int format = pixels.getFormat();
         if (format != ImageFormat.YUV_420_888) {
-            throw new IllegalArgumentException("Unsupported image format " + format);
+            throw new IllegalArgumentException("Unsupported Image format " + format);
         }
 
-        Image.Plane[] planes = pixels.getPlanes();
-        nativeSetThumbnailImage(pixels.getWidth(), pixels.getHeight(), planes[0].getBuffer(),
-                planes[0].getRowStride(), planes[0].getPixelStride(), planes[1].getBuffer(),
-                planes[1].getRowStride(), planes[1].getPixelStride(), planes[1].getBuffer(),
-                planes[1].getRowStride(), planes[1].getPixelStride());
+        int width = pixels.getWidth();
+        int height = pixels.getHeight();
+
+        if (width > MAX_THUMBNAIL_DIMENSION || height > MAX_THUMBNAIL_DIMENSION) {
+            throw new IllegalArgumentException("Thumbnail dimensions width,height (" + width +
+                    "," + height + ") too large, dimensions must be smaller than " +
+                    MAX_THUMBNAIL_DIMENSION);
+        }
+
+        ByteBuffer rgbBuffer = convertToRGB(pixels);
+        nativeSetThumbnail(rgbBuffer, width, height);
 
         return this;
     }
 
-
     /**
      * Set image location metadata.
      *
@@ -219,10 +225,26 @@
      *
      * @throws java.lang.IllegalArgumentException if the given location object doesn't
      *          contain enough information to set location metadata.
-     * @hide
      */
     public DngCreator setLocation(Location location) {
-        /*TODO*/
+        if (location == null) {
+            throw new IllegalArgumentException("Null location passed to setLocation");
+        }
+        double latitude = location.getLatitude();
+        double longitude = location.getLongitude();
+        long time = location.getTime();
+
+        int[] latTag = toExifLatLong(latitude);
+        int[] longTag = toExifLatLong(longitude);
+        String latRef = latitude >= 0 ? GPS_LAT_REF_NORTH : GPS_LAT_REF_SOUTH;
+        String longRef = longitude >= 0 ? GPS_LONG_REF_EAST : GPS_LONG_REF_WEST;
+
+        String dateTag = sExifGPSDateStamp.format(time);
+        mGPSTimeStampCalendar.setTimeInMillis(time);
+        int[] timeTag = new int[] { mGPSTimeStampCalendar.get(Calendar.HOUR_OF_DAY), 1,
+                mGPSTimeStampCalendar.get(Calendar.MINUTE), 1,
+                mGPSTimeStampCalendar.get(Calendar.SECOND), 1 };
+        nativeSetGpsTags(latTag, latRef, longTag, longRef, dateTag, timeTag);
         return this;
     }
 
@@ -235,10 +257,12 @@
      *
      * @param description the user description string.
      * @return this {@link #DngCreator} object.
-     * @hide
      */
     public DngCreator setDescription(String description) {
-        /*TODO*/
+        if (description == null) {
+            throw new IllegalArgumentException("Null description passed to setDescription.");
+        }
+        nativeSetDescription(description);
         return this;
     }
 
@@ -268,14 +292,26 @@
      * @throws java.lang.IllegalStateException if not enough metadata information has been
      *          set to write a well-formatted DNG file.
      * @throws java.lang.IllegalArgumentException if the size passed in does not match the
-     * @hide
      */
     public void writeInputStream(OutputStream dngOutput, Size size, InputStream pixels, long offset)
             throws IOException {
-        if (dngOutput == null || pixels == null) {
-            throw new NullPointerException("Null argument to writeImage");
+        if (dngOutput == null) {
+            throw new IllegalArgumentException("Null dngOutput passed to writeInputStream");
+        } else if (size == null) {
+            throw new IllegalArgumentException("Null size passed to writeInputStream");
+        } else if (pixels == null) {
+            throw new IllegalArgumentException("Null pixels passed to writeInputStream");
+        } else if (offset < 0) {
+            throw new IllegalArgumentException("Negative offset passed to writeInputStream");
         }
-        nativeWriteInputStream(dngOutput, pixels, offset);
+
+        int width = size.getWidth();
+        int height = size.getHeight();
+        if (width <= 0 || height <= 0) {
+            throw new IllegalArgumentException("Size with invalid width, height: (" + width + "," +
+                    height + ") passed to writeInputStream");
+        }
+        nativeWriteInputStream(dngOutput, pixels, width, height, offset);
     }
 
     /**
@@ -294,6 +330,11 @@
      * {@link java.lang.IllegalStateException} will be thrown.
      * </p>
      *
+     * <p>
+     * Any mark or limit set on this {@link ByteBuffer} is ignored, and will be cleared by this
+     * method.
+     * </p>
+     *
      * @param dngOutput an {@link java.io.OutputStream} to write the DNG file to.
      * @param size the {@link Size} of the image to write, in pixels.
      * @param pixels an {@link java.nio.ByteBuffer} of pixel data to write.
@@ -303,14 +344,24 @@
      * @throws IOException if an error was encountered in the input or output stream.
      * @throws java.lang.IllegalStateException if not enough metadata information has been
      *          set to write a well-formatted DNG file.
-     * @hide
      */
     public void writeByteBuffer(OutputStream dngOutput, Size size, ByteBuffer pixels, long offset)
             throws IOException {
-        if (dngOutput == null || pixels == null) {
-            throw new NullPointerException("Null argument to writeImage");
+        if (dngOutput == null) {
+            throw new IllegalArgumentException("Null dngOutput passed to writeByteBuffer");
+        } else if (size == null) {
+            throw new IllegalArgumentException("Null size passed to writeByteBuffer");
+        } else if (pixels == null) {
+            throw new IllegalArgumentException("Null pixels passed to writeByteBuffer");
+        } else if (offset < 0) {
+            throw new IllegalArgumentException("Negative offset passed to writeByteBuffer");
         }
-        nativeWriteByteBuffer(dngOutput, pixels, offset);
+
+        int width = size.getWidth();
+        int height = size.getHeight();
+
+        writeByteBuffer(width, height, pixels, dngOutput, DEFAULT_PIXEL_STRIDE,
+                width * DEFAULT_PIXEL_STRIDE, offset);
     }
 
     /**
@@ -331,8 +382,10 @@
      *          set to write a well-formatted DNG file.
      */
     public void writeImage(OutputStream dngOutput, Image pixels) throws IOException {
-        if (dngOutput == null || pixels == null) {
-            throw new NullPointerException("Null argument to writeImage");
+        if (dngOutput == null) {
+            throw new IllegalArgumentException("Null dngOutput to writeImage");
+        } else if (pixels == null) {
+            throw new IllegalArgumentException("Null pixels to writeImage");
         }
 
         int format = pixels.getFormat();
@@ -341,8 +394,13 @@
         }
 
         Image.Plane[] planes = pixels.getPlanes();
-        nativeWriteImage(dngOutput, pixels.getWidth(), pixels.getHeight(), planes[0].getBuffer(),
-                planes[0].getRowStride(), planes[0].getPixelStride());
+        if (planes == null || planes.length <= 0) {
+            throw new IllegalArgumentException("Image with no planes passed to writeImage");
+        }
+
+        ByteBuffer buf = planes[0].getBuffer();
+        writeByteBuffer(pixels.getWidth(), pixels.getHeight(), buf, dngOutput,
+                planes[0].getPixelStride(), planes[0].getRowStride(), 0);
     }
 
     @Override
@@ -350,6 +408,11 @@
         nativeDestroy();
     }
 
+    /**
+     * Max width or height dimension for thumbnails.
+     */
+    public static final int MAX_THUMBNAIL_DIMENSION = 256; // max pixel dimension for TIFF/EP
+
     @Override
     protected void finalize() throws Throwable {
         try {
@@ -359,13 +422,181 @@
         }
     }
 
+    private static final String GPS_LAT_REF_NORTH = "N";
+    private static final String GPS_LAT_REF_SOUTH = "S";
+    private static final String GPS_LONG_REF_EAST = "E";
+    private static final String GPS_LONG_REF_WEST = "W";
+
+    private static final String GPS_DATE_FORMAT_STR = "yyyy:MM:dd";
     private static final String TIFF_DATETIME_FORMAT = "yyyy:MM:dd kk:mm:ss";
+    private static final DateFormat sExifGPSDateStamp = new SimpleDateFormat(GPS_DATE_FORMAT_STR);
     private static final DateFormat sDateTimeStampFormat =
             new SimpleDateFormat(TIFF_DATETIME_FORMAT);
+    private final Calendar mGPSTimeStampCalendar = Calendar
+            .getInstance(TimeZone.getTimeZone("UTC"));
 
     static {
         sDateTimeStampFormat.setTimeZone(TimeZone.getDefault());
+        sExifGPSDateStamp.setTimeZone(TimeZone.getTimeZone("UTC"));
     }
+
+    private static final int DEFAULT_PIXEL_STRIDE = 2; // bytes per sample
+    private static final int BYTES_PER_RGB_PIX = 3; // byts per pixel
+
+    /**
+     * Offset, rowStride, and pixelStride are given in bytes.  Height and width are given in pixels.
+     */
+    private void writeByteBuffer(int width, int height, ByteBuffer pixels, OutputStream dngOutput,
+                                 int pixelStride, int rowStride, long offset)  throws IOException {
+        if (width <= 0 || height <= 0) {
+            throw new IllegalArgumentException("Image with invalid width, height: (" + width + "," +
+                    height + ") passed to write");
+        }
+        long capacity = pixels.capacity();
+        long totalSize = rowStride * height + offset;
+        if (capacity < totalSize) {
+            throw new IllegalArgumentException("Image size " + capacity +
+                    " is too small (must be larger than " + totalSize + ")");
+        }
+        int minRowStride = pixelStride * width;
+        if (minRowStride > rowStride) {
+            throw new IllegalArgumentException("Invalid image pixel stride, row byte width " +
+                    minRowStride + " is too large, expecting " + rowStride);
+        }
+        pixels.clear(); // Reset mark and limit
+        nativeWriteImage(dngOutput, width, height, pixels, rowStride, pixelStride, offset,
+                pixels.isDirect());
+        pixels.clear();
+    }
+
+    /**
+     * Convert a single YUV pixel to RGB.
+     */
+    private static void yuvToRgb(byte[] yuvData, int outOffset, /*out*/byte[] rgbOut) {
+        final int COLOR_MAX = 255;
+
+        float y = yuvData[0] & 0xFF;  // Y channel
+        float cb = yuvData[1] & 0xFF; // U channel
+        float cr = yuvData[2] & 0xFF; // V channel
+
+        // convert YUV -> RGB (from JFIF's "Conversion to and from RGB" section)
+        float r = y + 1.402f * (cr - 128);
+        float g = y - 0.34414f * (cb - 128) - 0.71414f * (cr - 128);
+        float b = y + 1.772f * (cb - 128);
+
+        // clamp to [0,255]
+        rgbOut[outOffset] = (byte) Math.max(0, Math.min(COLOR_MAX, r));
+        rgbOut[outOffset + 1] = (byte) Math.max(0, Math.min(COLOR_MAX, g));
+        rgbOut[outOffset + 2] = (byte) Math.max(0, Math.min(COLOR_MAX, b));
+    }
+
+    /**
+     * Convert a single {@link Color} pixel to RGB.
+     */
+    private static void colorToRgb(int color, int outOffset, /*out*/byte[] rgbOut) {
+        rgbOut[outOffset] = (byte) Color.red(color);
+        rgbOut[outOffset + 1] = (byte) Color.green(color);
+        rgbOut[outOffset + 2] = (byte) Color.blue(color);
+        // Discards Alpha
+    }
+
+    /**
+     * Generate a direct RGB {@link ByteBuffer} from a YUV420_888 {@link Image}.
+     */
+    private static ByteBuffer convertToRGB(Image yuvImage) {
+        // TODO: Optimize this with renderscript intrinsic.
+        int width = yuvImage.getWidth();
+        int height = yuvImage.getHeight();
+        ByteBuffer buf = ByteBuffer.allocateDirect(BYTES_PER_RGB_PIX * width * height);
+
+        Image.Plane yPlane = yuvImage.getPlanes()[0];
+        Image.Plane uPlane = yuvImage.getPlanes()[1];
+        Image.Plane vPlane = yuvImage.getPlanes()[2];
+
+        ByteBuffer yBuf = yPlane.getBuffer();
+        ByteBuffer uBuf = uPlane.getBuffer();
+        ByteBuffer vBuf = vPlane.getBuffer();
+
+        yBuf.rewind();
+        uBuf.rewind();
+        vBuf.rewind();
+
+        int yRowStride = yPlane.getRowStride();
+        int vRowStride = vPlane.getRowStride();
+        int uRowStride = uPlane.getRowStride();
+
+        int yPixStride = yPlane.getPixelStride();
+        int vPixStride = vPlane.getPixelStride();
+        int uPixStride = uPlane.getPixelStride();
+
+        byte[] yuvPixel = { 0, 0, 0 };
+        byte[] yFullRow = new byte[yPixStride * width];
+        byte[] uFullRow = new byte[uPixStride * width / 2];
+        byte[] vFullRow = new byte[vPixStride * width / 2];
+        byte[] finalRow = new byte[BYTES_PER_RGB_PIX * width];
+        for (int i = 0; i < height; i++) {
+            int halfH = i / 2;
+            yBuf.position(yRowStride * i);
+            yBuf.get(yFullRow);
+            uBuf.position(uRowStride * halfH);
+            uBuf.get(uFullRow);
+            vBuf.position(vRowStride * halfH);
+            vBuf.get(vFullRow);
+            for (int j = 0; j < width; j++) {
+                int halfW = j / 2;
+                yuvPixel[0] = yFullRow[yPixStride * j];
+                yuvPixel[1] = uFullRow[uPixStride * halfW];
+                yuvPixel[2] = vFullRow[vPixStride * halfW];
+                yuvToRgb(yuvPixel, j * BYTES_PER_RGB_PIX, /*out*/finalRow);
+            }
+            buf.put(finalRow);
+        }
+
+        yBuf.rewind();
+        uBuf.rewind();
+        vBuf.rewind();
+        buf.rewind();
+        return buf;
+    }
+
+    /**
+     * Generate a direct RGB {@link ByteBuffer} from a {@link Bitmap}.
+     */
+    private static ByteBuffer convertToRGB(Bitmap argbBitmap) {
+        // TODO: Optimize this.
+        int width = argbBitmap.getWidth();
+        int height = argbBitmap.getHeight();
+        ByteBuffer buf = ByteBuffer.allocateDirect(BYTES_PER_RGB_PIX * width * height);
+
+        int[] pixelRow = new int[width];
+        byte[] finalRow = new byte[BYTES_PER_RGB_PIX * width];
+        for (int i = 0; i < height; i++) {
+            argbBitmap.getPixels(pixelRow, /*offset*/0, /*stride*/width, /*x*/0, /*y*/i,
+                    /*width*/width, /*height*/1);
+            for (int j = 0; j < width; j++) {
+                colorToRgb(pixelRow[j], j * BYTES_PER_RGB_PIX, /*out*/finalRow);
+            }
+            buf.put(finalRow);
+        }
+
+        buf.rewind();
+        return buf;
+    }
+
+    /**
+     * Convert coordinate to EXIF GPS tag format.
+     */
+    private static int[] toExifLatLong(double value) {
+        // convert to the format dd/1 mm/1 ssss/100
+        value = Math.abs(value);
+        int degrees = (int) value;
+        value = (value - degrees) * 60;
+        int minutes = (int) value;
+        value = (value - minutes) * 6000;
+        int seconds = (int) value;
+        return new int[] { degrees, 1, minutes, 1, seconds, 100 };
+    }
+
     /**
      * This field is used by native code, do not access or modify.
      */
@@ -381,24 +612,22 @@
 
     private synchronized native void nativeSetOrientation(int orientation);
 
-    private synchronized native void nativeSetThumbnailBitmap(Bitmap bitmap);
+    private synchronized native void nativeSetDescription(String description);
 
-    private synchronized native void nativeSetThumbnailImage(int width, int height,
-                                                             ByteBuffer yBuffer, int yRowStride,
-                                                             int yPixStride, ByteBuffer uBuffer,
-                                                             int uRowStride, int uPixStride,
-                                                             ByteBuffer vBuffer, int vRowStride,
-                                                             int vPixStride);
+    private synchronized native void nativeSetGpsTags(int[] latTag, String latRef, int[] longTag,
+                                                      String longRef, String dateTag,
+                                                      int[] timeTag);
+
+    private synchronized native void nativeSetThumbnail(ByteBuffer buffer, int width, int height);
 
     private synchronized native void nativeWriteImage(OutputStream out, int width, int height,
                                                       ByteBuffer rawBuffer, int rowStride,
-                                                      int pixStride) throws IOException;
-
-    private synchronized native void nativeWriteByteBuffer(OutputStream out, ByteBuffer rawBuffer,
-                                                           long offset) throws IOException;
+                                                      int pixStride, long offset, boolean isDirect)
+                                                      throws IOException;
 
     private synchronized native void nativeWriteInputStream(OutputStream out, InputStream rawStream,
-                                                            long offset) throws IOException;
+                                                            int width, int height, long offset)
+                                                            throws IOException;
 
     static {
         nativeClassInit();
diff --git a/core/java/android/hardware/camera2/legacy/PerfMeasurement.java b/core/java/android/hardware/camera2/legacy/PerfMeasurement.java
new file mode 100644
index 0000000..b930ec2
--- /dev/null
+++ b/core/java/android/hardware/camera2/legacy/PerfMeasurement.java
@@ -0,0 +1,309 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.camera2.legacy;
+
+import android.os.SystemClock;
+import android.util.Log;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.Queue;
+
+/**
+ * GPU and CPU performance measurement for the legacy implementation.
+ *
+ * <p>Measures CPU and GPU processing duration for a set of operations, and dumps
+ * the results into a file.</p>
+ *
+ * <p>Rough usage:
+ * <pre>
+ * {@code
+ *   <set up workload>
+ *   <start long-running workload>
+ *   mPerfMeasurement.startTimer();
+ *   ...render a frame...
+ *   mPerfMeasurement.stopTimer();
+ *   <end workload>
+ *   mPerfMeasurement.dumpPerformanceData("/sdcard/my_data.txt");
+ * }
+ * </pre>
+ * </p>
+ *
+ * <p>All calls to this object must be made within the same thread, and the same GL context.
+ * PerfMeasurement cannot be used outside of a GL context.  The only exception is
+ * dumpPerformanceData, which can be called outside of a valid GL context.</p>
+ */
+class PerfMeasurement {
+    private static final String TAG = "PerfMeasurement";
+
+    public static final int DEFAULT_MAX_QUERIES = 3;
+
+    private final long mNativeContext;
+
+    private int mCompletedQueryCount = 0;
+
+    /**
+     * Values for completed measurements
+     */
+    private ArrayList<Long> mCollectedGpuDurations = new ArrayList<>();
+    private ArrayList<Long> mCollectedCpuDurations = new ArrayList<>();
+    private ArrayList<Long> mCollectedTimestamps = new ArrayList<>();
+
+    /**
+     * Values for in-progress measurements (waiting for async GPU results)
+     */
+    private Queue<Long> mTimestampQueue = new LinkedList<>();
+    private Queue<Long> mCpuDurationsQueue = new LinkedList<>();
+
+    private long mStartTimeNs;
+
+    /**
+     * The value returned by {@link #nativeGetNextGlDuration} if no new timing
+     * measurement is available since the last call.
+     */
+    private static final long NO_DURATION_YET = -1l;
+
+    /**
+     * The value returned by {@link #nativeGetNextGlDuration} if timing failed for
+     * the next timing interval
+     */
+    private static final long FAILED_TIMING = -2l;
+
+    /**
+     * Create a performance measurement object with a maximum of {@value #DEFAULT_MAX_QUERIES}
+     * in-progess queries.
+     */
+    public PerfMeasurement() {
+        mNativeContext = nativeCreateContext(DEFAULT_MAX_QUERIES);
+    }
+
+    /**
+     * Create a performance measurement object with maxQueries as the maximum number of
+     * in-progress queries.
+     *
+     * @param maxQueries maximum in-progress queries, must be larger than 0.
+     * @throws IllegalArgumentException if maxQueries is less than 1.
+     */
+    public PerfMeasurement(int maxQueries) {
+        if (maxQueries < 1) throw new IllegalArgumentException("maxQueries is less than 1");
+        mNativeContext = nativeCreateContext(maxQueries);
+    }
+
+    /**
+     * Returns true if the Gl timing methods will work, false otherwise.
+     *
+     * <p>Must be called within a valid GL context.</p>
+     */
+    public static boolean isGlTimingSupported() {
+        return nativeQuerySupport();
+    }
+
+    /**
+     * Dump collected data to file, and clear the stored data.
+     *
+     * <p>
+     * Format is a simple csv-like text file with a header,
+     * followed by a 3-column list of values in nanoseconds:
+     * <pre>
+     *   timestamp gpu_duration cpu_duration
+     *   <long> <long> <long>
+     *   <long> <long> <long>
+     *   <long> <long> <long>
+     *   ....
+     * </pre>
+     * </p>
+     */
+    public void dumpPerformanceData(String path) {
+        try (BufferedWriter dump = new BufferedWriter(new FileWriter(path))) {
+            dump.write("timestamp gpu_duration cpu_duration\n");
+            for (int i = 0; i < mCollectedGpuDurations.size(); i++) {
+                dump.write(String.format("%d %d %d\n",
+                                mCollectedTimestamps.get(i),
+                                mCollectedGpuDurations.get(i),
+                                mCollectedCpuDurations.get(i)));
+            }
+            mCollectedTimestamps.clear();
+            mCollectedGpuDurations.clear();
+            mCollectedCpuDurations.clear();
+        } catch (IOException e) {
+            Log.e(TAG, "Error writing data dump to " + path + ":" + e);
+        }
+    }
+
+    /**
+     * Start a GPU/CPU timing measurement.
+     *
+     * <p>Call before starting a rendering pass. Only one timing measurement can be active at once,
+     * so {@link #stopTimer} must be called before the next call to this method.</p>
+     *
+     * @throws IllegalStateException if the maximum number of queries are in progress already,
+     *                               or the method is called multiple times in a row, or there is
+     *                               a GPU error.
+     */
+    public void startTimer() {
+        nativeStartGlTimer(mNativeContext);
+        mStartTimeNs = SystemClock.elapsedRealtimeNanos();
+    }
+
+    /**
+     * Finish a GPU/CPU timing measurement.
+     *
+     * <p>Call after finishing all the drawing for a rendering pass. Only one timing measurement can
+     * be active at once, so {@link #startTimer} must be called before the next call to this
+     * method.</p>
+     *
+     * @throws IllegalStateException if no GL timer is currently started, or there is a GPU
+     *                               error.
+     */
+    public void stopTimer() {
+        // Complete CPU timing
+        long endTimeNs = SystemClock.elapsedRealtimeNanos();
+        mCpuDurationsQueue.add(endTimeNs - mStartTimeNs);
+        // Complete GL timing
+        nativeStopGlTimer(mNativeContext);
+
+        // Poll to see if GL timing results have arrived; if so
+        // store the results for a frame
+        long duration = getNextGlDuration();
+        if (duration > 0) {
+            mCollectedGpuDurations.add(duration);
+            mCollectedTimestamps.add(mTimestampQueue.isEmpty() ?
+                    NO_DURATION_YET : mTimestampQueue.poll());
+            mCollectedCpuDurations.add(mCpuDurationsQueue.isEmpty() ?
+                    NO_DURATION_YET : mCpuDurationsQueue.poll());
+        }
+        if (duration == FAILED_TIMING) {
+            // Discard timestamp and CPU measurement since GPU measurement failed
+            if (!mTimestampQueue.isEmpty()) {
+                mTimestampQueue.poll();
+            }
+            if (!mCpuDurationsQueue.isEmpty()) {
+                mCpuDurationsQueue.poll();
+            }
+        }
+    }
+
+    /**
+     * Add a timestamp to a timing measurement. These are queued up and matched to completed
+     * workload measurements as they become available.
+     */
+    public void addTimestamp(long timestamp) {
+        mTimestampQueue.add(timestamp);
+    }
+
+    /**
+     * Get the next available GPU timing measurement.
+     *
+     * <p>Since the GPU works asynchronously, the results of a single start/stopGlTimer measurement
+     * will only be available some time after the {@link #stopTimer} call is made. Poll this method
+     * until the result becomes available. If multiple start/endTimer measurements are made in a
+     * row, the results will be available in FIFO order.</p>
+     *
+     * @return The measured duration of the GPU workload for the next pending query, or
+     *         {@link #NO_DURATION_YET} if no queries are pending or the next pending query has not
+     *         yet finished, or {@link #FAILED_TIMING} if the GPU was unable to complete the
+     *         measurement.
+     *
+     * @throws IllegalStateException If there is a GPU error.
+     *
+     */
+    private long getNextGlDuration() {
+        long duration = nativeGetNextGlDuration(mNativeContext);
+        if (duration > 0) {
+            mCompletedQueryCount++;
+        }
+        return duration;
+    }
+
+    /**
+     * Returns the number of measurements so far that returned a valid duration
+     * measurement.
+     */
+    public int getCompletedQueryCount() {
+        return mCompletedQueryCount;
+    }
+
+    @Override
+    protected void finalize() {
+        nativeDeleteContext(mNativeContext);
+    }
+
+    /**
+     * Create a native performance measurement context.
+     *
+     * @param maxQueryCount maximum in-progress queries; must be >= 1.
+     */
+    private static native long nativeCreateContext(int maxQueryCount);
+
+    /**
+     * Delete the native context.
+     *
+     * <p>Not safe to call more than once.</p>
+     */
+    private static native void nativeDeleteContext(long contextHandle);
+
+    /**
+     * Query whether the relevant Gl extensions are available for Gl timing
+     */
+    private static native boolean nativeQuerySupport();
+
+    /**
+     * Start a GL timing section.
+     *
+     * <p>All GL commands between this method and the next {@link #nativeEndGlTimer} will be
+     * included in the timing.</p>
+     *
+     * <p>Must be called from the same thread as calls to {@link #nativeEndGlTimer} and
+     * {@link #nativeGetNextGlDuration}.</p>
+     *
+     * @throws IllegalStateException if a GL error occurs or start is called repeatedly.
+     */
+    protected static native void nativeStartGlTimer(long contextHandle);
+
+    /**
+     * Finish a GL timing section.
+     *
+     * <p>Some time after this call returns, the time the GPU took to
+     * execute all work submitted between the latest {@link #nativeStartGlTimer} and
+     * this call, will become available from calling {@link #nativeGetNextGlDuration}.</p>
+     *
+     * <p>Must be called from the same thread as calls to {@link #nativeStartGlTimer} and
+     * {@link #nativeGetNextGlDuration}.</p>
+     *
+     * @throws IllegalStateException if a GL error occurs or stop is called before start
+     */
+    protected static native void nativeStopGlTimer(long contextHandle);
+
+    /**
+     * Get the next available GL duration measurement, in nanoseconds.
+     *
+     * <p>Must be called from the same thread as calls to {@link #nativeStartGlTimer} and
+     * {@link #nativeEndGlTimer}.</p>
+     *
+     * @return the next GL duration measurement, or {@link #NO_DURATION_YET} if
+     *         no new measurement is available, or {@link #FAILED_TIMING} if timing
+     *         failed for the next duration measurement.
+     * @throws IllegalStateException if a GL error occurs
+     */
+    protected static native long nativeGetNextGlDuration(long contextHandle);
+
+
+}
diff --git a/core/java/android/hardware/camera2/legacy/SurfaceTextureRenderer.java b/core/java/android/hardware/camera2/legacy/SurfaceTextureRenderer.java
index 9969fd2..daa64c0 100644
--- a/core/java/android/hardware/camera2/legacy/SurfaceTextureRenderer.java
+++ b/core/java/android/hardware/camera2/legacy/SurfaceTextureRenderer.java
@@ -17,6 +17,7 @@
 
 import android.graphics.ImageFormat;
 import android.graphics.SurfaceTexture;
+import android.os.Environment;
 import android.opengl.EGL14;
 import android.opengl.EGLConfig;
 import android.opengl.EGLContext;
@@ -25,10 +26,13 @@
 import android.opengl.GLES11Ext;
 import android.opengl.GLES20;
 import android.opengl.Matrix;
+import android.text.format.Time;
 import android.util.Log;
 import android.util.Size;
 import android.view.Surface;
+import android.os.SystemProperties;
 
+import java.io.File;
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
 import java.nio.FloatBuffer;
@@ -126,6 +130,9 @@
     private int maPositionHandle;
     private int maTextureHandle;
 
+    private PerfMeasurement mPerfMeasurer = null;
+    private static final String LEGACY_PERF_PROPERTY = "persist.camera.legacy_perf";
+
     public SurfaceTextureRenderer() {
         mTriangleVertices = ByteBuffer.allocateDirect(mTriangleVerticesData.length *
                 FLOAT_SIZE_BYTES).order(ByteOrder.nativeOrder()).asFloatBuffer();
@@ -219,7 +226,6 @@
 
         GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, /*offset*/ 0, /*count*/ 4);
         checkGlError("glDrawArrays");
-        GLES20.glFinish();
     }
 
     /**
@@ -327,9 +333,16 @@
                 EGL14.EGL_NONE
         };
         for (EGLSurfaceHolder holder : surfaces) {
-            holder.eglSurface = EGL14.eglCreateWindowSurface(mEGLDisplay, mConfigs, holder.surface,
-                    surfaceAttribs, 0);
-            checkEglError("eglCreateWindowSurface");
+            try {
+                Size size = LegacyCameraDevice.getSurfaceSize(holder.surface);
+                holder.width = size.getWidth();
+                holder.height = size.getHeight();
+                holder.eglSurface = EGL14.eglCreateWindowSurface(mEGLDisplay, mConfigs,
+                        holder.surface, surfaceAttribs, /*offset*/ 0);
+                checkEglError("eglCreateWindowSurface");
+            } catch (LegacyExceptionUtils.BufferQueueAbandonedException e) {
+                Log.w(TAG, "Surface abandoned, skipping...", e);
+            }
         }
     }
 
@@ -367,6 +380,7 @@
         if (mEGLDisplay != EGL14.EGL_NO_DISPLAY) {
             EGL14.eglMakeCurrent(mEGLDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE,
                     EGL14.EGL_NO_CONTEXT);
+            dumpGlTiming();
             if (mSurfaces != null) {
                 for (EGLSurfaceHolder holder : mSurfaces) {
                     if (holder.eglSurface != null) {
@@ -418,6 +432,65 @@
     }
 
     /**
+     * Save a measurement dump to disk, in
+     * {@code /sdcard/CameraLegacy/durations_<time>_<width1>x<height1>_...txt}
+     */
+    private void dumpGlTiming() {
+        if (mPerfMeasurer == null) return;
+
+        File legacyStorageDir = new File(Environment.getExternalStorageDirectory(), "CameraLegacy");
+        if (!legacyStorageDir.exists()){
+            if (!legacyStorageDir.mkdirs()){
+                Log.e(TAG, "Failed to create directory for data dump");
+                return;
+            }
+        }
+
+        StringBuilder path = new StringBuilder(legacyStorageDir.getPath());
+        path.append(File.separator);
+        path.append("durations_");
+
+        Time now = new Time();
+        now.setToNow();
+        path.append(now.format2445());
+        path.append("_S");
+        for (EGLSurfaceHolder surface : mSurfaces) {
+            path.append(String.format("_%d_%d", surface.width, surface.height));
+        }
+        path.append("_C");
+        for (EGLSurfaceHolder surface : mConversionSurfaces) {
+            path.append(String.format("_%d_%d", surface.width, surface.height));
+        }
+        path.append(".txt");
+        mPerfMeasurer.dumpPerformanceData(path.toString());
+    }
+
+    private void setupGlTiming() {
+        if (PerfMeasurement.isGlTimingSupported()) {
+            Log.d(TAG, "Enabling GL performance measurement");
+            mPerfMeasurer = new PerfMeasurement();
+        } else {
+            Log.d(TAG, "GL performance measurement not supported on this device");
+            mPerfMeasurer = null;
+        }
+    }
+
+    private void beginGlTiming() {
+        if (mPerfMeasurer == null) return;
+        mPerfMeasurer.startTimer();
+    }
+
+    private void addGlTimestamp(long timestamp) {
+        if (mPerfMeasurer == null) return;
+        mPerfMeasurer.addTimestamp(timestamp);
+    }
+
+    private void endGlTiming() {
+        if (mPerfMeasurer == null) return;
+        mPerfMeasurer.stopTimer();
+    }
+
+    /**
      * Return the surface texture to draw to - this is the texture use to when producing output
      * surface buffers.
      *
@@ -474,6 +547,11 @@
                 mConversionSurfaces.get(0).eglSurface);
         initializeGLState();
         mSurfaceTexture = new SurfaceTexture(getTextureId());
+
+        // Set up performance tracking if enabled
+        if (SystemProperties.getBoolean(LEGACY_PERF_PROPERTY, false)) {
+            setupGlTiming();
+        }
     }
 
     /**
@@ -494,8 +572,19 @@
         }
 
         checkGlError("before updateTexImage");
+
+        if (targetSurfaces == null) {
+            mSurfaceTexture.updateTexImage();
+            return;
+        }
+
+        beginGlTiming();
+
         mSurfaceTexture.updateTexImage();
-        if (targetSurfaces == null) return;
+
+        long timestamp = mSurfaceTexture.getTimestamp();
+        addGlTimestamp(timestamp);
+
         List<Long> targetSurfaceIds = LegacyCameraDevice.getSurfaceIds(targetSurfaces);
         for (EGLSurfaceHolder holder : mSurfaces) {
             if (LegacyCameraDevice.containsSurfaceId(holder.surface, targetSurfaceIds)) {
@@ -522,6 +611,8 @@
                 }
             }
         }
+
+        endGlTiming();
     }
 
     /**
diff --git a/core/java/android/hardware/hdmi/HdmiClient.java b/core/java/android/hardware/hdmi/HdmiClient.java
index bb0f4ba..c43e21a 100644
--- a/core/java/android/hardware/hdmi/HdmiClient.java
+++ b/core/java/android/hardware/hdmi/HdmiClient.java
@@ -24,6 +24,29 @@
         mService = service;
     }
 
+    /**
+     * Send a key event to other logical device.
+     *
+     * @param keyCode key code to send. Defined in {@link android.view.KeyEvent}.
+     * @param isPressed true if this is key press event
+     */
+    public void sendKeyEvent(int keyCode, boolean isPressed) {
+        try {
+            mService.sendKeyEvent(getDeviceType(), keyCode, isPressed);
+        } catch (RemoteException e) {
+            Log.e(TAG, "queryDisplayStatus threw exception ", e);
+        }
+    }
+
+    /**
+     * Send vendor-specific command.
+     *
+     * @param targetAddress address of the target device
+     * @param params vendor-specific parameter. For &lt;Vendor Command With ID&gt; do not
+     *               include the first 3 bytes (vendor ID).
+     * @param hasVendorId {@code true} if the command type will be &lt;Vendor Command With ID&gt;.
+     *                    {@code false} if the command will be &lt;Vendor Command&gt;
+     */
     public void sendVendorCommand(int targetAddress, byte[] params, boolean hasVendorId) {
         try {
             mService.sendVendorCommand(getDeviceType(), targetAddress, params, hasVendorId);
@@ -32,6 +55,11 @@
         }
     }
 
+    /**
+     * Add a listener used to receive incoming vendor-specific command.
+     *
+     * @param listener listener object
+     */
     public void addVendorCommandListener(VendorCommandListener listener) {
         try {
             mService.addVendorCommandListener(getListenerWrapper(listener), getDeviceType());
diff --git a/core/java/android/hardware/hdmi/IHdmiControlService.aidl b/core/java/android/hardware/hdmi/IHdmiControlService.aidl
index 3a477fb..f3931e3 100644
--- a/core/java/android/hardware/hdmi/IHdmiControlService.aidl
+++ b/core/java/android/hardware/hdmi/IHdmiControlService.aidl
@@ -43,7 +43,7 @@
     void addDeviceEventListener(IHdmiDeviceEventListener listener);
     void deviceSelect(int logicalAddress, IHdmiControlCallback callback);
     void portSelect(int portId, IHdmiControlCallback callback);
-    void sendKeyEvent(int keyCode, boolean isPressed);
+    void sendKeyEvent(int deviceType, int keyCode, boolean isPressed);
     List<HdmiPortInfo> getPortInfo();
     boolean canChangeSystemAudioMode();
     boolean getSystemAudioMode();
diff --git a/core/java/android/net/NetworkCapabilities.java b/core/java/android/net/NetworkCapabilities.java
index 835b269..53f9fcd 100644
--- a/core/java/android/net/NetworkCapabilities.java
+++ b/core/java/android/net/NetworkCapabilities.java
@@ -57,6 +57,7 @@
             mTransportTypes = nc.mTransportTypes;
             mLinkUpBandwidthKbps = nc.mLinkUpBandwidthKbps;
             mLinkDownBandwidthKbps = nc.mLinkDownBandwidthKbps;
+            mNetworkSpecifier = nc.mNetworkSpecifier;
         }
     }
 
@@ -449,7 +450,8 @@
      * </p>
      * The interpretation of this {@code String} is bearer specific and bearers that use
      * it should document their particulars.  For example, Bluetooth may use some sort of
-     * device id while WiFi could used ssid and/or bssid.  Cellular may use carrier spn.
+     * device id while WiFi could used SSID and/or BSSID.  Cellular may use carrier SPN (name)
+     * or Subscription ID.
      *
      * @param networkSpecifier An {@code String} of opaque format used to specify the bearer
      *                         specific network specifier where the bearer has a choice of
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index d0a6f08..c22c4b6 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -2076,7 +2076,7 @@
                     if (val != 0) hasData = true;
                 }
                 if (hasData) {
-                    dumpLine(pw, 0 /* uid */, category, USER_ACTIVITY_DATA, args);
+                    dumpLine(pw, uid /* uid */, category, USER_ACTIVITY_DATA, args);
                 }
             }
             
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index f48855a..07397973 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -5112,6 +5112,61 @@
        public static final String INSTALL_NON_MARKET_APPS = Secure.INSTALL_NON_MARKET_APPS;
 
        /**
+        * Whether HDMI control shall be enabled. If disabled, no CEC/MHL command will be
+        * sent or processed. (0 = false, 1 = true)
+        * @hide
+        */
+       public static final String HDMI_CONTROL_ENABLED = "hdmi_control_enabled";
+
+       /**
+        * Whether HDMI system audio is enabled. If enabled, TV internal speaker is muted,
+        * and the output is redirected to AV Receiver connected via
+        * {@Global#HDMI_SYSTEM_AUDIO_OUTPUT}.
+        * @hide
+        */
+       public static final String HDMI_SYSTEM_AUDIO_ENABLED = "hdmi_system_audio_enabled";
+
+       /**
+        * Output of the audio to be used for system audio mode, as defined in AudioSystem.java.
+        * <ul>
+        * <li>DEVICE_OUT_SPDIF</li>
+        * <li>DEVICE_OUT_HDMI_ARC</li>
+        * <li>DEVICE_OUT_LINE</li>
+        * </ul>
+        * @hide
+        */
+       public static final String HDMI_SYSTEM_AUDIO_OUTPUT = "hdmi_system_audio_output";
+
+       /**
+        * Whether TV will automatically turn on upon reception of the CEC command
+        * &lt;Text View On&gt; or &lt;Image View On&gt;. (0 = false, 1 = true)
+        * @hide
+        */
+       public static final String HDMI_CONTROL_AUTO_WAKEUP_ENABLED =
+               "hdmi_control_auto_wakeup_enabled";
+
+       /**
+        * Whether TV will also turn off other CEC devices when it goes to standby mode.
+        * (0 = false, 1 = true)
+        * @hide
+        */
+       public static final String HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED =
+               "hdmi_control_auto_device_off_enabled";
+
+       /**
+        * Whether TV will switch to MHL port when a mobile device is plugged in.
+        * (0 = false, 1 = true)
+        * @hide
+        */
+       public static final String MHL_INPUT_SWITCHING_ENABLED = "mhl_input_switching_enabled";
+
+       /**
+        * Whether TV will charge the mobile device connected at MHL port. (0 = false, 1 = true)
+        * @hide
+        */
+       public static final String MHL_POWER_CHARGE_ENABLED = "mhl_power_charge_enabled";
+
+       /**
         * Whether mobile data connections are allowed by the user.  See
         * ConnectivityManager for more info.
         * @hide
diff --git a/core/java/android/widget/ActionMenuView.java b/core/java/android/widget/ActionMenuView.java
index acee592..96abf51 100644
--- a/core/java/android/widget/ActionMenuView.java
+++ b/core/java/android/widget/ActionMenuView.java
@@ -18,6 +18,7 @@
 import android.content.Context;
 import android.content.res.Configuration;
 import android.util.AttributeSet;
+import android.view.ContextThemeWrapper;
 import android.view.Gravity;
 import android.view.Menu;
 import android.view.MenuItem;
@@ -28,7 +29,6 @@
 import com.android.internal.view.menu.ActionMenuItemView;
 import com.android.internal.view.menu.MenuBuilder;
 import com.android.internal.view.menu.MenuItemImpl;
-import com.android.internal.view.menu.MenuPresenter;
 import com.android.internal.view.menu.MenuView;
 
 /**
@@ -45,6 +45,12 @@
 
     private MenuBuilder mMenu;
 
+    /** Context against which to inflate popup menus. */
+    private Context mPopupContext;
+
+    /** Theme resource against which to inflate popup menus. */
+    private int mPopupTheme;
+
     private boolean mReserveOverflow;
     private ActionMenuPresenter mPresenter;
     private boolean mFormatItems;
@@ -64,9 +70,41 @@
         final float density = context.getResources().getDisplayMetrics().density;
         mMinCellSize = (int) (MIN_CELL_SIZE * density);
         mGeneratedItemPadding = (int) (GENERATED_ITEM_PADDING * density);
+        mPopupContext = context;
+        mPopupTheme = 0;
     }
 
-    /** @hide */
+    /**
+     * Specifies the theme to use when inflating popup menus. By default, uses
+     * the same theme as the action menu view itself.
+     *
+     * @param resId theme used to inflate popup menus
+     * @see #getPopupTheme()
+     */
+    public void setPopupTheme(int resId) {
+        if (mPopupTheme != resId) {
+            mPopupTheme = resId;
+            if (resId == 0) {
+                mPopupContext = mContext;
+            } else {
+                mPopupContext = new ContextThemeWrapper(mContext, resId);
+            }
+        }
+    }
+
+    /**
+     * @return resource identifier of the theme used to inflate popup menus, or
+     *         0 if menus are inflated against the action menu view theme
+     * @see #setPopupTheme(int)
+     */
+    public int getPopupTheme() {
+        return mPopupTheme;
+    }
+
+    /**
+     * @param presenter Menu presenter used to display popup menu
+     * @hide
+     */
     public void setPresenter(ActionMenuPresenter presenter) {
         mPresenter = presenter;
         mPresenter.setMenuView(this);
@@ -571,7 +609,7 @@
             mMenu.setCallback(new MenuBuilderCallback());
             mPresenter = new ActionMenuPresenter(context);
             mPresenter.setCallback(new ActionMenuPresenterCallback());
-            mMenu.addMenuPresenter(mPresenter);
+            mMenu.addMenuPresenter(mPresenter, mPopupContext);
             mPresenter.setMenuView(this);
         }
 
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index dd58ba6..fac0eb2 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -279,6 +279,7 @@
     private ColorStateList mTextColor;
     private ColorStateList mHintTextColor;
     private ColorStateList mLinkTextColor;
+    @ViewDebug.ExportedProperty(category = "text")
     private int mCurTextColor;
     private int mCurHintTextColor;
     private boolean mFreezesText;
@@ -2519,6 +2520,26 @@
     }
 
     /**
+     * @return the size (in scaled pixels) of thee default text size in this TextView.
+     * @hide
+     */
+    @ViewDebug.ExportedProperty(category = "text")
+    public float getScaledTextSize() {
+        return mTextPaint.getTextSize() / mTextPaint.density;
+    }
+
+    /** @hide */
+    @ViewDebug.ExportedProperty(category = "text", mapping = {
+            @ViewDebug.IntToString(from = Typeface.NORMAL, to = "NORMAL"),
+            @ViewDebug.IntToString(from = Typeface.BOLD, to = "BOLD"),
+            @ViewDebug.IntToString(from = Typeface.ITALIC, to = "ITALIC"),
+            @ViewDebug.IntToString(from = Typeface.BOLD_ITALIC, to = "BOLD_ITALIC")
+    })
+    public int getTypefaceStyle() {
+        return mTextPaint.getTypeface().getStyle();
+    }
+
+    /**
      * Set the default text size to the given value, interpreted as "scaled
      * pixel" units.  This size is adjusted based on the current density and
      * user font size preference.
diff --git a/core/java/android/widget/Toolbar.java b/core/java/android/widget/Toolbar.java
index 24950f6..712e6d0 100644
--- a/core/java/android/widget/Toolbar.java
+++ b/core/java/android/widget/Toolbar.java
@@ -14,7 +14,6 @@
  * limitations under the License.
  */
 
-
 package android.widget;
 
 import android.annotation.NonNull;
@@ -27,16 +26,15 @@
 import android.text.Layout;
 import android.text.TextUtils;
 import android.util.AttributeSet;
-import android.util.Log;
 import android.view.CollapsibleActionView;
+import android.view.ContextThemeWrapper;
 import android.view.Gravity;
 import android.view.Menu;
 import android.view.MenuInflater;
 import android.view.MenuItem;
 import android.view.View;
-import android.view.ViewDebug;
 import android.view.ViewGroup;
-import android.view.Window;
+
 import com.android.internal.R;
 import com.android.internal.view.menu.MenuBuilder;
 import com.android.internal.view.menu.MenuItemImpl;
@@ -104,6 +102,12 @@
     private ImageButton mCollapseButtonView;
     View mExpandedActionView;
 
+    /** Context against which to inflate popup menus. */
+    private Context mPopupContext;
+
+    /** Theme resource against which to inflate popup menus. */
+    private int mPopupTheme;
+
     private int mTitleTextAppearance;
     private int mSubtitleTextAppearance;
     private int mNavButtonStyle;
@@ -230,6 +234,36 @@
             setSubtitle(subtitle);
         }
         a.recycle();
+
+        mPopupContext = context;
+        mPopupTheme = 0;
+    }
+
+    /**
+     * Specifies the theme to use when inflating popup menus. By default, uses
+     * the same theme as the toolbar itself.
+     *
+     * @param resId theme used to inflate popup menus
+     * @see #getPopupTheme()
+     */
+    public void setPopupTheme(int resId) {
+        if (mPopupTheme != resId) {
+            mPopupTheme = resId;
+            if (resId == 0) {
+                mPopupContext = mContext;
+            } else {
+                mPopupContext = new ContextThemeWrapper(mContext, resId);
+            }
+        }
+    }
+
+    /**
+     * @return resource identifier of the theme used to inflate popup menus, or
+     *         0 if menus are inflated against the toolbar theme
+     * @see #setPopupTheme(int)
+     */
+    public int getPopupTheme() {
+        return mPopupTheme;
     }
 
     @Override
@@ -306,22 +340,21 @@
             oldMenu.removeMenuPresenter(mExpandedMenuPresenter);
         }
 
-        final Context context = getContext();
-
         if (mExpandedMenuPresenter == null) {
             mExpandedMenuPresenter = new ExpandedActionViewMenuPresenter();
         }
 
         outerPresenter.setExpandedActionViewsExclusive(true);
         if (menu != null) {
-            menu.addMenuPresenter(outerPresenter);
-            menu.addMenuPresenter(mExpandedMenuPresenter);
+            menu.addMenuPresenter(outerPresenter, mPopupContext);
+            menu.addMenuPresenter(mExpandedMenuPresenter, mPopupContext);
         } else {
-            outerPresenter.initForMenu(context, null);
-            mExpandedMenuPresenter.initForMenu(context, null);
+            outerPresenter.initForMenu(mPopupContext, null);
+            mExpandedMenuPresenter.initForMenu(mPopupContext, null);
             outerPresenter.updateMenuView(true);
             mExpandedMenuPresenter.updateMenuView(true);
         }
+        mMenuView.setPopupTheme(mPopupTheme);
         mMenuView.setPresenter(outerPresenter);
         mOuterActionMenuPresenter = outerPresenter;
     }
@@ -768,13 +801,14 @@
                 mExpandedMenuPresenter = new ExpandedActionViewMenuPresenter();
             }
             mMenuView.setExpandedActionViewsExclusive(true);
-            menu.addMenuPresenter(mExpandedMenuPresenter);
+            menu.addMenuPresenter(mExpandedMenuPresenter, mPopupContext);
         }
     }
 
     private void ensureMenuView() {
         if (mMenuView == null) {
             mMenuView = new ActionMenuView(getContext());
+            mMenuView.setPopupTheme(mPopupTheme);
             mMenuView.setOnMenuItemClickListener(mMenuViewItemClickListener);
             final LayoutParams lp = generateDefaultLayoutParams();
             lp.gravity = Gravity.END | (mButtonGravity & Gravity.VERTICAL_GRAVITY_MASK);
diff --git a/core/java/com/android/internal/app/ToolbarActionBar.java b/core/java/com/android/internal/app/ToolbarActionBar.java
index e8a3f0a..7af52f3 100644
--- a/core/java/com/android/internal/app/ToolbarActionBar.java
+++ b/core/java/com/android/internal/app/ToolbarActionBar.java
@@ -122,6 +122,16 @@
     }
 
     @Override
+    public void setElevation(float elevation) {
+        mToolbar.setElevation(elevation);
+    }
+
+    @Override
+    public float getElevation() {
+        return mToolbar.getElevation();
+    }
+
+    @Override
     public Context getThemedContext() {
         return mToolbar.getContext();
     }
diff --git a/core/java/com/android/internal/app/WindowDecorActionBar.java b/core/java/com/android/internal/app/WindowDecorActionBar.java
index 7bd316f..b5ff0cc 100644
--- a/core/java/com/android/internal/app/WindowDecorActionBar.java
+++ b/core/java/com/android/internal/app/WindowDecorActionBar.java
@@ -222,6 +222,10 @@
         if (a.getBoolean(R.styleable.ActionBar_hideOnContentScroll, false)) {
             setHideOnContentScrollEnabled(true);
         }
+        final int elevation = a.getDimensionPixelSize(R.styleable.ActionBar_elevation, 0);
+        if (elevation != 0) {
+            setElevation(elevation);
+        }
         a.recycle();
     }
 
@@ -236,6 +240,19 @@
         }
     }
 
+    @Override
+    public void setElevation(float elevation) {
+        mContainerView.setElevation(elevation);
+        if (mSplitView != null) {
+            mSplitView.setElevation(elevation);
+        }
+    }
+
+    @Override
+    public float getElevation() {
+        return mContainerView.getElevation();
+    }
+
     public void onConfigurationChanged(Configuration newConfig) {
         setHasEmbeddedTabs(ActionBarPolicy.get(mContext).hasEmbeddedTabs());
     }
diff --git a/core/java/com/android/internal/view/menu/MenuBuilder.java b/core/java/com/android/internal/view/menu/MenuBuilder.java
index 5d7d322..e8d1ead 100644
--- a/core/java/com/android/internal/view/menu/MenuBuilder.java
+++ b/core/java/com/android/internal/view/menu/MenuBuilder.java
@@ -212,8 +212,21 @@
      * @param presenter The presenter to add
      */
     public void addMenuPresenter(MenuPresenter presenter) {
+        addMenuPresenter(presenter, mContext);
+    }
+
+    /**
+     * Add a presenter to this menu that uses an alternate context for
+     * inflating menu items. This will only hold a WeakReference; you do not
+     * need to explicitly remove a presenter, but you can using
+     * {@link #removeMenuPresenter(MenuPresenter)}.
+     *
+     * @param presenter The presenter to add
+     * @param menuContext The context used to inflate menu items
+     */
+    public void addMenuPresenter(MenuPresenter presenter, Context menuContext) {
         mPresenters.add(new WeakReference<MenuPresenter>(presenter));
-        presenter.initForMenu(mContext, this);
+        presenter.initForMenu(menuContext, this);
         mIsActionItemsStale = true;
     }
 
diff --git a/core/java/com/android/internal/view/menu/MenuPopupHelper.java b/core/java/com/android/internal/view/menu/MenuPopupHelper.java
index 5a12893..40f58e9 100644
--- a/core/java/com/android/internal/view/menu/MenuPopupHelper.java
+++ b/core/java/com/android/internal/view/menu/MenuPopupHelper.java
@@ -95,7 +95,8 @@
 
         mAnchorView = anchorView;
 
-        menu.addMenuPresenter(this);
+        // Present the menu using our context, not the menu builder's context.
+        menu.addMenuPresenter(this, context);
     }
 
     public void setAnchorView(View anchor) {
diff --git a/core/java/com/android/internal/view/menu/MenuPresenter.java b/core/java/com/android/internal/view/menu/MenuPresenter.java
index d913a39..f207b98 100644
--- a/core/java/com/android/internal/view/menu/MenuPresenter.java
+++ b/core/java/com/android/internal/view/menu/MenuPresenter.java
@@ -18,7 +18,6 @@
 
 import android.content.Context;
 import android.os.Parcelable;
-import android.view.Menu;
 import android.view.ViewGroup;
 
 /**
@@ -61,7 +60,7 @@
 
     /**
      * Retrieve a MenuView to display the menu specified in
-     * {@link #initForMenu(Context, Menu)}.
+     * {@link #initForMenu(Context, MenuBuilder)}.
      *
      * @param root Intended parent of the MenuView.
      * @return A freshly created MenuView.
diff --git a/core/java/com/android/internal/widget/AbsActionBarView.java b/core/java/com/android/internal/widget/AbsActionBarView.java
index 9e7ff93..850ea23 100644
--- a/core/java/com/android/internal/widget/AbsActionBarView.java
+++ b/core/java/com/android/internal/widget/AbsActionBarView.java
@@ -16,6 +16,9 @@
 package com.android.internal.widget;
 
 import com.android.internal.R;
+
+import android.util.TypedValue;
+import android.view.ContextThemeWrapper;
 import android.widget.ActionMenuPresenter;
 import android.widget.ActionMenuView;
 
@@ -32,6 +35,15 @@
 import android.view.animation.DecelerateInterpolator;
 
 public abstract class AbsActionBarView extends ViewGroup {
+    private static final TimeInterpolator sAlphaInterpolator = new DecelerateInterpolator();
+
+    private static final int FADE_DURATION = 200;
+
+    protected final VisibilityAnimListener mVisAnimListener = new VisibilityAnimListener();
+
+    /** Context against which to inflate popup menus. */
+    protected final Context mPopupContext;
+
     protected ActionMenuView mMenuView;
     protected ActionMenuPresenter mActionMenuPresenter;
     protected ViewGroup mSplitView;
@@ -40,11 +52,6 @@
     protected int mContentHeight;
 
     protected Animator mVisibilityAnim;
-    protected final VisibilityAnimListener mVisAnimListener = new VisibilityAnimListener();
-
-    private static final TimeInterpolator sAlphaInterpolator = new DecelerateInterpolator();
-
-    private static final int FADE_DURATION = 200;
 
     public AbsActionBarView(Context context) {
         this(context, null);
@@ -61,6 +68,14 @@
     public AbsActionBarView(
             Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
+
+        final TypedValue tv = new TypedValue();
+        if (context.getTheme().resolveAttribute(R.attr.actionBarPopupTheme, tv, true)
+                && tv.resourceId != 0) {
+            mPopupContext = new ContextThemeWrapper(context, tv.resourceId);
+        } else {
+            mPopupContext = context;
+        }
     }
 
     @Override
diff --git a/core/java/com/android/internal/widget/ActionBarContextView.java b/core/java/com/android/internal/widget/ActionBarContextView.java
index 6ff77a0..c7ac815 100644
--- a/core/java/com/android/internal/widget/ActionBarContextView.java
+++ b/core/java/com/android/internal/widget/ActionBarContextView.java
@@ -16,6 +16,9 @@
 package com.android.internal.widget;
 
 import com.android.internal.R;
+
+import android.util.TypedValue;
+import android.view.ContextThemeWrapper;
 import android.widget.ActionMenuPresenter;
 import android.widget.ActionMenuView;
 import com.android.internal.view.menu.MenuBuilder;
@@ -231,7 +234,7 @@
         final LayoutParams layoutParams = new LayoutParams(LayoutParams.WRAP_CONTENT,
                 LayoutParams.MATCH_PARENT);
         if (!mSplitActionBar) {
-            menu.addMenuPresenter(mActionMenuPresenter);
+            menu.addMenuPresenter(mActionMenuPresenter, mPopupContext);
             mMenuView = (ActionMenuView) mActionMenuPresenter.getMenuView(this);
             mMenuView.setBackgroundDrawable(null);
             addView(mMenuView, layoutParams);
@@ -244,7 +247,7 @@
             // Span the whole width
             layoutParams.width = LayoutParams.MATCH_PARENT;
             layoutParams.height = mContentHeight;
-            menu.addMenuPresenter(mActionMenuPresenter);
+            menu.addMenuPresenter(mActionMenuPresenter, mPopupContext);
             mMenuView = (ActionMenuView) mActionMenuPresenter.getMenuView(this);
             mMenuView.setBackgroundDrawable(mSplitBackground);
             mSplitView.addView(mMenuView, layoutParams);
diff --git a/core/java/com/android/internal/widget/ActionBarView.java b/core/java/com/android/internal/widget/ActionBarView.java
index 77559c0..e53af69 100644
--- a/core/java/com/android/internal/widget/ActionBarView.java
+++ b/core/java/com/android/internal/widget/ActionBarView.java
@@ -29,7 +29,9 @@
 import android.text.Layout;
 import android.text.TextUtils;
 import android.util.AttributeSet;
+import android.util.TypedValue;
 import android.view.CollapsibleActionView;
+import android.view.ContextThemeWrapper;
 import android.view.Gravity;
 import android.view.LayoutInflater;
 import android.view.Menu;
@@ -435,11 +437,11 @@
 
     private void configPresenters(MenuBuilder builder) {
         if (builder != null) {
-            builder.addMenuPresenter(mActionMenuPresenter);
-            builder.addMenuPresenter(mExpandedMenuPresenter);
+            builder.addMenuPresenter(mActionMenuPresenter, mPopupContext);
+            builder.addMenuPresenter(mExpandedMenuPresenter, mPopupContext);
         } else {
-            mActionMenuPresenter.initForMenu(mContext, null);
-            mExpandedMenuPresenter.initForMenu(mContext, null);
+            mActionMenuPresenter.initForMenu(mPopupContext, null);
+            mExpandedMenuPresenter.initForMenu(mPopupContext, null);
             mActionMenuPresenter.updateMenuView(true);
             mExpandedMenuPresenter.updateMenuView(true);
         }
diff --git a/core/jni/Android.mk b/core/jni/Android.mk
index 9b96495..ef7ef0a 100644
--- a/core/jni/Android.mk
+++ b/core/jni/Android.mk
@@ -139,6 +139,7 @@
 	android_hardware_Camera.cpp \
 	android_hardware_camera2_CameraMetadata.cpp \
 	android_hardware_camera2_legacy_LegacyCameraDevice.cpp \
+	android_hardware_camera2_legacy_PerfMeasurement.cpp \
 	android_hardware_camera2_DngCreator.cpp \
 	android_hardware_SensorManager.cpp \
 	android_hardware_SerialPort.cpp \
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index f7cd8c3..9b66734 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -79,6 +79,7 @@
 extern int register_android_hardware_Camera(JNIEnv *env);
 extern int register_android_hardware_camera2_CameraMetadata(JNIEnv *env);
 extern int register_android_hardware_camera2_legacy_LegacyCameraDevice(JNIEnv *env);
+extern int register_android_hardware_camera2_legacy_PerfMeasurement(JNIEnv *env);
 extern int register_android_hardware_camera2_DngCreator(JNIEnv *env);
 extern int register_android_hardware_SensorManager(JNIEnv *env);
 extern int register_android_hardware_SerialPort(JNIEnv *env);
@@ -1314,6 +1315,7 @@
     REG_JNI(register_android_hardware_Camera),
     REG_JNI(register_android_hardware_camera2_CameraMetadata),
     REG_JNI(register_android_hardware_camera2_legacy_LegacyCameraDevice),
+    REG_JNI(register_android_hardware_camera2_legacy_PerfMeasurement),
     REG_JNI(register_android_hardware_camera2_DngCreator),
     REG_JNI(register_android_hardware_SensorManager),
     REG_JNI(register_android_hardware_SerialPort),
diff --git a/core/jni/android_hardware_camera2_DngCreator.cpp b/core/jni/android_hardware_camera2_DngCreator.cpp
index 33100bf..3a3328f 100644
--- a/core/jni/android_hardware_camera2_DngCreator.cpp
+++ b/core/jni/android_hardware_camera2_DngCreator.cpp
@@ -24,11 +24,14 @@
 #include <img_utils/TiffIfd.h>
 #include <img_utils/TiffWriter.h>
 #include <img_utils/Output.h>
+#include <img_utils/Input.h>
+#include <img_utils/StripSource.h>
 
 #include <utils/Log.h>
 #include <utils/Errors.h>
 #include <utils/StrongPointer.h>
 #include <utils/RefBase.h>
+#include <utils/Vector.h>
 #include <cutils/properties.h>
 
 #include <string.h>
@@ -42,17 +45,17 @@
 using namespace android;
 using namespace img_utils;
 
-#define BAIL_IF_INVALID(expr, jnienv, tagId) \
+#define BAIL_IF_INVALID(expr, jnienv, tagId, writer) \
     if ((expr) != OK) { \
         jniThrowExceptionFmt(jnienv, "java/lang/IllegalArgumentException", \
-                "Invalid metadata for tag %x", tagId); \
+                "Invalid metadata for tag %s (%x)", (writer)->getTagName(tagId), (tagId)); \
         return; \
     }
 
-#define BAIL_IF_EMPTY(entry, jnienv, tagId) \
+#define BAIL_IF_EMPTY(entry, jnienv, tagId, writer) \
     if (entry.count == 0) { \
         jniThrowExceptionFmt(jnienv, "java/lang/IllegalArgumentException", \
-                "Missing metadata fields for tag %x", tagId); \
+                "Missing metadata fields for tag %s (%x)", (writer)->getTagName(tagId), (tagId)); \
         return; \
     }
 
@@ -66,15 +69,98 @@
     jmethodID mWriteMethod;
 } gOutputStreamClassInfo;
 
+static struct {
+    jmethodID mReadMethod;
+    jmethodID mSkipMethod;
+} gInputStreamClassInfo;
+
+static struct {
+    jmethodID mGetMethod;
+} gInputByteBufferClassInfo;
+
 enum {
     BITS_PER_SAMPLE = 16,
     BYTES_PER_SAMPLE = 2,
-    TIFF_IFD_0 = 0
+    BYTES_PER_RGB_PIXEL = 3,
+    BITS_PER_RGB_SAMPLE = 8,
+    BYTES_PER_RGB_SAMPLE = 1,
+    SAMPLES_PER_RGB_PIXEL = 3,
+    SAMPLES_PER_RAW_PIXEL = 1,
+    TIFF_IFD_0 = 0,
+    TIFF_IFD_SUB1 = 1,
+    TIFF_IFD_GPSINFO = 2,
 };
 
 // ----------------------------------------------------------------------------
 
-// This class is not intended to be used across JNI calls.
+/**
+ * Container class for the persistent native context.
+ */
+
+class NativeContext : public LightRefBase<NativeContext> {
+
+public:
+    NativeContext();
+    virtual ~NativeContext();
+
+    TiffWriter* getWriter();
+
+    uint32_t getThumbnailWidth();
+    uint32_t getThumbnailHeight();
+    const uint8_t* getThumbnail();
+
+    bool setThumbnail(const uint8_t* buffer, uint32_t width, uint32_t height);
+
+private:
+    Vector<uint8_t> mCurrentThumbnail;
+    TiffWriter mWriter;
+    uint32_t mThumbnailWidth;
+    uint32_t mThumbnailHeight;
+};
+
+NativeContext::NativeContext() : mThumbnailWidth(0), mThumbnailHeight(0) {}
+
+NativeContext::~NativeContext() {}
+
+TiffWriter* NativeContext::getWriter() {
+    return &mWriter;
+}
+
+uint32_t NativeContext::getThumbnailWidth() {
+    return mThumbnailWidth;
+}
+
+uint32_t NativeContext::getThumbnailHeight() {
+    return mThumbnailHeight;
+}
+
+const uint8_t* NativeContext::getThumbnail() {
+    return mCurrentThumbnail.array();
+}
+
+bool NativeContext::setThumbnail(const uint8_t* buffer, uint32_t width, uint32_t height) {
+    mThumbnailWidth = width;
+    mThumbnailHeight = height;
+
+    size_t size = BYTES_PER_RGB_PIXEL * width * height;
+    if (mCurrentThumbnail.resize(size) < 0) {
+        ALOGE("%s: Could not resize thumbnail buffer.", __FUNCTION__);
+        return false;
+    }
+
+    uint8_t* thumb = mCurrentThumbnail.editArray();
+    memcpy(thumb, buffer, size);
+    return true;
+}
+
+// End of NativeContext
+// ----------------------------------------------------------------------------
+
+/**
+ * Wrapper class for a Java OutputStream.
+ *
+ * This class is not intended to be used across JNI calls.
+ */
 class JniOutputStream : public Output, public LightRefBase<JniOutputStream> {
 public:
     JniOutputStream(JNIEnv* env, jobject outStream);
@@ -82,11 +168,13 @@
     virtual ~JniOutputStream();
 
     status_t open();
+
     status_t write(const uint8_t* buf, size_t offset, size_t count);
+
     status_t close();
 private:
     enum {
-        BYTE_ARRAY_LENGTH = 1024
+        BYTE_ARRAY_LENGTH = 4096
     };
     jobject mOutputStream;
     JNIEnv* mEnv;
@@ -138,27 +226,465 @@
     return OK;
 }
 
+// End of JniOutputStream
 // ----------------------------------------------------------------------------
 
+/**
+ * Wrapper class for a Java InputStream.
+ *
+ * This class is not intended to be used across JNI calls.
+ */
+class JniInputStream : public Input, public LightRefBase<JniInputStream> {
+public:
+    JniInputStream(JNIEnv* env, jobject inStream);
+
+    status_t open();
+
+    status_t close();
+
+    ssize_t read(uint8_t* buf, size_t offset, size_t count);
+
+    ssize_t skip(size_t count);
+
+    virtual ~JniInputStream();
+private:
+    enum {
+        BYTE_ARRAY_LENGTH = 4096
+    };
+    jobject mInStream;
+    JNIEnv* mEnv;
+    jbyteArray mByteArray;
+
+};
+
+JniInputStream::JniInputStream(JNIEnv* env, jobject inStream) : mInStream(inStream), mEnv(env) {
+    mByteArray = env->NewByteArray(BYTE_ARRAY_LENGTH);
+    if (mByteArray == NULL) {
+        jniThrowException(env, "java/lang/OutOfMemoryError", "Could not allocate byte array.");
+    }
+}
+
+JniInputStream::~JniInputStream() {
+    mEnv->DeleteLocalRef(mByteArray);
+}
+
+ssize_t JniInputStream::read(uint8_t* buf, size_t offset, size_t count) {
+
+    jint realCount = BYTE_ARRAY_LENGTH;
+    if (count < BYTE_ARRAY_LENGTH) {
+        realCount = count;
+    }
+    jint actual = mEnv->CallIntMethod(mInStream, gInputStreamClassInfo.mReadMethod, mByteArray, 0,
+            realCount);
+
+    if (actual < 0) {
+        return NOT_ENOUGH_DATA;
+    }
+
+    if (mEnv->ExceptionCheck()) {
+        return BAD_VALUE;
+    }
+
+    mEnv->GetByteArrayRegion(mByteArray, 0, actual, reinterpret_cast<jbyte*>(buf + offset));
+    if (mEnv->ExceptionCheck()) {
+        return BAD_VALUE;
+    }
+    return actual;
+}
+
+ssize_t JniInputStream::skip(size_t count) {
+    jlong actual = mEnv->CallLongMethod(mInStream, gInputStreamClassInfo.mSkipMethod,
+            static_cast<jlong>(count));
+
+    if (mEnv->ExceptionCheck()) {
+        return BAD_VALUE;
+    }
+    if (actual < 0) {
+        return NOT_ENOUGH_DATA;
+    }
+    return actual;
+}
+
+status_t JniInputStream::open() {
+    // Do nothing
+    return OK;
+}
+
+status_t JniInputStream::close() {
+    // Do nothing
+    return OK;
+}
+
+// End of JniInputStream
+// ----------------------------------------------------------------------------
+
+/**
+ * Wrapper class for a non-direct Java ByteBuffer.
+ *
+ * This class is not intended to be used across JNI calls.
+ */
+class JniInputByteBuffer : public Input, public LightRefBase<JniInputByteBuffer> {
+public:
+    JniInputByteBuffer(JNIEnv* env, jobject inBuf);
+
+    status_t open();
+
+    status_t close();
+
+    ssize_t read(uint8_t* buf, size_t offset, size_t count);
+
+    virtual ~JniInputByteBuffer();
+private:
+    enum {
+        BYTE_ARRAY_LENGTH = 4096
+    };
+    jobject mInBuf;
+    JNIEnv* mEnv;
+    jbyteArray mByteArray;
+};
+
+JniInputByteBuffer::JniInputByteBuffer(JNIEnv* env, jobject inBuf) : mInBuf(inBuf), mEnv(env) {
+    mByteArray = env->NewByteArray(BYTE_ARRAY_LENGTH);
+    if (mByteArray == NULL) {
+        jniThrowException(env, "java/lang/OutOfMemoryError", "Could not allocate byte array.");
+    }
+}
+
+JniInputByteBuffer::~JniInputByteBuffer() {
+    mEnv->DeleteLocalRef(mByteArray);
+}
+
+ssize_t JniInputByteBuffer::read(uint8_t* buf, size_t offset, size_t count) {
+    jint realCount = BYTE_ARRAY_LENGTH;
+    if (count < BYTE_ARRAY_LENGTH) {
+        realCount = count;
+    }
+
+    mEnv->CallObjectMethod(mInBuf, gInputByteBufferClassInfo.mGetMethod, mByteArray, 0,
+            realCount);
+
+    if (mEnv->ExceptionCheck()) {
+        return BAD_VALUE;
+    }
+
+    mEnv->GetByteArrayRegion(mByteArray, 0, realCount, reinterpret_cast<jbyte*>(buf + offset));
+    if (mEnv->ExceptionCheck()) {
+        return BAD_VALUE;
+    }
+    return realCount;
+}
+
+status_t JniInputByteBuffer::open() {
+    // Do nothing
+    return OK;
+}
+
+status_t JniInputByteBuffer::close() {
+    // Do nothing
+    return OK;
+}
+
+// End of JniInputByteBuffer
+// ----------------------------------------------------------------------------
+
+/**
+ * StripSource subclass for Input types.
+ *
+ * This class is not intended to be used across JNI calls.
+ */
+
+class InputStripSource : public StripSource, public LightRefBase<InputStripSource> {
+public:
+    InputStripSource(JNIEnv* env, Input& input, uint32_t ifd, uint32_t width, uint32_t height,
+            uint32_t pixStride, uint32_t rowStride, uint64_t offset, uint32_t bytesPerSample,
+            uint32_t samplesPerPixel);
+
+    virtual ~InputStripSource();
+
+    virtual status_t writeToStream(Output& stream, uint32_t count);
+
+    virtual uint32_t getIfd() const;
+protected:
+    uint32_t mIfd;
+    Input* mInput;
+    uint32_t mWidth;
+    uint32_t mHeight;
+    uint32_t mPixStride;
+    uint32_t mRowStride;
+    uint64_t mOffset;
+    JNIEnv* mEnv;
+    uint32_t mBytesPerSample;
+    uint32_t mSamplesPerPixel;
+};
+
+InputStripSource::InputStripSource(JNIEnv* env, Input& input, uint32_t ifd, uint32_t width,
+        uint32_t height, uint32_t pixStride, uint32_t rowStride, uint64_t offset,
+        uint32_t bytesPerSample, uint32_t samplesPerPixel) : mIfd(ifd), mInput(&input),
+        mWidth(width), mHeight(height), mPixStride(pixStride), mRowStride(rowStride),
+        mOffset(offset), mEnv(env), mBytesPerSample(bytesPerSample),
+        mSamplesPerPixel(samplesPerPixel) {}
+
+InputStripSource::~InputStripSource() {}
+
+status_t InputStripSource::writeToStream(Output& stream, uint32_t count) {
+    status_t err = OK;
+    uint32_t fullSize = mRowStride * mHeight;
+    jlong offset = mOffset;
+
+    if (fullSize != count) {
+        ALOGE("%s: Amount to write %u doesn't match image size %u", __FUNCTION__, count,
+                fullSize);
+        jniThrowException(mEnv, "java/lang/IllegalStateException", "Not enough data to write");
+        return BAD_VALUE;
+    }
+
+    // Skip offset
+    while (offset > 0) {
+        ssize_t skipped = mInput->skip(offset);
+        if (skipped <= 0) {
+            if (skipped == NOT_ENOUGH_DATA || skipped == 0) {
+                jniThrowExceptionFmt(mEnv, "java/io/IOException",
+                        "Early EOF encountered in skip, not enough pixel data for image of size %u",
+                        fullSize);
+                skipped = NOT_ENOUGH_DATA;
+            } else {
+                if (!mEnv->ExceptionCheck()) {
+                    jniThrowException(mEnv, "java/io/IOException",
+                            "Error encountered while skip bytes in input stream.");
+                }
+            }
+
+            return skipped;
+        }
+        offset -= skipped;
+    }
+
+    Vector<uint8_t> row;
+    if (row.resize(mRowStride) < 0) {
+        jniThrowException(mEnv, "java/lang/OutOfMemoryError", "Could not allocate row vector.");
+        return BAD_VALUE;
+    }
+
+    uint8_t* rowBytes = row.editArray();
+
+    for (uint32_t i = 0; i < mHeight; ++i) {
+        size_t rowFillAmt = 0;
+        size_t rowSize = mPixStride;
+
+        while (rowFillAmt < mRowStride) {
+            ssize_t bytesRead = mInput->read(rowBytes, rowFillAmt, rowSize);
+            if (bytesRead <= 0) {
+                if (bytesRead == NOT_ENOUGH_DATA || bytesRead == 0) {
+                    jniThrowExceptionFmt(mEnv, "java/io/IOException",
+                            "Early EOF encountered, not enough pixel data for image of size %u",
+                            fullSize);
+                    bytesRead = NOT_ENOUGH_DATA;
+                } else {
+                    if (!mEnv->ExceptionCheck()) {
+                        jniThrowException(mEnv, "java/io/IOException",
+                                "Error encountered while reading");
+                    }
+                }
+                return bytesRead;
+            }
+            rowFillAmt += bytesRead;
+            rowSize -= bytesRead;
+        }
+
+        if (mPixStride == mBytesPerSample * mSamplesPerPixel) {
+            ALOGV("%s: Using stream per-row write for strip.", __FUNCTION__);
+
+            if (stream.write(rowBytes, 0, mBytesPerSample * mSamplesPerPixel * mWidth) != OK ||
+                    mEnv->ExceptionCheck()) {
+                if (!mEnv->ExceptionCheck()) {
+                    jniThrowException(mEnv, "java/io/IOException", "Failed to write pixel data");
+                }
+                return BAD_VALUE;
+            }
+        } else {
+            ALOGV("%s: Using stream per-pixel write for strip.", __FUNCTION__);
+            jniThrowException(mEnv, "java/lang/IllegalStateException",
+                    "Per-pixel strides are not supported for RAW16 -- pixels must be contiguous");
+            return BAD_VALUE;
+
+            // TODO: Add support for non-contiguous pixels if needed.
+        }
+    }
+    return OK;
+}
+
+uint32_t InputStripSource::getIfd() const {
+    return mIfd;
+}
+
+// End of InputStripSource
+// ----------------------------------------------------------------------------
+
+/**
+ * StripSource subclass for direct buffer types.
+ *
+ * This class is not intended to be used across JNI calls.
+ */
+
+class DirectStripSource : public StripSource, public LightRefBase<DirectStripSource> {
+public:
+    DirectStripSource(JNIEnv* env, const uint8_t* pixelBytes, uint32_t ifd, uint32_t width,
+            uint32_t height, uint32_t pixStride, uint32_t rowStride, uint64_t offset,
+            uint32_t bytesPerSample, uint32_t samplesPerPixel);
+
+    virtual ~DirectStripSource();
+
+    virtual status_t writeToStream(Output& stream, uint32_t count);
+
+    virtual uint32_t getIfd() const;
+protected:
+    uint32_t mIfd;
+    const uint8_t* mPixelBytes;
+    uint32_t mWidth;
+    uint32_t mHeight;
+    uint32_t mPixStride;
+    uint32_t mRowStride;
+    uint16_t mOffset;
+    JNIEnv* mEnv;
+    uint32_t mBytesPerSample;
+    uint32_t mSamplesPerPixel;
+};
+
+DirectStripSource::DirectStripSource(JNIEnv* env, const uint8_t* pixelBytes, uint32_t ifd,
+            uint32_t width, uint32_t height, uint32_t pixStride, uint32_t rowStride,
+            uint64_t offset, uint32_t bytesPerSample, uint32_t samplesPerPixel) : mIfd(ifd),
+            mPixelBytes(pixelBytes), mWidth(width), mHeight(height), mPixStride(pixStride),
+            mRowStride(rowStride), mOffset(offset), mEnv(env), mBytesPerSample(bytesPerSample),
+            mSamplesPerPixel(samplesPerPixel) {}
+
+DirectStripSource::~DirectStripSource() {}
+
+status_t DirectStripSource::writeToStream(Output& stream, uint32_t count) {
+    uint32_t fullSize = mRowStride * mHeight;
+
+    if (fullSize != count) {
+        ALOGE("%s: Amount to write %u doesn't match image size %u", __FUNCTION__, count,
+                fullSize);
+        jniThrowException(mEnv, "java/lang/IllegalStateException", "Not enough data to write");
+        return BAD_VALUE;
+    }
+
+    if (mPixStride == mBytesPerSample * mSamplesPerPixel
+            && mRowStride == mWidth * mBytesPerSample * mSamplesPerPixel) {
+        ALOGV("%s: Using direct single-pass write for strip.", __FUNCTION__);
+
+        if (stream.write(mPixelBytes, mOffset, fullSize) != OK || mEnv->ExceptionCheck()) {
+            if (!mEnv->ExceptionCheck()) {
+                jniThrowException(mEnv, "java/io/IOException", "Failed to write pixel data");
+            }
+            return BAD_VALUE;
+        }
+    } else if (mPixStride == mBytesPerSample * mSamplesPerPixel) {
+        ALOGV("%s: Using direct per-row write for strip.", __FUNCTION__);
+
+        for (size_t i = 0; i < mHeight; ++i) {
+            if (stream.write(mPixelBytes, mOffset + i * mRowStride, mPixStride * mWidth) != OK ||
+                        mEnv->ExceptionCheck()) {
+                if (!mEnv->ExceptionCheck()) {
+                    jniThrowException(mEnv, "java/io/IOException", "Failed to write pixel data");
+                }
+                return BAD_VALUE;
+            }
+        }
+    } else {
+        ALOGV("%s: Using direct per-pixel write for strip.", __FUNCTION__);
+
+        jniThrowException(mEnv, "java/lang/IllegalStateException",
+                "Per-pixel strides are not supported for RAW16 -- pixels must be contiguous");
+        return BAD_VALUE;
+
+        // TODO: Add support for non-contiguous pixels if needed.
+    }
+    return OK;
+
+}
+
+uint32_t DirectStripSource::getIfd() const {
+    return mIfd;
+}
+
+// End of DirectStripSource
+// ----------------------------------------------------------------------------
+
+static bool validateDngHeader(JNIEnv* env, TiffWriter* writer, jint width, jint height) {
+    bool hasThumbnail = writer->hasIfd(TIFF_IFD_SUB1);
+
+    // TODO: handle lens shading map, etc. conversions for other raw buffer sizes.
+    uint32_t metadataWidth = *(writer->getEntry(TAG_IMAGEWIDTH, (hasThumbnail) ? TIFF_IFD_SUB1 : TIFF_IFD_0)->getData<uint32_t>());
+    uint32_t metadataHeight = *(writer->getEntry(TAG_IMAGELENGTH, (hasThumbnail) ? TIFF_IFD_SUB1 : TIFF_IFD_0)->getData<uint32_t>());
+
+    if (width < 0 || metadataWidth != static_cast<uint32_t>(width)) {
+        jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException", \
+                        "Metadata width %d doesn't match image width %d", metadataWidth, width);
+        return false;
+    }
+
+    if (height < 0 || metadataHeight != static_cast<uint32_t>(height)) {
+        jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException", \
+                        "Metadata height %d doesn't match image height %d", metadataHeight, height);
+        return false;
+    }
+
+    return true;
+}
+
+static status_t moveEntries(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) {
+            ALOGE("%s: moveEntries failed, entry %u not found in IFD %u", __FUNCTION__, tagId,
+                    ifdFrom);
+            return BAD_VALUE;
+        }
+        if (writer->addEntry(entry, ifdTo) != OK) {
+            ALOGE("%s: moveEntries failed, could not add entry %u to IFD %u", __FUNCTION__, tagId,
+                    ifdFrom);
+            return BAD_VALUE;
+        }
+        writer->removeEntry(tagId, ifdFrom);
+    }
+    return OK;
+}
+
+// ----------------------------------------------------------------------------
 extern "C" {
 
-static TiffWriter* DngCreator_getCreator(JNIEnv* env, jobject thiz) {
+static NativeContext* DngCreator_getNativeContext(JNIEnv* env, jobject thiz) {
     ALOGV("%s:", __FUNCTION__);
-    return reinterpret_cast<TiffWriter*>(env->GetLongField(thiz,
+    return reinterpret_cast<NativeContext*>(env->GetLongField(thiz,
             gDngCreatorClassInfo.mNativeContext));
 }
 
-static void DngCreator_setCreator(JNIEnv* env, jobject thiz, sp<TiffWriter> writer) {
+static void DngCreator_setNativeContext(JNIEnv* env, jobject thiz, sp<NativeContext> context) {
     ALOGV("%s:", __FUNCTION__);
-    TiffWriter* current = DngCreator_getCreator(env, thiz);
-    if (writer != NULL) {
-        writer->incStrong((void*) DngCreator_setCreator);
+    NativeContext* current = DngCreator_getNativeContext(env, thiz);
+
+    if (context != NULL) {
+        context->incStrong((void*) DngCreator_setNativeContext);
     }
+
     if (current) {
-        current->decStrong((void*) DngCreator_setCreator);
+        current->decStrong((void*) DngCreator_setNativeContext);
     }
+
     env->SetLongField(thiz, gDngCreatorClassInfo.mNativeContext,
-            reinterpret_cast<jlong>(writer.get()));
+            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) {
@@ -174,6 +700,19 @@
     LOG_ALWAYS_FATAL_IF(outputStreamClazz == NULL, "Can't find java/io/OutputStream class");
     gOutputStreamClassInfo.mWriteMethod = env->GetMethodID(outputStreamClazz, "write", "([BII)V");
     LOG_ALWAYS_FATAL_IF(gOutputStreamClassInfo.mWriteMethod == NULL, "Can't find write method");
+
+    jclass inputStreamClazz = env->FindClass("java/io/InputStream");
+    LOG_ALWAYS_FATAL_IF(inputStreamClazz == NULL, "Can't find java/io/InputStream class");
+    gInputStreamClassInfo.mReadMethod = env->GetMethodID(inputStreamClazz, "read", "([BII)I");
+    LOG_ALWAYS_FATAL_IF(gInputStreamClassInfo.mReadMethod == NULL, "Can't find read method");
+    gInputStreamClassInfo.mSkipMethod = env->GetMethodID(inputStreamClazz, "skip", "(J)J");
+    LOG_ALWAYS_FATAL_IF(gInputStreamClassInfo.mSkipMethod == NULL, "Can't find skip method");
+
+    jclass inputBufferClazz = env->FindClass("java/nio/ByteBuffer");
+    LOG_ALWAYS_FATAL_IF(inputBufferClazz == NULL, "Can't find java/nio/ByteBuffer class");
+    gInputByteBufferClassInfo.mGetMethod = env->GetMethodID(inputBufferClazz, "get",
+            "([BII)Ljava/nio/ByteBuffer;");
+    LOG_ALWAYS_FATAL_IF(gInputByteBufferClassInfo.mGetMethod == NULL, "Can't find get method");
 }
 
 static void DngCreator_init(JNIEnv* env, jobject thiz, jobject characteristicsPtr,
@@ -192,7 +731,8 @@
         return;
     }
 
-    sp<TiffWriter> writer = new TiffWriter();
+    sp<NativeContext> nativeContext = new NativeContext();
+    TiffWriter* writer = nativeContext->getWriter();
 
     writer->addIfd(TIFF_IFD_0);
 
@@ -208,96 +748,99 @@
 
     // TODO: Greensplit.
     // TODO: Add remaining non-essential tags
+
+    // Setup main image tags
+
     {
         // Set orientation
         uint16_t orientation = 1; // Normal
         BAIL_IF_INVALID(writer->addEntry(TAG_ORIENTATION, 1, &orientation, TIFF_IFD_0), env,
-                TAG_ORIENTATION);
+                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);
+                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,
-                TAG_BITSPERSAMPLE);
+                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);
+                TAG_COMPRESSION, writer);
     }
 
     {
         // Set dimensions
         camera_metadata_entry entry =
                 characteristics.find(ANDROID_SENSOR_INFO_ACTIVE_ARRAY_SIZE);
-        BAIL_IF_EMPTY(entry, env, TAG_IMAGEWIDTH);
+        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);
+                TAG_IMAGEWIDTH, writer);
         BAIL_IF_INVALID(writer->addEntry(TAG_IMAGELENGTH, 1, &height, TIFF_IFD_0), env,
-                TAG_IMAGELENGTH);
+                TAG_IMAGELENGTH, writer);
         imageWidth = width;
         imageHeight = height;
     }
 
     {
         // Set photometric interpretation
-        uint16_t interpretation = 32803;
+        uint16_t interpretation = 32803; // CFA
         BAIL_IF_INVALID(writer->addEntry(TAG_PHOTOMETRICINTERPRETATION, 1, &interpretation,
-                TIFF_IFD_0), env, TAG_PHOTOMETRICINTERPRETATION);
+                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);
+        BAIL_IF_EMPTY(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);
+                TAG_BLACKLEVEL, writer);
 
         uint16_t repeatDim[2] = {2, 2};
         BAIL_IF_INVALID(writer->addEntry(TAG_BLACKLEVELREPEATDIM, 2, repeatDim, TIFF_IFD_0), env,
-                TAG_BLACKLEVELREPEATDIM);
+                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),
-                env, TAG_SAMPLESPERPIXEL);
+                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);
+                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);
+                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);
+        BAIL_IF_EMPTY(entry, env, TAG_CFAPATTERN, writer);
         camera_metadata_enum_android_sensor_info_color_filter_arrangement_t cfa =
                 static_cast<camera_metadata_enum_android_sensor_info_color_filter_arrangement_t>(
                 entry.data.u8[0]);
@@ -305,28 +848,28 @@
             case ANDROID_SENSOR_INFO_COLOR_FILTER_ARRANGEMENT_RGGB: {
                 uint8_t cfa[4] = {0, 1, 1, 2};
                 BAIL_IF_INVALID(writer->addEntry(TAG_CFAPATTERN, 4, cfa, TIFF_IFD_0),
-                                                env, TAG_CFAPATTERN);
+                                                env, TAG_CFAPATTERN, writer);
                 opcodeCfaLayout = OpcodeListBuilder::CFA_RGGB;
                 break;
             }
             case ANDROID_SENSOR_INFO_COLOR_FILTER_ARRANGEMENT_GRBG: {
                 uint8_t cfa[4] = {1, 0, 2, 1};
                 BAIL_IF_INVALID(writer->addEntry(TAG_CFAPATTERN, 4, cfa, TIFF_IFD_0),
-                                                env, TAG_CFAPATTERN);
+                                                env, TAG_CFAPATTERN, writer);
                 opcodeCfaLayout = OpcodeListBuilder::CFA_GRBG;
                 break;
             }
             case ANDROID_SENSOR_INFO_COLOR_FILTER_ARRANGEMENT_GBRG: {
                 uint8_t cfa[4] = {1, 2, 0, 1};
                 BAIL_IF_INVALID(writer->addEntry(TAG_CFAPATTERN, 4, cfa, TIFF_IFD_0),
-                                                env, TAG_CFAPATTERN);
+                                                env, TAG_CFAPATTERN, writer);
                 opcodeCfaLayout = OpcodeListBuilder::CFA_GBRG;
                 break;
             }
             case ANDROID_SENSOR_INFO_COLOR_FILTER_ARRANGEMENT_BGGR: {
                 uint8_t cfa[4] = {2, 1, 1, 0};
                 BAIL_IF_INVALID(writer->addEntry(TAG_CFAPATTERN, 4, cfa, TIFF_IFD_0),
-                                env, TAG_CFAPATTERN);
+                                env, TAG_CFAPATTERN, writer);
                 opcodeCfaLayout = OpcodeListBuilder::CFA_BGGR;
                 break;
             }
@@ -342,21 +885,21 @@
         // Set CFA plane color
         uint8_t cfaPlaneColor[3] = {0, 1, 2};
         BAIL_IF_INVALID(writer->addEntry(TAG_CFAPLANECOLOR, 3, cfaPlaneColor, TIFF_IFD_0),
-                env, TAG_CFAPLANECOLOR);
+                env, TAG_CFAPLANECOLOR, writer);
     }
 
     {
         // Set CFA layout
         uint16_t cfaLayout = 1;
         BAIL_IF_INVALID(writer->addEntry(TAG_CFALAYOUT, 1, &cfaLayout, TIFF_IFD_0),
-                env, TAG_CFALAYOUT);
+                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);
+                env, TAG_IMAGEDESCRIPTION, writer);
     }
 
     {
@@ -368,7 +911,7 @@
         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);
+                TIFF_IFD_0), env, TAG_MAKE, writer);
     }
 
     {
@@ -380,23 +923,23 @@
         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);
+                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),
-                env, TAG_XRESOLUTION);
+                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);
+                env, TAG_YRESOLUTION, writer);
 
         uint16_t unit = 2; // inches
         BAIL_IF_INVALID(writer->addEntry(TAG_RESOLUTIONUNIT, 1, &unit, TIFF_IFD_0),
-                env, TAG_RESOLUTIONUNIT);
+                env, TAG_RESOLUTIONUNIT, writer);
     }
 
     {
@@ -405,7 +948,7 @@
         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);
+                TIFF_IFD_0), env, TAG_SOFTWARE, writer);
     }
 
     {
@@ -420,12 +963,22 @@
             return;
         }
 
-        BAIL_IF_INVALID(writer->addEntry(TAG_DATETIME, DATETIME_COUNT,
-                reinterpret_cast<const uint8_t*>(captureTime), TIFF_IFD_0), env, TAG_DATETIMEORIGINAL);
+        if (writer->addEntry(TAG_DATETIME, DATETIME_COUNT,
+                reinterpret_cast<const uint8_t*>(captureTime), TIFF_IFD_0) != OK) {
+            env->ReleaseStringUTFChars(formattedCaptureTime, captureTime);
+            jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException",
+                    "Invalid metadata for tag %x", TAG_DATETIME);
+            return;
+        }
 
         // datetime original
-        BAIL_IF_INVALID(writer->addEntry(TAG_DATETIMEORIGINAL, DATETIME_COUNT,
-                reinterpret_cast<const uint8_t*>(captureTime), TIFF_IFD_0), env, TAG_DATETIMEORIGINAL);
+        if (writer->addEntry(TAG_DATETIMEORIGINAL, DATETIME_COUNT,
+                reinterpret_cast<const uint8_t*>(captureTime), TIFF_IFD_0) != OK) {
+            env->ReleaseStringUTFChars(formattedCaptureTime, captureTime);
+            jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException",
+                    "Invalid metadata for tag %x", TAG_DATETIMEORIGINAL);
+            return;
+        }
         env->ReleaseStringUTFChars(formattedCaptureTime, captureTime);
     }
 
@@ -433,21 +986,21 @@
         // TIFF/EP standard id
         uint8_t standardId[] = { 1, 0, 0, 0 };
         BAIL_IF_INVALID(writer->addEntry(TAG_TIFFEPSTANDARDID, 4, standardId,
-                TIFF_IFD_0), env, TAG_TIFFEPSTANDARDID);
+                TIFF_IFD_0), env, TAG_TIFFEPSTANDARDID, writer);
     }
 
     {
         // copyright
         uint8_t copyright = '\0'; // empty
         BAIL_IF_INVALID(writer->addEntry(TAG_COPYRIGHT, 1, &copyright,
-                TIFF_IFD_0), env, TAG_COPYRIGHT);
+                TIFF_IFD_0), env, TAG_COPYRIGHT, writer);
     }
 
     {
         // exposure time
         camera_metadata_entry entry =
             results.find(ANDROID_SENSOR_EXPOSURE_TIME);
-        BAIL_IF_EMPTY(entry, env, TAG_EXPOSURETIME);
+        BAIL_IF_EMPTY(entry, env, TAG_EXPOSURETIME, writer);
 
         int64_t exposureTime = *(entry.data.i64);
 
@@ -473,7 +1026,7 @@
 
         uint32_t exposure[] = { static_cast<uint32_t>(exposureTime), denominator };
         BAIL_IF_INVALID(writer->addEntry(TAG_EXPOSURETIME, 1, exposure,
-                TIFF_IFD_0), env, TAG_EXPOSURETIME);
+                TIFF_IFD_0), env, TAG_EXPOSURETIME, writer);
 
     }
 
@@ -481,7 +1034,7 @@
         // ISO speed ratings
         camera_metadata_entry entry =
             results.find(ANDROID_SENSOR_SENSITIVITY);
-        BAIL_IF_EMPTY(entry, env, TAG_ISOSPEEDRATINGS);
+        BAIL_IF_EMPTY(entry, env, TAG_ISOSPEEDRATINGS, writer);
 
         int32_t tempIso = *(entry.data.i32);
         if (tempIso < 0) {
@@ -497,57 +1050,57 @@
 
         uint16_t iso = static_cast<uint16_t>(tempIso);
         BAIL_IF_INVALID(writer->addEntry(TAG_ISOSPEEDRATINGS, 1, &iso,
-                TIFF_IFD_0), env, TAG_ISOSPEEDRATINGS);
+                TIFF_IFD_0), env, TAG_ISOSPEEDRATINGS, writer);
     }
 
     {
         // focal length
         camera_metadata_entry entry =
             results.find(ANDROID_LENS_FOCAL_LENGTH);
-        BAIL_IF_EMPTY(entry, env, TAG_FOCALLENGTH);
+        BAIL_IF_EMPTY(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,
-                TIFF_IFD_0), env, TAG_FOCALLENGTH);
+                TIFF_IFD_0), env, TAG_FOCALLENGTH, writer);
     }
 
     {
         // f number
         camera_metadata_entry entry =
             results.find(ANDROID_LENS_APERTURE);
-        BAIL_IF_EMPTY(entry, env, TAG_FNUMBER);
+        BAIL_IF_EMPTY(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,
-                TIFF_IFD_0), env, TAG_FNUMBER);
+                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),
-                env, TAG_DNGVERSION);
+                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);
+                env, TAG_DNGBACKWARDVERSION, writer);
     }
 
     {
         // Set whitelevel
         camera_metadata_entry entry =
                 characteristics.find(ANDROID_SENSOR_INFO_WHITE_LEVEL);
-        BAIL_IF_EMPTY(entry, env, TAG_WHITELEVEL);
+        BAIL_IF_EMPTY(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);
+                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);
+                env, TAG_DEFAULTSCALE, writer);
     }
 
     bool singleIlluminant = false;
@@ -555,7 +1108,7 @@
         // Set calibration illuminants
         camera_metadata_entry entry1 =
             characteristics.find(ANDROID_SENSOR_REFERENCE_ILLUMINANT1);
-        BAIL_IF_EMPTY(entry1, env, TAG_CALIBRATIONILLUMINANT1);
+        BAIL_IF_EMPTY(entry1, env, TAG_CALIBRATIONILLUMINANT1, writer);
         camera_metadata_entry entry2 =
             characteristics.find(ANDROID_SENSOR_REFERENCE_ILLUMINANT2);
         if (entry2.count == 0) {
@@ -564,12 +1117,12 @@
         uint16_t ref1 = entry1.data.u8[0];
 
         BAIL_IF_INVALID(writer->addEntry(TAG_CALIBRATIONILLUMINANT1, 1, &ref1,
-                TIFF_IFD_0), env, TAG_CALIBRATIONILLUMINANT1);
+                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,
-                    TIFF_IFD_0), env, TAG_CALIBRATIONILLUMINANT2);
+                    TIFF_IFD_0), env, TAG_CALIBRATIONILLUMINANT2, writer);
         }
     }
 
@@ -577,7 +1130,7 @@
         // Set color transforms
         camera_metadata_entry entry1 =
             characteristics.find(ANDROID_SENSOR_COLOR_TRANSFORM1);
-        BAIL_IF_EMPTY(entry1, env, TAG_COLORMATRIX1);
+        BAIL_IF_EMPTY(entry1, env, TAG_COLORMATRIX1, writer);
 
         int32_t colorTransform1[entry1.count * 2];
 
@@ -587,12 +1140,12 @@
             colorTransform1[ctr++] = entry1.data.r[i].denominator;
         }
 
-        BAIL_IF_INVALID(writer->addEntry(TAG_COLORMATRIX1, entry1.count, colorTransform1, TIFF_IFD_0),
-                env, TAG_COLORMATRIX1);
+        BAIL_IF_INVALID(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);
+            BAIL_IF_EMPTY(entry2, env, TAG_COLORMATRIX2, writer);
             int32_t colorTransform2[entry2.count * 2];
 
             ctr = 0;
@@ -601,8 +1154,8 @@
                 colorTransform2[ctr++] = entry2.data.r[i].denominator;
             }
 
-            BAIL_IF_INVALID(writer->addEntry(TAG_COLORMATRIX2, entry2.count, colorTransform2, TIFF_IFD_0),
-                    env, TAG_COLORMATRIX2);
+            BAIL_IF_INVALID(writer->addEntry(TAG_COLORMATRIX2, entry2.count, colorTransform2,
+                    TIFF_IFD_0), env, TAG_COLORMATRIX2, writer);
         }
     }
 
@@ -610,7 +1163,7 @@
         // Set calibration transforms
         camera_metadata_entry entry1 =
             characteristics.find(ANDROID_SENSOR_CALIBRATION_TRANSFORM1);
-        BAIL_IF_EMPTY(entry1, env, TAG_CAMERACALIBRATION1);
+        BAIL_IF_EMPTY(entry1, env, TAG_CAMERACALIBRATION1, writer);
 
         int32_t calibrationTransform1[entry1.count * 2];
 
@@ -621,12 +1174,12 @@
         }
 
         BAIL_IF_INVALID(writer->addEntry(TAG_CAMERACALIBRATION1, entry1.count, calibrationTransform1,
-                TIFF_IFD_0), env, TAG_CAMERACALIBRATION1);
+                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);
+            BAIL_IF_EMPTY(entry2, env, TAG_CAMERACALIBRATION2, writer);
             int32_t calibrationTransform2[entry2.count * 2];
 
             ctr = 0;
@@ -636,7 +1189,7 @@
             }
 
             BAIL_IF_INVALID(writer->addEntry(TAG_CAMERACALIBRATION2, entry2.count, calibrationTransform1,
-                    TIFF_IFD_0),  env, TAG_CAMERACALIBRATION2);
+                    TIFF_IFD_0),  env, TAG_CAMERACALIBRATION2, writer);
         }
     }
 
@@ -644,7 +1197,7 @@
         // Set forward transforms
         camera_metadata_entry entry1 =
             characteristics.find(ANDROID_SENSOR_FORWARD_MATRIX1);
-        BAIL_IF_EMPTY(entry1, env, TAG_FORWARDMATRIX1);
+        BAIL_IF_EMPTY(entry1, env, TAG_FORWARDMATRIX1, writer);
 
         int32_t forwardTransform1[entry1.count * 2];
 
@@ -655,12 +1208,12 @@
         }
 
         BAIL_IF_INVALID(writer->addEntry(TAG_FORWARDMATRIX1, entry1.count, forwardTransform1,
-                TIFF_IFD_0), env, TAG_FORWARDMATRIX1);
+                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);
+            BAIL_IF_EMPTY(entry2, env, TAG_FORWARDMATRIX2, writer);
             int32_t forwardTransform2[entry2.count * 2];
 
             ctr = 0;
@@ -670,7 +1223,7 @@
             }
 
             BAIL_IF_INVALID(writer->addEntry(TAG_FORWARDMATRIX2, entry2.count, forwardTransform2,
-                    TIFF_IFD_0),  env, TAG_FORWARDMATRIX2);
+                    TIFF_IFD_0),  env, TAG_FORWARDMATRIX2, writer);
         }
     }
 
@@ -678,7 +1231,7 @@
         // Set camera neutral
         camera_metadata_entry entry =
             results.find(ANDROID_SENSOR_NEUTRAL_COLOR_POINT);
-        BAIL_IF_EMPTY(entry, env, TAG_ASSHOTNEUTRAL);
+        BAIL_IF_EMPTY(entry, env, TAG_ASSHOTNEUTRAL, writer);
         uint32_t cameraNeutral[entry.count * 2];
 
         size_t ctr = 0;
@@ -690,23 +1243,18 @@
         }
 
         BAIL_IF_INVALID(writer->addEntry(TAG_ASSHOTNEUTRAL, entry.count, cameraNeutral,
-                TIFF_IFD_0), env, TAG_ASSHOTNEUTRAL);
+                TIFF_IFD_0), env, TAG_ASSHOTNEUTRAL, writer);
     }
 
     {
         // Setup data strips
         // TODO: Switch to tiled implementation.
-        uint32_t offset = 0;
-        BAIL_IF_INVALID(writer->addEntry(TAG_STRIPOFFSETS, 1, &offset, TIFF_IFD_0), env,
-                TAG_STRIPOFFSETS);
-
-        BAIL_IF_INVALID(writer->addEntry(TAG_ROWSPERSTRIP, 1, &imageHeight, TIFF_IFD_0), env,
-                TAG_ROWSPERSTRIP);
-
-        uint32_t byteCount = imageWidth * imageHeight * bitsPerSample * samplesPerPixel /
-                bitsPerByte;
-        BAIL_IF_INVALID(writer->addEntry(TAG_STRIPBYTECOUNTS, 1, &byteCount, TIFF_IFD_0), env,
-                TAG_STRIPBYTECOUNTS);
+        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;
+        }
     }
 
     {
@@ -717,9 +1265,9 @@
             uint32_t defaultCropOrigin[] = {margin, margin};
             uint32_t defaultCropSize[] = {imageWidth - margin, imageHeight - margin};
             BAIL_IF_INVALID(writer->addEntry(TAG_DEFAULTCROPORIGIN, 2, defaultCropOrigin,
-                    TIFF_IFD_0), env, TAG_DEFAULTCROPORIGIN);
+                    TIFF_IFD_0), env, TAG_DEFAULTCROPORIGIN, writer);
             BAIL_IF_INVALID(writer->addEntry(TAG_DEFAULTCROPSIZE, 2, defaultCropSize,
-                    TIFF_IFD_0), env, TAG_DEFAULTCROPSIZE);
+                    TIFF_IFD_0), env, TAG_DEFAULTCROPSIZE, writer);
         }
     }
 
@@ -742,21 +1290,26 @@
 
         BAIL_IF_INVALID(writer->addEntry(TAG_UNIQUECAMERAMODEL, cameraModel.size() + 1,
                 reinterpret_cast<const uint8_t*>(cameraModel.string()), TIFF_IFD_0), env,
-                TAG_UNIQUECAMERAMODEL);
+                TAG_UNIQUECAMERAMODEL, writer);
     }
 
     {
         // Setup opcode List 2
         camera_metadata_entry entry1 =
                 characteristics.find(ANDROID_LENS_INFO_SHADING_MAP_SIZE);
-        BAIL_IF_EMPTY(entry1, env, TAG_OPCODELIST2);
-        uint32_t lsmWidth = static_cast<uint32_t>(entry1.data.i32[0]);
-        uint32_t lsmHeight = static_cast<uint32_t>(entry1.data.i32[1]);
+
+        uint32_t lsmWidth = 0;
+        uint32_t lsmHeight = 0;
+
+        if (entry1.count != 0) {
+            lsmWidth = static_cast<uint32_t>(entry1.data.i32[0]);
+            lsmHeight = static_cast<uint32_t>(entry1.data.i32[1]);
+        }
 
         camera_metadata_entry entry2 =
                 results.find(ANDROID_STATISTICS_LENS_SHADING_MAP);
-        BAIL_IF_EMPTY(entry2, env, TAG_OPCODELIST2);
-        if (entry2.count == lsmWidth * lsmHeight * 4) {
+
+        if (entry2.count > 0 && entry2.count == lsmWidth * lsmHeight * 4) {
 
             OpcodeListBuilder builder;
             status_t err = builder.addGainMapsForMetadata(lsmWidth,
@@ -773,7 +1326,7 @@
                 err = builder.buildOpList(opcodeListBuf);
                 if (err == OK) {
                     BAIL_IF_INVALID(writer->addEntry(TAG_OPCODELIST2, listSize, opcodeListBuf,
-                            TIFF_IFD_0), env, TAG_OPCODELIST2);
+                            TIFF_IFD_0), env, TAG_OPCODELIST2, writer);
                 } else {
                     ALOGE("%s: Could not build Lens shading map opcode.", __FUNCTION__);
                     jniThrowRuntimeException(env, "failed to construct lens shading map opcode.");
@@ -783,38 +1336,347 @@
                 jniThrowRuntimeException(env, "failed to add lens shading map.");
             }
         } else {
-            ALOGW("%s: Lens shading map not present in results, skipping...", __FUNCTION__);
+            ALOGW("%s: No lens shading map found in result metadata. Image quality may be reduced.",
+                    __FUNCTION__);
         }
     }
 
-    DngCreator_setCreator(env, thiz, writer);
+    DngCreator_setNativeContext(env, thiz, nativeContext);
 }
 
 static void DngCreator_destroy(JNIEnv* env, jobject thiz) {
     ALOGV("%s:", __FUNCTION__);
-    DngCreator_setCreator(env, thiz, NULL);
+    DngCreator_setNativeContext(env, thiz, NULL);
 }
 
-static void DngCreator_nativeSetOrientation(JNIEnv* env, jobject thiz) {
+static void DngCreator_nativeSetOrientation(JNIEnv* env, jobject thiz, jint orient) {
     ALOGV("%s:", __FUNCTION__);
-    jniThrowRuntimeException(env, "nativeSetOrientation is not implemented");
+
+    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_nativeSetThumbnailBitmap(JNIEnv* env, jobject thiz, jobject bitmap) {
+static void DngCreator_nativeSetDescription(JNIEnv* env, jobject thiz, jstring description) {
     ALOGV("%s:", __FUNCTION__);
-    jniThrowRuntimeException(env, "nativeSetThumbnailBitmap is not implemented");
+
+    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_nativeSetThumbnailImage(JNIEnv* env, jobject thiz, jint width, jint height,
-        jobject yBuffer, jint yRowStride, jint yPixStride, jobject uBuffer, jint uRowStride,
-        jint uPixStride, jobject vBuffer, jint vRowStride, jint vPixStride) {
+static void DngCreator_nativeSetGpsTags(JNIEnv* env, jobject thiz, jintArray latTag, jstring latRef,
+        jintArray longTag, jstring longRef, jstring dateTag, jintArray timeTag) {
     ALOGV("%s:", __FUNCTION__);
-    jniThrowRuntimeException(env, "nativeSetThumbnailImage is not implemented");
+
+    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;
+        }
+    }
+
+    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;
+    }
+
+    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 (capacity != 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;
+        }
+
+        Vector<uint16_t> tagsToMove;
+        tagsToMove.add(TAG_ORIENTATION);
+        tagsToMove.add(TAG_NEWSUBFILETYPE);
+        tagsToMove.add(TAG_BITSPERSAMPLE);
+        tagsToMove.add(TAG_COMPRESSION);
+        tagsToMove.add(TAG_IMAGEWIDTH);
+        tagsToMove.add(TAG_IMAGELENGTH);
+        tagsToMove.add(TAG_PHOTOMETRICINTERPRETATION);
+        tagsToMove.add(TAG_BLACKLEVEL);
+        tagsToMove.add(TAG_BLACKLEVELREPEATDIM);
+        tagsToMove.add(TAG_SAMPLESPERPIXEL);
+        tagsToMove.add(TAG_PLANARCONFIGURATION);
+        tagsToMove.add(TAG_CFAREPEATPATTERNDIM);
+        tagsToMove.add(TAG_CFAPATTERN);
+        tagsToMove.add(TAG_CFAPLANECOLOR);
+        tagsToMove.add(TAG_CFALAYOUT);
+        tagsToMove.add(TAG_XRESOLUTION);
+        tagsToMove.add(TAG_YRESOLUTION);
+        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);
+
+        if (moveEntries(writer, TIFF_IFD_0, TIFF_IFD_SUB1, tagsToMove) != OK) {
+            jniThrowException(env, "java/lang/IllegalStateException", "Failed to move entries");
+            return;
+        }
+
+        // Make sure both IFDs get the same orientation tag
+        sp<TiffEntry> orientEntry = writer->getEntry(TAG_ORIENTATION, TIFF_IFD_SUB1);
+        if (orientEntry != NULL) {
+            writer->addEntry(orientEntry, TIFF_IFD_0);
+        }
+    }
+
+    // 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;
+        }
+        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;
+        }
+    }
+
+    if (!context->setThumbnail(pixelBytes, width, height)) {
+        jniThrowException(env, "java/lang/IllegalStateException",
+                "Failed to set thumbnail.");
+        return;
+    }
+}
+
+// TODO: Refactor out common preamble for the two nativeWrite methods.
 static void DngCreator_nativeWriteImage(JNIEnv* env, jobject thiz, jobject outStream, jint width,
-        jint height, jobject inBuffer, jint rowStride, jint pixStride) {
+        jint height, jobject inBuffer, jint rowStride, jint pixStride, jlong offset,
+        jboolean isDirect) {
     ALOGV("%s:", __FUNCTION__);
+    ALOGV("%s: nativeWriteImage called with: width=%d, height=%d, rowStride=%d, pixStride=%d,"
+              " offset=%lld", __FUNCTION__, width, height, rowStride, pixStride, offset);
+    uint32_t rStride = static_cast<uint32_t>(rowStride);
+    uint32_t pStride = static_cast<uint32_t>(pixStride);
+    uint32_t uWidth = static_cast<uint32_t>(width);
+    uint32_t uHeight = static_cast<uint32_t>(height);
+    uint64_t uOffset = static_cast<uint64_t>(offset);
 
     sp<JniOutputStream> out = new JniOutputStream(env, outStream);
     if(env->ExceptionCheck()) {
@@ -822,99 +1684,157 @@
         return;
     }
 
-    uint8_t* pixelBytes = reinterpret_cast<uint8_t*>(env->GetDirectBufferAddress(inBuffer));
-    if (pixelBytes == NULL) {
-        ALOGE("%s: Could not get native byte buffer", __FUNCTION__);
-        jniThrowException(env, "java/lang/IllegalArgumentException", "Invalid bytebuffer");
-        return;
-    }
-
     TiffWriter* writer = DngCreator_getCreator(env, thiz);
-    if (writer == NULL) {
+    NativeContext* context = DngCreator_getNativeContext(env, thiz);
+    if (writer == NULL || context == NULL) {
         ALOGE("%s: Failed to initialize DngCreator", __FUNCTION__);
         jniThrowException(env, "java/lang/AssertionError",
                 "Write called with uninitialized DngCreator");
         return;
     }
-    // TODO: handle lens shading map, etc. conversions for other raw buffer sizes.
-    uint32_t metadataWidth = *(writer->getEntry(TAG_IMAGEWIDTH, TIFF_IFD_0)->getData<uint32_t>());
-    uint32_t metadataHeight = *(writer->getEntry(TAG_IMAGELENGTH, TIFF_IFD_0)->getData<uint32_t>());
-    if (metadataWidth != width) {
-        jniThrowExceptionFmt(env, "java/lang/IllegalStateException", \
-                        "Metadata width %d doesn't match image width %d", metadataWidth, width);
+
+    // Validate DNG header
+    if (!validateDngHeader(env, writer, width, height)) {
         return;
     }
 
-    if (metadataHeight != height) {
-        jniThrowExceptionFmt(env, "java/lang/IllegalStateException", \
-                        "Metadata height %d doesn't match image height %d", metadataHeight, height);
-        return;
+    sp<JniInputByteBuffer> inBuf;
+    Vector<StripSource*> sources;
+    sp<DirectStripSource> thumbnailSource;
+    uint32_t targetIfd = TIFF_IFD_0;
+
+    bool hasThumbnail = writer->hasIfd(TIFF_IFD_SUB1);
+
+    if (hasThumbnail) {
+        ALOGV("%s: Adding thumbnail strip sources.", __FUNCTION__);
+        uint32_t bytesPerPixel = SAMPLES_PER_RGB_PIXEL * BYTES_PER_RGB_SAMPLE;
+        uint32_t thumbWidth = context->getThumbnailWidth();
+        thumbnailSource = new DirectStripSource(env, context->getThumbnail(), TIFF_IFD_0,
+                thumbWidth, context->getThumbnailHeight(), bytesPerPixel,
+                bytesPerPixel * thumbWidth, /*offset*/0, BYTES_PER_RGB_SAMPLE,
+                SAMPLES_PER_RGB_PIXEL);
+        sources.add(thumbnailSource.get());
+        targetIfd = TIFF_IFD_SUB1;
     }
 
-    uint32_t stripOffset = writer->getTotalSize();
-
-    BAIL_IF_INVALID(writer->addEntry(TAG_STRIPOFFSETS, 1, &stripOffset, TIFF_IFD_0), env,
-                    TAG_STRIPOFFSETS);
-
-    if (writer->write(out.get()) != OK) {
-        if (!env->ExceptionCheck()) {
-            jniThrowException(env, "java/io/IOException", "Failed to write metadata");
+    if (isDirect) {
+        size_t fullSize = rStride * uHeight;
+        jlong capacity = env->GetDirectBufferCapacity(inBuffer);
+        if (capacity < 0 || fullSize + uOffset > static_cast<uint64_t>(capacity)) {
+            jniThrowExceptionFmt(env, "java/lang/IllegalStateException",
+                    "Invalid size %d for Image, size given in metadata is %d at current stride",
+                    capacity, fullSize);
+            return;
         }
-        return;
-    }
 
-    size_t fullSize = rowStride * height;
-    jlong capacity = env->GetDirectBufferCapacity(inBuffer);
-    if (capacity < 0 || fullSize > capacity) {
-        jniThrowExceptionFmt(env, "java/lang/IllegalStateException",
-                "Invalid size %d for Image, size given in metadata is %d at current stride",
-                capacity, fullSize);
-        return;
-    }
+        uint8_t* pixelBytes = reinterpret_cast<uint8_t*>(env->GetDirectBufferAddress(inBuffer));
+        if (pixelBytes == NULL) {
+            ALOGE("%s: Could not get native ByteBuffer", __FUNCTION__);
+            jniThrowException(env, "java/lang/IllegalArgumentException", "Invalid ByteBuffer");
+            return;
+        }
 
-    if (pixStride == BYTES_PER_SAMPLE && rowStride == width * BYTES_PER_SAMPLE) {
-        if (out->write(pixelBytes, 0, fullSize) != OK || env->ExceptionCheck()) {
+        ALOGV("%s: Using direct-type strip source.", __FUNCTION__);
+        DirectStripSource stripSource(env, pixelBytes, targetIfd, uWidth, uHeight, pStride,
+                rStride, uOffset, BYTES_PER_SAMPLE, SAMPLES_PER_RAW_PIXEL);
+        sources.add(&stripSource);
+
+        status_t ret = OK;
+        if ((ret = writer->write(out.get(), sources.editArray(), sources.size())) != OK) {
+            ALOGE("%s: write failed with error %d.", __FUNCTION__, ret);
             if (!env->ExceptionCheck()) {
-                jniThrowException(env, "java/io/IOException", "Failed to write pixel data");
+                jniThrowExceptionFmt(env, "java/io/IOException",
+                        "Encountered error %d while writing file.", ret);
             }
             return;
         }
-    } else if (pixStride == BYTES_PER_SAMPLE) {
-        for (size_t i = 0; i < height; ++i) {
-            if (out->write(pixelBytes, i * rowStride, pixStride * width) != OK ||
-                        env->ExceptionCheck()) {
-                if (!env->ExceptionCheck()) {
-                    jniThrowException(env, "java/io/IOException", "Failed to write pixel data");
-                }
-                return;
-            }
-        }
     } else {
-        for (size_t i = 0; i < height; ++i) {
-            for (size_t j = 0; j < width; ++j) {
-                if (out->write(pixelBytes, i * rowStride + j * pixStride,
-                        BYTES_PER_SAMPLE) != OK || !env->ExceptionCheck()) {
-                    if (env->ExceptionCheck()) {
-                        jniThrowException(env, "java/io/IOException", "Failed to write pixel data");
-                    }
-                    return;
-                }
+        inBuf = new JniInputByteBuffer(env, inBuffer);
+
+        ALOGV("%s: Using input-type strip source.", __FUNCTION__);
+        InputStripSource stripSource(env, *inBuf, targetIfd, uWidth, uHeight, pStride,
+                 rStride, uOffset, BYTES_PER_SAMPLE, SAMPLES_PER_RAW_PIXEL);
+        sources.add(&stripSource);
+
+        status_t ret = OK;
+        if ((ret = writer->write(out.get(), sources.editArray(), sources.size())) != OK) {
+            ALOGE("%s: write failed with error %d.", __FUNCTION__, ret);
+            if (!env->ExceptionCheck()) {
+                jniThrowExceptionFmt(env, "java/io/IOException",
+                        "Encountered error %d while writing file.", ret);
             }
+            return;
         }
     }
-
-}
-
-static void DngCreator_nativeWriteByteBuffer(JNIEnv* env, jobject thiz, jobject outStream,
-        jobject rawBuffer, jlong offset) {
-    ALOGV("%s:", __FUNCTION__);
-    jniThrowRuntimeException(env, "nativeWriteByteBuffer is not implemented.");
 }
 
 static void DngCreator_nativeWriteInputStream(JNIEnv* env, jobject thiz, jobject outStream,
-        jobject inStream, jlong offset) {
+        jobject inStream, jint width, jint height, jlong offset) {
     ALOGV("%s:", __FUNCTION__);
-    jniThrowRuntimeException(env, "nativeWriteInputStream is not implemented.");
+
+    uint32_t rowStride = width * BYTES_PER_SAMPLE;
+    uint32_t pixStride = BYTES_PER_SAMPLE;
+    uint32_t uWidth = static_cast<uint32_t>(width);
+    uint32_t uHeight = static_cast<uint32_t>(height);
+    uint64_t uOffset = static_cast<uint32_t>(offset);
+
+    ALOGV("%s: nativeWriteInputStream called with: width=%d, height=%d, rowStride=%u,"
+              "pixStride=%u, offset=%lld", __FUNCTION__, width, height, rowStride, pixStride,
+              offset);
+
+    sp<JniOutputStream> out = new JniOutputStream(env, outStream);
+    if(env->ExceptionCheck()) {
+        ALOGE("%s: Could not allocate buffers for output stream", __FUNCTION__);
+        return;
+    }
+
+    TiffWriter* writer = DngCreator_getCreator(env, thiz);
+    NativeContext* context = DngCreator_getNativeContext(env, thiz);
+    if (writer == NULL || context == NULL) {
+        ALOGE("%s: Failed to initialize DngCreator", __FUNCTION__);
+        jniThrowException(env, "java/lang/AssertionError",
+                "Write called with uninitialized DngCreator");
+        return;
+    }
+
+    // Validate DNG header
+    if (!validateDngHeader(env, writer, width, height)) {
+        return;
+    }
+
+    sp<DirectStripSource> thumbnailSource;
+    uint32_t targetIfd = TIFF_IFD_0;
+    bool hasThumbnail = writer->hasIfd(TIFF_IFD_SUB1);
+    Vector<StripSource*> sources;
+
+    if (hasThumbnail) {
+        ALOGV("%s: Adding thumbnail strip sources.", __FUNCTION__);
+        uint32_t bytesPerPixel = SAMPLES_PER_RGB_PIXEL * BYTES_PER_RGB_SAMPLE;
+        uint32_t width = context->getThumbnailWidth();
+        thumbnailSource = new DirectStripSource(env, context->getThumbnail(), TIFF_IFD_0,
+                width, context->getThumbnailHeight(), bytesPerPixel,
+                bytesPerPixel * width, /*offset*/0, BYTES_PER_RGB_SAMPLE,
+                SAMPLES_PER_RGB_PIXEL);
+        sources.add(thumbnailSource.get());
+        targetIfd = TIFF_IFD_SUB1;
+    }
+
+    sp<JniInputStream> in = new JniInputStream(env, inStream);
+
+    ALOGV("%s: Using input-type strip source.", __FUNCTION__);
+    InputStripSource stripSource(env, *in, targetIfd, uWidth, uHeight, pixStride,
+             rowStride, uOffset, BYTES_PER_SAMPLE, SAMPLES_PER_RAW_PIXEL);
+    sources.add(&stripSource);
+
+    status_t ret = OK;
+    if ((ret = writer->write(out.get(), sources.editArray(), sources.size())) != OK) {
+        ALOGE("%s: write failed with error %d.", __FUNCTION__, ret);
+        if (!env->ExceptionCheck()) {
+            jniThrowExceptionFmt(env, "java/io/IOException",
+                    "Encountered error %d while writing file.", ret);
+        }
+        return;
+    }
 }
 
 } /*extern "C" */
@@ -926,16 +1846,13 @@
             (void*) DngCreator_init},
     {"nativeDestroy",           "()V",      (void*) DngCreator_destroy},
     {"nativeSetOrientation",    "(I)V",     (void*) DngCreator_nativeSetOrientation},
-    {"nativeSetThumbnailBitmap","(Landroid/graphics/Bitmap;)V",
-            (void*) DngCreator_nativeSetThumbnailBitmap},
-    {"nativeSetThumbnailImage",
-            "(IILjava/nio/ByteBuffer;IILjava/nio/ByteBuffer;IILjava/nio/ByteBuffer;II)V",
-            (void*) DngCreator_nativeSetThumbnailImage},
-    {"nativeWriteImage",        "(Ljava/io/OutputStream;IILjava/nio/ByteBuffer;II)V",
+    {"nativeSetDescription",    "(Ljava/lang/String;)V", (void*) DngCreator_nativeSetDescription},
+    {"nativeSetGpsTags",    "([ILjava/lang/String;[ILjava/lang/String;Ljava/lang/String;[I)V",
+            (void*) DngCreator_nativeSetGpsTags},
+    {"nativeSetThumbnail","(Ljava/nio/ByteBuffer;II)V", (void*) DngCreator_nativeSetThumbnail},
+    {"nativeWriteImage",        "(Ljava/io/OutputStream;IILjava/nio/ByteBuffer;IIJZ)V",
             (void*) DngCreator_nativeWriteImage},
-    {"nativeWriteByteBuffer",    "(Ljava/io/OutputStream;Ljava/nio/ByteBuffer;J)V",
-            (void*) DngCreator_nativeWriteByteBuffer},
-    {"nativeWriteInputStream",    "(Ljava/io/OutputStream;Ljava/io/InputStream;J)V",
+    {"nativeWriteInputStream",    "(Ljava/io/OutputStream;Ljava/io/InputStream;IIJ)V",
             (void*) DngCreator_nativeWriteInputStream},
 };
 
diff --git a/core/jni/android_hardware_camera2_legacy_PerfMeasurement.cpp b/core/jni/android_hardware_camera2_legacy_PerfMeasurement.cpp
new file mode 100644
index 0000000..93473a5
--- /dev/null
+++ b/core/jni/android_hardware_camera2_legacy_PerfMeasurement.cpp
@@ -0,0 +1,335 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "Camera2-Legacy-PerfMeasurement-JNI"
+#include <utils/Log.h>
+#include <utils/Errors.h>
+#include <utils/Trace.h>
+#include <utils/Vector.h>
+
+#include "jni.h"
+#include "JNIHelp.h"
+#include "android_runtime/AndroidRuntime.h"
+
+#include <ui/GraphicBuffer.h>
+#include <system/window.h>
+#include <GLES2/gl2.h>
+#include <GLES2/gl2ext.h>
+
+using namespace android;
+
+// fully-qualified class name
+#define PERF_MEASUREMENT_CLASS_NAME "android/hardware/camera2/legacy/PerfMeasurement"
+
+/** GL utility methods copied from com_google_android_gles_jni_GLImpl.cpp */
+
+// Check if the extension at the head of pExtensions is pExtension. Note that pExtensions is
+// terminated by either 0 or space, while pExtension is terminated by 0.
+
+static bool
+extensionEqual(const GLubyte* pExtensions, const GLubyte* pExtension) {
+    while (true) {
+        char a = *pExtensions++;
+        char b = *pExtension++;
+        bool aEnd = a == '\0' || a == ' ';
+        bool bEnd = b == '\0';
+        if (aEnd || bEnd) {
+            return aEnd == bEnd;
+        }
+        if (a != b) {
+            return false;
+        }
+    }
+}
+
+static const GLubyte*
+nextExtension(const GLubyte* pExtensions) {
+    while (true) {
+        char a = *pExtensions++;
+        if (a == '\0') {
+            return pExtensions-1;
+        } else if ( a == ' ') {
+            return pExtensions;
+        }
+    }
+}
+
+static bool
+checkForExtension(const GLubyte* pExtensions, const GLubyte* pExtension) {
+    for (; *pExtensions != '\0'; pExtensions = nextExtension(pExtensions)) {
+        if (extensionEqual(pExtensions, pExtension)) {
+            return true;
+        }
+    }
+    return false;
+}
+
+/** End copied GL utility methods */
+
+bool checkGlError(JNIEnv* env) {
+    int error;
+    if ((error = glGetError()) != GL_NO_ERROR) {
+        jniThrowExceptionFmt(env, "java/lang/IllegalStateException",
+                "GLES20 error: 0x%d", error);
+        return true;
+    }
+    return false;
+}
+
+/**
+ * Asynchronous low-overhead GL performance measurement using
+ * http://www.khronos.org/registry/gles/extensions/EXT/EXT_disjoint_timer_query.txt
+ *
+ * Measures the duration of GPU processing for a set of GL commands, delivering
+ * the measurement asynchronously once processing completes.
+ *
+ * All calls must come from a single thread with a valid GL context active.
+ **/
+class PerfMeasurementContext {
+  private:
+    Vector<GLuint> mTimingQueries;
+    size_t mTimingStartIndex;
+    size_t mTimingEndIndex;
+    size_t mTimingQueryIndex;
+    size_t mFreeQueries;
+
+    bool mInitDone;
+  public:
+
+    /**
+     * maxQueryCount should be a conservative estimate of how many query objects
+     * will be active at once, which is a function of the GPU's level of
+     * pipelining and the frequency of queries.
+     */
+    PerfMeasurementContext(size_t maxQueryCount):
+            mTimingStartIndex(0),
+            mTimingEndIndex(0),
+            mTimingQueryIndex(0) {
+        mTimingQueries.resize(maxQueryCount);
+        mFreeQueries = maxQueryCount;
+        mInitDone = false;
+    }
+
+    int getMaxQueryCount() {
+        return mTimingQueries.size();
+    }
+
+    /**
+     * Start a measurement period using the next available query object.
+     * Returns INVALID_OPERATION if called multiple times in a row,
+     * and BAD_VALUE if no more query objects are available.
+     */
+    int startGlTimer() {
+        // Lazy init of queries to avoid needing GL context during construction
+        if (!mInitDone) {
+            glGenQueriesEXT(mTimingQueries.size(), mTimingQueries.editArray());
+            mInitDone = true;
+        }
+
+        if (mTimingEndIndex != mTimingStartIndex) {
+            return INVALID_OPERATION;
+        }
+
+        if (mFreeQueries == 0) {
+            return BAD_VALUE;
+        }
+
+        glBeginQueryEXT(GL_TIME_ELAPSED_EXT, mTimingQueries[mTimingStartIndex]);
+
+        mTimingStartIndex = (mTimingStartIndex + 1) % mTimingQueries.size();
+        mFreeQueries--;
+
+        return OK;
+    }
+
+    /**
+     * Finish the current measurement period
+     * Returns INVALID_OPERATION if called before any startGLTimer calls
+     * or if called multiple times in a row.
+     */
+    int stopGlTimer() {
+        size_t nextEndIndex = (mTimingEndIndex + 1) % mTimingQueries.size();
+        if (nextEndIndex != mTimingStartIndex) {
+            return INVALID_OPERATION;
+        }
+        glEndQueryEXT(GL_TIME_ELAPSED_EXT);
+
+        mTimingEndIndex = nextEndIndex;
+
+        return OK;
+    }
+
+    static const nsecs_t NO_DURATION_YET = -1L;
+    static const nsecs_t FAILED_MEASUREMENT = -2L;
+
+    /**
+     * Get the next available duration measurement.
+     *
+     * Returns NO_DURATION_YET if no new measurement is available,
+     * and FAILED_MEASUREMENT if an error occurred during the next
+     * measurement period.
+     *
+     * Otherwise returns a positive number of nanoseconds measuring the
+     * duration of the oldest completed query.
+     */
+    nsecs_t getNextGlDuration() {
+        if (!mInitDone) {
+            // No start/stop called yet
+            return NO_DURATION_YET;
+        }
+
+        GLint available;
+        glGetQueryObjectivEXT(mTimingQueries[mTimingQueryIndex],
+                GL_QUERY_RESULT_AVAILABLE_EXT, &available);
+        if (!available) {
+            return NO_DURATION_YET;
+        }
+
+        GLint64 duration = FAILED_MEASUREMENT;
+        GLint disjointOccurred;
+        glGetIntegerv(GL_GPU_DISJOINT_EXT, &disjointOccurred);
+
+        if (!disjointOccurred) {
+            glGetQueryObjecti64vEXT(mTimingQueries[mTimingQueryIndex],
+                    GL_QUERY_RESULT_EXT,
+                    &duration);
+        }
+
+        mTimingQueryIndex = (mTimingQueryIndex + 1) % mTimingQueries.size();
+        mFreeQueries++;
+
+        return static_cast<nsecs_t>(duration);
+    }
+
+    static bool isMeasurementSupported() {
+        const GLubyte* extensions = glGetString(GL_EXTENSIONS);
+        return checkForExtension(extensions,
+                reinterpret_cast<const GLubyte*>("GL_EXT_disjoint_timer_query"));
+    }
+
+};
+
+PerfMeasurementContext* getContext(jlong context) {
+    return reinterpret_cast<PerfMeasurementContext*>(context);
+}
+
+extern "C" {
+
+static jlong PerfMeasurement_nativeCreateContext(JNIEnv* env, jobject thiz,
+        jint maxQueryCount) {
+    PerfMeasurementContext *context = new PerfMeasurementContext(maxQueryCount);
+    return reinterpret_cast<jlong>(context);
+}
+
+static void PerfMeasurement_nativeDeleteContext(JNIEnv* env, jobject thiz,
+        jlong contextHandle) {
+    PerfMeasurementContext *context = getContext(contextHandle);
+    delete(context);
+}
+
+static jboolean PerfMeasurement_nativeQuerySupport(JNIEnv* env, jobject thiz) {
+    bool supported = PerfMeasurementContext::isMeasurementSupported();
+    checkGlError(env);
+    return static_cast<jboolean>(supported);
+}
+
+static void PerfMeasurement_nativeStartGlTimer(JNIEnv* env, jobject thiz,
+        jlong contextHandle) {
+
+    PerfMeasurementContext *context = getContext(contextHandle);
+    status_t err = context->startGlTimer();
+    if (err != OK) {
+        switch (err) {
+            case INVALID_OPERATION:
+                jniThrowExceptionFmt(env, "java/lang/IllegalStateException",
+                        "Mismatched start/end GL timing calls");
+                return;
+            case BAD_VALUE:
+                jniThrowExceptionFmt(env, "java/lang/IllegalStateException",
+                        "Too many timing queries in progress, max %d",
+                        context->getMaxQueryCount());
+                return;
+            default:
+                jniThrowExceptionFmt(env, "java/lang/IllegalStateException",
+                        "Unknown error starting GL timing");
+                return;
+        }
+    }
+    checkGlError(env);
+}
+
+static void PerfMeasurement_nativeStopGlTimer(JNIEnv* env, jobject thiz,
+            jlong contextHandle) {
+
+    PerfMeasurementContext *context = getContext(contextHandle);
+    status_t err = context->stopGlTimer();
+    if (err != OK) {
+        switch (err) {
+            case INVALID_OPERATION:
+                jniThrowExceptionFmt(env, "java/lang/IllegalStateException",
+                        "Mismatched start/end GL timing calls");
+                return;
+            default:
+                jniThrowExceptionFmt(env, "java/lang/IllegalStateException",
+                        "Unknown error ending GL timing");
+                return;
+        }
+    }
+    checkGlError(env);
+}
+
+static jlong PerfMeasurement_nativeGetNextGlDuration(JNIEnv* env,
+        jobject thiz, jlong contextHandle) {
+    PerfMeasurementContext *context = getContext(contextHandle);
+    nsecs_t duration = context->getNextGlDuration();
+
+    checkGlError(env);
+    return static_cast<jlong>(duration);
+}
+
+} // extern "C"
+
+static JNINativeMethod gPerfMeasurementMethods[] = {
+    { "nativeCreateContext",
+      "(I)J",
+      (jlong *)PerfMeasurement_nativeCreateContext },
+    { "nativeDeleteContext",
+      "(J)V",
+      (void *)PerfMeasurement_nativeDeleteContext },
+    { "nativeQuerySupport",
+      "()Z",
+      (jboolean *)PerfMeasurement_nativeQuerySupport },
+    { "nativeStartGlTimer",
+      "(J)V",
+      (void *)PerfMeasurement_nativeStartGlTimer },
+    { "nativeStopGlTimer",
+      "(J)V",
+      (void *)PerfMeasurement_nativeStopGlTimer },
+    { "nativeGetNextGlDuration",
+      "(J)J",
+      (jlong *)PerfMeasurement_nativeGetNextGlDuration }
+};
+
+
+// Get all the required offsets in java class and register native functions
+int register_android_hardware_camera2_legacy_PerfMeasurement(JNIEnv* env)
+{
+    // Register native functions
+    return AndroidRuntime::registerNativeMethods(env,
+            PERF_MEASUREMENT_CLASS_NAME,
+            gPerfMeasurementMethods,
+            NELEM(gPerfMeasurementMethods));
+}
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 53eb9d6..b4a4098 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -730,6 +730,9 @@
         <attr name="actionBarTabTextStyle" format="reference" />
         <attr name="actionOverflowButtonStyle" format="reference" />
         <attr name="actionOverflowMenuStyle" format="reference" />
+        <!-- Reference to a theme that should be used to inflate popups
+             shown by widgets in the action bar. -->
+        <attr name="actionBarPopupTheme" format="reference" />
         <!-- Reference to a style for the Action Bar -->
         <attr name="actionBarStyle" format="reference" />
         <!-- Reference to a style for the split Action Bar. This style
@@ -6780,10 +6783,20 @@
         <attr name="itemPadding" format="dimension" />
         <!-- Set true to hide the action bar on a vertical nested scroll of content. -->
         <attr name="hideOnContentScroll" format="boolean" />
+        <!-- Minimum inset for content views within a bar. Navigation buttons and
+             menu views are excepted. Only valid for some themes and configurations. -->
         <attr name="contentInsetStart" format="dimension" />
+        <!-- Minimum inset for content views within a bar. Navigation buttons and
+             menu views are excepted. Only valid for some themes and configurations. -->
         <attr name="contentInsetEnd" format="dimension" />
+        <!-- Minimum inset for content views within a bar. Navigation buttons and
+             menu views are excepted. Only valid for some themes and configurations. -->
         <attr name="contentInsetLeft" format="dimension" />
+        <!-- Minimum inset for content views within a bar. Navigation buttons and
+             menu views are excepted. Only valid for some themes and configurations. -->
         <attr name="contentInsetRight" format="dimension" />
+        <!-- Elevation for the action bar itself -->
+        <attr name="elevation" />
     </declare-styleable>
 
     <declare-styleable name="ActionMode">
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 2dd67ba..ca0dc1a 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2232,6 +2232,7 @@
   <public type="attr" name="buttonBarNeutralButtonStyle" />
   <public type="attr" name="buttonBarNegativeButtonStyle" />
   <public type="attr" name="popupElevation" />
+  <public type="attr" name="actionBarPopupTheme" />
 
   <public-padding type="dimen" name="l_resource_pad" end="0x01050010" />
 
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index e8bd99a..7c60c6e 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -2152,8 +2152,8 @@
     <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permdesc_accessDrmCertificates">Allows an application to provision and use DRM certficates. Should never be needed for normal apps.</string>
 
-    <string name="permlab_handoverStatus">Receive handover transfer broadcasts.</string>
-    <string name="permdesc_handoverStatus">Allows receiving handover transfer status information.</string>
+    <string name="permlab_handoverStatus">Receive Android Beam transfer status</string>
+    <string name="permdesc_handoverStatus">Allows this application to receive information about current Android Beam transfers</string>
 
     <!-- Policy administration -->
 
diff --git a/core/res/res/values/styles_material.xml b/core/res/res/values/styles_material.xml
index 3880fb3..4623258 100644
--- a/core/res/res/values/styles_material.xml
+++ b/core/res/res/values/styles_material.xml
@@ -804,6 +804,7 @@
         <item name="homeLayout">@layout/action_bar_home_material</item>
         <item name="gravity">center_vertical</item>
         <item name="contentInsetStart">16dp</item>
+        <item name="elevation">8dp</item>
     </style>
 
     <style name="Widget.Material.ActionBar.Solid">
diff --git a/core/res/res/values/themes.xml b/core/res/res/values/themes.xml
index b63a8c1..d61253f 100644
--- a/core/res/res/values/themes.xml
+++ b/core/res/res/values/themes.xml
@@ -362,6 +362,7 @@
         <item name="actionMenuTextAppearance">@style/TextAppearance.Holo.Widget.ActionBar.Menu</item>
         <item name="actionMenuTextColor">?attr/textColorPrimary</item>
         <item name="actionBarWidgetTheme">@null</item>
+        <item name="actionBarPopupTheme">@null</item>
         <item name="actionBarTheme">@null</item>
         <item name="actionBarDivider">?attr/dividerVertical</item>
         <item name="actionBarItemBackground">?attr/selectableItemBackground</item>
diff --git a/core/res/res/values/themes_holo.xml b/core/res/res/values/themes_holo.xml
index 78405e3..a9150c6 100644
--- a/core/res/res/values/themes_holo.xml
+++ b/core/res/res/values/themes_holo.xml
@@ -341,6 +341,7 @@
         <item name="actionBarSize">@dimen/action_bar_default_height</item>
         <item name="actionModePopupWindowStyle">@style/Widget.Holo.PopupWindow.ActionMode</item>
         <item name="actionBarWidgetTheme">@null</item>
+        <item name="actionBarPopupTheme">@null</item>
         <item name="actionBarTheme">@null</item>
 
         <item name="actionModeCutDrawable">@drawable/ic_menu_cut_holo_dark</item>
diff --git a/core/res/res/values/themes_material.xml b/core/res/res/values/themes_material.xml
index 472177f..47f3778 100644
--- a/core/res/res/values/themes_material.xml
+++ b/core/res/res/values/themes_material.xml
@@ -315,6 +315,7 @@
         <item name="actionBarSize">@dimen/action_bar_default_height_material</item>
         <item name="actionModePopupWindowStyle">@style/Widget.Material.PopupWindow.ActionMode</item>
         <item name="actionBarWidgetTheme">@null</item>
+        <item name="actionBarPopupTheme">@null</item>
         <item name="actionBarTheme">@style/ThemeOverlay.Material.ActionBar</item>
         <item name="actionBarItemBackground">?attr/selectableItemBackgroundBorderless</item>
 
@@ -498,7 +499,7 @@
         <item name="windowFullscreen">false</item>
         <item name="windowOverscan">false</item>
         <item name="windowIsFloating">false</item>
-        <item name="windowContentOverlay">@drawable/ab_solid_shadow_material</item>
+        <item name="windowContentOverlay">@null</item>
         <item name="windowShowWallpaper">false</item>
         <item name="windowTitleStyle">@style/WindowTitle.Material</item>
         <item name="windowTitleSize">@dimen/action_bar_default_height_material</item>
@@ -653,6 +654,7 @@
         <item name="actionButtonStyle">@style/Widget.Material.Light.ActionButton</item>
         <item name="actionOverflowButtonStyle">@style/Widget.Material.Light.ActionButton.Overflow</item>
         <item name="actionOverflowMenuStyle">@style/Widget.Material.Light.PopupMenu.Overflow</item>
+        <item name="actionBarPopupTheme">@null</item>
         <item name="actionModeBackground">@drawable/cab_background_top_holo_light</item>
         <item name="actionModeSplitBackground">@drawable/cab_background_bottom_holo_light</item>
         <item name="actionModeCloseDrawable">@drawable/ic_cab_done_material</item>
@@ -737,6 +739,7 @@
     <style name="Theme.Material.Light.DarkActionBar">
         <item name="actionBarWidgetTheme">@null</item>
         <item name="actionBarTheme">@style/ThemeOverlay.Material.Dark.ActionBar</item>
+        <item name="actionBarPopupTheme">@style/ThemeOverlay.Material.Light</item>
 
         <item name="colorPrimaryDark">@color/material_blue_grey_900</item>
         <item name="colorPrimary">@color/material_blue_grey_800</item>
@@ -778,6 +781,7 @@
         <item name="fastScrollPreviewBackgroundLeft">@drawable/fastscroll_label_left_holo_light</item>
         <item name="fastScrollPreviewBackgroundRight">@drawable/fastscroll_label_right_holo_light</item>
 
+        <item name="colorControlNormal">?attr/textColorSecondary</item>
         <item name="colorControlHighlight">@color/ripple_material_light</item>
         <item name="colorButtonNormal">@color/btn_default_material_light</item>
     </style>
@@ -814,6 +818,7 @@
         <item name="fastScrollPreviewBackgroundLeft">@drawable/fastscroll_label_left_holo_dark</item>
         <item name="fastScrollPreviewBackgroundRight">@drawable/fastscroll_label_right_holo_dark</item>
 
+        <item name="colorControlNormal">?attr/textColorSecondary</item>
         <item name="colorControlHighlight">@color/ripple_material_dark</item>
         <item name="colorButtonNormal">@color/btn_default_material_dark</item>
     </style>
diff --git a/data/fonts/fallback_fonts.xml b/data/fonts/fallback_fonts.xml
index a7e159a..0f0281b 100644
--- a/data/fonts/fallback_fonts.xml
+++ b/data/fonts/fallback_fonts.xml
@@ -234,12 +234,12 @@
     </family>
     <family>
         <fileset>
-            <file lang="zh-CN">NotoSansHans-Regular.otf</file>
+            <file lang="zh-Hans">NotoSansHans-Regular.otf</file>
         </fileset>
     </family>
     <family>
         <fileset>
-            <file lang="zh-TW">NotoSansHant-Regular.otf</file>
+            <file lang="zh-Hant">NotoSansHant-Regular.otf</file>
         </fileset>
     </family>
     <family>
diff --git a/graphics/java/android/graphics/drawable/Drawable.java b/graphics/java/android/graphics/drawable/Drawable.java
index b050a02..e7a073c 100644
--- a/graphics/java/android/graphics/drawable/Drawable.java
+++ b/graphics/java/android/graphics/drawable/Drawable.java
@@ -1019,9 +1019,6 @@
             drawable = new StateListDrawable();
         } else if (name.equals("animated-selector")) {
             drawable = new AnimatedStateListDrawable();
-        } else if (name.equals("material-progress")) {
-            // TODO: Replace this with something less ridiculous.
-            drawable = new MaterialProgressDrawable();
         } else if (name.equals("level-list")) {
             drawable = new LevelListDrawable();
         } else if (name.equals("layer-list")) {
diff --git a/graphics/java/android/graphics/drawable/MaterialProgressDrawable.java b/graphics/java/android/graphics/drawable/MaterialProgressDrawable.java
deleted file mode 100644
index c484094..0000000
--- a/graphics/java/android/graphics/drawable/MaterialProgressDrawable.java
+++ /dev/null
@@ -1,548 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.graphics.drawable;
-
-import android.animation.Animator;
-import android.animation.ObjectAnimator;
-import android.animation.TimeInterpolator;
-import android.content.res.ColorStateList;
-import android.content.res.Resources;
-import android.content.res.Resources.Theme;
-import android.content.res.TypedArray;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.ColorFilter;
-import android.graphics.Paint;
-import android.graphics.PorterDuffColorFilter;
-import android.graphics.Paint.Cap;
-import android.graphics.Paint.Style;
-import android.graphics.PorterDuff.Mode;
-import android.graphics.PixelFormat;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.util.AttributeSet;
-import android.view.animation.AccelerateDecelerateInterpolator;
-import android.view.animation.LinearInterpolator;
-
-import com.android.internal.R;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
-import java.io.IOException;
-import java.util.ArrayList;
-
-/**
- * Fancy progress indicator for Material theme.
- *
- * TODO: Replace this class with something less ridiculous.
- */
-class MaterialProgressDrawable extends Drawable implements Animatable {
-    private static final TimeInterpolator LINEAR_INTERPOLATOR = new LinearInterpolator();
-    private static final TimeInterpolator END_CURVE_INTERPOLATOR = new EndCurveInterpolator();
-    private static final TimeInterpolator START_CURVE_INTERPOLATOR = new StartCurveInterpolator();
-
-    /** The duration of a single progress spin in milliseconds. */
-    private static final int ANIMATION_DURATION = 1000 * 80 / 60;
-
-    /** The number of points in the progress "star". */
-    private static final int NUM_POINTS = 5;
-
-    /** The list of animators operating on this drawable. */
-    private final ArrayList<Animator> mAnimators = new ArrayList<Animator>();
-
-    /** The indicator ring, used to manage animation state. */
-    private final Ring mRing;
-
-    private MaterialProgressState mState;
-    private PorterDuffColorFilter mTintFilter;
-
-    /** Canvas rotation in degrees. */
-    private float mRotation;
-
-    private boolean mMutated;
-
-    public MaterialProgressDrawable() {
-        this(new MaterialProgressState(null), null);
-    }
-
-    private MaterialProgressDrawable(MaterialProgressState state, Theme theme) {
-        mState = state;
-        if (theme != null && state.canApplyTheme()) {
-            applyTheme(theme);
-        }
-
-        mRing = new Ring(mCallback);
-        mMutated = false;
-
-        initializeFromState();
-        setupAnimators();
-    }
-
-    private void initializeFromState() {
-        final MaterialProgressState state = mState;
-
-        final Ring ring = mRing;
-        ring.setStrokeWidth(state.mStrokeWidth);
-
-        final int color = state.mColor.getColorForState(getState(), Color.TRANSPARENT);
-        ring.setColor(color);
-
-        final float minEdge = Math.min(state.mWidth, state.mHeight);
-        if (state.mInnerRadius <= 0 || minEdge < 0) {
-            ring.setInsets((int) Math.ceil(state.mStrokeWidth / 2.0f));
-        } else {
-            float insets = minEdge / 2.0f - state.mInnerRadius;
-            ring.setInsets(insets);
-        }
-
-        mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode);
-    }
-
-    @Override
-    public Drawable mutate() {
-        if (!mMutated && super.mutate() == this) {
-            mState = new MaterialProgressState(mState);
-            mMutated = true;
-        }
-        return this;
-    }
-
-    @Override
-    protected boolean onStateChange(int[] stateSet) {
-        boolean changed = super.onStateChange(stateSet);
-
-        final MaterialProgressState state = mState;
-        final int color = state.mColor.getColorForState(stateSet, Color.TRANSPARENT);
-        if (color != mRing.getColor()) {
-            mRing.setColor(color);
-            changed = true;
-        }
-
-        if (state.mTint != null && state.mTintMode != null) {
-            mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode);
-            changed = true;
-        }
-
-        return changed;
-    }
-
-    @Override
-    public boolean isStateful() {
-        return super.isStateful() || mState.mColor.isStateful();
-    }
-
-    @Override
-    public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme)
-            throws XmlPullParserException, IOException {
-        final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.MaterialProgressDrawable);
-        super.inflateWithAttributes(r, parser, a, R.styleable.MaterialProgressDrawable_visible);
-        updateStateFromTypedArray(a);
-        a.recycle();
-
-        initializeFromState();
-    }
-
-    @Override
-    public void applyTheme(Theme t) {
-        final TypedArray a = t.resolveAttributes(mState.mThemeAttrs,
-                R.styleable.MaterialProgressDrawable);
-        updateStateFromTypedArray(a);
-        a.recycle();
-    }
-
-    private void updateStateFromTypedArray(TypedArray a) {
-        final MaterialProgressState state = mState;
-        state.mThemeAttrs = a.extractThemeAttrs();
-        state.mWidth = a.getDimensionPixelSize(
-                R.styleable.MaterialProgressDrawable_width, state.mWidth);
-        state.mHeight = a.getDimensionPixelSize(
-                R.styleable.MaterialProgressDrawable_height, state.mHeight);
-        state.mInnerRadius = a.getDimension(
-                R.styleable.MaterialProgressDrawable_innerRadius, state.mInnerRadius);
-        state.mStrokeWidth = a.getDimension(
-                R.styleable.MaterialProgressDrawable_thickness, state.mStrokeWidth);
-
-        if (a.hasValue(R.styleable.MaterialProgressDrawable_color)) {
-            state.mColor = a.getColorStateList(R.styleable.MaterialProgressDrawable_color);
-        }
-    }
-
-    @Override
-    public boolean setVisible(boolean visible, boolean restart) {
-        boolean changed = super.setVisible(visible, restart);
-        if (visible) {
-            if (changed || restart) {
-                start();
-            }
-        } else {
-            stop();
-        }
-        return changed;
-    }
-
-    @Override
-    public int getIntrinsicHeight() {
-        return mState.mHeight;
-    }
-
-    @Override
-    public int getIntrinsicWidth() {
-        return mState.mWidth;
-    }
-
-    @Override
-    public void draw(Canvas c) {
-        final Rect bounds = getBounds();
-        final int saveCount = c.save();
-        c.rotate(mRotation, bounds.exactCenterX(), bounds.exactCenterY());
-        mRing.draw(c, bounds);
-        c.restoreToCount(saveCount);
-    }
-
-    @Override
-    public void setAlpha(int alpha) {
-        mRing.setAlpha(alpha);
-    }
-
-    @Override
-    public int getAlpha() {
-        return mRing.getAlpha();
-    }
-
-    @Override
-    public void setColorFilter(ColorFilter colorFilter) {
-        mRing.setColorFilter(colorFilter);
-    }
-
-    @Override
-    public ColorFilter getColorFilter() {
-        return mRing.getColorFilter();
-    }
-
-    @Override
-    public void setTint(ColorStateList tint, Mode tintMode) {
-        if (mState.mTint != tint || mState.mTintMode != tintMode) {
-            mState.mTint = tint;
-            mState.mTintMode = tintMode;
-
-            mTintFilter = updateTintFilter(mTintFilter, tint, tintMode);
-            invalidateSelf();
-        }
-    }
-
-    @SuppressWarnings("unused")
-    private void setRotation(float rotation) {
-        mRotation = rotation;
-        invalidateSelf();
-    }
-
-    @SuppressWarnings("unused")
-    private float getRotation() {
-        return mRotation;
-    }
-
-    @Override
-    public int getOpacity() {
-        return PixelFormat.TRANSLUCENT;
-    }
-
-    @Override
-    public boolean isRunning() {
-        final ArrayList<Animator> animators = mAnimators;
-        final int N = animators.size();
-        for (int i = 0; i < N; i++) {
-            final Animator animator = animators.get(i);
-            if (animator.isRunning()) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    @Override
-    public void start() {
-        final ArrayList<Animator> animators = mAnimators;
-        final int N = animators.size();
-        for (int i = 0; i < N; i++) {
-            final Animator animator = animators.get(i);
-            if (animator.isPaused()) {
-                animator.resume();
-            } else if (!animator.isRunning()){
-                animator.start();
-            }
-        }
-    }
-
-    @Override
-    public void stop() {
-        final ArrayList<Animator> animators = mAnimators;
-        final int N = animators.size();
-        for (int i = 0; i < N; i++) {
-            final Animator animator = animators.get(i);
-            animator.pause();
-        }
-    }
-
-    private void setupAnimators() {
-        final Ring ring = mRing;
-
-        final ObjectAnimator endTrim = ObjectAnimator.ofFloat(ring, "endTrim", 0, 0.75f);
-        endTrim.setDuration(ANIMATION_DURATION);
-        endTrim.setInterpolator(START_CURVE_INTERPOLATOR);
-        endTrim.setRepeatCount(ObjectAnimator.INFINITE);
-        endTrim.setRepeatMode(ObjectAnimator.RESTART);
-
-        final ObjectAnimator startTrim = ObjectAnimator.ofFloat(ring, "startTrim", 0.0f, 0.75f);
-        startTrim.setDuration(ANIMATION_DURATION);
-        startTrim.setInterpolator(END_CURVE_INTERPOLATOR);
-        startTrim.setRepeatCount(ObjectAnimator.INFINITE);
-        startTrim.setRepeatMode(ObjectAnimator.RESTART);
-
-        final ObjectAnimator rotation = ObjectAnimator.ofFloat(ring, "rotation", 0.0f, 0.25f);
-        rotation.setDuration(ANIMATION_DURATION);
-        rotation.setInterpolator(LINEAR_INTERPOLATOR);
-        rotation.setRepeatCount(ObjectAnimator.INFINITE);
-        rotation.setRepeatMode(ObjectAnimator.RESTART);
-
-        final ObjectAnimator groupRotation = ObjectAnimator.ofFloat(this, "rotation", 0.0f, 360.0f);
-        groupRotation.setDuration(NUM_POINTS * ANIMATION_DURATION);
-        groupRotation.setInterpolator(LINEAR_INTERPOLATOR);
-        groupRotation.setRepeatCount(ObjectAnimator.INFINITE);
-        groupRotation.setRepeatMode(ObjectAnimator.RESTART);
-
-        mAnimators.add(endTrim);
-        mAnimators.add(startTrim);
-        mAnimators.add(rotation);
-        mAnimators.add(groupRotation);
-    }
-
-    private final Callback mCallback = new Callback() {
-        @Override
-        public void invalidateDrawable(Drawable d) {
-            invalidateSelf();
-        }
-
-        @Override
-        public void scheduleDrawable(Drawable d, Runnable what, long when) {
-            scheduleSelf(what, when);
-        }
-
-        @Override
-        public void unscheduleDrawable(Drawable d, Runnable what) {
-            unscheduleSelf(what);
-        }
-    };
-
-    private static class MaterialProgressState extends ConstantState {
-        private int[] mThemeAttrs = null;
-        private float mStrokeWidth = 5.0f;
-        private float mInnerRadius = -1.0f;
-        private int mWidth = -1;
-        private int mHeight = -1;
-        private ColorStateList mColor = ColorStateList.valueOf(Color.TRANSPARENT);
-        private ColorStateList mTint = null;
-        private Mode mTintMode = null;
-
-        public MaterialProgressState(MaterialProgressState orig) {
-            if (orig != null) {
-                mThemeAttrs = orig.mThemeAttrs;
-                mStrokeWidth = orig.mStrokeWidth;
-                mInnerRadius = orig.mInnerRadius;
-                mWidth = orig.mWidth;
-                mHeight = orig.mHeight;
-                mColor = orig.mColor;
-                mTint = orig.mTint;
-                mTintMode = orig.mTintMode;
-            }
-        }
-
-        @Override
-        public boolean canApplyTheme() {
-            return mThemeAttrs != null;
-        }
-
-        @Override
-        public Drawable newDrawable() {
-            return newDrawable(null, null);
-        }
-
-        @Override
-        public Drawable newDrawable(Resources res) {
-            return newDrawable(res, null);
-        }
-
-        @Override
-        public Drawable newDrawable(Resources res, Theme theme) {
-            return new MaterialProgressDrawable(this, theme);
-        }
-
-        @Override
-        public int getChangingConfigurations() {
-            return 0;
-        }
-    }
-
-    private static class Ring {
-        private final RectF mTempBounds = new RectF();
-        private final Paint mPaint = new Paint();
-
-        private final Callback mCallback;
-
-        private float mStartTrim = 0.0f;
-        private float mEndTrim = 0.0f;
-        private float mRotation = 0.0f;
-        private float mStrokeWidth = 5.0f;
-        private float mStrokeInset = 2.5f;
-
-        private int mAlpha = 0xFF;
-        private int mColor = Color.BLACK;
-
-        public Ring(Callback callback) {
-            mCallback = callback;
-
-            mPaint.setStrokeCap(Cap.ROUND);
-            mPaint.setAntiAlias(true);
-            mPaint.setStyle(Style.STROKE);
-        }
-
-        public void draw(Canvas c, Rect bounds) {
-            final RectF arcBounds = mTempBounds;
-            arcBounds.set(bounds);
-            arcBounds.inset(mStrokeInset, mStrokeInset);
-
-            final float startAngle = (mStartTrim + mRotation) * 360;
-            final float endAngle = (mEndTrim + mRotation) * 360;
-            float sweepAngle = endAngle - startAngle;
-
-            // Ensure the sweep angle isn't too small to draw.
-            final float diameter = Math.min(arcBounds.width(), arcBounds.height());
-            final float minAngle = (float) (360.0 / (diameter * Math.PI));
-            if (sweepAngle < minAngle && sweepAngle > -minAngle) {
-                sweepAngle = Math.signum(sweepAngle) * minAngle;
-            }
-
-            c.drawArc(arcBounds, startAngle, sweepAngle, false, mPaint);
-        }
-
-        public void setColorFilter(ColorFilter filter) {
-            mPaint.setColorFilter(filter);
-            invalidateSelf();
-        }
-
-        public ColorFilter getColorFilter() {
-            return mPaint.getColorFilter();
-        }
-
-        public void setAlpha(int alpha) {
-            mAlpha = alpha;
-            mPaint.setColor(mColor & 0xFFFFFF | alpha << 24);
-            invalidateSelf();
-        }
-
-        public int getAlpha() {
-            return mAlpha;
-        }
-
-        public void setColor(int color) {
-            mColor = color;
-            mPaint.setColor(color & 0xFFFFFF | mAlpha << 24);
-            invalidateSelf();
-        }
-
-        public int getColor() {
-            return mColor;
-        }
-
-        public void setStrokeWidth(float strokeWidth) {
-            mStrokeWidth = strokeWidth;
-            mPaint.setStrokeWidth(strokeWidth);
-            invalidateSelf();
-        }
-
-        @SuppressWarnings("unused")
-        public float getStrokeWidth() {
-            return mStrokeWidth;
-        }
-
-        @SuppressWarnings("unused")
-        public void setStartTrim(float startTrim) {
-            mStartTrim = startTrim;
-            invalidateSelf();
-        }
-
-        @SuppressWarnings("unused")
-        public float getStartTrim() {
-            return mStartTrim;
-        }
-
-        @SuppressWarnings("unused")
-        public void setEndTrim(float endTrim) {
-            mEndTrim = endTrim;
-            invalidateSelf();
-        }
-
-        @SuppressWarnings("unused")
-        public float getEndTrim() {
-            return mEndTrim;
-        }
-
-        @SuppressWarnings("unused")
-        public void setRotation(float rotation) {
-            mRotation = rotation;
-            invalidateSelf();
-        }
-
-        @SuppressWarnings("unused")
-        public float getRotation() {
-            return mRotation;
-        }
-
-        public void setInsets(float insets) {
-            mStrokeInset = insets;
-        }
-
-        @SuppressWarnings("unused")
-        public float getInsets() {
-            return mStrokeInset;
-        }
-
-        private void invalidateSelf() {
-            mCallback.invalidateDrawable(null);
-        }
-    }
-
-    /**
-     * Squishes the interpolation curve into the second half of the animation.
-     */
-    private static class EndCurveInterpolator extends AccelerateDecelerateInterpolator {
-        @Override
-        public float getInterpolation(float input) {
-            return super.getInterpolation(Math.max(0, (input - 0.5f) * 2.0f));
-        }
-    }
-
-    /**
-     * Squishes the interpolation curve into the first half of the animation.
-     */
-    private static class StartCurveInterpolator extends AccelerateDecelerateInterpolator {
-        @Override
-        public float getInterpolation(float input) {
-            return super.getInterpolation(Math.min(1, input * 2.0f));
-        }
-    }
-}
diff --git a/graphics/java/android/graphics/drawable/VectorDrawable.java b/graphics/java/android/graphics/drawable/VectorDrawable.java
index 4ea0046..8783994 100644
--- a/graphics/java/android/graphics/drawable/VectorDrawable.java
+++ b/graphics/java/android/graphics/drawable/VectorDrawable.java
@@ -299,11 +299,10 @@
         return color;
     }
 
-
     @Override
     public void inflate(Resources res, XmlPullParser parser, AttributeSet attrs, Theme theme)
             throws XmlPullParserException, IOException {
-        final TypedArray a = obtainAttributes(res, theme,  attrs,R.styleable.VectorDrawable);
+        final TypedArray a = obtainAttributes(res, theme, attrs, R.styleable.VectorDrawable);
         updateStateFromTypedArray(a);
         a.recycle();
 
@@ -358,7 +357,7 @@
                 if (SHAPE_PATH.equals(tagName)) {
                     final VPath path = new VPath();
                     path.inflate(res, attrs, theme);
-                    currentGroup.add(path);
+                    currentGroup.mChildren.add(path);
                     if (path.getPathName() != null) {
                         pathRenderer.mVGTargetsMap.put(path.getPathName(), path);
                     }
@@ -375,7 +374,7 @@
                 } else if (SHAPE_GROUP.equals(tagName)) {
                     VGroup newChildGroup = new VGroup();
                     newChildGroup.inflate(res, attrs, theme);
-                    currentGroup.mChildGroupList.add(newChildGroup);
+                    currentGroup.mChildren.add(newChildGroup);
                     groupStack.push(newChildGroup);
                     if (newChildGroup.getGroupName() != null) {
                         pathRenderer.mVGTargetsMap.put(newChildGroup.getGroupName(),
@@ -424,16 +423,19 @@
 
     private void printGroupTree(VGroup currentGroup, int level) {
         String indent = "";
-        for (int i = 0 ; i < level ; i++) {
+        for (int i = 0; i < level; i++) {
             indent += "    ";
         }
         // Print the current node
-        Log.v(LOGTAG, indent + "current group is :" +  currentGroup.getGroupName()
+        Log.v(LOGTAG, indent + "current group is :" + currentGroup.getGroupName()
                 + " rotation is " + currentGroup.mRotate);
-        Log.v(LOGTAG, indent + "matrix is :" +  currentGroup.getLocalMatrix().toString());
-        // Then print all the children
-        for (int i = 0 ; i < currentGroup.mChildGroupList.size(); i++) {
-            printGroupTree(currentGroup.mChildGroupList.get(i), level + 1);
+        Log.v(LOGTAG, indent + "matrix is :" + currentGroup.getLocalMatrix().toString());
+        // Then print all the children groups
+        for (int i = 0; i < currentGroup.mChildren.size(); i++) {
+            Object child = currentGroup.mChildren.get(i);
+            if (child instanceof VGroup) {
+                printGroupTree((VGroup) child, level + 1);
+            }
         }
     }
 
@@ -552,21 +554,21 @@
         private boolean recursiveCanApplyTheme(VGroup currentGroup) {
             // We can do a tree traverse here, if there is one path return true,
             // then we return true for the whole tree.
-            final ArrayList<VPath> paths = currentGroup.mPathList;
-            for (int j = paths.size() - 1; j >= 0; j--) {
-                final VPath path = paths.get(j);
-                if (path.canApplyTheme()) {
-                    return true;
-                }
-            }
+            final ArrayList<Object> children = currentGroup.mChildren;
 
-            final ArrayList<VGroup> childGroups = currentGroup.mChildGroupList;
-
-            for (int i = 0; i < childGroups.size(); i++) {
-                VGroup childGroup = childGroups.get(i);
-                if (childGroup.canApplyTheme()
-                        || recursiveCanApplyTheme(childGroup)) {
-                    return true;
+            for (int i = 0; i < children.size(); i++) {
+                Object child = children.get(i);
+                if (child instanceof VGroup) {
+                    VGroup childGroup = (VGroup) child;
+                    if (childGroup.canApplyTheme()
+                            || recursiveCanApplyTheme(childGroup)) {
+                        return true;
+                    }
+                } else if (child instanceof VPath) {
+                    VPath childPath = (VPath) child;
+                    if (childPath.canApplyTheme()) {
+                        return true;
+                    }
                 }
             }
             return false;
@@ -580,24 +582,22 @@
         private void recursiveApplyTheme(VGroup currentGroup, Theme t) {
             // We can do a tree traverse here, apply theme to all paths which
             // can apply theme.
-            final ArrayList<VPath> paths = currentGroup.mPathList;
-            for (int j = paths.size() - 1; j >= 0; j--) {
-                final VPath path = paths.get(j);
-                if (path.canApplyTheme()) {
-                    path.applyTheme(t);
+            final ArrayList<Object> children = currentGroup.mChildren;
+            for (int i = 0; i < children.size(); i++) {
+                Object child = children.get(i);
+                if (child instanceof VGroup) {
+                    VGroup childGroup = (VGroup) child;
+                    if (childGroup.canApplyTheme()) {
+                        childGroup.applyTheme(t);
+                    }
+                    recursiveApplyTheme(childGroup, t);
+                } else if (child instanceof VPath) {
+                    VPath childPath = (VPath) child;
+                    if (childPath.canApplyTheme()) {
+                        childPath.applyTheme(t);
+                    }
                 }
             }
-
-            final ArrayList<VGroup> childGroups = currentGroup.mChildGroupList;
-
-            for (int i = 0; i < childGroups.size(); i++) {
-                VGroup childGroup = childGroups.get(i);
-                if (childGroup.canApplyTheme()) {
-                    childGroup.applyTheme(t);
-                }
-                recursiveApplyTheme(childGroup, t);
-            }
-
         }
 
         public void setColorFilter(ColorFilter colorFilter) {
@@ -624,11 +624,18 @@
             currentGroup.mStackedMatrix.preConcat(currentGroup.mLocalMatrix);
 
             float stackedAlpha = currentAlpha * currentGroup.mGroupAlpha;
-            drawPath(currentGroup, stackedAlpha, canvas, w, h);
-            // Draw the group tree in post order.
-            for (int i = 0 ; i < currentGroup.mChildGroupList.size(); i++) {
-                drawGroupTree(currentGroup.mChildGroupList.get(i),
-                        currentGroup.mStackedMatrix, stackedAlpha, canvas, w, h);
+
+            // Draw the group tree in the same order as the XML file.
+            for (int i = 0; i < currentGroup.mChildren.size(); i++) {
+                Object child = currentGroup.mChildren.get(i);
+                if (child instanceof VGroup) {
+                    VGroup childGroup = (VGroup) child;
+                    drawGroupTree(childGroup, currentGroup.mStackedMatrix,
+                            stackedAlpha, canvas, w, h);
+                } else if (child instanceof VPath) {
+                    VPath childPath = (VPath) child;
+                    drawPath(currentGroup, childPath, stackedAlpha, canvas, w, h);
+                }
             }
         }
 
@@ -637,7 +644,8 @@
             drawGroupTree(mRootGroup, IDENTITY_MATRIX, ((float) mRootAlpha) / 0xFF, canvas, w, h);
         }
 
-        private void drawPath(VGroup vGroup, float stackedAlpha, Canvas canvas, int w, int h) {
+        private void drawPath(VGroup vGroup, VPath vPath, float stackedAlpha,
+                Canvas canvas, int w, int h) {
             final float scale = Math.min(h / mViewportHeight, w / mViewportWidth);
 
             mFinalPathMatrix.set(vGroup.mStackedMatrix);
@@ -645,75 +653,71 @@
             mFinalPathMatrix.postTranslate(
                     w / 2f - mViewportWidth / 2f, h / 2f - mViewportHeight / 2f);
 
-            ArrayList<VPath> paths = vGroup.getPaths();
-            for (int i = 0; i < paths.size(); i++) {
-                VPath vPath = paths.get(i);
-                vPath.toPath(mPath);
-                final Path path = mPath;
+            vPath.toPath(mPath);
+            final Path path = mPath;
 
-                if (vPath.mTrimPathStart != 0.0f || vPath.mTrimPathEnd != 1.0f) {
-                    float start = (vPath.mTrimPathStart + vPath.mTrimPathOffset) % 1.0f;
-                    float end = (vPath.mTrimPathEnd + vPath.mTrimPathOffset) % 1.0f;
+            if (vPath.mTrimPathStart != 0.0f || vPath.mTrimPathEnd != 1.0f) {
+                float start = (vPath.mTrimPathStart + vPath.mTrimPathOffset) % 1.0f;
+                float end = (vPath.mTrimPathEnd + vPath.mTrimPathOffset) % 1.0f;
 
-                    if (mPathMeasure == null) {
-                        mPathMeasure = new PathMeasure();
+                if (mPathMeasure == null) {
+                    mPathMeasure = new PathMeasure();
+                }
+                mPathMeasure.setPath(mPath, false);
+
+                float len = mPathMeasure.getLength();
+                start = start * len;
+                end = end * len;
+                path.reset();
+                if (start > end) {
+                    mPathMeasure.getSegment(start, len, path, true);
+                    mPathMeasure.getSegment(0f, end, path, true);
+                } else {
+                    mPathMeasure.getSegment(start, end, path, true);
+                }
+                path.rLineTo(0, 0); // fix bug in measure
+            }
+
+            mRenderPath.reset();
+
+            mRenderPath.addPath(path, mFinalPathMatrix);
+
+            if (vPath.mClip) {
+                canvas.clipPath(mRenderPath, Region.Op.REPLACE);
+            } else {
+                if (vPath.mFillColor != 0) {
+                    if (mFillPaint == null) {
+                        mFillPaint = new Paint();
+                        mFillPaint.setColorFilter(mColorFilter);
+                        mFillPaint.setStyle(Paint.Style.FILL);
+                        mFillPaint.setAntiAlias(true);
                     }
-                    mPathMeasure.setPath(mPath, false);
-
-                    float len = mPathMeasure.getLength();
-                    start = start * len;
-                    end = end * len;
-                    path.reset();
-                    if (start > end) {
-                        mPathMeasure.getSegment(start, len, path, true);
-                        mPathMeasure.getSegment(0f, end, path, true);
-                    } else {
-                        mPathMeasure.getSegment(start, end, path, true);
-                    }
-                    path.rLineTo(0, 0); // fix bug in measure
+                    mFillPaint.setColor(applyAlpha(vPath.mFillColor, stackedAlpha));
+                    canvas.drawPath(mRenderPath, mFillPaint);
                 }
 
-                mRenderPath.reset();
-
-                mRenderPath.addPath(path, mFinalPathMatrix);
-
-                if (vPath.mClip) {
-                    canvas.clipPath(mRenderPath, Region.Op.REPLACE);
-                } else {
-                   if (vPath.mFillColor != 0) {
-                        if (mFillPaint == null) {
-                            mFillPaint = new Paint();
-                            mFillPaint.setColorFilter(mColorFilter);
-                            mFillPaint.setStyle(Paint.Style.FILL);
-                            mFillPaint.setAntiAlias(true);
-                        }
-                        mFillPaint.setColor(applyAlpha(vPath.mFillColor, stackedAlpha));
-                        canvas.drawPath(mRenderPath, mFillPaint);
+                if (vPath.mStrokeColor != 0) {
+                    if (mStrokePaint == null) {
+                        mStrokePaint = new Paint();
+                        mStrokePaint.setColorFilter(mColorFilter);
+                        mStrokePaint.setStyle(Paint.Style.STROKE);
+                        mStrokePaint.setAntiAlias(true);
                     }
 
-                    if (vPath.mStrokeColor != 0) {
-                        if (mStrokePaint == null) {
-                            mStrokePaint = new Paint();
-                            mStrokePaint.setColorFilter(mColorFilter);
-                            mStrokePaint.setStyle(Paint.Style.STROKE);
-                            mStrokePaint.setAntiAlias(true);
-                        }
-
-                        final Paint strokePaint = mStrokePaint;
-                        if (vPath.mStrokeLineJoin != null) {
-                            strokePaint.setStrokeJoin(vPath.mStrokeLineJoin);
-                        }
-
-                        if (vPath.mStrokeLineCap != null) {
-                            strokePaint.setStrokeCap(vPath.mStrokeLineCap);
-                        }
-
-                        strokePaint.setStrokeMiter(vPath.mStrokeMiterlimit * scale);
-
-                        strokePaint.setColor(applyAlpha(vPath.mStrokeColor, stackedAlpha));
-                        strokePaint.setStrokeWidth(vPath.mStrokeWidth * scale);
-                        canvas.drawPath(mRenderPath, strokePaint);
+                    final Paint strokePaint = mStrokePaint;
+                    if (vPath.mStrokeLineJoin != null) {
+                        strokePaint.setStrokeJoin(vPath.mStrokeLineJoin);
                     }
+
+                    if (vPath.mStrokeLineCap != null) {
+                        strokePaint.setStrokeCap(vPath.mStrokeLineCap);
+                    }
+
+                    strokePaint.setStrokeMiter(vPath.mStrokeMiterlimit * scale);
+
+                    strokePaint.setColor(applyAlpha(vPath.mStrokeColor, stackedAlpha));
+                    strokePaint.setStrokeWidth(vPath.mStrokeWidth * scale);
+                    canvas.drawPath(mRenderPath, strokePaint);
                 }
             }
         }
@@ -742,7 +746,7 @@
         }
 
         private void parseSize(Resources r, AttributeSet attrs)
-                throws XmlPullParserException  {
+                throws XmlPullParserException {
             final TypedArray a = r.obtainAttributes(attrs, R.styleable.VectorDrawableSize);
 
             // Account for any configuration changes.
@@ -771,8 +775,7 @@
 
         /////////////////////////////////////////////////////
         // Variables below need to be copied (deep copy if applicable) for mutation.
-        private final ArrayList<VPath> mPathList = new ArrayList<VPath>();
-        private final ArrayList<VGroup> mChildGroupList = new ArrayList<VGroup>();
+        final ArrayList<Object> mChildren = new ArrayList<Object>();
 
         private float mRotate = 0;
         private float mPivotX = 0;
@@ -808,19 +811,21 @@
 
             mLocalMatrix.set(copy.mLocalMatrix);
 
-            for (int i = 0; i < copy.mPathList.size(); i ++) {
-                VPath copyPath = copy.mPathList.get(i);
-                VPath newPath = new VPath(copyPath);
-                mPathList.add(newPath);
-                if (newPath.mPathName != null) {
-                    targetsMap.put(copyPath.mPathName, newPath);
+            final ArrayList<Object> children = copy.mChildren;
+            for (int i = 0; i < children.size(); i++) {
+                Object copyChild = children.get(i);
+                if (copyChild instanceof VGroup) {
+                    VGroup copyGroup = (VGroup) copyChild;
+                    mChildren.add(new VGroup(copyGroup, targetsMap));
+                } else if (copyChild instanceof VPath) {
+                    VPath copyPath = (VPath) copyChild;
+                    VPath newPath = new VPath(copyPath);
+                    mChildren.add(newPath);
+                    if (newPath.mPathName != null) {
+                        targetsMap.put(newPath.mPathName, newPath);
+                    }
                 }
             }
-
-            for (int i = 0; i < copy.mChildGroupList.size(); i ++) {
-                VGroup currentGroup = copy.mChildGroupList.get(i);
-                mChildGroupList.add(new VGroup(currentGroup, targetsMap));
-            }
         }
 
         public VGroup() {
@@ -922,10 +927,6 @@
             return mLocalMatrix;
         }
 
-        public void add(VPath path) {
-            mPathList.add(path);
-         }
-
         public boolean canApplyTheme() {
             return mThemeAttrs != null;
         }
@@ -980,15 +981,6 @@
             mLocalMatrix.postRotate(mRotate, 0, 0);
             mLocalMatrix.postTranslate(mTranslateX + mPivotX, mTranslateY + mPivotY);
         }
-
-        /**
-         * Must return in order of adding
-         * @return ordered list of paths
-         */
-        public ArrayList<VPath> getPaths() {
-            return mPathList;
-        }
-
     }
 
     private static class VPath {
@@ -1012,7 +1004,7 @@
         float mStrokeMiterlimit = 4;
 
         private PathParser.PathDataNode[] mNodes = null;
-        private String mPathName;
+        String mPathName;
         private int mChangingConfigurations;
 
         public VPath() {
diff --git a/packages/SystemUI/res/layout/zen_mode_panel.xml b/packages/SystemUI/res/layout/zen_mode_panel.xml
index 9d959fa..16ee5c8 100644
--- a/packages/SystemUI/res/layout/zen_mode_panel.xml
+++ b/packages/SystemUI/res/layout/zen_mode_panel.xml
@@ -79,15 +79,4 @@
         android:orientation="vertical"
         android:paddingTop="3dp" />
 
-    <TextView
-        android:id="@+id/zen_alarm_warning"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:paddingBottom="8dp"
-        android:paddingLeft="@dimen/qs_panel_padding"
-        android:paddingRight="@dimen/qs_panel_padding"
-        android:paddingTop="8dp"
-        android:text="@string/zen_alarm_warning"
-        android:textAppearance="@style/TextAppearance.QS.Warning" />
-
 </com.android.systemui.volume.ZenModePanel>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index ff60f1d..848fdf8 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -595,8 +595,8 @@
     <!-- Description of the left direction in which one can to slide the handle in the Slide unlock screen. [CHAR LIMIT=NONE] -->
     <string name="description_direction_left">"Slide left for <xliff:g id="target_description" example="Unlock">%s</xliff:g>.</string>
 
-    <!-- Zen mode: Alarm warning. [CHAR LIMIT=40] -->
-    <string name="zen_alarm_warning">You won\'t hear alarms or timers</string>
+    <!-- Zen mode: No interruptions title, with a warning about alarms and timers. [CHAR LIMIT=60] -->
+    <string name="zen_no_interruptions_with_warning">No interruptions, including alarms and timers</string>
 
     <!-- Zen mode: No interruptions. [CHAR LIMIT=40] -->
     <string name="zen_no_interruptions">No interruptions</string>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index d4acb11..f99b68b 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -185,11 +185,6 @@
         <item name="android:textColor">@color/qs_subhead</item>
     </style>
 
-    <style name="TextAppearance.QS.Warning">
-        <item name="android:textSize">12sp</item>
-        <item name="android:textColor">@color/qs_subhead</item>
-    </style>
-
     <style name="TextAppearance.QS.SegmentedButton">
         <item name="android:textSize">12sp</item>
         <item name="android:textAllCaps">true</item>
@@ -259,11 +254,6 @@
         <item name="android:layout_width">match_parent</item>
     </style>
 
-    <style name="QSWhiteTheme" parent="@android:style/Theme.DeviceDefault">
-        <item name="android:colorControlNormal">#ffffffff</item>
-        <item name="android:colorControlActivated">#ffffffff</item>
-    </style>
-
      <style name="QSBorderlessButton">
         <item name="android:padding">12dp</item>
         <item name="android:background">@drawable/btn_borderless_rect</item>
diff --git a/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java b/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java
index b9d07d5..9bc00a9 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java
@@ -85,7 +85,6 @@
     private TextView mZenSubheadExpanded;
     private View mMoreSettings;
     private LinearLayout mZenConditions;
-    private View mAlarmWarning;
 
     private Callback mCallback;
     private ZenModeController mController;
@@ -100,7 +99,7 @@
         super(context, attrs);
         mContext = context;
         mFavorites = new Favorites();
-        mInflater = LayoutInflater.from(new ContextThemeWrapper(context, R.style.QSWhiteTheme));
+        mInflater = LayoutInflater.from(mContext.getApplicationContext());
         mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(mContext,
                 android.R.interpolator.fast_out_slow_in);
         updateTag();
@@ -152,8 +151,6 @@
         });
 
         mZenConditions = (LinearLayout) findViewById(R.id.zen_conditions);
-
-        mAlarmWarning = findViewById(R.id.zen_alarm_warning);
     }
 
     @Override
@@ -279,10 +276,9 @@
         mZenSubheadCollapsed.setVisibility(!mExpanded ? VISIBLE : GONE);
         mMoreSettings.setVisibility(zenImportant && mExpanded ? VISIBLE : GONE);
         mZenConditions.setVisibility(!zenOff && mExpanded ? VISIBLE : GONE);
-        mAlarmWarning.setVisibility(zenNone && mExpanded ? VISIBLE : GONE);
 
         if (zenNone) {
-            mZenSubheadExpanded.setText(R.string.zen_no_interruptions);
+            mZenSubheadExpanded.setText(R.string.zen_no_interruptions_with_warning);
             mZenSubheadCollapsed.setText(mExitConditionText);
         } else if (zenImportant) {
             mZenSubheadExpanded.setText(R.string.zen_important_interruptions);
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 5a331ac..e8e1536 100755
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -17047,22 +17047,34 @@
         try {
             Intent intent;
             if (oldUserId >= 0) {
-                intent = new Intent(Intent.ACTION_USER_BACKGROUND);
-                intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
-                        | Intent.FLAG_RECEIVER_FOREGROUND);
-                intent.putExtra(Intent.EXTRA_USER_HANDLE, oldUserId);
-                broadcastIntentLocked(null, null, intent,
-                        null, null, 0, null, null, null, AppOpsManager.OP_NONE,
-                        false, false, MY_PID, Process.SYSTEM_UID, oldUserId);
+                // Send USER_BACKGROUND broadcast to all profiles of the outgoing user
+                List<UserInfo> profiles = mUserManager.getProfiles(oldUserId, false);
+                int count = profiles.size();
+                for (int i = 0; i < count; i++) {
+                    int profileUserId = profiles.get(i).id;
+                    intent = new Intent(Intent.ACTION_USER_BACKGROUND);
+                    intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
+                            | Intent.FLAG_RECEIVER_FOREGROUND);
+                    intent.putExtra(Intent.EXTRA_USER_HANDLE, profileUserId);
+                    broadcastIntentLocked(null, null, intent,
+                            null, null, 0, null, null, null, AppOpsManager.OP_NONE,
+                            false, false, MY_PID, Process.SYSTEM_UID, profileUserId);
+                }
             }
             if (newUserId >= 0) {
-                intent = new Intent(Intent.ACTION_USER_FOREGROUND);
-                intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
-                        | Intent.FLAG_RECEIVER_FOREGROUND);
-                intent.putExtra(Intent.EXTRA_USER_HANDLE, newUserId);
-                broadcastIntentLocked(null, null, intent,
-                        null, null, 0, null, null, null, AppOpsManager.OP_NONE,
-                        false, false, MY_PID, Process.SYSTEM_UID, newUserId);
+                // Send USER_FOREGROUND broadcast to all profiles of the incoming user
+                List<UserInfo> profiles = mUserManager.getProfiles(newUserId, false);
+                int count = profiles.size();
+                for (int i = 0; i < count; i++) {
+                    int profileUserId = profiles.get(i).id;
+                    intent = new Intent(Intent.ACTION_USER_FOREGROUND);
+                    intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
+                            | Intent.FLAG_RECEIVER_FOREGROUND);
+                    intent.putExtra(Intent.EXTRA_USER_HANDLE, profileUserId);
+                    broadcastIntentLocked(null, null, intent,
+                            null, null, 0, null, null, null, AppOpsManager.OP_NONE,
+                            false, false, MY_PID, Process.SYSTEM_UID, profileUserId);
+                }
                 intent = new Intent(Intent.ACTION_USER_SWITCHED);
                 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
                         | Intent.FLAG_RECEIVER_FOREGROUND);
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecController.java b/services/core/java/com/android/server/hdmi/HdmiCecController.java
index a5a502a..b1496cc 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecController.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecController.java
@@ -276,6 +276,12 @@
         nativeClearLogicalAddress(mNativePtr);
     }
 
+    @ServiceThreadOnly
+    void clearLocalDevices() {
+        assertRunOnServiceThread();
+        mLocalDevices.clear();
+    }
+
     /**
      * Return the physical address of the device.
      *
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
index 0cae459..07a564d 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
@@ -17,7 +17,9 @@
 package com.android.server.hdmi;
 
 import android.hardware.hdmi.HdmiCecDeviceInfo;
+import android.os.Handler;
 import android.os.Looper;
+import android.os.Message;
 import android.util.Slog;
 
 import com.android.internal.annotations.GuardedBy;
@@ -35,6 +37,11 @@
 abstract class HdmiCecLocalDevice {
     private static final String TAG = "HdmiCecLocalDevice";
 
+    private static final int MSG_DISABLE_DEVICE_TIMEOUT = 1;
+    // Timeout in millisecond for device clean up (5s).
+    // Normal actions timeout is 2s but some of them would have several sequence of timeout.
+    private static final int DEVICE_CLEANUP_TIMEOUT = 5000;
+
     protected final HdmiControlService mService;
     protected final int mDeviceType;
     protected int mAddress;
@@ -58,6 +65,27 @@
     // Note that access to this collection should happen in service thread.
     private final LinkedList<FeatureAction> mActions = new LinkedList<>();
 
+    private Handler mHandler = new Handler () {
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_DISABLE_DEVICE_TIMEOUT:
+                    handleDisableDeviceTimeout();
+                    break;
+            }
+        }
+    };
+
+    /**
+     * A callback interface to get notified when all pending action is cleared.
+     * It can be called when timeout happened.
+     */
+    interface PendingActionClearedCallback {
+        void onCleared(HdmiCecLocalDevice device);
+    }
+
+    protected PendingActionClearedCallback mPendingActionClearedCallback;
+
     protected HdmiCecLocalDevice(HdmiControlService service, int deviceType) {
         mService = service;
         mDeviceType = deviceType;
@@ -482,10 +510,11 @@
     }
 
     protected void checkIfPendingActionsCleared() {
-        if (mActions.isEmpty()) {
-            mService.onPendingActionsCleared();
+        if (mActions.isEmpty() && mPendingActionClearedCallback != null) {
+            mPendingActionClearedCallback.onCleared(this);
         }
     }
+
     protected void assertRunOnServiceThread() {
         if (Looper.myLooper() != mService.getServiceLooper()) {
             throw new IllegalStateException("Should run on service thread.");
@@ -578,21 +607,48 @@
     }
 
     /**
-     * Called when the system started transition to standby mode.
-     *
-     * @param initiatedByCec true if this power sequence is initiated
-     *         by the reception the CEC messages like <StandBy>
-     */
-    protected void onTransitionToStandby(boolean initiatedByCec) {
-        // If there are no outstanding actions, we'll go to STANDBY state.
-        checkIfPendingActionsCleared();
-    }
-
-    /**
      * Called when the system goes to standby mode.
      *
      * @param initiatedByCec true if this power sequence is initiated
-     *         by the reception the CEC messages like <StandBy>
+     *         by the reception the CEC messages like &lt;Standby&gt;
      */
-    protected void onStandBy(boolean initiatedByCec) {}
+    protected void onStandby(boolean initiatedByCec) {}
+
+    /**
+     * Disable device. {@code callback} is used to get notified when all pending
+     * actions are completed or timeout is issued.
+     *
+     * @param initiatedByCec true if this sequence is initiated
+     *         by the reception the CEC messages like &lt;Standby&gt;
+     * @param callback callback interface to get notified when all pending actions are cleared
+     */
+    protected void disableDevice(boolean initiatedByCec, PendingActionClearedCallback callback) {
+        mPendingActionClearedCallback = callback;
+        mHandler.sendMessageDelayed(Message.obtain(mHandler, MSG_DISABLE_DEVICE_TIMEOUT),
+                DEVICE_CLEANUP_TIMEOUT);
+    }
+
+    @ServiceThreadOnly
+    private void handleDisableDeviceTimeout() {
+        assertRunOnServiceThread();
+
+        // If all actions are not cleared in DEVICE_CLEANUP_TIMEOUT, enforce to finish them.
+        // onCleard will be called at the last action's finish method.
+        Iterator<FeatureAction> iter = mActions.iterator();
+        while (iter.hasNext()) {
+            FeatureAction action = iter.next();
+            action.finish();
+            iter.remove();
+        }
+    }
+
+    /**
+     * Send a key event to other device.
+     *
+     * @param keyCode key code defined in {@link android.view.KeyEvent}
+     * @param isPressed {@code true} for key down event
+     */
+    protected void sendKeyEvent(int keyCode, boolean isPressed) {
+        Slog.w(TAG, "sendKeyEvent not implemented");
+    }
 }
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
index 7ab2e8c..9807659 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
@@ -141,7 +141,9 @@
 
     @Override
     @ServiceThreadOnly
-    protected void onTransitionToStandby(boolean initiatedByCec) {
+    protected void disableDevice(boolean initiatedByCec, PendingActionClearedCallback callback) {
+        super.disableDevice(initiatedByCec, callback);
+
         assertRunOnServiceThread();
         if (!initiatedByCec && mIsActiveSource) {
             mService.sendCecCommand(HdmiCecMessageBuilder.buildInactiveSource(
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
index 4185d25..c8c7401 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
@@ -224,10 +224,11 @@
      * Sends key to a target CEC device.
      *
      * @param keyCode key code to send. Defined in {@link android.view.KeyEvent}.
-     * @param isPressed true if this is keypress event
+     * @param isPressed true if this is key press event
      */
+    @Override
     @ServiceThreadOnly
-    void sendKeyEvent(int keyCode, boolean isPressed) {
+    protected void sendKeyEvent(int keyCode, boolean isPressed) {
         assertRunOnServiceThread();
         List<SendKeyAction> action = getActions(SendKeyAction.class);
         if (!action.isEmpty()) {
@@ -1033,19 +1034,58 @@
 
     @Override
     @ServiceThreadOnly
-    protected void onTransitionToStandby(boolean initiatedByCec) {
+    protected void disableDevice(boolean initiatedByCec, PendingActionClearedCallback callback) {
+        super.disableDevice(initiatedByCec, callback);
         assertRunOnServiceThread();
         // Remove any repeated working actions.
         // HotplugDetectionAction will be reinstated during the wake up process.
         // HdmiControlService.onWakeUp() -> initializeLocalDevices() ->
         //     LocalDeviceTv.onAddressAllocated() -> launchDeviceDiscovery().
+        removeAction(DeviceDiscoveryAction.class);
         removeAction(HotplugDetectionAction.class);
+
+        disableSystemAudioIfExist();
+        disableArcIfExist();
         checkIfPendingActionsCleared();
     }
 
+    @ServiceThreadOnly
+    private void disableSystemAudioIfExist() {
+        assertRunOnServiceThread();
+        if (getAvrDeviceInfo() == null) {
+            return;
+        }
+
+        // Seq #31.
+        removeAction(SystemAudioActionFromAvr.class);
+        removeAction(SystemAudioActionFromTv.class);
+        removeAction(SystemAudioAutoInitiationAction.class);
+        removeAction(SystemAudioStatusAction.class);
+        removeAction(VolumeControlAction.class);
+
+        // Once adding additional param which describes whether to record it to NVM or not to this
+        // method, put "false" for it.
+        setSystemAudioMode(false);
+    }
+
+    @ServiceThreadOnly
+    private void disableArcIfExist() {
+        assertRunOnServiceThread();
+        HdmiCecDeviceInfo avr = getAvrDeviceInfo();
+        if (avr == null) {
+            return;
+        }
+
+        // Seq #44.
+        removeAction(RequestArcInitiationAction.class);
+        if (!hasAction(RequestArcTerminationAction.class) && isArcEstabilished()) {
+            addAndStartAction(new RequestArcTerminationAction(this, avr.getLogicalAddress()));
+        }
+    }
+
     @Override
     @ServiceThreadOnly
-    protected void onStandBy(boolean initiatedByCec) {
+    protected void onStandby(boolean initiatedByCec) {
         assertRunOnServiceThread();
         // Seq #11
         if (!mService.isControlEnabled()) {
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java b/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java
index 57f89b1..0b6c3c5 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java
@@ -274,7 +274,7 @@
      * @return newly created {@link HdmiCecMessage}
      */
     static HdmiCecMessage buildInactiveSource(int src, int physicalAddress) {
-        return buildCommand(src, Constants.ADDR_BROADCAST,
+        return buildCommand(src, Constants.ADDR_TV,
                 Constants.MESSAGE_INACTIVE_SOURCE, physicalAddressToParam(physicalAddress));
     }
 
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index d35f03d..b0155f5 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -50,6 +50,7 @@
 import com.android.server.SystemService;
 import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;
 import com.android.server.hdmi.HdmiCecController.AllocateAddressCallback;
+import com.android.server.hdmi.HdmiCecLocalDevice.PendingActionClearedCallback;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -72,9 +73,11 @@
          * Called when {@link HdmiControlService#sendCecCommand} is completed.
          *
          * @param error result of send request.
-         * @see {@link #SEND_RESULT_SUCCESS}
-         * @see {@link #SEND_RESULT_NAK}
-         * @see {@link #SEND_RESULT_FAILURE}
+         * <ul>
+         * <li>{@link Constants#SEND_RESULT_SUCCESS}
+         * <li>{@link Constants#SEND_RESULT_NAK}
+         * <li>{@link Constants#SEND_RESULT_FAILURE}
+         * </ul>
          */
         void onSendCompleted(int error);
     }
@@ -691,19 +694,17 @@
         }
 
         @Override
-        public void sendKeyEvent(final int keyCode, final boolean isPressed) {
+        public void sendKeyEvent(final int deviceType, final int keyCode, final boolean isPressed) {
             enforceAccessPermission();
             runOnServiceThread(new Runnable() {
                 @Override
                 public void run() {
-                    // TODO: sendKeyEvent is for TV device only for now. Allow other
-                    //       local devices of different types to use this as well.
-                    HdmiCecLocalDeviceTv tv = tv();
-                    if (tv == null) {
-                        Slog.w(TAG, "Local tv device not available");
+                    HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(deviceType);
+                    if (localDevice == null) {
+                        Slog.w(TAG, "Local device not available");
                         return;
                     }
-                    tv.sendKeyEvent(keyCode, isPressed);
+                    localDevice.sendKeyEvent(keyCode, isPressed);
                 }
             });
         }
@@ -841,27 +842,11 @@
         @Override
         public void setControlEnabled(final boolean enabled) {
             enforceAccessPermission();
-            synchronized (mLock) {
-                mHdmiControlEnabled = enabled;
-            }
-            // TODO: Stop the running actions when disabled, and start
-            //       address allocation/device discovery when enabled.
-            if (!enabled) {
-                return;
-            }
             runOnServiceThread(new Runnable() {
                 @Override
                 public void run() {
-                    HdmiCecLocalDeviceTv tv = tv();
-                    if (tv == null) {
-                        return;
-                    }
-                    int value = enabled ? HdmiTvClient.ENABLED : HdmiTvClient.DISABLED;
-                    mCecController.setOption(HdmiTvClient.OPTION_CEC_ENABLE, value);
-                    if (mMhlController != null) {
-                        mMhlController.setOption(HdmiTvClient.OPTION_MHL_ENABLE, value);
-                    }
-                    tv.launchRoutingControl(false);
+                    handleHdmiControlStatusChanged(enabled);
+
                 }
             });
         }
@@ -1241,25 +1226,48 @@
     private void onStandby() {
         assertRunOnServiceThread();
         mPowerStatus = HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY;
+
+        final List<HdmiCecLocalDevice> devices = getAllLocalDevices();
+        disableDevices(new PendingActionClearedCallback() {
+            @Override
+            public void onCleared(HdmiCecLocalDevice device) {
+                Slog.v(TAG, "On standby-action cleared:" + device.mDeviceType);
+                devices.remove(device);
+                if (devices.isEmpty()) {
+                    clearLocalDevices();
+                    onStandbyCompleted();
+                }
+            }
+        });
+    }
+
+    private void disableDevices(PendingActionClearedCallback callback) {
         for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
-            device.onTransitionToStandby(mStandbyMessageReceived);
+            device.disableDevice(mStandbyMessageReceived, callback);
         }
     }
 
-    /**
-     * Called when there are the outstanding actions in the local devices.
-     * This callback is used to wait for when the action queue is empty
-     * during the power state transition to standby.
-     */
     @ServiceThreadOnly
-    void onPendingActionsCleared() {
+    private void clearLocalDevices() {
         assertRunOnServiceThread();
+        if (mCecController == null) {
+            return;
+        }
+        mCecController.clearLogicalAddress();
+        mCecController.clearLocalDevices();
+    }
+
+    @ServiceThreadOnly
+    private void onStandbyCompleted() {
+        assertRunOnServiceThread();
+        Slog.v(TAG, "onStandbyCompleted");
+
         if (mPowerStatus != HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY) {
             return;
         }
         mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY;
         for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
-            device.onStandBy(mStandbyMessageReceived);
+            device.onStandby(mStandbyMessageReceived);
         }
         mStandbyMessageReceived = false;
         mCecController.setOption(HdmiTvClient.OPTION_CEC_SERVICE_CONTROL, HdmiTvClient.DISABLED);
@@ -1305,4 +1313,36 @@
             mProhibitMode = enabled;
         }
     }
+
+    @ServiceThreadOnly
+    private void handleHdmiControlStatusChanged(boolean enabled) {
+        assertRunOnServiceThread();
+
+        int value = enabled ? HdmiTvClient.ENABLED : HdmiTvClient.DISABLED;
+        mCecController.setOption(HdmiTvClient.OPTION_CEC_ENABLE, value);
+        if (mMhlController != null) {
+            mMhlController.setOption(HdmiTvClient.OPTION_MHL_ENABLE, value);
+        }
+
+        synchronized (mLock) {
+            mHdmiControlEnabled = enabled;
+        }
+
+        if (enabled) {
+            // TODO: call initalizedLocalDevice with additional param once putting
+            // it to address allocation result.
+            HdmiCecLocalDeviceTv tv = tv();
+            if (tv != null) {
+                tv.launchRoutingControl(false);
+            }
+        } else {
+            disableDevices(new PendingActionClearedCallback() {
+                @Override
+                public void onCleared(HdmiCecLocalDevice device) {
+                    assertRunOnServiceThread();
+                    clearLocalDevices();
+                }
+            });
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java
index cab2728..7f8b232 100644
--- a/services/core/java/com/android/server/job/JobSchedulerService.java
+++ b/services/core/java/com/android/server/job/JobSchedulerService.java
@@ -140,12 +140,10 @@
      * This cancels the job if it's already been scheduled, and replaces it with the one provided.
      * @param job JobInfo object containing execution parameters
      * @param uId The package identifier of the application this job is for.
-     * @param canPersistJob Whether or not the client has the appropriate permissions for
-     *                       persisting this job.
      * @return Result of this operation. See <code>JobScheduler#RESULT_*</code> return codes.
      */
-    public int schedule(JobInfo job, int uId, boolean canPersistJob) {
-        JobStatus jobStatus = new JobStatus(job, uId, canPersistJob);
+    public int schedule(JobInfo job, int uId) {
+        JobStatus jobStatus = new JobStatus(job, uId);
         cancelJob(uId, job.getId());
         startTrackingJob(jobStatus);
         return JobScheduler.RESULT_SUCCESS;
@@ -668,11 +666,16 @@
             final int uid = Binder.getCallingUid();
 
             enforceValidJobRequest(uid, job);
-            final boolean canPersist = canPersistJobs(pid, uid);
+            if (job.isPersisted()) {
+                if (!canPersistJobs(pid, uid)) {
+                    throw new IllegalArgumentException("Error: requested job be persisted without"
+                            + " holding RECEIVE_BOOT_COMPLETED permission.");
+                }
+            }
 
             long ident = Binder.clearCallingIdentity();
             try {
-                return JobSchedulerService.this.schedule(job, uid, canPersist);
+                return JobSchedulerService.this.schedule(job, uid);
             } finally {
                 Binder.restoreCallingIdentity(ident);
             }
diff --git a/services/core/java/com/android/server/job/JobStore.java b/services/core/java/com/android/server/job/JobStore.java
index 4ac26c1..8736980 100644
--- a/services/core/java/com/android/server/job/JobStore.java
+++ b/services/core/java/com/android/server/job/JobStore.java
@@ -160,7 +160,9 @@
             }
             return false;
         }
-        maybeWriteStatusToDiskAsync();
+        if (jobStatus.isPersisted()) {
+            maybeWriteStatusToDiskAsync();
+        }
         return removed;
     }
 
@@ -429,7 +431,8 @@
             }
         }
 
-        private List<JobStatus> readJobMapImpl(FileInputStream fis) throws XmlPullParserException, IOException {
+        private List<JobStatus> readJobMapImpl(FileInputStream fis)
+                throws XmlPullParserException, IOException {
             XmlPullParser parser = Xml.newPullParser();
             parser.setInput(fis, null);
 
@@ -498,6 +501,7 @@
             // Read out job identifier attributes.
             try {
                 jobBuilder = buildBuilderFromXml(parser);
+                jobBuilder.setIsPersisted(true);
                 uid = Integer.valueOf(parser.getAttributeValue(null, "uid"));
             } catch (NumberFormatException e) {
                 Slog.e(TAG, "Error parsing job's required fields, skipping");
diff --git a/services/core/java/com/android/server/job/controllers/JobStatus.java b/services/core/java/com/android/server/job/controllers/JobStatus.java
index 53337c4..9ee2869 100644
--- a/services/core/java/com/android/server/job/controllers/JobStatus.java
+++ b/services/core/java/com/android/server/job/controllers/JobStatus.java
@@ -43,9 +43,6 @@
     final JobInfo job;
     final int uId;
 
-    /** At reschedule time we need to know whether to update job on disk. */
-    final boolean persisted;
-
     // Constraints.
     final AtomicBoolean chargingConstraintSatisfied = new AtomicBoolean();
     final AtomicBoolean timeDelayConstraintSatisfied = new AtomicBoolean();
@@ -72,16 +69,15 @@
         return uId;
     }
 
-    private JobStatus(JobInfo job, int uId, boolean persisted, int numFailures) {
+    private JobStatus(JobInfo job, int uId, int numFailures) {
         this.job = job;
         this.uId = uId;
         this.numFailures = numFailures;
-        this.persisted = persisted;
     }
 
     /** Create a newly scheduled job. */
-    public JobStatus(JobInfo job, int uId, boolean persisted) {
-        this(job, uId, persisted, 0);
+    public JobStatus(JobInfo job, int uId) {
+        this(job, uId, 0);
 
         final long elapsedNow = SystemClock.elapsedRealtime();
 
@@ -105,7 +101,7 @@
      */
     public JobStatus(JobInfo job, int uId, long earliestRunTimeElapsedMillis,
                       long latestRunTimeElapsedMillis) {
-        this(job, uId, true, 0);
+        this(job, uId, 0);
 
         this.earliestRunTimeElapsedMillis = earliestRunTimeElapsedMillis;
         this.latestRunTimeElapsedMillis = latestRunTimeElapsedMillis;
@@ -114,7 +110,7 @@
     /** Create a new job to be rescheduled with the provided parameters. */
     public JobStatus(JobStatus rescheduling, long newEarliestRuntimeElapsedMillis,
                       long newLatestRuntimeElapsedMillis, int backoffAttempt) {
-        this(rescheduling.job, rescheduling.getUid(), rescheduling.isPersisted(), backoffAttempt);
+        this(rescheduling.job, rescheduling.getUid(), backoffAttempt);
 
         earliestRunTimeElapsedMillis = newEarliestRuntimeElapsedMillis;
         latestRunTimeElapsedMillis = newLatestRuntimeElapsedMillis;
@@ -172,6 +168,10 @@
         return job.isRequireDeviceIdle();
     }
 
+    public boolean isPersisted() {
+        return job.isPersisted();
+    }
+
     public long getEarliestRunTime() {
         return earliestRunTimeElapsedMillis;
     }
@@ -180,9 +180,6 @@
         return latestRunTimeElapsedMillis;
     }
 
-    public boolean isPersisted() {
-        return persisted;
-    }
     /**
      * @return Whether or not this job is ready to run, based on its requirements.
      */
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 71b8974..bb900db 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -3136,6 +3136,9 @@
         FileUtils.setPermissions(path.toString(), FileUtils.S_IRWXU | FileUtils.S_IRWXG
                 | FileUtils.S_IXOTH, -1, -1);
         for (PackageSetting ps : mPackages.values()) {
+            if (ps.pkg == null || ps.pkg.applicationInfo == null) {
+                continue;
+            }
             // Only system apps are initially installed.
             ps.setInstalled((ps.pkgFlags&ApplicationInfo.FLAG_SYSTEM) != 0, userHandle);
             // Need to create a data directory for all apps under this user.
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 61f09f6..5445dc0 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -3592,6 +3592,30 @@
     }
 
     @Override
+    public boolean switchUser(ComponentName who, UserHandle userHandle) {
+        synchronized (this) {
+            if (who == null) {
+                throw new NullPointerException("ComponentName is null");
+            }
+            getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
+
+            long id = Binder.clearCallingIdentity();
+            try {
+                int userId = UserHandle.USER_OWNER;
+                if (userHandle != null) {
+                    userId = userHandle.getIdentifier();
+                }
+                return ActivityManagerNative.getDefault().switchUser(userId);
+            } catch (RemoteException e) {
+                Log.e(LOG_TAG, "Couldn't switch user", e);
+                return false;
+            } finally {
+                restoreCallingIdentity(id);
+            }
+        }
+    }
+
+    @Override
     public Bundle getApplicationRestrictions(ComponentName who, String packageName) {
         final UserHandle userHandle = new UserHandle(UserHandle.getCallingUserId());
 
diff --git a/services/tests/servicestests/src/com/android/server/task/TaskStoreTest.java b/services/tests/servicestests/src/com/android/server/task/TaskStoreTest.java
index 7a7fa07..cb8da70 100644
--- a/services/tests/servicestests/src/com/android/server/task/TaskStoreTest.java
+++ b/services/tests/servicestests/src/com/android/server/task/TaskStoreTest.java
@@ -63,8 +63,9 @@
                 .setBackoffCriteria(initialBackoff, JobInfo.BackoffPolicy.EXPONENTIAL)
                 .setOverrideDeadline(runByMillis)
                 .setMinimumLatency(runFromMillis)
+                .setIsPersisted(true)
                 .build();
-        final JobStatus ts = new JobStatus(task, SOME_UID, true /* persisted */);
+        final JobStatus ts = new JobStatus(task, SOME_UID);
         mTaskStoreUnderTest.add(ts);
         Thread.sleep(IO_WAIT);
         // Manually load tasks from xml file.
@@ -89,15 +90,17 @@
                 .setRequiresDeviceIdle(true)
                 .setPeriodic(10000L)
                 .setRequiresCharging(true)
+                .setIsPersisted(true)
                 .build();
         final JobInfo task2 = new Builder(12, mComponent)
                 .setMinimumLatency(5000L)
                 .setBackoffCriteria(15000L, JobInfo.BackoffPolicy.LINEAR)
                 .setOverrideDeadline(30000L)
                 .setRequiredNetworkCapabilities(JobInfo.NetworkType.UNMETERED)
+                .setIsPersisted(true)
                 .build();
-        final JobStatus taskStatus1 = new JobStatus(task1, SOME_UID, true /* persisted */);
-        final JobStatus taskStatus2 = new JobStatus(task2, SOME_UID, true /* persisted */);
+        final JobStatus taskStatus1 = new JobStatus(task1, SOME_UID);
+        final JobStatus taskStatus2 = new JobStatus(task2, SOME_UID);
         mTaskStoreUnderTest.add(taskStatus1);
         mTaskStoreUnderTest.add(taskStatus2);
         Thread.sleep(IO_WAIT);
@@ -128,7 +131,8 @@
         JobInfo.Builder b = new Builder(8, mComponent)
                 .setRequiresDeviceIdle(true)
                 .setPeriodic(10000L)
-                .setRequiresCharging(true);
+                .setRequiresCharging(true)
+                .setIsPersisted(true);
 
         PersistableBundle extras = new PersistableBundle();
         extras.putDouble("hello", 3.2);
@@ -136,7 +140,7 @@
         extras.putInt("into", 3);
         b.setExtras(extras);
         final JobInfo task = b.build();
-        JobStatus taskStatus = new JobStatus(task, SOME_UID, true /* persisted */);
+        JobStatus taskStatus = new JobStatus(task, SOME_UID);
 
         mTaskStoreUnderTest.add(taskStatus);
         Thread.sleep(IO_WAIT);
diff --git a/tests/VectorDrawableTest/res/drawable/vector_drawable25.xml b/tests/VectorDrawableTest/res/drawable/vector_drawable25.xml
new file mode 100644
index 0000000..a3f0447
--- /dev/null
+++ b/tests/VectorDrawableTest/res/drawable/vector_drawable25.xml
@@ -0,0 +1,93 @@
+<!--
+ Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android" >
+
+    <size
+        android:height="64dp"
+        android:width="64dp" />
+
+    <viewport
+        android:viewportHeight="400"
+        android:viewportWidth="400" />
+
+    <group
+        android:name="FirstLevelGroup"
+        android:alpha="0.9"
+        android:translateX="100.0"
+        android:translateY="0.0" >
+        <group
+            android:name="SecondLevelGroup1"
+            android:alpha="0.9"
+            android:translateX="-100.0"
+            android:translateY="50.0" >
+            <path
+                android:fill="#FF00FF00"
+                android:pathData="@string/rectangle200" />
+
+            <group
+                android:name="ThridLevelGroup1"
+                android:alpha="0.9"
+                android:translateX="-100.0"
+                android:translateY="50.0" >
+                <path
+                    android:fill="#FF0000FF"
+                    android:pathData="@string/rectangle200" />
+            </group>
+            <group
+                android:name="ThridLevelGroup2"
+                android:alpha="0.8"
+                android:translateX="100.0"
+                android:translateY="50.0" >
+                <path
+                    android:fill="#FF000000"
+                    android:pathData="@string/rectangle200" />
+            </group>
+        </group>
+        <group
+            android:name="SecondLevelGroup2"
+            android:alpha="0.8"
+            android:translateX="100.0"
+            android:translateY="50.0" >
+            <path
+                android:fill="#FF0000FF"
+                android:pathData="@string/rectangle200" />
+
+            <group
+                android:name="ThridLevelGroup3"
+                android:alpha="0.9"
+                android:translateX="-100.0"
+                android:translateY="50.0" >
+                <path
+                    android:fill="#FFFF0000"
+                    android:pathData="@string/rectangle200" />
+            </group>
+            <group
+                android:name="ThridLevelGroup4"
+                android:alpha="0.8"
+                android:translateX="100.0"
+                android:translateY="50.0" >
+                <path
+                    android:fill="#FF00FF00"
+                    android:pathData="@string/rectangle200" />
+            </group>
+        </group>
+
+        <path
+            android:fill="#FFFF0000"
+            android:pathData="@string/rectangle200" />
+    </group>
+
+</vector>
\ No newline at end of file
diff --git a/tests/VectorDrawableTest/res/values/strings.xml b/tests/VectorDrawableTest/res/values/strings.xml
index 54cffa8..a550549 100644
--- a/tests/VectorDrawableTest/res/values/strings.xml
+++ b/tests/VectorDrawableTest/res/values/strings.xml
@@ -24,5 +24,5 @@
     <string name="equal2">    "M300,35 l 0,-35 70,0  0,35z M300,105 l 70,0 0,35 -70,0z"</string>
     <string name="round_box">"m2.10001,-6c-1.9551,0 -0.5,0.02499 -2.10001,0.02499c-1.575,0 0.0031,-0.02499 -1.95,-0.02499c-2.543,0 -4,2.2816 -4,4.85001c0,3.52929 0.25,6.25 5.95,6.25c5.7,0 6,-2.72071 6,-6.25c0,-2.56841 -1.35699,-4.85001 -3.89999,-4.85001"</string>
     <string name="heart">    "m4.5,-7c-1.95509,0 -3.83009,1.26759 -4.5,3c-0.66991,-1.73241 -2.54691,-3 -4.5,-3c-2.543,0 -4.5,1.93159 -4.5,4.5c0,3.5293 3.793,6.2578 9,11.5c5.207,-5.2422 9,-7.9707 9,-11.5c0,-2.56841 -1.957,-4.5 -4.5,-4.5"</string>
-
+    <string name="rectangle200">"M 0,0 l 200,0 l 0, 200 l -200, 0 z"</string>
 </resources>
\ No newline at end of file
diff --git a/tests/VectorDrawableTest/src/com/android/test/dynamic/VectorDrawablePerformance.java b/tests/VectorDrawableTest/src/com/android/test/dynamic/VectorDrawablePerformance.java
index 814deb8..e8b6952 100644
--- a/tests/VectorDrawableTest/src/com/android/test/dynamic/VectorDrawablePerformance.java
+++ b/tests/VectorDrawableTest/src/com/android/test/dynamic/VectorDrawablePerformance.java
@@ -52,6 +52,7 @@
             R.drawable.vector_drawable22,
             R.drawable.vector_drawable23,
             R.drawable.vector_drawable24,
+            R.drawable.vector_drawable25,
     };
 
     @Override