Merge "Elevation for action bars"
diff --git a/api/current.txt b/api/current.txt
index 2d4ff58..ed38885 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 = 16843918; // 0x101048e
     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 = 16843918; // 0x101048e
+    field public static final int multiArch = 16843919; // 0x101048f
     field public static final int multiprocess = 16842771; // 0x1010013
     field public static final int name = 16842755; // 0x1010003
     field public static final int navigationBarColor = 16843860; // 0x1010454
@@ -918,6 +919,7 @@
     field public static final int popupAnimationStyle = 16843465; // 0x10102c9
     field public static final int popupBackground = 16843126; // 0x1010176
     field public static final int popupCharacters = 16843332; // 0x1010244
+    field public static final int popupElevation = 16843918; // 0x101048e
     field public static final int popupKeyboard = 16843331; // 0x1010243
     field public static final int popupLayout = 16843323; // 0x101023b
     field public static final int popupMenuStyle = 16843520; // 0x1010300
@@ -5344,6 +5346,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";
@@ -12823,7 +12826,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 {
@@ -12834,6 +12845,12 @@
 
 package android.hardware.camera2.params {
 
+  public final class BlackLevelPattern {
+    method public void copyTo(int[], int);
+    method public int getOffsetForIndex(int, int);
+    field public static final int COUNT = 4; // 0x4
+  }
+
   public final class ColorSpaceTransform {
     ctor public ColorSpaceTransform(android.util.Rational[]);
     ctor public ColorSpaceTransform(int[]);
@@ -12990,12 +13007,14 @@
     field public static final int GEOFENCE_ENTERED = 1; // 0x1
     field public static final int GEOFENCE_ERROR_ID_EXISTS = 2; // 0x2
     field public static final int GEOFENCE_ERROR_ID_UNKNOWN = 3; // 0x3
+    field public static final int GEOFENCE_ERROR_INSUFFICIENT_MEMORY = 6; // 0x6
     field public static final int GEOFENCE_ERROR_INVALID_TRANSITION = 4; // 0x4
     field public static final int GEOFENCE_ERROR_TOO_MANY_GEOFENCES = 1; // 0x1
     field public static final int GEOFENCE_EXITED = 2; // 0x2
     field public static final int GEOFENCE_FAILURE = 5; // 0x5
     field public static final int GEOFENCE_SUCCESS = 0; // 0x0
     field public static final int GEOFENCE_UNCERTAIN = 4; // 0x4
+    field public static final int MONITORING_TYPE_FUSED_HARDWARE = 1; // 0x1
     field public static final int MONITORING_TYPE_GPS_HARDWARE = 0; // 0x0
     field public static final int MONITOR_CURRENTLY_AVAILABLE = 0; // 0x0
     field public static final int MONITOR_CURRENTLY_UNAVAILABLE = 1; // 0x1
@@ -16292,10 +16311,12 @@
   public class ConnectivityManager {
     method public android.net.NetworkInfo getActiveNetworkInfo();
     method public android.net.NetworkInfo[] getAllNetworkInfo();
+    method public android.net.Network[] getAllNetworks();
     method public deprecated boolean getBackgroundDataSetting();
     method public android.net.LinkProperties getLinkProperties(android.net.Network);
     method public android.net.NetworkCapabilities getNetworkCapabilities(android.net.Network);
     method public android.net.NetworkInfo getNetworkInfo(int);
+    method public android.net.NetworkInfo getNetworkInfo(android.net.Network);
     method public deprecated int getNetworkPreference();
     method public static android.net.Network getProcessDefaultNetwork();
     method public boolean isActiveNetworkMetered();
@@ -16499,6 +16520,7 @@
     field public static final int NET_CAPABILITY_MMS = 0; // 0x0
     field public static final int NET_CAPABILITY_NOT_METERED = 11; // 0xb
     field public static final int NET_CAPABILITY_NOT_RESTRICTED = 13; // 0xd
+    field public static final int NET_CAPABILITY_NOT_VPN = 15; // 0xf
     field public static final int NET_CAPABILITY_RCS = 8; // 0x8
     field public static final int NET_CAPABILITY_SUPL = 1; // 0x1
     field public static final int NET_CAPABILITY_TRUSTED = 14; // 0xe
@@ -16507,6 +16529,7 @@
     field public static final int TRANSPORT_BLUETOOTH = 2; // 0x2
     field public static final int TRANSPORT_CELLULAR = 0; // 0x0
     field public static final int TRANSPORT_ETHERNET = 3; // 0x3
+    field public static final int TRANSPORT_VPN = 4; // 0x4
     field public static final int TRANSPORT_WIFI = 1; // 0x1
   }
 
@@ -16570,6 +16593,7 @@
     method public android.net.NetworkRequest build();
     method public android.net.NetworkRequest.Builder removeCapability(int);
     method public android.net.NetworkRequest.Builder removeTransportType(int);
+    method public android.net.NetworkRequest.Builder setNetworkSpecifier(java.lang.String);
   }
 
   public abstract interface PSKKeyManager {
@@ -16816,12 +16840,14 @@
 
   public class VpnService extends android.app.Service {
     ctor public VpnService();
+    method public boolean addAddress(java.net.InetAddress, int);
     method public android.os.IBinder onBind(android.content.Intent);
     method public void onRevoke();
     method public static android.content.Intent prepare(android.content.Context);
     method public boolean protect(int);
     method public boolean protect(java.net.Socket);
     method public boolean protect(java.net.DatagramSocket);
+    method public boolean removeAddress(java.net.InetAddress, int);
     field public static final java.lang.String SERVICE_INTERFACE = "android.net.VpnService";
   }
 
@@ -16829,11 +16855,15 @@
     ctor public VpnService.Builder();
     method public android.net.VpnService.Builder addAddress(java.net.InetAddress, int);
     method public android.net.VpnService.Builder addAddress(java.lang.String, int);
+    method public android.net.VpnService.Builder addAllowedApplication(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
+    method public android.net.VpnService.Builder addDisallowedApplication(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
     method public android.net.VpnService.Builder addDnsServer(java.net.InetAddress);
     method public android.net.VpnService.Builder addDnsServer(java.lang.String);
     method public android.net.VpnService.Builder addRoute(java.net.InetAddress, int);
     method public android.net.VpnService.Builder addRoute(java.lang.String, int);
     method public android.net.VpnService.Builder addSearchDomain(java.lang.String);
+    method public android.net.VpnService.Builder allowBypass();
+    method public android.net.VpnService.Builder allowFamily(int);
     method public android.os.ParcelFileDescriptor establish();
     method public android.net.VpnService.Builder setConfigureIntent(android.app.PendingIntent);
     method public android.net.VpnService.Builder setMtu(int);
@@ -22956,9 +22986,14 @@
     field public static final android.net.Uri CONTENT_URI;
     field public static final android.net.Uri CONTENT_URI_WITH_VOICEMAIL;
     field public static final java.lang.String COUNTRY_ISO = "countryiso";
+    field public static final java.lang.String DATA_USAGE = "data_usage";
     field public static final java.lang.String DATE = "date";
     field public static final java.lang.String DEFAULT_SORT_ORDER = "date DESC";
     field public static final java.lang.String DURATION = "duration";
+    field public static final java.lang.String EXTRA_CALL_TYPE_FILTER = "extra_call_type_filter";
+    field public static final java.lang.String FEATURES = "features";
+    field public static final int FEATURES_NONE = 0; // 0x0
+    field public static final int FEATURES_VIDEO = 1; // 0x1
     field public static final java.lang.String GEOCODED_LOCATION = "geocoded_location";
     field public static final int INCOMING_TYPE = 1; // 0x1
     field public static final java.lang.String IS_READ = "is_read";
@@ -23580,6 +23615,7 @@
   public static class ContactsContract.Contacts implements android.provider.BaseColumns android.provider.ContactsContract.ContactNameColumns android.provider.ContactsContract.ContactOptionsColumns android.provider.ContactsContract.ContactStatusColumns android.provider.ContactsContract.ContactsColumns {
     method public static android.net.Uri getLookupUri(android.content.ContentResolver, android.net.Uri);
     method public static android.net.Uri getLookupUri(long, java.lang.String);
+    method public static boolean isCorpContactId(long);
     method public static android.net.Uri lookupContact(android.content.ContentResolver, android.net.Uri);
     method public static deprecated void markAsContacted(android.content.ContentResolver, long);
     method public static java.io.InputStream openContactPhotoInputStream(android.content.ContentResolver, android.net.Uri, boolean);
@@ -26313,16 +26349,17 @@
   }
 
   public static class NotificationListenerService.Ranking {
+    ctor public NotificationListenerService.Ranking();
     method public java.lang.String getKey();
     method public int getRank();
     method public boolean isAmbient();
-    method public boolean isInterceptedByDoNotDisturb();
+    method public boolean meetsInterruptionFilter();
   }
 
   public static class NotificationListenerService.RankingMap implements android.os.Parcelable {
     method public int describeContents();
     method public java.lang.String[] getOrderedKeys();
-    method public android.service.notification.NotificationListenerService.Ranking getRanking(java.lang.String);
+    method public boolean getRanking(java.lang.String, android.service.notification.NotificationListenerService.Ranking);
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator CREATOR;
   }
@@ -27441,6 +27478,8 @@
     field public static final int MERGE_CALLS = 4; // 0x4
     field public static final int MUTE = 64; // 0x40
     field public static final int RESPOND_VIA_TEXT = 32; // 0x20
+    field public static final int SUPPORTS_VT_LOCAL = 256; // 0x100
+    field public static final int SUPPORTS_VT_REMOTE = 512; // 0x200
     field public static final int SUPPORT_HOLD = 2; // 0x2
     field public static final int SWAP_CALLS = 8; // 0x8
   }
@@ -27539,6 +27578,7 @@
     method public final boolean getAudioModeIsVoip();
     method public final android.telecomm.CallAudioState getCallAudioState();
     method public final int getCallCapabilities();
+    method public final android.telecomm.CallVideoProvider getCallVideoProvider();
     method public final java.util.List<android.telecomm.Connection> getChildConnections();
     method public final int getFeatures();
     method public final android.net.Uri getHandle();
@@ -36073,11 +36113,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();
   }
 
@@ -37276,6 +37318,7 @@
     method public int getAnimationStyle();
     method public android.graphics.drawable.Drawable getBackground();
     method public android.view.View getContentView();
+    method public float getElevation();
     method public int getHeight();
     method public int getInputMethodMode();
     method public int getMaxAvailableHeight(android.view.View);
@@ -37293,6 +37336,7 @@
     method public void setBackgroundDrawable(android.graphics.drawable.Drawable);
     method public void setClippingEnabled(boolean);
     method public void setContentView(android.view.View);
+    method public void setElevation(float);
     method public void setFocusable(boolean);
     method public void setHeight(int);
     method public void setIgnoreCheekPress();
@@ -38303,6 +38347,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();
@@ -38324,6 +38369,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/ContextImpl.java b/core/java/android/app/ContextImpl.java
index cacb2df..bbfb05e 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -565,9 +565,7 @@
 
         registerService(PHONE_SERVICE, new ServiceFetcher() {
                 public Object createService(ContextImpl ctx) {
-                    IBinder b = ServiceManager.getService(TELECOMM_SERVICE);
-                    return new PhoneManager(ctx.getOuterContext(),
-                            ITelecommService.Stub.asInterface(b));
+                    return new PhoneManager(ctx.getOuterContext());
                 }});
 
         registerService(UI_MODE_SERVICE, new ServiceFetcher() {
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/content/Context.java b/core/java/android/content/Context.java
index 04275d8..535aaa1 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -2422,6 +2422,7 @@
      * @see android.net.wifi.WifiScanner
      * @hide
      */
+    @SystemApi
     public static final String WIFI_SCANNING_SERVICE = "wifiscanner";
 
     /**
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index 44e3f76..4b1659f 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -1680,14 +1680,17 @@
      * <p>This tag specifies the zero light value for each of the CFA mosaic
      * channels in the camera sensor.  The maximal value output by the
      * sensor is represented by the value in {@link CameraCharacteristics#SENSOR_INFO_WHITE_LEVEL android.sensor.info.whiteLevel}.</p>
-     * <p>The values are given in row-column scan order, with the first value
-     * corresponding to the element of the CFA in row=0, column=0.</p>
+     * <p>The values are given in the same order as channels listed for the CFA
+     * layout tag (see {@link CameraCharacteristics#SENSOR_INFO_COLOR_FILTER_ARRANGEMENT android.sensor.info.colorFilterArrangement}), i.e. the
+     * nth value given corresponds to the black level offset for the nth
+     * color channel listed in the CFA.</p>
      * <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
      *
+     * @see CameraCharacteristics#SENSOR_INFO_COLOR_FILTER_ARRANGEMENT
      * @see CameraCharacteristics#SENSOR_INFO_WHITE_LEVEL
      */
-    public static final Key<int[]> SENSOR_BLACK_LEVEL_PATTERN =
-            new Key<int[]>("android.sensor.blackLevelPattern", int[].class);
+    public static final Key<android.hardware.camera2.params.BlackLevelPattern> SENSOR_BLACK_LEVEL_PATTERN =
+            new Key<android.hardware.camera2.params.BlackLevelPattern>("android.sensor.blackLevelPattern", android.hardware.camera2.params.BlackLevelPattern.class);
 
     /**
      * <p>Maximum sensitivity that is implemented
diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java
index 6eeeff2..fa35f44 100644
--- a/core/java/android/hardware/camera2/CameraMetadata.java
+++ b/core/java/android/hardware/camera2/CameraMetadata.java
@@ -1131,7 +1131,8 @@
      * image while recording video) use case.</p>
      * <p>The camera device should take the highest-quality image
      * possible (given the other settings) without disrupting the
-     * frame rate of video recording.  </p>
+     * frame rate of video recording.<br />
+     * </p>
      * @see CaptureRequest#CONTROL_CAPTURE_INTENT
      */
     public static final int CONTROL_CAPTURE_INTENT_VIDEO_SNAPSHOT = 4;
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/impl/CameraMetadataNative.java b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
index 83aee5d..6de5c25 100644
--- a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
+++ b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
@@ -27,6 +27,7 @@
 import android.hardware.camera2.marshal.MarshalRegistry;
 import android.hardware.camera2.marshal.impl.MarshalQueryableArray;
 import android.hardware.camera2.marshal.impl.MarshalQueryableBoolean;
+import android.hardware.camera2.marshal.impl.MarshalQueryableBlackLevelPattern;
 import android.hardware.camera2.marshal.impl.MarshalQueryableColorSpaceTransform;
 import android.hardware.camera2.marshal.impl.MarshalQueryableEnum;
 import android.hardware.camera2.marshal.impl.MarshalQueryableMeteringRectangle;
@@ -1013,6 +1014,7 @@
                 new MarshalQueryableStreamConfiguration(),
                 new MarshalQueryableStreamConfigurationDuration(),
                 new MarshalQueryableRggbChannelVector(),
+                new MarshalQueryableBlackLevelPattern(),
 
                 // generic parcelable marshaler (MUST BE LAST since it has lowest priority)
                 new MarshalQueryableParcelable(),
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/camera2/marshal/impl/MarshalQueryableBlackLevelPattern.java b/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableBlackLevelPattern.java
new file mode 100644
index 0000000..bcb035e
--- /dev/null
+++ b/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableBlackLevelPattern.java
@@ -0,0 +1,76 @@
+/*
+ * 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.marshal.impl;
+
+import android.hardware.camera2.marshal.MarshalQueryable;
+import android.hardware.camera2.marshal.Marshaler;
+import android.hardware.camera2.params.BlackLevelPattern;
+import android.hardware.camera2.utils.TypeReference;
+
+import java.nio.ByteBuffer;
+
+import static android.hardware.camera2.impl.CameraMetadataNative.TYPE_INT32;
+import static android.hardware.camera2.marshal.MarshalHelpers.SIZEOF_INT32;
+
+/**
+ * Marshal {@link BlackLevelPattern} to/from {@link #TYPE_INT32} {@code x 4}
+ */
+public class MarshalQueryableBlackLevelPattern implements MarshalQueryable<BlackLevelPattern> {
+    private static final int SIZE = SIZEOF_INT32 * BlackLevelPattern.COUNT;
+
+    private class MarshalerBlackLevelPattern extends Marshaler<BlackLevelPattern> {
+        protected MarshalerBlackLevelPattern(TypeReference<BlackLevelPattern> typeReference,
+                                               int nativeType) {
+            super(MarshalQueryableBlackLevelPattern.this, typeReference, nativeType);
+        }
+
+        @Override
+        public void marshal(BlackLevelPattern value, ByteBuffer buffer) {
+            for (int i = 0; i < BlackLevelPattern.COUNT / 2; ++i) {
+                for (int j = 0; j < BlackLevelPattern.COUNT / 2; ++j) {
+                    buffer.putInt(value.getOffsetForIndex(j, i));
+                }
+            }
+        }
+
+        @Override
+        public BlackLevelPattern unmarshal(ByteBuffer buffer) {
+            int[] channelOffsets = new int[BlackLevelPattern.COUNT];
+            for (int i = 0; i < BlackLevelPattern.COUNT; ++i) {
+                channelOffsets[i] = buffer.getInt();
+            }
+            return new BlackLevelPattern(channelOffsets);
+        }
+
+        @Override
+        public int getNativeSize() {
+            return SIZE;
+        }
+    }
+
+    @Override
+    public Marshaler<BlackLevelPattern> createMarshaler(
+            TypeReference<BlackLevelPattern> managedType, int nativeType) {
+        return new MarshalerBlackLevelPattern(managedType, nativeType);
+    }
+
+    @Override
+    public boolean isTypeMappingSupported(
+            TypeReference<BlackLevelPattern> managedType, int nativeType) {
+        return nativeType == TYPE_INT32 &&
+                (BlackLevelPattern.class.equals(managedType.getType()));
+    }
+}
diff --git a/core/java/android/hardware/camera2/params/BlackLevelPattern.java b/core/java/android/hardware/camera2/params/BlackLevelPattern.java
new file mode 100644
index 0000000..a09f3d9
--- /dev/null
+++ b/core/java/android/hardware/camera2/params/BlackLevelPattern.java
@@ -0,0 +1,128 @@
+/*
+ * 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.params;
+
+import java.util.Arrays;
+
+import static com.android.internal.util.Preconditions.checkNotNull;
+
+/**
+ * Immutable class to store a 4-element vector of integers corresponding to a 2x2 pattern
+ * of color channel offsets used for the black level offsets of each color channel.
+ */
+public final class BlackLevelPattern {
+
+    /**
+     * The number of offsets in this vector.
+     */
+    public static final int COUNT = 4;
+
+    /**
+     * Create a new {@link BlackLevelPattern} from a given offset array.
+     *
+     * <p>The given offset array must contain offsets for each color channel in
+     * a 2x2 pattern corresponding to the color filter arrangement.  Offsets are
+     * given in row-column scan order.</p>
+     *
+     * @param offsets an array containing a 2x2 pattern of offsets.
+     *
+     * @throws IllegalArgumentException if the given array has an incorrect length.
+     * @throws NullPointerException if the given array is null.
+     * @hide
+     */
+    public BlackLevelPattern(int[] offsets) {
+        if (offsets == null) {
+            throw new NullPointerException("Null offsets array passed to constructor");
+        }
+        if (offsets.length < COUNT) {
+            throw new IllegalArgumentException("Invalid offsets array length");
+        }
+        mCfaOffsets = Arrays.copyOf(offsets, COUNT);
+    }
+
+    /**
+     * Return the color channel offset for a given index into the array of raw pixel values.
+     *
+     * @param column the column index in the the raw pixel array.
+     * @param row the row index in the raw pixel array.
+     * @return a color channel offset.
+     *
+     * @throws IllegalArgumentException if a column or row given is negative.
+     */
+    public int getOffsetForIndex(int column, int row) {
+        if (row < 0 || column < 0) {
+            throw new IllegalArgumentException("column, row arguments must be positive");
+        }
+        return mCfaOffsets[((row & 1) << 1) | (column & 1)];
+    }
+
+    /**
+     * Copy the ColorChannel offsets into the destination vector.
+     *
+     * <p>Offsets are given in row-column scan order for a given 2x2 color pattern.</p>
+     *
+     * @param destination an array big enough to hold at least {@value #COUNT} elements after the
+     *          {@code offset}
+     * @param offset a non-negative offset into the array
+     *
+     * @throws IllegalArgumentException if the offset is invalid.
+     * @throws ArrayIndexOutOfBoundsException if the destination vector is too small.
+     * @throws NullPointerException if the destination is null.
+     */
+    public void copyTo(int[] destination, int offset) {
+        checkNotNull(destination, "destination must not be null");
+        if (offset < 0) {
+            throw new IllegalArgumentException("Null offset passed to copyTo");
+        }
+        if (destination.length - offset < COUNT) {
+            throw new ArrayIndexOutOfBoundsException("destination too small to fit elements");
+        }
+        for (int i = 0; i < COUNT; ++i) {
+            destination[offset + i] = mCfaOffsets[i];
+        }
+    }
+
+    /**
+     * Check if this {@link BlackLevelPattern} is equal to another {@link BlackLevelPattern}.
+     *
+     * <p>Two vectors are only equal if and only if each of the respective elements is equal.</p>
+     *
+     * @return {@code true} if the objects were equal, {@code false} otherwise
+     */
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == null) {
+            return false;
+        } else if (this == obj) {
+            return true;
+        } else if (obj instanceof BlackLevelPattern) {
+            final BlackLevelPattern other = (BlackLevelPattern) obj;
+            return Arrays.equals(other.mCfaOffsets, mCfaOffsets);
+        }
+        return false;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int hashCode() {
+        return Arrays.hashCode(mCfaOffsets);
+    }
+
+    private final int[] mCfaOffsets;
+}
diff --git a/core/java/android/hardware/camera2/params/RggbChannelVector.java b/core/java/android/hardware/camera2/params/RggbChannelVector.java
index 30591f6..cf3e1de 100644
--- a/core/java/android/hardware/camera2/params/RggbChannelVector.java
+++ b/core/java/android/hardware/camera2/params/RggbChannelVector.java
@@ -146,7 +146,7 @@
      */
     public void copyTo(final float[] destination, final int offset) {
         checkNotNull(destination, "destination must not be null");
-        if (destination.length + offset < COUNT) {
+        if (destination.length - offset < COUNT) {
             throw new ArrayIndexOutOfBoundsException("destination too small to fit elements");
         }
 
@@ -167,11 +167,9 @@
     public boolean equals(final Object obj) {
         if (obj == null) {
             return false;
-        }
-        if (this == obj) {
+        } else if (this == obj) {
             return true;
-        }
-        if (obj instanceof RggbChannelVector) {
+        } else if (obj instanceof RggbChannelVector) {
             final RggbChannelVector other = (RggbChannelVector) obj;
             return mRed == other.mRed &&
                     mGreenEven == other.mGreenEven &&
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/hardware/location/GeofenceHardware.java b/core/java/android/hardware/location/GeofenceHardware.java
index 4c074e9..2d7b7e1 100644
--- a/core/java/android/hardware/location/GeofenceHardware.java
+++ b/core/java/android/hardware/location/GeofenceHardware.java
@@ -56,8 +56,6 @@
 
     /**
      * Constant for geofence monitoring done by the Fused hardware.
-     *
-     * @hide
      */
     public static final int MONITORING_TYPE_FUSED_HARDWARE = 1;
 
@@ -128,8 +126,6 @@
 
     /**
      * The constant used to indicate that the operation failed due to insufficient memory.
-     *
-     * @hide
      */
     public static final int GEOFENCE_ERROR_INSUFFICIENT_MEMORY = 6;
 
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index fb4912f..a7e03fc 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -743,7 +743,7 @@
      *        network type or {@code null} if the type is not
      *        supported by the device.
      *
-     * <p>This method requires the call to hold the permission
+     * <p>This method requires the caller to hold the permission
      * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
      */
     public NetworkInfo getNetworkInfo(int networkType) {
@@ -755,13 +755,34 @@
     }
 
     /**
+     * Returns connection status information about a particular
+     * Network.
+     *
+     * @param network {@link Network} specifying which network
+     *        in which you're interested.
+     * @return a {@link NetworkInfo} object for the requested
+     *        network or {@code null} if the {@code Network}
+     *        is not valid.
+     *
+     * <p>This method requires the caller to hold the permission
+     * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
+     */
+    public NetworkInfo getNetworkInfo(Network network) {
+        try {
+            return mService.getNetworkInfoForNetwork(network);
+        } catch (RemoteException e) {
+            return null;
+        }
+    }
+
+    /**
      * Returns connection status information about all network
      * types supported by the device.
      *
      * @return an array of {@link NetworkInfo} objects.  Check each
      * {@link NetworkInfo#getType} for which type each applies.
      *
-     * <p>This method requires the call to hold the permission
+     * <p>This method requires the caller to hold the permission
      * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
      */
     public NetworkInfo[] getAllNetworkInfo() {
@@ -773,6 +794,23 @@
     }
 
     /**
+     * Returns an array of all {@link Network} currently tracked by the
+     * framework.
+     *
+     * @return an array of {@link Network} objects.
+     *
+     * <p>This method requires the caller to hold the permission
+     * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
+     */
+    public Network[] getAllNetworks() {
+        try {
+            return mService.getAllNetworks();
+        } catch (RemoteException e) {
+            return null;
+        }
+    }
+
+    /**
      * Returns details about the Provisioning or currently active default data network. When
      * connected, this network is the default route for outgoing connections.
      * You should always check {@link NetworkInfo#isConnected()} before initiating
@@ -1462,6 +1500,20 @@
     }
 
     /**
+     * Get the set of tethered dhcp ranges.
+     *
+     * @return an array of 0 or more {@code String} of tethered dhcp ranges.
+     * {@hide}
+     */
+    public String[] getTetheredDhcpRanges() {
+        try {
+            return mService.getTetheredDhcpRanges();
+        } catch (RemoteException e) {
+            return new String[0];
+        }
+    }
+
+    /**
      * Attempt to tether the named interface.  This will setup a dhcp server
      * on the interface, forward and NAT IP packets and forward DNS requests
      * to the best active upstream network interface.  Note that if no upstream
diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl
index 51d9b8c..b9c6491 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -48,7 +48,9 @@
     NetworkInfo getActiveNetworkInfo();
     NetworkInfo getActiveNetworkInfoForUid(int uid);
     NetworkInfo getNetworkInfo(int networkType);
+    NetworkInfo getNetworkInfoForNetwork(in Network network);
     NetworkInfo[] getAllNetworkInfo();
+    Network[] getAllNetworks();
 
     NetworkInfo getProvisioningOrActiveNetworkInfo();
 
@@ -91,6 +93,8 @@
 
     String[] getTetheringErroredIfaces();
 
+    String[] getTetheredDhcpRanges();
+
     String[] getTetherableUsbRegexs();
 
     String[] getTetherableWifiRegexs();
@@ -111,8 +115,6 @@
 
     void setDataDependency(int networkType, boolean met);
 
-    boolean protectVpn(in ParcelFileDescriptor socket);
-
     boolean prepareVpn(String oldPackage, String newPackage);
 
     ParcelFileDescriptor establishVpn(in VpnConfig config);
diff --git a/core/java/android/net/NetworkAgent.java b/core/java/android/net/NetworkAgent.java
index 3d0874b..41eab02 100644
--- a/core/java/android/net/NetworkAgent.java
+++ b/core/java/android/net/NetworkAgent.java
@@ -92,6 +92,20 @@
      */
     public static final int EVENT_NETWORK_SCORE_CHANGED = BASE + 4;
 
+    /**
+     * Sent by the NetworkAgent to ConnectivityService to add new UID ranges
+     * to be forced into this Network.  For VPNs only.
+     * obj = UidRange[] to forward
+     */
+    public static final int EVENT_UID_RANGES_ADDED = BASE + 5;
+
+    /**
+     * Sent by the NetworkAgent to ConnectivityService to remove UID ranges
+     * from being forced into this Network.  For VPNs only.
+     * obj = UidRange[] to stop forwarding
+     */
+    public static final int EVENT_UID_RANGES_REMOVED = BASE + 6;
+
     public NetworkAgent(Looper looper, Context context, String logTag, NetworkInfo ni,
             NetworkCapabilities nc, LinkProperties lp, int score) {
         super(looper);
@@ -194,6 +208,22 @@
     }
 
     /**
+     * Called by the VPN code when it wants to add ranges of UIDs to be routed
+     * through the VPN network.
+     */
+    public void addUidRanges(UidRange[] ranges) {
+        queueOrSendMessage(EVENT_UID_RANGES_ADDED, ranges);
+    }
+
+    /**
+     * Called by the VPN code when it wants to remove ranges of UIDs from being routed
+     * through the VPN network.
+     */
+    public void removeUidRanges(UidRange[] ranges) {
+        queueOrSendMessage(EVENT_UID_RANGES_REMOVED, ranges);
+    }
+
+    /**
      * Called when ConnectivityService has indicated they no longer want this network.
      * The parent factory should (previously) have received indication of the change
      * as well, either canceling NetworkRequests or altering their score such that this
diff --git a/core/java/android/net/NetworkCapabilities.java b/core/java/android/net/NetworkCapabilities.java
index 00200d0..53f9fcd 100644
--- a/core/java/android/net/NetworkCapabilities.java
+++ b/core/java/android/net/NetworkCapabilities.java
@@ -18,6 +18,7 @@
 
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.text.TextUtils;
 import android.util.Log;
 
 import java.lang.IllegalArgumentException;
@@ -56,6 +57,7 @@
             mTransportTypes = nc.mTransportTypes;
             mLinkUpBandwidthKbps = nc.mLinkUpBandwidthKbps;
             mLinkDownBandwidthKbps = nc.mLinkDownBandwidthKbps;
+            mNetworkSpecifier = nc.mNetworkSpecifier;
         }
     }
 
@@ -64,7 +66,7 @@
      * by any Network that matches all of them.
      */
     private long mNetworkCapabilities = (1 << NET_CAPABILITY_NOT_RESTRICTED) |
-            (1 << NET_CAPABILITY_TRUSTED);
+            (1 << NET_CAPABILITY_TRUSTED) | (1 << NET_CAPABILITY_NOT_VPN);
 
     /**
      * Indicates this is a network that has the ability to reach the
@@ -158,9 +160,15 @@
      */
     public static final int NET_CAPABILITY_TRUSTED        = 14;
 
+    /*
+     * Indicates that this network is not a VPN.  This capability is set by default and should be
+     * explicitly cleared when creating VPN networks.
+     */
+    public static final int NET_CAPABILITY_NOT_VPN        = 15;
+
 
     private static final int MIN_NET_CAPABILITY = NET_CAPABILITY_MMS;
-    private static final int MAX_NET_CAPABILITY = NET_CAPABILITY_TRUSTED;
+    private static final int MAX_NET_CAPABILITY = NET_CAPABILITY_NOT_VPN;
 
     /**
      * Adds the given capability to this {@code NetworkCapability} instance.
@@ -271,8 +279,13 @@
      */
     public static final int TRANSPORT_ETHERNET = 3;
 
+    /**
+     * Indicates this network uses a VPN transport.
+     */
+    public static final int TRANSPORT_VPN = 4;
+
     private static final int MIN_TRANSPORT = TRANSPORT_CELLULAR;
-    private static final int MAX_TRANSPORT = TRANSPORT_ETHERNET;
+    private static final int MAX_TRANSPORT = TRANSPORT_VPN;
 
     /**
      * Adds the given transport type to this {@code NetworkCapability} instance.
@@ -292,6 +305,7 @@
             throw new IllegalArgumentException("TransportType out of range");
         }
         mTransportTypes |= 1 << transportType;
+        setNetworkSpecifier(mNetworkSpecifier); // used for exception checking
         return this;
     }
 
@@ -307,6 +321,7 @@
             throw new IllegalArgumentException("TransportType out of range");
         }
         mTransportTypes &= ~(1 << transportType);
+        setNetworkSpecifier(mNetworkSpecifier); // used for exception checking
         return this;
     }
 
@@ -426,6 +441,62 @@
                 this.mLinkDownBandwidthKbps == nc.mLinkDownBandwidthKbps);
     }
 
+    private String mNetworkSpecifier;
+    /**
+     * Sets the optional bearer specific network specifier.
+     * This has no meaning if a single transport is also not specified, so calling
+     * this without a single transport set will generate an exception, as will
+     * subsequently adding or removing transports after this is set.
+     * </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 (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
+     *                         networks.
+     * @hide
+     */
+    public void setNetworkSpecifier(String networkSpecifier) {
+        if (TextUtils.isEmpty(networkSpecifier) == false && Long.bitCount(mTransportTypes) != 1) {
+            throw new IllegalStateException("Must have a single transport specified to use " +
+                    "setNetworkSpecifier");
+        }
+        mNetworkSpecifier = networkSpecifier;
+    }
+
+    /**
+     * Gets the optional bearer specific network specifier.
+     *
+     * @return The optional {@code String} specifying the bearer specific network specifier.
+     *         See {@link #setNetworkSpecifier}.
+     * @hide
+     */
+    public String getNetworkSpecifier() {
+        return mNetworkSpecifier;
+    }
+
+    private void combineSpecifiers(NetworkCapabilities nc) {
+        String otherSpecifier = nc.getNetworkSpecifier();
+        if (TextUtils.isEmpty(otherSpecifier)) return;
+        if (TextUtils.isEmpty(mNetworkSpecifier) == false) {
+            throw new IllegalStateException("Can't combine two networkSpecifiers");
+        }
+        setNetworkSpecifier(otherSpecifier);
+    }
+    private boolean satisfiedBySpecifier(NetworkCapabilities nc) {
+        return (TextUtils.isEmpty(mNetworkSpecifier) ||
+                mNetworkSpecifier.equals(nc.mNetworkSpecifier));
+    }
+    private boolean equalsSpecifier(NetworkCapabilities nc) {
+        if (TextUtils.isEmpty(mNetworkSpecifier)) {
+            return TextUtils.isEmpty(nc.mNetworkSpecifier);
+        } else {
+            return mNetworkSpecifier.equals(nc.mNetworkSpecifier);
+        }
+    }
+
     /**
      * Combine a set of Capabilities to this one.  Useful for coming up with the complete set
      * {@hide}
@@ -434,6 +505,7 @@
         combineNetCapabilities(nc);
         combineTransportTypes(nc);
         combineLinkBandwidths(nc);
+        combineSpecifiers(nc);
     }
 
     /**
@@ -444,7 +516,8 @@
         return (nc != null &&
                 satisfiedByNetCapabilities(nc) &&
                 satisfiedByTransportTypes(nc) &&
-                satisfiedByLinkBandwidths(nc));
+                satisfiedByLinkBandwidths(nc) &&
+                satisfiedBySpecifier(nc));
     }
 
     @Override
@@ -453,7 +526,8 @@
         NetworkCapabilities that = (NetworkCapabilities)obj;
         return (equalsNetCapabilities(that) &&
                 equalsTransportTypes(that) &&
-                equalsLinkBandwidths(that));
+                equalsLinkBandwidths(that) &&
+                equalsSpecifier(that));
     }
 
     @Override
@@ -463,7 +537,8 @@
                 ((int)(mTransportTypes & 0xFFFFFFFF) * 5) +
                 ((int)(mTransportTypes >> 32) * 7) +
                 (mLinkUpBandwidthKbps * 11) +
-                (mLinkDownBandwidthKbps * 13));
+                (mLinkDownBandwidthKbps * 13) +
+                (TextUtils.isEmpty(mNetworkSpecifier) ? 0 : mNetworkSpecifier.hashCode() * 17));
     }
 
     public int describeContents() {
@@ -474,6 +549,7 @@
         dest.writeLong(mTransportTypes);
         dest.writeInt(mLinkUpBandwidthKbps);
         dest.writeInt(mLinkDownBandwidthKbps);
+        dest.writeString(mNetworkSpecifier);
     }
     public static final Creator<NetworkCapabilities> CREATOR =
         new Creator<NetworkCapabilities>() {
@@ -484,6 +560,7 @@
                 netCap.mTransportTypes = in.readLong();
                 netCap.mLinkUpBandwidthKbps = in.readInt();
                 netCap.mLinkDownBandwidthKbps = in.readInt();
+                netCap.mNetworkSpecifier = in.readString();
                 return netCap;
             }
             public NetworkCapabilities[] newArray(int size) {
@@ -500,6 +577,7 @@
                 case TRANSPORT_WIFI:        transports += "WIFI"; break;
                 case TRANSPORT_BLUETOOTH:   transports += "BLUETOOTH"; break;
                 case TRANSPORT_ETHERNET:    transports += "ETHERNET"; break;
+                case TRANSPORT_VPN:         transports += "VPN"; break;
             }
             if (++i < types.length) transports += "|";
         }
@@ -523,6 +601,7 @@
                 case NET_CAPABILITY_INTERNET:       capabilities += "INTERNET"; break;
                 case NET_CAPABILITY_NOT_RESTRICTED: capabilities += "NOT_RESTRICTED"; break;
                 case NET_CAPABILITY_TRUSTED:        capabilities += "TRUSTED"; break;
+                case NET_CAPABILITY_NOT_VPN:        capabilities += "NOT_VPN"; break;
             }
             if (++i < types.length) capabilities += "&";
         }
@@ -532,6 +611,9 @@
         String dnBand = ((mLinkDownBandwidthKbps > 0) ? " LinkDnBandwidth>=" +
                 mLinkDownBandwidthKbps + "Kbps" : "");
 
-        return "[" + transports + capabilities + upBand + dnBand + "]";
+        String specifier = (mNetworkSpecifier == null ?
+                "" : " Specifier: <" + mNetworkSpecifier + ">");
+
+        return "[" + transports + capabilities + upBand + dnBand + specifier + "]";
     }
 }
diff --git a/core/java/android/net/NetworkRequest.java b/core/java/android/net/NetworkRequest.java
index 36dc573..83bdfaa 100644
--- a/core/java/android/net/NetworkRequest.java
+++ b/core/java/android/net/NetworkRequest.java
@@ -153,6 +153,25 @@
             mNetworkCapabilities.setLinkDownstreamBandwidthKbps(downKbps);
             return this;
         }
+
+        /**
+         * Sets the optional bearer specific network specifier.
+         * This has no meaning if a single transport is also not specified, so calling
+         * this without a single transport set will generate an exception, as will
+         * subsequently adding or removing transports after this is set.
+         * </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.
+         *
+         * @param networkSpecifier An {@code String} of opaque format used to specify the bearer
+         *                         specific network specifier where the bearer has a choice of
+         *                         networks.
+         */
+        public Builder setNetworkSpecifier(String networkSpecifier) {
+            mNetworkCapabilities.setNetworkSpecifier(networkSpecifier);
+            return this;
+        }
     }
 
     // implement the Parcelable interface
diff --git a/core/java/android/net/NetworkUtils.java b/core/java/android/net/NetworkUtils.java
index c4b17b6..aa1e123 100644
--- a/core/java/android/net/NetworkUtils.java
+++ b/core/java/android/net/NetworkUtils.java
@@ -155,6 +155,13 @@
     public native static boolean bindSocketToNetwork(int socketfd, int netId);
 
     /**
+     * Protect {@code socketfd} from VPN connections.  After protecting, data sent through
+     * this socket will go directly to the underlying network, so its traffic will not be
+     * forwarded through the VPN.
+     */
+    public native static boolean protectFromVpn(int socketfd);
+
+    /**
      * Convert a IPv4 address from an integer to an InetAddress.
      * @param hostAddress an int corresponding to the IPv4 address in network byte order
      */
diff --git a/core/java/android/net/UidRange.aidl b/core/java/android/net/UidRange.aidl
new file mode 100644
index 0000000..f9be628
--- /dev/null
+++ b/core/java/android/net/UidRange.aidl
@@ -0,0 +1,24 @@
+/*
+ * 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.net;
+
+/**
+ * An inclusive range of UIDs.
+ *
+ * {@hide}
+ */
+parcelable UidRange;
diff --git a/core/java/android/net/UidRange.java b/core/java/android/net/UidRange.java
new file mode 100644
index 0000000..2e586b3
--- /dev/null
+++ b/core/java/android/net/UidRange.java
@@ -0,0 +1,102 @@
+/*
+ * 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.net;
+
+import static android.os.UserHandle.PER_USER_RANGE;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.IllegalArgumentException;
+
+/**
+ * An inclusive range of UIDs.
+ *
+ * @hide
+ */
+public final class UidRange implements Parcelable {
+    public final int start;
+    public final int stop;
+
+    public UidRange(int startUid, int stopUid) {
+        if (startUid < 0) throw new IllegalArgumentException("Invalid start UID.");
+        if (stopUid < 0) throw new IllegalArgumentException("Invalid stop UID.");
+        if (startUid > stopUid) throw new IllegalArgumentException("Invalid UID range.");
+        start = startUid;
+        stop  = stopUid;
+    }
+
+    public static UidRange createForUser(int userId) {
+        return new UidRange(userId * PER_USER_RANGE, (userId + 1) * PER_USER_RANGE - 1);
+    }
+
+    public int getStartUser() {
+        return start / PER_USER_RANGE;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = 17;
+        result = 31 * result + start;
+        result = 31 * result + stop;
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o instanceof UidRange) {
+            UidRange other = (UidRange) o;
+            return start == other.start && stop == other.stop;
+        }
+        return false;
+    }
+
+    @Override
+    public String toString() {
+        return start + "-" + stop;
+    }
+
+    // implement the Parcelable interface
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(start);
+        dest.writeInt(stop);
+    }
+
+    public static final Creator<UidRange> CREATOR =
+        new Creator<UidRange>() {
+            @Override
+            public UidRange createFromParcel(Parcel in) {
+                int start = in.readInt();
+                int stop = in.readInt();
+
+                return new UidRange(start, stop);
+            }
+            @Override
+            public UidRange[] newArray(int size) {
+                return new UidRange[size];
+            }
+    };
+}
diff --git a/core/java/android/net/VpnService.java b/core/java/android/net/VpnService.java
index 7385dff..5d61de2 100644
--- a/core/java/android/net/VpnService.java
+++ b/core/java/android/net/VpnService.java
@@ -16,11 +16,16 @@
 
 package android.net;
 
+import static android.system.OsConstants.AF_INET;
+import static android.system.OsConstants.AF_INET6;
+
 import android.app.Activity;
 import android.app.PendingIntent;
 import android.app.Service;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.net.NetworkUtils;
 import android.os.Binder;
 import android.os.IBinder;
 import android.os.Parcel;
@@ -165,19 +170,7 @@
      * @return {@code true} on success.
      */
     public boolean protect(int socket) {
-        ParcelFileDescriptor dup = null;
-        try {
-            dup = ParcelFileDescriptor.fromFd(socket);
-            return getService().protectVpn(dup);
-        } catch (Exception e) {
-            return false;
-        } finally {
-            try {
-                dup.close();
-            } catch (Exception e) {
-                // ignore
-            }
-        }
+        return NetworkUtils.protectFromVpn(socket);
     }
 
     /**
@@ -202,6 +195,52 @@
     }
 
     /**
+     * Adds a network address to the VPN interface.
+     *
+     * Both IPv4 and IPv6 addresses are supported. The VPN must already be established. Fails if the
+     * address is already in use or cannot be assigned to the interface for any other reason.
+     *
+     * Adding an address implicitly allows traffic from that address family (i.e., IPv4 or IPv6) to
+     * be routed over the VPN. @see Builder#allowFamily
+     *
+     * @throws {@link IllegalArgumentException} if the address is invalid.
+     *
+     * @param address The IP address (IPv4 or IPv6) to assign to the VPN interface.
+     * @param prefixLength The prefix length of the address.
+     *
+     * @return {@code true} on success.
+     * @see Builder#addAddress
+     */
+    public boolean addAddress(InetAddress address, int prefixLength) {
+        // TODO
+        return true;
+    }
+
+    /**
+     * Removes a network address from the VPN interface.
+     *
+     * Both IPv4 and IPv6 addresses are supported. The VPN must already be established. Fails if the
+     * address is not assigned to the VPN interface, or if it is the only address assigned (thus
+     * cannot be removed), or if the address cannot be removed for any other reason.
+     *
+     * After removing an address, if there are no addresses, routes or DNS servers of a particular
+     * address family (i.e., IPv4 or IPv6) configured on the VPN, that <b>DOES NOT</b> block that
+     * family from being routed. In other words, once an address family has been allowed, it stays
+     * allowed for the rest of the VPN's session. @see Builder#allowFamily
+     *
+     * @throws {@link IllegalArgumentException} if the address is invalid.
+     *
+     * @param address The IP address (IPv4 or IPv6) to assign to the VPN interface.
+     * @param prefixLength The prefix length of the address.
+     *
+     * @return {@code true} on success.
+     */
+    public boolean removeAddress(InetAddress address, int prefixLength) {
+        // TODO
+        return true;
+    }
+
+    /**
      * Return the communication interface to the service. This method returns
      * {@code null} on {@link Intent}s other than {@link #SERVICE_INTERFACE}
      * action. Applications overriding this method must identify the intent
@@ -322,6 +361,9 @@
          * addresses are supported. At least one address must be set before
          * calling {@link #establish}.
          *
+         * Adding an address implicitly allows traffic from that address family
+         * (i.e., IPv4 or IPv6) to be routed over the VPN. @see #allowFamily
+         *
          * @throws IllegalArgumentException if the address is invalid.
          */
         public Builder addAddress(InetAddress address, int prefixLength) {
@@ -339,6 +381,9 @@
          * using a numeric address string. See {@link InetAddress} for the
          * definitions of numeric address formats.
          *
+         * Adding an address implicitly allows traffic from that address family
+         * (i.e., IPv4 or IPv6) to be routed over the VPN. @see #allowFamily
+         *
          * @throws IllegalArgumentException if the address is invalid.
          * @see #addAddress(InetAddress, int)
          */
@@ -350,6 +395,9 @@
          * Add a network route to the VPN interface. Both IPv4 and IPv6
          * routes are supported.
          *
+         * Adding a route implicitly allows traffic from that address family
+         * (i.e., IPv4 or IPv6) to be routed over the VPN. @see #allowFamily
+         *
          * @throws IllegalArgumentException if the route is invalid.
          */
         public Builder addRoute(InetAddress address, int prefixLength) {
@@ -373,6 +421,9 @@
          * using a numeric address string. See {@link InetAddress} for the
          * definitions of numeric address formats.
          *
+         * Adding a route implicitly allows traffic from that address family
+         * (i.e., IPv4 or IPv6) to be routed over the VPN. @see #allowFamily
+         *
          * @throws IllegalArgumentException if the route is invalid.
          * @see #addRoute(InetAddress, int)
          */
@@ -385,6 +436,9 @@
          * addresses are supported. If none is set, the DNS servers of
          * the default network will be used.
          *
+         * Adding a server implicitly allows traffic from that address family
+         * (i.e., IPv4 or IPv6) to be routed over the VPN. @see #allowFamily
+         *
          * @throws IllegalArgumentException if the address is invalid.
          */
         public Builder addDnsServer(InetAddress address) {
@@ -403,6 +457,9 @@
          * using a numeric address string. See {@link InetAddress} for the
          * definitions of numeric address formats.
          *
+         * Adding a server implicitly allows traffic from that address family
+         * (i.e., IPv4 or IPv6) to be routed over the VPN. @see #allowFamily
+         *
          * @throws IllegalArgumentException if the address is invalid.
          * @see #addDnsServer(InetAddress)
          */
@@ -422,6 +479,95 @@
         }
 
         /**
+         * Allows traffic from the specified address family.
+         *
+         * By default, if no address, route or DNS server of a specific family (IPv4 or IPv6) is
+         * added to this VPN, then all outgoing traffic of that family is blocked. If any address,
+         * route or DNS server is added, that family is allowed.
+         *
+         * This method allows an address family to be unblocked even without adding an address,
+         * route or DNS server of that family. Traffic of that family will then typically
+         * fall-through to the underlying network if it's supported.
+         *
+         * {@code family} must be either {@code AF_INET} (for IPv4) or {@code AF_INET6} (for IPv6).
+         * {@link IllegalArgumentException} is thrown if it's neither.
+         *
+         * @param family The address family ({@code AF_INET} or {@code AF_INET6}) to allow.
+         *
+         * @return this {@link Builder} object to facilitate chaining of method calls.
+         */
+        public Builder allowFamily(int family) {
+            // TODO
+            return this;
+        }
+
+        /**
+         * Adds an application that's allowed to access the VPN connection.
+         *
+         * If this method is called at least once, only applications added through this method (and
+         * no others) are allowed access. Else (if this method is never called), all applications
+         * are allowed by default.
+         *
+         * A {@link Builder} may have only a set of allowed applications OR a set of disallowed
+         * ones, but not both. Calling this method after {@link #addDisallowedApplication} has
+         * already been called, or vice versa, will throw an {@link UnsupportedOperationException}.
+         *
+         * {@code packageName} must be the canonical name of a currently installed application.
+         * {@link PackageManager.NameNotFoundException} is thrown if there's no such application.
+         *
+         * @throws {@link PackageManager.NameNotFoundException} If the application isn't installed.
+         *
+         * @param packageName The full name (e.g.: "com.google.apps.contacts") of an application.
+         *
+         * @return this {@link Builder} object to facilitate chaining method calls.
+         */
+        public Builder addAllowedApplication(String packageName)
+                throws PackageManager.NameNotFoundException {
+            // TODO
+            return this;
+        }
+
+        /**
+         * Adds an application that's denied access to the VPN connection.
+         *
+         * By default, all applications are allowed access, except for those denied through this
+         * method.
+         *
+         * A {@link Builder} may have only a set of allowed applications OR a set of disallowed
+         * ones, but not both. Calling this method after {@link #addAllowedApplication} has already
+         * been called, or vice versa, will throw an {@link UnsupportedOperationException}.
+         *
+         * {@code packageName} must be the canonical name of a currently installed application.
+         * {@link PackageManager.NameNotFoundException} is thrown if there's no such application.
+         *
+         * @throws {@link PackageManager.NameNotFoundException} If the application isn't installed.
+         *
+         * @param packageName The full name (e.g.: "com.google.apps.contacts") of an application.
+         *
+         * @return this {@link Builder} object to facilitate chaining method calls.
+         */
+        public Builder addDisallowedApplication(String packageName)
+                throws PackageManager.NameNotFoundException {
+            // TODO
+            return this;
+        }
+
+        /**
+         * Allows all apps to bypass this VPN connection.
+         *
+         * By default, all traffic from apps is forwarded through the VPN interface and it is not
+         * possible for apps to side-step the VPN. If this method is called, apps may use methods
+         * such as {@link ConnectivityManager#setProcessDefaultNetwork} to instead send/receive
+         * directly over the underlying network or any other network they have permissions for.
+         *
+         * @return this {@link Builder} object to facilitate chaining of method calls.
+         */
+        public Builder allowBypass() {
+            // TODO
+            return this;
+        }
+
+        /**
          * Create a VPN interface using the parameters supplied to this
          * builder. The interface works on IP packets, and a file descriptor
          * is returned for the application to access them. Each read
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/os/INetworkManagementService.aidl b/core/java/android/os/INetworkManagementService.aidl
index eb9ba13..d997e44 100644
--- a/core/java/android/os/INetworkManagementService.aidl
+++ b/core/java/android/os/INetworkManagementService.aidl
@@ -22,6 +22,7 @@
 import android.net.LinkAddress;
 import android.net.NetworkStats;
 import android.net.RouteInfo;
+import android.net.UidRange;
 import android.net.wifi.WifiConfiguration;
 import android.os.INetworkActivityListener;
 
@@ -325,28 +326,14 @@
     void setFirewallUidRule(int uid, boolean allow);
 
     /**
-     * Set all packets from users [uid_start,uid_end] to go through interface iface
-     * iface must already be set for marked forwarding by {@link setMarkedForwarding}
+     * Set all packets from users in ranges to go through VPN specified by netId.
      */
-    void setUidRangeRoute(String iface, int uid_start, int uid_end, boolean forward_dns);
+    void addVpnUidRanges(int netId, in UidRange[] ranges);
 
     /**
-     * Clears the special routing rules for users [uid_start,uid_end]
+     * Clears the special VPN rules for users in ranges and VPN specified by netId.
      */
-    void clearUidRangeRoute(String iface, int uid_start, int uid_end);
-
-    /**
-     * Setup an interface for routing packets marked by {@link setUidRangeRoute}
-     *
-     * This sets up a dedicated routing table for packets marked for {@code iface} and adds
-     * source-NAT rules so that the marked packets have the correct source address.
-     */
-    void setMarkedForwarding(String iface);
-
-    /**
-     * Removes marked forwarding for an interface
-     */
-    void clearMarkedForwarding(String iface);
+    void removeVpnUidRanges(int netId, in UidRange[] ranges);
 
     /**
      * Get the SO_MARK associated with routing packets for user {@code uid}
@@ -410,9 +397,14 @@
     boolean isNetworkActive();
 
     /**
-     * Setup a new network.
+     * Setup a new physical network.
      */
-    void createNetwork(int netId);
+    void createPhysicalNetwork(int netId);
+
+    /**
+     * Setup a new VPN.
+     */
+    void createVirtualNetwork(int netId, boolean hasDNS);
 
     /**
      * Remove a network.
@@ -437,4 +429,14 @@
 
     void setPermission(boolean internal, boolean changeNetState, in int[] uids);
     void clearPermission(in int[] uids);
+
+    /**
+     * Allow UID to call protect().
+     */
+    void allowProtect(int uid);
+
+    /**
+     * Deny UID from calling protect().
+     */
+    void denyProtect(int uid);
 }
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index 86c749a..8caea25 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -1085,4 +1085,18 @@
          */
         public boolean usingWrapper;
     }
+
+    /**
+     * Kill all processes in a process group started for the given
+     * pid.
+     * @hide
+     */
+    public static final native int killProcessGroup(int uid, int pid);
+
+    /**
+     * Remove all process groups.  Expected to be called when ActivityManager
+     * is restarted.
+     * @hide
+     */
+    public static final native void removeAllProcessGroups();
 }
diff --git a/core/java/android/provider/CallLog.java b/core/java/android/provider/CallLog.java
index acc74d8..760f2a5 100644
--- a/core/java/android/provider/CallLog.java
+++ b/core/java/android/provider/CallLog.java
@@ -20,6 +20,7 @@
 import android.content.ContentResolver;
 import android.content.ContentValues;
 import android.content.Context;
+import android.content.Intent;
 import android.database.Cursor;
 import android.net.Uri;
 import android.provider.ContactsContract.CommonDataKinds.Callable;
@@ -87,6 +88,27 @@
         public static final String ALLOW_VOICEMAILS_PARAM_KEY = "allow_voicemails";
 
         /**
+         * An optional extra used with {@link #CONTENT_TYPE Calls.CONTENT_TYPE} and
+         * {@link Intent#ACTION_VIEW} to specify that the presented list of calls should be
+         * filtered for a particular call type.
+         *
+         * Applications implementing a call log UI should check for this extra, and display a
+         * filtered list of calls based on the specified call type. If not applicable within the
+         * application's UI, it should be silently ignored.
+         *
+         * <p>
+         * The following example brings up the call log, showing only missed calls.
+         * <pre>
+         * Intent intent = new Intent(Intent.ACTION_VIEW);
+         * intent.setType(CallLog.Calls.CONTENT_TYPE);
+         * intent.putExtra(CallLog.Calls.EXTRA_CALL_TYPE_FILTER, CallLog.Calls.MISSED_TYPE);
+         * startActivity(intent);
+         * </pre>
+         * </p>
+         */
+        public static final String EXTRA_CALL_TYPE_FILTER = "extra_call_type_filter";
+
+        /**
          * Content uri used to access call log entries, including voicemail records. You must have
          * the READ_CALL_LOG and WRITE_CALL_LOG permissions to read and write to the call log.
          */
@@ -127,6 +149,18 @@
         public static final int VOICEMAIL_TYPE = 4;
 
         /**
+         * Bit-mask describing features of the call (e.g. video).
+         *
+         * <P>Type: INTEGER (int)</P>
+         */
+        public static final String FEATURES = "features";
+
+        /** Call had no associated features (e.g. voice-only). */
+        public static final int FEATURES_NONE = 0x0;
+        /** Call had video. */
+        public static final int FEATURES_VIDEO = 0x1;
+
+        /**
          * The phone number as the user entered it.
          * <P>Type: TEXT</P>
          */
@@ -180,6 +214,12 @@
         public static final String DURATION = "duration";
 
         /**
+         * The data usage of the call in bytes.
+         * <P>Type: INTEGER (long)</P>
+         */
+        public static final String DATA_USAGE = "data_usage";
+
+        /**
          * Whether or not the call has been acknowledged
          * <P>Type: INTEGER (boolean)</P>
          */
@@ -302,14 +342,18 @@
          *        is set by the network and denotes the number presenting rules for
          *        "allowed", "payphone", "restricted" or "unknown"
          * @param callType enumerated values for "incoming", "outgoing", or "missed"
+         * @param features features of the call (e.g. Video).
          * @param account The account object describing the provider of the call
          * @param start time stamp for the call in milliseconds
          * @param duration call duration in seconds
+         * @param dataUsage data usage for the call in bytes, null if data usage was not tracked for
+         *                  the call.
          *
          * {@hide}
          */
         public static Uri addCall(CallerInfo ci, Context context, String number,
-                int presentation, int callType, PhoneAccount account, long start, int duration) {
+                int presentation, int callType, int features, PhoneAccount account, long start,
+                int duration, Long dataUsage) {
             final ContentResolver resolver = context.getContentResolver();
             int numberPresentation = PRESENTATION_ALLOWED;
 
@@ -346,8 +390,12 @@
             values.put(NUMBER, number);
             values.put(NUMBER_PRESENTATION, Integer.valueOf(numberPresentation));
             values.put(TYPE, Integer.valueOf(callType));
+            values.put(FEATURES, features);
             values.put(DATE, Long.valueOf(start));
             values.put(DURATION, Long.valueOf(duration));
+            if (dataUsage != null) {
+                values.put(DATA_USAGE, dataUsage);
+            }
             values.put(PHONE_ACCOUNT_COMPONENT_NAME, accountComponentString);
             values.put(PHONE_ACCOUNT_ID, accountId);
             values.put(NEW, Integer.valueOf(1));
diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java
index cfe926d..93f834a 100644
--- a/core/java/android/provider/ContactsContract.java
+++ b/core/java/android/provider/ContactsContract.java
@@ -1670,6 +1670,24 @@
          */
         public static final String CONTENT_VCARD_TYPE = "text/x-vcard";
 
+
+        /**
+         * Mimimal ID for corp contacts returned from
+         * {@link PhoneLookup#ENTERPRISE_CONTENT_FILTER_URI}.
+         *
+         * @hide
+         */
+        public static long CORP_CONTACT_ID_BASE = 1000000000; // slightly smaller than 2 ** 30
+
+        /**
+         * Return TRUE if a contact ID is from the contacts provider on the corp profile.
+         *
+         * {@link PhoneLookup#ENTERPRISE_CONTENT_FILTER_URI} may return such a contact.
+         */
+        public static boolean isCorpContactId(long contactId) {
+            return (contactId >= CORP_CONTACT_ID_BASE) && (contactId < Profile.MIN_ID);
+        }
+
         /**
          * A sub-directory of a single contact that contains all of the constituent raw contact
          * {@link ContactsContract.Data} rows.  This directory can be used either
@@ -4842,20 +4860,15 @@
          * <p>
          * If a result is from the corp profile, it makes the following changes to the data:
          * <ul>
-         *     <li>The following columns will be set to null, as they don't make sense on a
-         *     different profile:
-         *     {@link #_ID},
-         *     {@link #PHOTO_ID},
-         *     {@link #PHOTO_FILE_ID},
-         *     {@link #LOOKUP_KEY},
-         *     {@link #CUSTOM_RINGTONE},
-         *     {@link #IN_VISIBLE_GROUP},
-         *     and {@link #IN_DEFAULT_DIRECTORY}.
-         *     </li>
          *     <li>
          *     {@link #PHOTO_THUMBNAIL_URI} and {@link #PHOTO_URI} will be rewritten to special
          *     URIs.  Use {@link ContentResolver#openAssetFileDescriptor} or its siblings to
          *     load pictures from them.
+         *     {@link #PHOTO_ID} and {@link #PHOTO_FILE_ID} will be set to null.  Do not use them.
+         *     </li>
+         *     <li>
+         *     Corp contacts will get artificial {@link #_ID}s.  In order to tell whether a contact
+         *     is from the corp profile, use {@link ContactsContract.Contacts#isCorpContactId(long)}.
          *     </li>
          * </ul>
          * <p>
diff --git a/core/java/android/provider/SearchIndexablesContract.java b/core/java/android/provider/SearchIndexablesContract.java
index a8b4cfb0..1b5f72a 100644
--- a/core/java/android/provider/SearchIndexablesContract.java
+++ b/core/java/android/provider/SearchIndexablesContract.java
@@ -65,7 +65,7 @@
     public static final String NON_INDEXABLES_KEYS_PATH = SETTINGS + "/" + NON_INDEXABLES_KEYS;
 
     /**
-     * Indexable xml resources colums.
+     * Indexable xml resources columns.
      */
     public static final String[] INDEXABLES_XML_RES_COLUMNS = new String[] {
             XmlResource.COLUMN_RANK,                    // 0
@@ -78,7 +78,7 @@
     };
 
     /**
-     * Indexable xml resources colums indices.
+     * Indexable xml resources columns indices.
      */
     public static final int COLUMN_INDEX_XML_RES_RANK = 0;
     public static final int COLUMN_INDEX_XML_RES_RESID = 1;
@@ -89,7 +89,7 @@
     public static final int COLUMN_INDEX_XML_RES_INTENT_TARGET_CLASS = 6;
 
     /**
-     * Indexable raw data colums.
+     * Indexable raw data columns.
      */
     public static final String[] INDEXABLES_RAW_COLUMNS = new String[] {
             RawData.COLUMN_RANK,                    // 0
@@ -105,10 +105,11 @@
             RawData.COLUMN_INTENT_TARGET_PACKAGE,   // 10
             RawData.COLUMN_INTENT_TARGET_CLASS,     // 11
             RawData.COLUMN_KEY,                     // 12
+            RawData.COLUMN_USER_ID,                 // 13
     };
 
     /**
-     * Indexable raw data colums indices.
+     * Indexable raw data columns indices.
      */
     public static final int COLUMN_INDEX_RAW_RANK = 0;
     public static final int COLUMN_INDEX_RAW_TITLE = 1;
@@ -123,16 +124,17 @@
     public static final int COLUMN_INDEX_RAW_INTENT_TARGET_PACKAGE = 10;
     public static final int COLUMN_INDEX_RAW_INTENT_TARGET_CLASS = 11;
     public static final int COLUMN_INDEX_RAW_KEY = 12;
+    public static final int COLUMN_INDEX_RAW_USER_ID = 13;
 
     /**
-     * Indexable raw data colums.
+     * Indexable raw data columns.
      */
     public static final String[] NON_INDEXABLES_KEYS_COLUMNS = new String[] {
             NonIndexableKey.COLUMN_KEY_VALUE      // 0
     };
 
     /**
-     * Non indexable data keys colums indices.
+     * Non indexable data keys columns indices.
      */
     public static final int COLUMN_INDEX_NON_INDEXABLE_KEYS_KEY_VALUE = 0;
 
@@ -204,6 +206,11 @@
          * Key associated with the raw data. The key needs to be unique.
          */
         public static final String COLUMN_KEY = "key";
+
+        /**
+         * UserId associated with the raw data.
+         */
+        public static final String COLUMN_USER_ID = "user_id";
     }
 
     /**
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/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index 8bd0f4d..7d5ff33 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -29,7 +29,6 @@
 import android.os.Parcelable;
 import android.os.RemoteException;
 import android.os.ServiceManager;
-import android.util.ArrayMap;
 import android.util.Log;
 
 import java.util.List;
@@ -410,28 +409,20 @@
     }
 
     /**
-     * Provides access to ranking information on a currently active
-     * notification.
+     * Stores ranking related information on a currently active notification.
      *
      * <p>
-     * Note that this object is not updated on notification events (such as
-     * {@link #onNotificationPosted(StatusBarNotification, RankingMap)},
-     * {@link #onNotificationRemoved(StatusBarNotification)}, etc.). Make sure
-     * to retrieve a new Ranking from the current {@link RankingMap} whenever
-     * a notification event occurs.
+     * Ranking objects aren't automatically updated as notification events
+     * occur. Instead, ranking information has to be retrieved again via the
+     * current {@link RankingMap}.
      */
     public static class Ranking {
-        private final String mKey;
-        private final int mRank;
-        private final boolean mIsAmbient;
-        private final boolean mIsInterceptedByDnd;
+        private String mKey;
+        private int mRank = -1;
+        private boolean mIsAmbient;
+        private boolean mMeetsInterruptionFilter;
 
-        private Ranking(String key, int rank, boolean isAmbient, boolean isInterceptedByDnd) {
-            mKey = key;
-            mRank = rank;
-            mIsAmbient = isAmbient;
-            mIsInterceptedByDnd = isInterceptedByDnd;
-        }
+        public Ranking() {}
 
         /**
          * Returns the key of the notification this Ranking applies to.
@@ -459,11 +450,19 @@
         }
 
         /**
-         * Returns whether the notification was intercepted by
-         * &quot;Do not disturb&quot;.
+         * Returns whether the notification meets the user's interruption
+         * filter.
          */
-        public boolean isInterceptedByDoNotDisturb() {
-            return mIsInterceptedByDnd;
+        public boolean meetsInterruptionFilter() {
+            return mMeetsInterruptionFilter;
+        }
+
+        private void populate(String key, int rank, boolean isAmbient,
+                boolean meetsInterruptionFilter) {
+            mKey = key;
+            mRank = rank;
+            mIsAmbient = isAmbient;
+            mMeetsInterruptionFilter = meetsInterruptionFilter;
         }
     }
 
@@ -477,12 +476,9 @@
      */
     public static class RankingMap implements Parcelable {
         private final NotificationRankingUpdate mRankingUpdate;
-        private final ArrayMap<String, Ranking> mRankingCache;
-        private boolean mRankingCacheInitialized;
 
         private RankingMap(NotificationRankingUpdate rankingUpdate) {
             mRankingUpdate = rankingUpdate;
-            mRankingCache = new ArrayMap<>(rankingUpdate.getOrderedKeys().length);
         }
 
         /**
@@ -496,37 +492,42 @@
         }
 
         /**
-         * Returns the Ranking for the notification with the given key.
+         * Populates outRanking with ranking information for the notification
+         * with the given key.
          *
-         * @return the Ranking of the notification with the given key;
-         *     <code>null</code> when the key is unknown.
+         * @return true if a valid key has been passed and outRanking has
+         *     been populated; false otherwise
          */
-        public Ranking getRanking(String key) {
-            synchronized (mRankingCache) {
-                if (!mRankingCacheInitialized) {
-                    initializeRankingCache();
-                    mRankingCacheInitialized = true;
-                }
-            }
-            return mRankingCache.get(key);
+        public boolean getRanking(String key, Ranking outRanking) {
+            int rank = getRank(key);
+            outRanking.populate(key, rank, isAmbient(key), !isIntercepted(key));
+            return rank >= 0;
         }
 
-        private void initializeRankingCache() {
+        private int getRank(String key) {
+            // TODO: Optimize.
             String[] orderedKeys = mRankingUpdate.getOrderedKeys();
-            int firstAmbientIndex = mRankingUpdate.getFirstAmbientIndex();
             for (int i = 0; i < orderedKeys.length; i++) {
-                String key = orderedKeys[i];
-                boolean isAmbient = firstAmbientIndex > -1 && firstAmbientIndex <= i;
-                boolean isInterceptedByDnd = false;
-                // TODO: Optimize.
-                for (String s : mRankingUpdate.getDndInterceptedKeys()) {
-                    if (s.equals(key)) {
-                        isInterceptedByDnd = true;
-                        break;
-                    }
+                if (orderedKeys[i].equals(key)) {
+                    return i;
                 }
-                mRankingCache.put(key, new Ranking(key, i, isAmbient, isInterceptedByDnd));
             }
+            return -1;
+        }
+
+        private boolean isAmbient(String key) {
+            int rank = getRank(key);
+            return rank >= 0 && rank >= mRankingUpdate.getFirstAmbientIndex();
+        }
+
+        private boolean isIntercepted(String key) {
+            // TODO: Optimize.
+            for (String interceptedKey : mRankingUpdate.getInterceptedKeys()) {
+                if (interceptedKey.equals(key)) {
+                    return true;
+                }
+            }
+            return false;
         }
 
         // ----------- Parcelable
diff --git a/core/java/android/service/notification/NotificationRankingUpdate.java b/core/java/android/service/notification/NotificationRankingUpdate.java
index 4b13d95..26af38b 100644
--- a/core/java/android/service/notification/NotificationRankingUpdate.java
+++ b/core/java/android/service/notification/NotificationRankingUpdate.java
@@ -24,20 +24,20 @@
 public class NotificationRankingUpdate implements Parcelable {
     // TODO: Support incremental updates.
     private final String[] mKeys;
-    private final String[] mDndInterceptedKeys;
+    private final String[] mInterceptedKeys;
     private final int mFirstAmbientIndex;
 
-    public NotificationRankingUpdate(String[] keys, String[] dndInterceptedKeys,
+    public NotificationRankingUpdate(String[] keys, String[] interceptedKeys,
                                      int firstAmbientIndex) {
         mKeys = keys;
         mFirstAmbientIndex = firstAmbientIndex;
-        mDndInterceptedKeys = dndInterceptedKeys;
+        mInterceptedKeys = interceptedKeys;
     }
 
     public NotificationRankingUpdate(Parcel in) {
         mKeys = in.readStringArray();
         mFirstAmbientIndex = in.readInt();
-        mDndInterceptedKeys = in.readStringArray();
+        mInterceptedKeys = in.readStringArray();
     }
 
     @Override
@@ -49,7 +49,7 @@
     public void writeToParcel(Parcel out, int flags) {
         out.writeStringArray(mKeys);
         out.writeInt(mFirstAmbientIndex);
-        out.writeStringArray(mDndInterceptedKeys);
+        out.writeStringArray(mInterceptedKeys);
     }
 
     public static final Parcelable.Creator<NotificationRankingUpdate> CREATOR
@@ -71,7 +71,7 @@
         return mFirstAmbientIndex;
     }
 
-    public String[] getDndInterceptedKeys() {
-        return mDndInterceptedKeys;
+    public String[] getInterceptedKeys() {
+        return mInterceptedKeys;
     }
 }
diff --git a/core/java/android/view/HardwareRenderer.java b/core/java/android/view/HardwareRenderer.java
index d69d01d..be677ea 100644
--- a/core/java/android/view/HardwareRenderer.java
+++ b/core/java/android/view/HardwareRenderer.java
@@ -18,6 +18,7 @@
 
 import android.content.Context;
 import android.graphics.Bitmap;
+import android.graphics.Rect;
 import android.util.DisplayMetrics;
 import android.view.Surface.OutOfResourcesException;
 
@@ -247,21 +248,24 @@
     abstract void detachSurfaceTexture(long hardwareLayer);
 
     /**
-     * Setup the hardware renderer for drawing. This is called whenever the
-     * size of the target surface changes or when the surface is first created.
+     * Setup the hardware renderer for drawing. This is called whenever the size
+     * of the target surface changes or when the surface is first created.
      *
      * @param width Width of the drawing surface.
      * @param height Height of the drawing surface.
+     * @param surfaceInsets Insets between the drawing surface and actual
+     *            surface bounds.
      * @param lightX X position of the shadow casting light
      * @param lightY Y position of the shadow casting light
      * @param lightZ Z position of the shadow casting light
      * @param lightRadius radius of the shadow casting light
      */
-    abstract void setup(int width, int height, float lightX, float lightY, float lightZ, float lightRadius);
+    abstract void setup(int width, int height, Rect surfaceInsets, float lightX, float lightY,
+            float lightZ, float lightRadius);
 
     /**
      * Gets the current width of the surface. This is the width that the surface
-     * was last set to in a call to {@link #setup(int, int, float, float, float, float)}.
+     * was last set to in a call to {@link #setup(int, int, Rect, float, float, float, float)}.
      *
      * @return the current width of the surface
      */
@@ -269,7 +273,7 @@
 
     /**
      * Gets the current height of the surface. This is the height that the surface
-     * was last set to in a call to {@link #setup(int, int, float, float, float, float)}.
+     * was last set to in a call to {@link #setup(int, int, Rect, float, float, float, float)}.
      *
      * @return the current width of the surface
      */
@@ -344,7 +348,6 @@
      * @param view The view to draw.
      * @param attachInfo AttachInfo tied to the specified view.
      * @param callbacks Callbacks invoked when drawing happens.
-     * @param dirty The dirty rectangle to update, can be null.
      */
     abstract void draw(View view, View.AttachInfo attachInfo, HardwareDrawCallbacks callbacks);
 
@@ -369,17 +372,18 @@
      * @param height The height of the drawing surface.
      * @param surface The surface to hardware accelerate
      * @param metrics The display metrics used to draw the output.
+     * @param surfaceInsets The drawing surface insets to apply
      *
      * @return true if the surface was initialized, false otherwise. Returning
      *         false might mean that the surface was already initialized.
      */
-    boolean initializeIfNeeded(int width, int height, Surface surface, DisplayMetrics metrics)
+    boolean initializeIfNeeded(int width, int height, Surface surface, Rect surfaceInsets, DisplayMetrics metrics)
             throws OutOfResourcesException {
         if (isRequested()) {
             // We lost the gl context, so recreate it.
             if (!isEnabled()) {
                 if (initialize(surface)) {
-                    setup(width, height, metrics);
+                    setup(width, height, surfaceInsets, metrics);
                     return true;
                 }
             }
@@ -387,12 +391,12 @@
         return false;
     }
 
-    void setup(int width, int height, DisplayMetrics metrics) {
+    void setup(int width, int height, Rect surfaceInsets, DisplayMetrics metrics) {
         float lightX = width / 2.0f;
         float lightY = -400 * metrics.density;
         float lightZ = 800 * metrics.density;
         float lightRadius = 800 * metrics.density;
-        setup(width, height, lightX, lightY, lightZ, lightRadius);
+        setup(width, height, surfaceInsets, lightX, lightY, lightZ, lightRadius);
     }
 
     /**
diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java
index 57d1beb..acb2fe4 100644
--- a/core/java/android/view/ThreadedRenderer.java
+++ b/core/java/android/view/ThreadedRenderer.java
@@ -19,6 +19,7 @@
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
+import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -67,7 +68,16 @@
         PROFILE_PROPERTY_VISUALIZE_BARS,
     };
 
+    // Size of the rendered content.
     private int mWidth, mHeight;
+
+    // Actual size of the drawing surface.
+    private int mSurfaceWidth, mSurfaceHeight;
+
+    // Insets between the drawing surface and rendered content. These are
+    // applied as translation when updating the root render node.
+    private int mInsetTop, mInsetLeft;
+
     private long mNativeProxy;
     private boolean mInitialized = false;
     private RenderNode mRootNode;
@@ -154,11 +164,23 @@
     }
 
     @Override
-    void setup(int width, int height, float lightX, float lightY, float lightZ, float lightRadius) {
+    void setup(int width, int height, Rect surfaceInsets, float lightX, float lightY, float lightZ,
+            float lightRadius) {
         mWidth = width;
         mHeight = height;
-        mRootNode.setLeftTopRightBottom(0, 0, mWidth, mHeight);
-        nSetup(mNativeProxy, width, height, lightX, lightY, lightZ, lightRadius);
+        if (surfaceInsets != null) {
+            mInsetLeft = surfaceInsets.left;
+            mInsetTop = surfaceInsets.top;
+            mSurfaceWidth = width + mInsetLeft + surfaceInsets.right;
+            mSurfaceHeight = height + mInsetTop + surfaceInsets.bottom;
+        } else {
+            mInsetLeft = 0;
+            mInsetTop = 0;
+            mSurfaceWidth = width;
+            mSurfaceHeight = height;
+        }
+        mRootNode.setLeftTopRightBottom(-mInsetLeft, -mInsetTop, mSurfaceWidth, mSurfaceHeight);
+        nSetup(mNativeProxy, mSurfaceWidth, mSurfaceHeight, lightX, lightY, lightZ, lightRadius);
     }
 
     @Override
@@ -214,9 +236,10 @@
         view.mPrivateFlags &= ~View.PFLAG_INVALIDATED;
 
         Trace.traceBegin(Trace.TRACE_TAG_VIEW, "getDisplayList");
-        HardwareCanvas canvas = mRootNode.start(mWidth, mHeight);
+        HardwareCanvas canvas = mRootNode.start(mSurfaceWidth, mSurfaceHeight);
         try {
             canvas.save();
+            canvas.translate(mInsetLeft, mInsetTop);
             callbacks.onHardwarePreDraw(canvas);
             canvas.drawRenderNode(view.getDisplayList());
             callbacks.onHardwarePostDraw(canvas);
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 5def940..9405299 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -1713,7 +1713,8 @@
                 if (hwInitialized ||
                         mWidth != mAttachInfo.mHardwareRenderer.getWidth() ||
                         mHeight != mAttachInfo.mHardwareRenderer.getHeight()) {
-                    mAttachInfo.mHardwareRenderer.setup(mWidth, mHeight,
+                    final Rect shadowInsets = params != null ? params.shadowInsets : null;
+                    mAttachInfo.mHardwareRenderer.setup(mWidth, mHeight, shadowInsets,
                             mAttachInfo.mRootView.getResources().getDisplayMetrics());
                     if (!hwInitialized) {
                         mAttachInfo.mHardwareRenderer.invalidate(mSurface);
@@ -2211,20 +2212,22 @@
         return measureSpec;
     }
 
+    int mHardwareXOffset;
     int mHardwareYOffset;
     int mResizeAlpha;
     final Paint mResizePaint = new Paint();
 
     @Override
     public void onHardwarePreDraw(HardwareCanvas canvas) {
-        canvas.translate(0, -mHardwareYOffset);
+        canvas.translate(-mHardwareXOffset, -mHardwareYOffset);
     }
 
     @Override
     public void onHardwarePostDraw(HardwareCanvas canvas) {
         if (mResizeBuffer != null) {
             mResizePaint.setAlpha(mResizeAlpha);
-            canvas.drawHardwareLayer(mResizeBuffer, 0.0f, mHardwareYOffset, mResizePaint);
+            canvas.drawHardwareLayer(mResizeBuffer, mHardwareXOffset, mHardwareYOffset,
+                    mResizePaint);
         }
         drawAccessibilityFocusedDrawableIfNeeded(canvas);
     }
@@ -2368,15 +2371,17 @@
             attachInfo.mTreeObserver.dispatchOnScrollChanged();
         }
 
-        int yoff;
+        final WindowManager.LayoutParams params = mWindowAttributes;
+        final Rect surfaceInsets = params != null ? params.shadowInsets : null;
         boolean animating = mScroller != null && mScroller.computeScrollOffset();
+        final int curScrollY;
         if (animating) {
-            yoff = mScroller.getCurrY();
+            curScrollY = mScroller.getCurrY();
         } else {
-            yoff = mScrollY;
+            curScrollY = mScrollY;
         }
-        if (mCurScrollY != yoff) {
-            mCurScrollY = yoff;
+        if (mCurScrollY != curScrollY) {
+            mCurScrollY = curScrollY;
             fullRedrawNeeded = true;
         }
 
@@ -2425,11 +2430,14 @@
 
         attachInfo.mTreeObserver.dispatchOnDraw();
 
+        final int xOffset = surfaceInsets != null ? -surfaceInsets.left : 0;
+        final int yOffset = curScrollY + (surfaceInsets != null ? -surfaceInsets.top : 0);
         if (!dirty.isEmpty() || mIsAnimating) {
             if (attachInfo.mHardwareRenderer != null && attachInfo.mHardwareRenderer.isEnabled()) {
                 // Draw with hardware renderer.
                 mIsAnimating = false;
-                mHardwareYOffset = yoff;
+                mHardwareYOffset = yOffset;
+                mHardwareXOffset = xOffset;
                 mResizeAlpha = resizeAlpha;
 
                 dirty.setEmpty();
@@ -2450,8 +2458,9 @@
                         attachInfo.mHardwareRenderer.isRequested()) {
 
                     try {
-                        attachInfo.mHardwareRenderer.initializeIfNeeded(mWidth, mHeight,
-                                mSurface, attachInfo.mRootView.getResources().getDisplayMetrics());
+                        attachInfo.mHardwareRenderer.initializeIfNeeded(
+                                mWidth, mHeight, mSurface, surfaceInsets,
+                                attachInfo.mRootView.getResources().getDisplayMetrics());
                     } catch (OutOfResourcesException e) {
                         handleOutOfResourcesException(e);
                         return;
@@ -2462,7 +2471,7 @@
                     return;
                 }
 
-                if (!drawSoftware(surface, attachInfo, yoff, scalingRequired, dirty)) {
+                if (!drawSoftware(surface, attachInfo, xOffset, yOffset, scalingRequired, dirty)) {
                     return;
                 }
             }
@@ -2475,9 +2484,9 @@
     }
 
     /**
-     * @return true if drawing was succesfull, false if an error occurred
+     * @return true if drawing was successful, false if an error occurred
      */
-    private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int yoff,
+    private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
             boolean scalingRequired, Rect dirty) {
 
         // Draw with software renderer.
@@ -2526,7 +2535,7 @@
             // If we are applying an offset, we need to clear the area
             // where the offset doesn't appear to avoid having garbage
             // left in the blank areas.
-            if (!canvas.isOpaque() || yoff != 0) {
+            if (!canvas.isOpaque() || yoff != 0 || xoff != 0) {
                 canvas.drawColor(0, PorterDuff.Mode.CLEAR);
             }
 
@@ -2542,7 +2551,7 @@
                         ", compatibilityInfo=" + cxt.getResources().getCompatibilityInfo());
             }
             try {
-                canvas.translate(0, -yoff);
+                canvas.translate(-xoff, -yoff);
                 if (mTranslator != null) {
                     mTranslator.translateCanvas(canvas);
                 }
@@ -3147,8 +3156,10 @@
                         if (mAttachInfo.mHardwareRenderer != null && mSurface.isValid()){
                             mFullRedrawNeeded = true;
                             try {
+                                final WindowManager.LayoutParams lp = mWindowAttributes;
+                                final Rect surfaceInsets = lp != null ? lp.shadowInsets : null;
                                 mAttachInfo.mHardwareRenderer.initializeIfNeeded(
-                                        mWidth, mHeight, mSurface,
+                                        mWidth, mHeight, mSurface, surfaceInsets,
                                         mAttachInfo.mRootView.getResources().getDisplayMetrics());
                             } catch (OutOfResourcesException e) {
                                 Log.e(TAG, "OutOfResourcesException locking surface", e);
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 4eecc6a..c06b5d8 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -19,7 +19,9 @@
 import android.app.Presentation;
 import android.content.Context;
 import android.content.pm.ActivityInfo;
+import android.graphics.Insets;
 import android.graphics.PixelFormat;
+import android.graphics.Rect;
 import android.os.IBinder;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -1290,6 +1292,13 @@
          * field is added with {@link #y} to supply the <var>yAdj</var> parameter.
          */
         public float verticalMargin;
+
+        /**
+         * Positive insets between the drawing surface and window content.
+         *
+         * @hide
+         */
+        public Rect shadowInsets = new Rect();
     
         /**
          * The desired bitmap format.  May be one of the constants in
@@ -1571,6 +1580,10 @@
             out.writeInt(hasSystemUiListeners ? 1 : 0);
             out.writeInt(inputFeatures);
             out.writeLong(userActivityTimeout);
+            out.writeInt(shadowInsets.left);
+            out.writeInt(shadowInsets.top);
+            out.writeInt(shadowInsets.right);
+            out.writeInt(shadowInsets.bottom);
         }
         
         public static final Parcelable.Creator<LayoutParams> CREATOR
@@ -1613,6 +1626,7 @@
             hasSystemUiListeners = in.readInt() != 0;
             inputFeatures = in.readInt();
             userActivityTimeout = in.readLong();
+            shadowInsets.set(in.readInt(), in.readInt(), in.readInt(), in.readInt());
         }
     
         @SuppressWarnings({"PointlessBitwiseExpression"})
@@ -1644,6 +1658,8 @@
         /** {@hide} */
         public static final int TRANSLUCENT_FLAGS_CHANGED = 1<<19;
         /** {@hide} */
+        public static final int SHADOW_INSETS_CHANGED = 1<<20;
+        /** {@hide} */
         public static final int EVERYTHING_CHANGED = 0xffffffff;
 
         // internal buffer to backup/restore parameters under compatibility mode.
@@ -1778,6 +1794,11 @@
                 changes |= USER_ACTIVITY_TIMEOUT_CHANGED;
             }
 
+            if (!shadowInsets.equals(o.shadowInsets)) {
+                shadowInsets.set(o.shadowInsets);
+                changes |= SHADOW_INSETS_CHANGED;
+            }
+
             return changes;
         }
     
@@ -1877,6 +1898,9 @@
             if (userActivityTimeout >= 0) {
                 sb.append(" userActivityTimeout=").append(userActivityTimeout);
             }
+            if (!shadowInsets.equals(Insets.NONE)) {
+                sb.append(" shadowInsets=").append(shadowInsets);
+            }
             sb.append('}');
             return sb.toString();
         }
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/PopupWindow.java b/core/java/android/widget/PopupWindow.java
index 01632ae..a35d447 100644
--- a/core/java/android/widget/PopupWindow.java
+++ b/core/java/android/widget/PopupWindow.java
@@ -22,6 +22,7 @@
 import android.content.Context;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
+import android.graphics.Insets;
 import android.graphics.PixelFormat;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
@@ -109,6 +110,8 @@
     private int mPopupWidth;
     private int mPopupHeight;
 
+    private float mElevation;
+
     private int[] mDrawingLocation = new int[2];
     private int[] mScreenLocation = new int[2];
     private Rect mTempRect = new Rect();
@@ -196,6 +199,7 @@
                 attrs, com.android.internal.R.styleable.PopupWindow, defStyleAttr, defStyleRes);
 
         mBackground = a.getDrawable(R.styleable.PopupWindow_popupBackground);
+        mElevation = a.getDimension(R.styleable.PopupWindow_popupElevation, 0);
         mOverlapAnchor = a.getBoolean(R.styleable.PopupWindow_overlapAnchor, false);
 
         final int animStyle = a.getResourceId(R.styleable.PopupWindow_popupAnimationStyle, -1);
@@ -319,25 +323,49 @@
     }
 
     /**
-     * <p>Return the drawable used as the popup window's background.</p>
+     * Return the drawable used as the popup window's background.
      *
-     * @return the background drawable or null
+     * @return the background drawable or {@code null} if not set
+     * @see #setBackgroundDrawable(Drawable)
+     * @attr ref android.R.styleable#PopupWindow_popupBackground
      */
     public Drawable getBackground() {
         return mBackground;
     }
 
     /**
-     * <p>Change the background drawable for this popup window. The background
-     * can be set to null.</p>
+     * Specifies the background drawable for this popup window. The background
+     * can be set to {@code null}.
      *
      * @param background the popup's background
+     * @see #getBackground()
+     * @attr ref android.R.styleable#PopupWindow_popupBackground
      */
     public void setBackgroundDrawable(Drawable background) {
         mBackground = background;
     }
 
     /**
+     * @return the elevation for this popup window in pixels
+     * @see #setElevation(float)
+     * @attr ref android.R.styleable#PopupWindow_popupElevation
+     */
+    public float getElevation() {
+        return mElevation;
+    }
+
+    /**
+     * Specifies the elevation for this popup window.
+     *
+     * @param elevation the popup's elevation in pixels
+     * @see #getElevation()
+     * @attr ref android.R.styleable#PopupWindow_popupElevation
+     */
+    public void setElevation(float elevation) {
+        mElevation = elevation;
+    }
+
+    /**
      * <p>Return the animation style to use the popup appears and disappears</p>
      *
      * @return the animation style to use the popup appears and disappears
@@ -973,7 +1001,7 @@
     /**
      * <p>Prepare the popup by embedding in into a new ViewGroup if the
      * background drawable is not null. If embedding is required, the layout
-     * parameters' height is mnodified to take into account the background's
+     * parameters' height is modified to take into account the background's
      * padding.</p>
      *
      * @param p the layout parameters of the popup's content view
@@ -998,13 +1026,15 @@
             PopupViewContainer.LayoutParams listParams = new PopupViewContainer.LayoutParams(
                     ViewGroup.LayoutParams.MATCH_PARENT, height
             );
-            popupViewContainer.setBackgroundDrawable(mBackground);
+            popupViewContainer.setBackground(mBackground);
             popupViewContainer.addView(mContentView, listParams);
 
             mPopupView = popupViewContainer;
         } else {
             mPopupView = mContentView;
         }
+
+        mPopupView.setElevation(mElevation);
         mPopupViewInitialLayoutDirectionInherited =
                 (mPopupView.getRawLayoutDirection() == View.LAYOUT_DIRECTION_INHERIT);
         mPopupWidth = p.width;
@@ -1066,6 +1096,10 @@
         p.softInputMode = mSoftInputMode;
         p.setTitle("PopupWindow:" + Integer.toHexString(hashCode()));
 
+        // TODO: Use real shadow insets once that algorithm is finalized.
+        final int shadowInset = (int) Math.ceil(mElevation * 2);
+        p.shadowInsets.set(shadowInset, shadowInset, shadowInset, shadowInset);
+
         return p;
     }
 
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/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 869a91b..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 \
@@ -242,7 +243,8 @@
 	libnetd_client \
 	libsoundtrigger \
 	libminikin \
-	libstlport
+	libstlport \
+	libprocessgroup \
 
 ifeq ($(USE_OPENGL_RENDERER),true)
 	LOCAL_SHARED_LIBRARIES += libhwui
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/jni/android_net_NetUtils.cpp b/core/jni/android_net_NetUtils.cpp
index 6f89800..a75d547 100644
--- a/core/jni/android_net_NetUtils.cpp
+++ b/core/jni/android_net_NetUtils.cpp
@@ -285,6 +285,11 @@
     return (jboolean) !setNetworkForSocket(netId, socket);
 }
 
+static jboolean android_net_utils_protectFromVpn(JNIEnv *env, jobject thiz, jint socket)
+{
+    return (jboolean) !protectFromVpn(socket);
+}
+
 // ----------------------------------------------------------------------------
 
 /*
@@ -308,6 +313,7 @@
     { "bindProcessToNetworkForHostResolution", "(I)Z", (void*) android_net_utils_bindProcessToNetworkForHostResolution },
     { "unbindProcessToNetworkForHostResolution", "()Z", (void*) android_net_utils_unbindProcessToNetworkForHostResolution },
     { "bindSocketToNetwork", "(II)Z", (void*) android_net_utils_bindSocketToNetwork },
+    { "protectFromVpn", "(I)Z", (void*)android_net_utils_protectFromVpn },
 };
 
 int register_android_net_NetworkUtils(JNIEnv* env)
diff --git a/core/jni/android_util_Process.cpp b/core/jni/android_util_Process.cpp
index a6b65cc..aaa680f 100644
--- a/core/jni/android_util_Process.cpp
+++ b/core/jni/android_util_Process.cpp
@@ -24,6 +24,7 @@
 #include <cutils/sched_policy.h>
 #include <utils/String8.h>
 #include <utils/Vector.h>
+#include <processgroup/processgroup.h>
 
 #include <android_runtime/AndroidRuntime.h>
 
@@ -1002,6 +1003,16 @@
     return pidArray;
 }
 
+jint android_os_Process_killProcessGroup(JNIEnv* env, jobject clazz, jint uid, jint pid)
+{
+    return killProcessGroup(uid, pid, SIGKILL);
+}
+
+void android_os_Process_removeAllProcessGroups(JNIEnv* env, jobject clazz)
+{
+    return removeAllProcessGroups();
+}
+
 static const JNINativeMethod methods[] = {
     {"getUidForName",       "(Ljava/lang/String;)I", (void*)android_os_Process_getUidForName},
     {"getGidForName",       "(Ljava/lang/String;)I", (void*)android_os_Process_getGidForName},
@@ -1029,6 +1040,8 @@
     {"getPss", "(I)J", (void*)android_os_Process_getPss},
     {"getPidsForCommands", "([Ljava/lang/String;)[I", (void*)android_os_Process_getPidsForCommands},
     //{"setApplicationObject", "(Landroid/os/IBinder;)V", (void*)android_os_Process_setApplicationObject},
+    {"killProcessGroup", "(II)I", (void*)android_os_Process_killProcessGroup},
+    {"removeAllProcessGroups", "()V", (void*)android_os_Process_removeAllProcessGroups},
 };
 
 const char* const kProcessPathName = "android/os/Process";
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index 0cdddba..989b60ee 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -41,6 +41,7 @@
 #include <cutils/sched_policy.h>
 #include <utils/String8.h>
 #include <selinux/android.h>
+#include <processgroup/processgroup.h>
 
 #include "android_runtime/AndroidRuntime.h"
 #include "JNIHelp.h"
@@ -435,6 +436,14 @@
       }
     }
 
+    if (!is_system_server) {
+        int rc = createProcessGroup(uid, getpid());
+        if (rc != 0) {
+            ALOGE("createProcessGroup(%d, %d) failed: %s", uid, pid, strerror(-rc));
+            RuntimeAbort(env);
+        }
+    }
+
     SetGids(env, javaGids);
 
     SetRLimits(env, javaRlimits);
diff --git a/core/res/res/drawable/popup_background_material.xml b/core/res/res/drawable/popup_background_material.xml
index 9e50790..b1f0cf5 100644
--- a/core/res/res/drawable/popup_background_material.xml
+++ b/core/res/res/drawable/popup_background_material.xml
@@ -14,7 +14,12 @@
      limitations under the License.
 -->
 
-<nine-patch xmlns:android="http://schemas.android.com/apk/res/android"
-    android:src="@drawable/popup_background_mtrl_mult"
-    android:tint="?attr/colorBackground"
-    android:tintMode="multiply" />
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+       android:shape="rectangle">
+
+    <corners
+            android:radius="2dp" />
+    <solid
+            android:color="?attr/colorBackground" />
+
+</shape>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index f8567e1..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
@@ -4057,6 +4060,8 @@
     <declare-styleable name="PopupWindow">
         <!-- The background to use for the popup window. -->
         <attr name="popupBackground" format="reference|color" />
+        <!-- Window elevation to use for the popup window. -->
+        <attr name="popupElevation" format="dimension" />
         <!-- The animation style to use for the popup window. -->
         <attr name="popupAnimationStyle" format="reference" />
         <!-- Whether the popup window should overlap its anchor view. -->
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 1eb8946..ca0dc1a 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2231,6 +2231,8 @@
   <public type="attr" name="buttonBarPositiveButtonStyle" />
   <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 4a76ef6..4623258 100644
--- a/core/res/res/values/styles_material.xml
+++ b/core/res/res/values/styles_material.xml
@@ -733,6 +733,7 @@
     <style name="Widget.Material.ListPopupWindow" parent="Widget.ListPopupWindow">
         <item name="dropDownSelector">?attr/listChoiceBackgroundIndicator</item>
         <item name="popupBackground">@drawable/popup_background_material</item>
+        <item name="popupElevation">@dimen/floating_window_z</item>
         <item name="popupAnimationStyle">@style/Animation.Material.Popup</item>
         <item name="dropDownVerticalOffset">0dip</item>
         <item name="dropDownHorizontalOffset">0dip</item>
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 13639d4..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>
 
@@ -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/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java
index f262390..7316e14 100644
--- a/media/java/android/media/AudioService.java
+++ b/media/java/android/media/AudioService.java
@@ -850,11 +850,10 @@
             streamType = getActiveStreamType(suggestedStreamType);
         }
 
-        // Play sounds on STREAM_RING only and if lock screen is not on.
+        // Play sounds on STREAM_RING and STREAM_REMOTE_MUSIC only.
         if ((streamType != STREAM_REMOTE_MUSIC) &&
                 (flags & AudioManager.FLAG_PLAY_SOUND) != 0 &&
-                ((mStreamVolumeAlias[streamType] != AudioSystem.STREAM_RING)
-                 || (mKeyguardManager != null && mKeyguardManager.isKeyguardLocked()))) {
+                (mStreamVolumeAlias[streamType] != AudioSystem.STREAM_RING)) {
             flags &= ~AudioManager.FLAG_PLAY_SOUND;
         }
 
diff --git a/packages/CaptivePortalLogin/Android.mk b/packages/CaptivePortalLogin/Android.mk
new file mode 100644
index 0000000..576debc
--- /dev/null
+++ b/packages/CaptivePortalLogin/Android.mk
@@ -0,0 +1,11 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := CaptivePortalLogin
+LOCAL_CERTIFICATE := platform
+
+include $(BUILD_PACKAGE)
diff --git a/packages/CaptivePortalLogin/AndroidManifest.xml b/packages/CaptivePortalLogin/AndroidManifest.xml
new file mode 100644
index 0000000..5f78afe
--- /dev/null
+++ b/packages/CaptivePortalLogin/AndroidManifest.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+ * 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.
+ */
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.captiveportallogin" >
+
+    <uses-permission android:name="android.permission.INTERNET" />
+
+    <application android:label="@string/app_name" >
+        <activity
+            android:name="com.android.captiveportallogin.CaptivePortalLoginActivity"
+            android:label="@string/action_bar_label"
+            android:theme="@android:style/Theme.Holo" >
+            <intent-filter>
+                <action android:name="android.intent.action.ACTION_SEND"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <data android:mimeType="text/plain"/>
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
diff --git a/packages/CaptivePortalLogin/res/layout/activity_captive_portal_login.xml b/packages/CaptivePortalLogin/res/layout/activity_captive_portal_login.xml
new file mode 100644
index 0000000..d8f2928
--- /dev/null
+++ b/packages/CaptivePortalLogin/res/layout/activity_captive_portal_login.xml
@@ -0,0 +1,20 @@
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:id="@+id/container"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    tools:context="com.android.captiveportallogin.CaptivePortalLoginActivity"
+    tools:ignore="MergeRootFrame">
+    <RelativeLayout
+    android:layout_width="match_parent"
+    android:layout_height="match_parent" >
+
+    <WebView
+        android:id="@+id/webview"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_alignParentBottom="false"
+        android:layout_alignParentRight="false" />
+
+</RelativeLayout>
+</FrameLayout>
diff --git a/packages/CaptivePortalLogin/res/menu/captive_portal_login.xml b/packages/CaptivePortalLogin/res/menu/captive_portal_login.xml
new file mode 100644
index 0000000..1a88c5c
--- /dev/null
+++ b/packages/CaptivePortalLogin/res/menu/captive_portal_login.xml
@@ -0,0 +1,15 @@
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    tools:context="com.android.captiveportallogin.CaptivePortalLoginActivity" >
+    <item
+        android:id="@+id/action_do_not_use_network"
+        android:orderInCategory="100"
+        android:showAsAction="never"
+        android:title="@string/action_do_not_use_network"/>
+    <item
+        android:id="@+id/action_use_network"
+        android:orderInCategory="200"
+        android:showAsAction="never"
+        android:title="@string/action_use_network"/>
+
+</menu>
diff --git a/packages/CaptivePortalLogin/res/values/dimens.xml b/packages/CaptivePortalLogin/res/values/dimens.xml
new file mode 100644
index 0000000..55c1e59
--- /dev/null
+++ b/packages/CaptivePortalLogin/res/values/dimens.xml
@@ -0,0 +1,7 @@
+<resources>
+
+    <!-- Default screen margins, per the Android Design guidelines. -->
+    <dimen name="activity_horizontal_margin">16dp</dimen>
+    <dimen name="activity_vertical_margin">16dp</dimen>
+
+</resources>
diff --git a/packages/CaptivePortalLogin/res/values/strings.xml b/packages/CaptivePortalLogin/res/values/strings.xml
new file mode 100644
index 0000000..1b0f0a4
--- /dev/null
+++ b/packages/CaptivePortalLogin/res/values/strings.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+    <string name="app_name">CaptivePortalLogin</string>
+    <string name="action_use_network">Use this network as is</string>
+    <string name="action_do_not_use_network">Do not use this network</string>
+    <string name="action_bar_label">Sign-in to network</string>
+
+</resources>
diff --git a/packages/CaptivePortalLogin/res/values/styles.xml b/packages/CaptivePortalLogin/res/values/styles.xml
new file mode 100644
index 0000000..6ce89c7
--- /dev/null
+++ b/packages/CaptivePortalLogin/res/values/styles.xml
@@ -0,0 +1,20 @@
+<resources>
+
+    <!--
+        Base application theme, dependent on API level. This theme is replaced
+        by AppBaseTheme from res/values-vXX/styles.xml on newer devices.
+    -->
+    <style name="AppBaseTheme" parent="android:Theme.Light">
+        <!--
+            Theme customizations available in newer API levels can go in
+            res/values-vXX/styles.xml, while customizations related to
+            backward-compatibility can go here.
+        -->
+    </style>
+
+    <!-- Application theme. -->
+    <style name="AppTheme" parent="AppBaseTheme">
+        <!-- All customizations that are NOT specific to a particular API-level can go here. -->
+    </style>
+
+</resources>
diff --git a/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java b/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java
new file mode 100644
index 0000000..2c1db02
--- /dev/null
+++ b/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java
@@ -0,0 +1,160 @@
+/*
+ * 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 com.android.captiveportallogin;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.net.ConnectivityManager;
+import android.net.Network;
+import android.os.Bundle;
+import android.provider.Settings;
+import android.provider.Settings.Global;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.Window;
+import android.webkit.WebChromeClient;
+import android.webkit.WebSettings;
+import android.webkit.WebView;
+import android.webkit.WebViewClient;
+
+import java.io.IOException;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.lang.InterruptedException;
+
+public class CaptivePortalLoginActivity extends Activity {
+    private static final String DEFAULT_SERVER = "clients3.google.com";
+    private static final int SOCKET_TIMEOUT_MS = 10000;
+
+    // Keep this in sync with NetworkMonitor.
+    // Intent broadcast to ConnectivityService indicating sign-in is complete.
+    // Extras:
+    //     EXTRA_TEXT       = netId
+    //     LOGGED_IN_RESULT = "1" if we should use network, "0" if not.
+    private static final String ACTION_CAPTIVE_PORTAL_LOGGED_IN =
+            "android.net.netmon.captive_portal_logged_in";
+    private static final String LOGGED_IN_RESULT = "result";
+
+    private URL mURL;
+    private int mNetId;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        String server = Settings.Global.getString(getContentResolver(), "captive_portal_server");
+        if (server == null) server = DEFAULT_SERVER;
+        try {
+            mURL = new URL("http://" + server + "/generate_204");
+        } catch (MalformedURLException e) {
+            done(true);
+        }
+
+        requestWindowFeature(Window.FEATURE_PROGRESS);
+        setContentView(R.layout.activity_captive_portal_login);
+
+        getActionBar().setDisplayShowHomeEnabled(false);
+
+        mNetId = Integer.parseInt(getIntent().getStringExtra(Intent.EXTRA_TEXT));
+        ConnectivityManager.setProcessDefaultNetwork(new Network(mNetId));
+
+        WebView myWebView = (WebView) findViewById(R.id.webview);
+        WebSettings webSettings = myWebView.getSettings();
+        webSettings.setJavaScriptEnabled(true);
+        myWebView.setWebViewClient(new MyWebViewClient());
+        myWebView.setWebChromeClient(new MyWebChromeClient());
+        myWebView.loadUrl(mURL.toString());
+    }
+
+    private void done(boolean use_network) {
+        Intent intent = new Intent(ACTION_CAPTIVE_PORTAL_LOGGED_IN);
+        intent.putExtra(Intent.EXTRA_TEXT, String.valueOf(mNetId));
+        intent.putExtra(LOGGED_IN_RESULT, use_network ? "1" : "0");
+        sendBroadcast(intent);
+        finish();
+    }
+
+    @Override
+    public boolean onCreateOptionsMenu(Menu menu) {
+        getMenuInflater().inflate(R.menu.captive_portal_login, menu);
+        return true;
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        int id = item.getItemId();
+        if (id == R.id.action_use_network) {
+            done(true);
+            return true;
+        }
+        if (id == R.id.action_do_not_use_network) {
+            done(false);
+            return true;
+        }
+        return super.onOptionsItemSelected(item);
+    }
+
+    private void testForCaptivePortal() {
+        new Thread(new Runnable() {
+            public void run() {
+                // Give time for captive portal to open.
+                try {
+                    Thread.sleep(1000);
+                } catch (InterruptedException e) {
+                }
+                HttpURLConnection urlConnection = null;
+                int httpResponseCode = 500;
+                try {
+                    urlConnection = (HttpURLConnection) mURL.openConnection();
+                    urlConnection.setInstanceFollowRedirects(false);
+                    urlConnection.setConnectTimeout(SOCKET_TIMEOUT_MS);
+                    urlConnection.setReadTimeout(SOCKET_TIMEOUT_MS);
+                    urlConnection.setUseCaches(false);
+                    urlConnection.getInputStream();
+                    httpResponseCode = urlConnection.getResponseCode();
+                } catch (IOException e) {
+                } finally {
+                    if (urlConnection != null) urlConnection.disconnect();
+                }
+                if (httpResponseCode == 204) {
+                    done(true);
+                }
+            }
+        }).start();
+    }
+
+    private class MyWebViewClient extends WebViewClient {
+        @Override
+        public void onPageStarted(WebView view, String url, Bitmap favicon) {
+            testForCaptivePortal();
+        }
+
+        @Override
+        public void onPageFinished(WebView view, String url) {
+            testForCaptivePortal();
+        }
+    }
+
+    private class MyWebChromeClient extends WebChromeClient {
+        @Override
+        public void onProgressChanged(WebView view, int newProgress) {
+            setProgress(newProgress*100);
+        }
+    }
+}
diff --git a/packages/SystemUI/res/layout/status_bar_expanded_header.xml b/packages/SystemUI/res/layout/status_bar_expanded_header.xml
index 2fd7bc4..8b79dbf 100644
--- a/packages/SystemUI/res/layout/status_bar_expanded_header.xml
+++ b/packages/SystemUI/res/layout/status_bar_expanded_header.xml
@@ -69,7 +69,7 @@
             android:layout_height="wrap_content"
             android:layout_gravity="center_vertical"
             android:layout_marginStart="8dp"
-            android:paddingEnd="4dp"
+            android:paddingEnd="@dimen/battery_level_padding_end"
             android:textColor="#ffffff"
             android:textSize="12sp"/>
     </LinearLayout>
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/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 0bad2c3..5400326 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -354,4 +354,7 @@
 
     <!-- The font size of the time when expanded in QS -->
     <dimen name="qs_time_expanded_size">20sp</dimen>
+
+    <!-- Battery level padding end when in expanded QS (but not on Keyguard) -->
+    <dimen name="battery_level_padding_end">4dp</dimen>
 </resources>
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/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
index 57f4565..d1efb57 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
@@ -106,6 +106,7 @@
         public void run() {
             // Mark Recents as no longer visible
             AlternateRecentsComponent.notifyVisibilityChanged(false);
+            mVisible = false;
             // Finish Recents
             if (mLaunchIntent != null) {
                 if (mLaunchOpts != null) {
@@ -170,7 +171,7 @@
         @Override
         public void onReceive(Context context, Intent intent) {
             String action = intent.getAction();
-            if (action.equals(Intent.ACTION_SCREEN_OFF)) {
+            if (action.equals(Intent.ACTION_SCREEN_OFF) && mVisible) {
                 mFinishLaunchHomeRunnable.run();
             } else if (action.equals(SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED)) {
                 // Refresh the search widget
@@ -518,8 +519,6 @@
         if (mConfig.searchBarAppWidgetId >= 0) {
             mAppWidgetHost.stopListening();
         }
-
-        mVisible = false;
     }
 
     @Override
@@ -641,6 +640,7 @@
     public void onTaskViewClicked() {
         // Mark recents as no longer visible
         AlternateRecentsComponent.notifyVisibilityChanged(false);
+        mVisible = false;
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
index 9921c55..ed3ebf5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
@@ -75,16 +75,18 @@
     }
 
     private final ArrayList<Entry> mEntries = new ArrayList<Entry>();
-    private RankingMap mRanking;
+    private RankingMap mRankingMap;
+    private final Ranking mTmpRanking = new Ranking();
     private final Comparator<Entry> mRankingComparator = new Comparator<Entry>() {
+        private final Ranking mRankingA = new Ranking();
+        private final Ranking mRankingB = new Ranking();
+
         @Override
         public int compare(Entry a, Entry b) {
-            if (mRanking != null) {
-                Ranking aRanking = mRanking.getRanking(a.key);
-                Ranking bRanking = mRanking.getRanking(b.key);
-                int aRank = aRanking != null ? aRanking.getRank() : -1;
-                int bRank = bRanking != null ? bRanking.getRank() : -1;
-                return aRank - bRank;
+            if (mRankingMap != null) {
+                mRankingMap.getRanking(a.key, mRankingA);
+                mRankingMap.getRanking(b.key, mRankingB);
+                return mRankingA.getRank() - mRankingB.getRank();
             }
 
             final StatusBarNotification na = a.notification;
@@ -138,7 +140,7 @@
 
     public boolean isAmbient(String key) {
         // TODO: Remove when switching to NotificationListener.
-        if (mRanking == null) {
+        if (mRankingMap == null) {
             for (Entry entry : mEntries) {
                 if (key.equals(entry.key)) {
                     return entry.notification.getNotification().priority ==
@@ -146,15 +148,15 @@
                 }
             }
         } else {
-            Ranking ranking = mRanking.getRanking(key);
-            return ranking != null && ranking.isAmbient();
+            mRankingMap.getRanking(key, mTmpRanking);
+            return mTmpRanking.isAmbient();
         }
         return false;
     }
 
     private void updateRankingAndSort(RankingMap ranking) {
         if (ranking != null) {
-            mRanking = ranking;
+            mRankingMap = ranking;
         }
         Collections.sort(mEntries, mRankingComparator);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
index e0a1ef1..55b3088 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -126,6 +126,8 @@
     private boolean mBlockTouches;
     private ArrayList<View> mSwipeTranslationViews = new ArrayList<>();
     private int mNotificationScrimWaitDistance;
+    private boolean mTwoFingerQsExpand;
+    private boolean mTwoFingerQsExpandPossible;
 
     /**
      * If we are in a panel collapsing motion, we reset scrollY of our scroll view but still
@@ -487,7 +489,7 @@
         if (mExpandedHeight != 0) {
             handleQsDown(event);
         }
-        if (mQsTracking || mQsExpanded) {
+        if (!mTwoFingerQsExpand && (mQsTracking || mQsExpanded)) {
             onQsTouch(event);
             if (!mConflictingQsExpansionGesture) {
                 return true;
@@ -497,6 +499,15 @@
                 || event.getActionMasked() == MotionEvent.ACTION_UP) {
             mConflictingQsExpansionGesture = false;
         }
+        if (event.getActionMasked() == MotionEvent.ACTION_DOWN && mExpandedHeight == 0) {
+            mTwoFingerQsExpandPossible = true;
+        }
+        if (mTwoFingerQsExpandPossible && event.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN
+                && event.getPointerCount() == 2
+                && event.getY(event.getActionIndex()) < mStatusBarMinHeight) {
+            mTwoFingerQsExpand = true;
+            requestPanelHeightUpdate();
+        }
         super.onTouchEvent(event);
         return true;
     }
@@ -842,7 +853,7 @@
             min = Math.max(min, minHeight);
         }
         int maxHeight;
-        if (mQsExpanded || mIsExpanding && mQsExpandedWhenExpandingStarted) {
+        if (mTwoFingerQsExpand || mQsExpanded || mIsExpanding && mQsExpandedWhenExpandingStarted) {
             maxHeight = (int) calculatePanelHeightQsExpanded();
         } else {
             int emptyBottomMargin = mNotificationStackScroller.getEmptyBottomMargin();
@@ -863,7 +874,7 @@
         if (!mQsExpanded) {
             positionClockAndNotifications();
         }
-        if (mQsExpanded && !mQsTracking && mQsExpansionAnimator == null
+        if (mTwoFingerQsExpand || mQsExpanded && !mQsTracking && mQsExpansionAnimator == null
                 && !mQsExpansionFromOverscroll) {
             float panelHeightQsCollapsed = mNotificationStackScroller.getIntrinsicPadding()
                     + mNotificationStackScroller.getMinStackHeight()
@@ -1049,6 +1060,8 @@
             mHeader.setListening(true);
             mQsPanel.setListening(true);
         }
+        mTwoFingerQsExpand = false;
+        mTwoFingerQsExpandPossible = false;
     }
 
     @Override
@@ -1060,7 +1073,7 @@
 
     @Override
     protected void setOverExpansion(float overExpansion, boolean isPixels) {
-        if (mConflictingQsExpansionGesture) {
+        if (mConflictingQsExpansionGesture || mTwoFingerQsExpand) {
             return;
         }
         if (mStatusBar.getBarState() != StatusBarState.KEYGUARD) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java
index c3cfa90..0fbdeeb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java
@@ -46,6 +46,7 @@
     private boolean mListening;
     private boolean mOverscrolled;
     private boolean mKeyguardShowing;
+    private boolean mCharging;
 
     private ViewGroup mSystemIconsContainer;
     private View mSystemIconsSuperContainer;
@@ -80,6 +81,7 @@
     private int mClockMarginBottomExpanded;
     private int mMultiUserSwitchWidthCollapsed;
     private int mMultiUserSwitchWidthExpanded;
+    private int mBatteryPaddingEnd;
 
     /**
      * In collapsed QS, the clock and avatar are scaled down a bit post-layout to allow for a nice
@@ -164,6 +166,8 @@
         mClockCollapsedScaleFactor =
                 getResources().getDimensionPixelSize(R.dimen.qs_time_collapsed_size)
                 / mClock.getTextSize();
+        mBatteryPaddingEnd =
+                getResources().getDimensionPixelSize(R.dimen.battery_level_padding_end);
     }
 
     public void setActivityStarter(ActivityStarter activityStarter) {
@@ -210,6 +214,7 @@
             updateClockScale();
             updateAvatarScale();
             updateClockLp();
+            updateBatteryLevelPaddingEnd();
         }
     }
 
@@ -273,12 +278,13 @@
                 ? VISIBLE : GONE);
         mMultiUserSwitch.setVisibility(mExpanded || !mKeyguardUserSwitcherShowing
                 ? VISIBLE : GONE);
-        mBatteryLevel.setVisibility(mExpanded && !mOverscrolled ? View.VISIBLE : View.GONE);
+        mBatteryLevel.setVisibility(mKeyguardShowing && mCharging || mExpanded && !mOverscrolled
+                ? View.VISIBLE : View.GONE);
     }
 
     private void updateSystemIconsLayoutParams() {
         RelativeLayout.LayoutParams lp = (LayoutParams) mSystemIconsSuperContainer.getLayoutParams();
-        lp.addRule(RelativeLayout.START_OF, mExpanded
+        lp.addRule(RelativeLayout.START_OF, mExpanded && !mOverscrolled
                 ? mSettingsButton.getId()
                 : mMultiUserSwitch.getId());
         lp.removeRule(ALIGN_PARENT_START);
@@ -318,9 +324,19 @@
         }
     }
 
+    private void updateBatteryLevelPaddingEnd() {
+        mBatteryLevel.setPaddingRelative(0, 0,
+                mKeyguardShowing && !mExpanded ? 0 : mBatteryPaddingEnd, 0);
+    }
+
     @Override
     public void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) {
         mBatteryLevel.setText(getResources().getString(R.string.battery_level_template, level));
+        boolean changed = mCharging != charging;
+        mCharging = charging;
+        if (changed) {
+            updateVisibilities();
+        }
     }
 
     private void updateClickTargets() {
@@ -361,14 +377,16 @@
 
     private void updateMultiUserSwitch() {
         int marginEnd;
-        if (mExpanded) {
+        if (mExpanded && !mOverscrolled) {
             marginEnd = mMultiUserExpandedMargin;
         } else if (mKeyguardShowing) {
             marginEnd = mMultiUserKeyguardMargin;
         } else {
             marginEnd = mMultiUserCollapsedMargin;
         }
-        int width = mExpanded ? mMultiUserSwitchWidthExpanded : mMultiUserSwitchWidthCollapsed;
+        int width = mExpanded && !mOverscrolled
+                ? mMultiUserSwitchWidthExpanded
+                : mMultiUserSwitchWidthCollapsed;
         MarginLayoutParams lp = (MarginLayoutParams) mMultiUserSwitch.getLayoutParams();
         if (marginEnd != lp.getMarginEnd() || lp.width != width) {
             lp.setMarginEnd(marginEnd);
@@ -421,6 +439,7 @@
         updatePadding();
         updateMultiUserSwitch();
         updateClickTargets();
+        updateBatteryLevelPaddingEnd();
     }
 
     public void setUserInfoController(UserInfoController userInfoController) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
index 799b41f..2b08902 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
@@ -34,6 +34,7 @@
 import android.os.Messenger;
 import android.provider.Settings;
 import android.telephony.PhoneStateListener;
+import android.telephony.Rlog;
 import android.telephony.ServiceState;
 import android.telephony.SignalStrength;
 import android.telephony.TelephonyManager;
@@ -474,8 +475,8 @@
     PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
         @Override
         public void onSignalStrengthsChanged(SignalStrength signalStrength) {
-            if (DEBUG) {
-                Log.d(TAG, "onSignalStrengthsChanged signalStrength=" + signalStrength +
+            if (true/*DEBUG*/) {
+                Rlog.d(TAG, "onSignalStrengthsChanged signalStrength=" + signalStrength +
                     ((signalStrength == null) ? "" : (" level=" + signalStrength.getLevel())));
             }
             mSignalStrength = signalStrength;
@@ -485,8 +486,8 @@
 
         @Override
         public void onServiceStateChanged(ServiceState state) {
-            if (DEBUG) {
-                Log.d(TAG, "onServiceStateChanged voiceState=" + state.getVoiceRegState()
+            if (true/*DEBUG*/) {
+                Rlog.d(TAG, "onServiceStateChanged voiceState=" + state.getVoiceRegState()
                         + " dataState=" + state.getDataRegState());
             }
             mServiceState = state;
@@ -498,8 +499,8 @@
 
         @Override
         public void onCallStateChanged(int state, String incomingNumber) {
-            if (DEBUG) {
-                Log.d(TAG, "onCallStateChanged state=" + state);
+            if (true/*DEBUG*/) {
+                Rlog.d(TAG, "onCallStateChanged state=" + state);
             }
             // In cdma, if a voice call is made, RSSI should switch to 1x.
             if (isCdma()) {
@@ -510,8 +511,8 @@
 
         @Override
         public void onDataConnectionStateChanged(int state, int networkType) {
-            if (DEBUG) {
-                Log.d(TAG, "onDataConnectionStateChanged: state=" + state
+            if (true/*DEBUG*/) {
+                Rlog.d(TAG, "onDataConnectionStateChanged: state=" + state
                         + " type=" + networkType);
             }
             mDataState = state;
@@ -523,8 +524,8 @@
 
         @Override
         public void onDataActivity(int direction) {
-            if (DEBUG) {
-                Log.d(TAG, "onDataActivity: direction=" + direction);
+            if (true/*DEBUG*/) {
+                Rlog.d(TAG, "onDataActivity: direction=" + direction);
             }
             mDataActivity = direction;
             updateDataIcon();
@@ -555,6 +556,7 @@
         } else {
             mSimState = IccCardConstants.State.UNKNOWN;
         }
+        Rlog.d(TAG, "updateSimState: mSimState=" + mSimState);
     }
 
     private boolean isCdma() {
@@ -562,6 +564,7 @@
     }
 
     private boolean hasService() {
+        boolean retVal;
         if (mServiceState != null) {
             // Consider the device to be in service if either voice or data service is available.
             // Some SIM cards are marketed as data-only and do not support voice service, and on
@@ -569,16 +572,18 @@
             // service" or "emergency calls only" text that indicates that voice is not available.
             switch(mServiceState.getVoiceRegState()) {
                 case ServiceState.STATE_POWER_OFF:
-                    return false;
+                    retVal = false;
                 case ServiceState.STATE_OUT_OF_SERVICE:
                 case ServiceState.STATE_EMERGENCY_ONLY:
-                    return mServiceState.getDataRegState() == ServiceState.STATE_IN_SERVICE;
+                    retVal = mServiceState.getDataRegState() == ServiceState.STATE_IN_SERVICE;
                 default:
-                    return true;
+                    retVal = true;
             }
         } else {
-            return false;
+            retVal = false;
         }
+        Rlog.d(TAG, "hasService: mServiceState=" + mServiceState + " retVal=" + retVal);
+        return retVal;
     }
 
     private void updateAirplaneMode() {
@@ -591,14 +596,15 @@
     }
 
     private final void updateTelephonySignalStrength() {
-        if (!hasService()) {
+        Rlog.d(TAG, "updateTelephonySignalStrength: hasService=" + hasService() + " ss=" + mSignalStrength);
+        if (false/*!hasService()*/) {
             if (CHATTY) Log.d(TAG, "updateTelephonySignalStrength: !hasService()");
             mPhoneSignalIconId = R.drawable.stat_sys_signal_null;
             mQSPhoneSignalIconId = R.drawable.ic_qs_signal_no_signal;
             mDataSignalIconId = R.drawable.stat_sys_signal_null;
         } else {
             if (mSignalStrength == null) {
-                if (CHATTY) Log.d(TAG, "updateTelephonySignalStrength: mSignalStrength == null");
+                if (true/*CHATTY*/) Rlog.d(TAG, "updateTelephonySignalStrength: mSignalStrength == null");
                 mPhoneSignalIconId = R.drawable.stat_sys_signal_null;
                 mQSPhoneSignalIconId = R.drawable.ic_qs_signal_no_signal;
                 mDataSignalIconId = R.drawable.stat_sys_signal_null;
@@ -609,7 +615,7 @@
                 int[] iconList;
                 if (isCdma() && mAlwaysShowCdmaRssi) {
                     mLastSignalLevel = iconLevel = mSignalStrength.getCdmaLevel();
-                    if(DEBUG) Log.d(TAG, "mAlwaysShowCdmaRssi=" + mAlwaysShowCdmaRssi
+                    if(true/*DEBUG*/) Rlog.d(TAG, "updateTelephonySignalStrength: mAlwaysShowCdmaRssi=" + mAlwaysShowCdmaRssi
                             + " set to cdmaLevel=" + mSignalStrength.getCdmaLevel()
                             + " instead of level=" + mSignalStrength.getLevel());
                 } else {
@@ -636,6 +642,7 @@
                 mContentDescriptionPhoneSignal = mContext.getString(
                         AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[iconLevel]);
                 mDataSignalIconId = TelephonyIcons.DATA_SIGNAL_STRENGTH[mInetCondition][iconLevel];
+                Rlog.d(TAG, "updateTelephonySignalStrength: iconLevel=" + iconLevel);
             }
         }
     }
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/phone/java/android/phone/PhoneManager.java b/phone/java/android/phone/PhoneManager.java
index 360565e..f61b054 100644
--- a/phone/java/android/phone/PhoneManager.java
+++ b/phone/java/android/phone/PhoneManager.java
@@ -30,20 +30,17 @@
     private static final String TAG = PhoneManager.class.getSimpleName();
 
     private final Context mContext;
-    private final ITelecommService mService;
 
     /**
      * @hide
      */
-    public PhoneManager(Context context, ITelecommService service) {
+    public PhoneManager(Context context) {
         Context appContext = context.getApplicationContext();
         if (appContext != null) {
             mContext = appContext;
         } else {
             mContext = context;
         }
-
-        mService = service;
     }
 
     /**
@@ -56,10 +53,13 @@
      * @return True if the digits were processed as an MMI code, false otherwise.
      */
     public boolean handlePinMmi(String dialString) {
-        try {
-            return mService.handlePinMmi(dialString);
-        } catch (RemoteException e) {
-            Log.e(TAG, "Error calling ITelecommService#handlePinMmi", e);
+        ITelecommService service = getTelecommService();
+        if (service != null) {
+            try {
+                return service.handlePinMmi(dialString);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Error calling ITelecommService#handlePinMmi", e);
+            }
         }
         return false;
     }
@@ -71,10 +71,13 @@
      * </p>
      */
     public void cancelMissedCallsNotification() {
-        try {
-            mService.cancelMissedCallsNotification();
-        } catch (RemoteException e) {
-            Log.e(TAG, "Error calling ITelecommService#cancelMissedCallNotification", e);
+        ITelecommService service = getTelecommService();
+        if (service != null) {
+            try {
+                service.cancelMissedCallsNotification();
+            } catch (RemoteException e) {
+                Log.e(TAG, "Error calling ITelecommService#cancelMissedCallsNotification", e);
+            }
         }
     }
 
@@ -89,10 +92,13 @@
      * @param showDialpad Brings up the in-call dialpad as part of showing the in-call screen.
      */
     public void showCallScreen(boolean showDialpad) {
-        try {
-            mService.showCallScreen(showDialpad);
-        } catch (RemoteException e) {
-            Log.e(TAG, "Error calling ITelecommService#showCallScreen", e);
+        ITelecommService service = getTelecommService();
+        if (service != null) {
+            try {
+                service.showCallScreen(showDialpad);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Error calling ITelecommService#showCallScreen", e);
+            }
         }
     }
 
@@ -103,11 +109,19 @@
      * </p>
      */
     public boolean isInAPhoneCall() {
-        try {
-            return mService.isInAPhoneCall();
-        } catch (RemoteException e) {
-            Log.e(TAG, "Error caling ITelecommService#isInAPhoneCall", e);
+        ITelecommService service = getTelecommService();
+        if (service != null) {
+            try {
+                return service.isInAPhoneCall();
+            } catch (RemoteException e) {
+                Log.e(TAG, "Error caling ITelecommService#isInAPhoneCall", e);
+            }
         }
         return false;
     }
+
+    private ITelecommService getTelecommService() {
+        return ITelecommService.Stub.asInterface(
+                ServiceManager.getService(Context.TELECOMM_SERVICE));
+    }
 }
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 1083c4c..ea05b98 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -87,6 +87,7 @@
 import android.net.ProxyInfo;
 import android.net.RouteInfo;
 import android.net.SamplingDataTracker;
+import android.net.UidRange;
 import android.net.Uri;
 import android.net.wimax.WimaxManagerConstants;
 import android.os.AsyncTask;
@@ -235,7 +236,6 @@
 
     @GuardedBy("mVpns")
     private final SparseArray<Vpn> mVpns = new SparseArray<Vpn>();
-    private VpnCallback mVpnCallback = new VpnCallback();
 
     private boolean mLockdownEnabled;
     private LockdownVpnTracker mLockdownTracker;
@@ -363,8 +363,6 @@
      */
     private static final int EVENT_SET_POLICY_DATA_ENABLE = 12;
 
-    private static final int EVENT_VPN_STATE_CHANGED = 13;
-
     /**
      * Used internally to disable fail fast of mobile data
      */
@@ -1071,6 +1069,10 @@
      */
     private NetworkInfo getFilteredNetworkInfo(int networkType, int uid) {
         NetworkInfo info = getNetworkInfoForType(networkType);
+        return getFilteredNetworkInfo(info, networkType, uid);
+    }
+
+    private NetworkInfo getFilteredNetworkInfo(NetworkInfo info, int networkType, int uid) {
         if (isNetworkBlocked(networkType, uid)) {
             // network is blocked; clone and override state
             info = new NetworkInfo(info);
@@ -1176,6 +1178,24 @@
     }
 
     @Override
+    public NetworkInfo getNetworkInfoForNetwork(Network network) {
+        enforceAccessPermission();
+        if (network == null) return null;
+
+        final int uid = Binder.getCallingUid();
+        NetworkAgentInfo nai = null;
+        synchronized (mNetworkForNetId) {
+            nai = mNetworkForNetId.get(network.netId);
+        }
+        if (nai == null) return null;
+        synchronized (nai) {
+            if (nai.networkInfo == null) return null;
+
+            return getFilteredNetworkInfo(nai.networkInfo, nai.networkInfo.getType(), uid);
+        }
+    }
+
+    @Override
     public NetworkInfo[] getAllNetworkInfo() {
         enforceAccessPermission();
         final int uid = Binder.getCallingUid();
@@ -1192,6 +1212,18 @@
     }
 
     @Override
+    public Network[] getAllNetworks() {
+        enforceAccessPermission();
+        final ArrayList<Network> result = new ArrayList();
+        synchronized (mNetworkForNetId) {
+            for (int i = 0; i < mNetworkForNetId.size(); i++) {
+                result.add(new Network(mNetworkForNetId.valueAt(i).network));
+            }
+        }
+        return result.toArray(new Network[result.size()]);
+    }
+
+    @Override
     public boolean isNetworkSupported(int networkType) {
         enforceAccessPermission();
         return (isNetworkTypeValid(networkType) && (getNetworkInfoForType(networkType) != null));
@@ -1223,16 +1255,31 @@
     @Override
     public LinkProperties getLinkProperties(Network network) {
         enforceAccessPermission();
-        NetworkAgentInfo nai = mNetworkForNetId.get(network.netId);
-        if (nai != null) return new LinkProperties(nai.linkProperties);
+        NetworkAgentInfo nai = null;
+        synchronized (mNetworkForNetId) {
+            nai = mNetworkForNetId.get(network.netId);
+        }
+
+        if (nai != null) {
+            synchronized (nai) {
+                return new LinkProperties(nai.linkProperties);
+            }
+        }
         return null;
     }
 
     @Override
     public NetworkCapabilities getNetworkCapabilities(Network network) {
         enforceAccessPermission();
-        NetworkAgentInfo nai = mNetworkForNetId.get(network.netId);
-        if (nai != null) return new NetworkCapabilities(nai.networkCapabilities);
+        NetworkAgentInfo nai = null;
+        synchronized (mNetworkForNetId) {
+            nai = mNetworkForNetId.get(network.netId);
+        }
+        if (nai != null) {
+            synchronized (nai) {
+                return new NetworkCapabilities(nai.networkCapabilities);
+            }
+        }
         return null;
     }
 
@@ -1777,8 +1824,10 @@
             }
             return false;
         }
-
-        DetailedState netState = nai.networkInfo.getDetailedState();
+        DetailedState netState;
+        synchronized (nai) {
+            netState = nai.networkInfo.getDetailedState();
+        }
 
         if ((netState != DetailedState.CONNECTED &&
                 netState != DetailedState.CAPTIVE_PORTAL_CHECK)) {
@@ -1792,9 +1841,13 @@
         final int uid = Binder.getCallingUid();
         final long token = Binder.clearCallingIdentity();
         try {
-            LinkProperties lp = nai.linkProperties;
-            boolean ok = modifyRouteToAddress(lp, addr, ADD, TO_DEFAULT_TABLE, exempt,
-                    nai.network.netId, uid);
+            LinkProperties lp = null;
+            int netId = INVALID_NET_ID;
+            synchronized (nai) {
+                lp = nai.linkProperties;
+                netId = nai.network.netId;
+            }
+            boolean ok = modifyRouteToAddress(lp, addr, ADD, TO_DEFAULT_TABLE, exempt, netId, uid);
             if (DBG) log("requestRouteToHostAddress ok=" + ok);
             return ok;
         } finally {
@@ -3096,7 +3149,9 @@
                     } else {
                         if (VDBG) log("Update of Linkproperties for " + nai.name());
                         LinkProperties oldLp = nai.linkProperties;
-                        nai.linkProperties = (LinkProperties)msg.obj;
+                        synchronized (nai) {
+                            nai.linkProperties = (LinkProperties)msg.obj;
+                        }
                         updateLinkProperties(nai, oldLp);
                     }
                     break;
@@ -3121,6 +3176,30 @@
                     if (score != null) updateNetworkScore(nai, score.intValue());
                     break;
                 }
+                case NetworkAgent.EVENT_UID_RANGES_ADDED: {
+                    NetworkAgentInfo nai = mNetworkAgentInfos.get(msg.replyTo);
+                    if (nai == null) {
+                        loge("EVENT_UID_RANGES_ADDED from unknown NetworkAgent");
+                        break;
+                    }
+                    try {
+                        mNetd.addVpnUidRanges(nai.network.netId, (UidRange[])msg.obj);
+                    } catch (RemoteException e) {
+                    }
+                    break;
+                }
+                case NetworkAgent.EVENT_UID_RANGES_REMOVED: {
+                    NetworkAgentInfo nai = mNetworkAgentInfos.get(msg.replyTo);
+                    if (nai == null) {
+                        loge("EVENT_UID_RANGES_REMOVED from unknown NetworkAgent");
+                        break;
+                    }
+                    try {
+                        mNetd.removeVpnUidRanges(nai.network.netId, (UidRange[])msg.obj);
+                    } catch (RemoteException e) {
+                    }
+                    break;
+                }
                 case NetworkMonitor.EVENT_NETWORK_VALIDATED: {
                     NetworkAgentInfo nai = (NetworkAgentInfo)msg.obj;
                     handleConnectionValidated(nai);
@@ -3131,6 +3210,16 @@
                     handleLingerComplete(nai);
                     break;
                 }
+                case NetworkMonitor.EVENT_PROVISIONING_NOTIFICATION: {
+                    NetworkAgentInfo nai = mNetworkAgentInfos.get(msg.replyTo);
+                    if (nai == null) {
+                        loge("EVENT_PROVISIONING_NOTIFICATION from unknown NetworkMonitor");
+                        break;
+                    }
+                    setProvNotificationVisibleIntent(msg.arg1 != 0, nai.networkInfo.getType(),
+                            nai.networkInfo.getExtraInfo(), (PendingIntent)msg.obj);
+                    break;
+                }
                 case NetworkStateTracker.EVENT_STATE_CHANGED: {
                     info = (NetworkInfo) msg.obj;
                     NetworkInfo.State state = info.getState();
@@ -3242,7 +3331,9 @@
                 loge("Error connecting NetworkAgent");
                 NetworkAgentInfo nai = mNetworkAgentInfos.remove(msg.replyTo);
                 if (nai != null) {
-                    mNetworkForNetId.remove(nai.network.netId);
+                    synchronized (mNetworkForNetId) {
+                        mNetworkForNetId.remove(nai.network.netId);
+                    }
                     mLegacyTypeTracker.remove(nai);
                 }
             }
@@ -3275,7 +3366,9 @@
             mNetworkAgentInfos.remove(msg.replyTo);
             updateClat(null, nai.linkProperties, nai);
             mLegacyTypeTracker.remove(nai);
-            mNetworkForNetId.remove(nai.network.netId);
+            synchronized (mNetworkForNetId) {
+                mNetworkForNetId.remove(nai.network.netId);
+            }
             // Since we've lost the network, go through all the requests that
             // it was satisfying and see if any other factory can satisfy them.
             final ArrayList<NetworkAgentInfo> toActivate = new ArrayList<NetworkAgentInfo>();
@@ -3388,12 +3481,11 @@
                 if (affectedNetwork != null) {
                     // check if this network still has live requests - otherwise, tear down
                     // TODO - probably push this to the NF/NA
-                    boolean keep = false;
-                    for (int i = 0; i < affectedNetwork.networkRequests.size(); i++) {
+                    boolean keep = affectedNetwork.isVPN();
+                    for (int i = 0; i < affectedNetwork.networkRequests.size() && !keep; i++) {
                         NetworkRequest r = affectedNetwork.networkRequests.valueAt(i);
                         if (mNetworkRequests.get(r).isRequest) {
                             keep = true;
-                            break;
                         }
                     }
                     if (keep == false) {
@@ -3473,12 +3565,6 @@
                     handleSetPolicyDataEnable(networkType, enabled);
                     break;
                 }
-                case EVENT_VPN_STATE_CHANGED: {
-                    if (mLockdownTracker != null) {
-                        mLockdownTracker.onVpnStateChanged((NetworkInfo) msg.obj);
-                    }
-                    break;
-                }
                 case EVENT_ENABLE_FAIL_FAST_MOBILE_DATA: {
                     int tag = mEnableFailFastMobileDataTag.get();
                     if (msg.arg1 == tag) {
@@ -3613,6 +3699,11 @@
         return mTethering.getErroredIfaces();
     }
 
+    public String[] getTetheredDhcpRanges() {
+        enforceConnectivityInternalPermission();
+        return mTethering.getTetheredDhcpRanges();
+    }
+
     // if ro.tether.denied = true we default to no tethering
     // gservices could set the secure setting to 1 though to enable it on a build where it
     // had previously been turned off.
@@ -3981,36 +4072,6 @@
     }
 
     /**
-     * Protect a socket from VPN routing rules. This method is used by
-     * VpnBuilder and not available in ConnectivityManager. Permissions
-     * are checked in Vpn class.
-     * @hide
-     */
-    @Override
-    public boolean protectVpn(ParcelFileDescriptor socket) {
-        throwIfLockdownEnabled();
-        try {
-            int type = mActiveDefaultNetwork;
-            int user = UserHandle.getUserId(Binder.getCallingUid());
-            if (ConnectivityManager.isNetworkTypeValid(type) && mNetTrackers[type] != null) {
-                synchronized(mVpns) {
-                    mVpns.get(user).protect(socket);
-                }
-                return true;
-            }
-        } catch (Exception e) {
-            // ignore
-        } finally {
-            try {
-                socket.close();
-            } catch (Exception e) {
-                // ignore
-            }
-        }
-        return false;
-    }
-
-    /**
      * Prepare for a VPN application. This method is used by VpnDialogs
      * and not available in ConnectivityManager. Permissions are checked
      * in Vpn class.
@@ -4104,144 +4165,6 @@
         }
     }
 
-    /**
-     * Callback for VPN subsystem. Currently VPN is not adapted to the service
-     * through NetworkStateTracker since it works differently. For example, it
-     * needs to override DNS servers but never takes the default routes. It
-     * relies on another data network, and it could keep existing connections
-     * alive after reconnecting, switching between networks, or even resuming
-     * from deep sleep. Calls from applications should be done synchronously
-     * to avoid race conditions. As these are all hidden APIs, refactoring can
-     * be done whenever a better abstraction is developed.
-     */
-    public class VpnCallback {
-        private VpnCallback() {
-        }
-
-        public void onStateChanged(NetworkInfo info) {
-            mHandler.obtainMessage(EVENT_VPN_STATE_CHANGED, info).sendToTarget();
-        }
-
-        public void override(String iface, List<String> dnsServers, List<String> searchDomains) {
-            if (dnsServers == null) {
-                restore();
-                return;
-            }
-
-            // Convert DNS servers into addresses.
-            List<InetAddress> addresses = new ArrayList<InetAddress>();
-            for (String address : dnsServers) {
-                // Double check the addresses and remove invalid ones.
-                try {
-                    addresses.add(InetAddress.parseNumericAddress(address));
-                } catch (Exception e) {
-                    // ignore
-                }
-            }
-            if (addresses.isEmpty()) {
-                restore();
-                return;
-            }
-
-            // Concatenate search domains into a string.
-            StringBuilder buffer = new StringBuilder();
-            if (searchDomains != null) {
-                for (String domain : searchDomains) {
-                    buffer.append(domain).append(' ');
-                }
-            }
-            String domains = buffer.toString().trim();
-
-            // Apply DNS changes.
-            synchronized (mDnsLock) {
-                // TODO: Re-enable this when the netId of the VPN is known.
-                // updateDnsLocked("VPN", netId, addresses, domains);
-            }
-
-            // Temporarily disable the default proxy (not global).
-            synchronized (mProxyLock) {
-                mDefaultProxyDisabled = true;
-                if (mGlobalProxy == null && mDefaultProxy != null) {
-                    sendProxyBroadcast(null);
-                }
-            }
-
-            // TODO: support proxy per network.
-        }
-
-        public void restore() {
-            synchronized (mProxyLock) {
-                mDefaultProxyDisabled = false;
-                if (mGlobalProxy == null && mDefaultProxy != null) {
-                    sendProxyBroadcast(mDefaultProxy);
-                }
-            }
-        }
-
-        public void protect(ParcelFileDescriptor socket) {
-            try {
-                final int mark = mNetd.getMarkForProtect();
-                NetworkUtils.markSocket(socket.getFd(), mark);
-            } catch (RemoteException e) {
-            }
-        }
-
-        public void setRoutes(String interfaze, List<RouteInfo> routes) {
-            for (RouteInfo route : routes) {
-                try {
-                    mNetd.setMarkedForwardingRoute(interfaze, route);
-                } catch (RemoteException e) {
-                }
-            }
-        }
-
-        public void setMarkedForwarding(String interfaze) {
-            try {
-                mNetd.setMarkedForwarding(interfaze);
-            } catch (RemoteException e) {
-            }
-        }
-
-        public void clearMarkedForwarding(String interfaze) {
-            try {
-                mNetd.clearMarkedForwarding(interfaze);
-            } catch (RemoteException e) {
-            }
-        }
-
-        public void addUserForwarding(String interfaze, int uid, boolean forwardDns) {
-            int uidStart = uid * UserHandle.PER_USER_RANGE;
-            int uidEnd = uidStart + UserHandle.PER_USER_RANGE - 1;
-            addUidForwarding(interfaze, uidStart, uidEnd, forwardDns);
-        }
-
-        public void clearUserForwarding(String interfaze, int uid, boolean forwardDns) {
-            int uidStart = uid * UserHandle.PER_USER_RANGE;
-            int uidEnd = uidStart + UserHandle.PER_USER_RANGE - 1;
-            clearUidForwarding(interfaze, uidStart, uidEnd, forwardDns);
-        }
-
-        public void addUidForwarding(String interfaze, int uidStart, int uidEnd,
-                boolean forwardDns) {
-            // TODO: Re-enable this when the netId of the VPN is known.
-            // try {
-            //     mNetd.setUidRangeRoute(netId, uidStart, uidEnd, forwardDns);
-            // } catch (RemoteException e) {
-            // }
-
-        }
-
-        public void clearUidForwarding(String interfaze, int uidStart, int uidEnd,
-                boolean forwardDns) {
-            // TODO: Re-enable this when the netId of the VPN is known.
-            // try {
-            //     mNetd.clearUidRangeRoute(interfaze, uidStart, uidEnd);
-            // } catch (RemoteException e) {
-            // }
-
-        }
-    }
-
     @Override
     public boolean updateLockdownVpn() {
         if (Binder.getCallingUid() != Process.SYSTEM_UID) {
@@ -5039,6 +4962,40 @@
             log("setProvNotificationVisible: E visible=" + visible + " networkType=" + networkType
                 + " extraInfo=" + extraInfo + " url=" + url);
         }
+        Intent intent = null;
+        PendingIntent pendingIntent = null;
+        if (visible) {
+            switch (networkType) {
+                case ConnectivityManager.TYPE_WIFI:
+                    intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
+                    intent.setFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT |
+                            Intent.FLAG_ACTIVITY_NEW_TASK);
+                    pendingIntent = PendingIntent.getActivity(mContext, 0, intent, 0);
+                    break;
+                case ConnectivityManager.TYPE_MOBILE:
+                case ConnectivityManager.TYPE_MOBILE_HIPRI:
+                    intent = new Intent(CONNECTED_TO_PROVISIONING_NETWORK_ACTION);
+                    intent.putExtra("EXTRA_URL", url);
+                    intent.setFlags(0);
+                    pendingIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0);
+                    break;
+                default:
+                    intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
+                    intent.setFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT |
+                            Intent.FLAG_ACTIVITY_NEW_TASK);
+                    pendingIntent = PendingIntent.getActivity(mContext, 0, intent, 0);
+                    break;
+            }
+        }
+        setProvNotificationVisibleIntent(visible, networkType, extraInfo, pendingIntent);
+    }
+
+    private void setProvNotificationVisibleIntent(boolean visible, int networkType,
+            String extraInfo, PendingIntent intent) {
+        if (DBG) {
+            log("setProvNotificationVisibleIntent: E visible=" + visible + " networkType=" +
+                networkType + " extraInfo=" + extraInfo);
+        }
 
         Resources r = Resources.getSystem();
         NotificationManager notificationManager = (NotificationManager) mContext
@@ -5048,7 +5005,6 @@
             CharSequence title;
             CharSequence details;
             int icon;
-            Intent intent;
             Notification notification = new Notification();
             switch (networkType) {
                 case ConnectivityManager.TYPE_WIFI:
@@ -5056,10 +5012,6 @@
                     details = r.getString(R.string.network_available_sign_in_detailed,
                             extraInfo);
                     icon = R.drawable.stat_notify_wifi_in_range;
-                    intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
-                    intent.setFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT |
-                            Intent.FLAG_ACTIVITY_NEW_TASK);
-                    notification.contentIntent = PendingIntent.getActivity(mContext, 0, intent, 0);
                     break;
                 case ConnectivityManager.TYPE_MOBILE:
                 case ConnectivityManager.TYPE_MOBILE_HIPRI:
@@ -5068,20 +5020,12 @@
                     // name has been added to it
                     details = mTelephonyManager.getNetworkOperatorName();
                     icon = R.drawable.stat_notify_rssi_in_range;
-                    intent = new Intent(CONNECTED_TO_PROVISIONING_NETWORK_ACTION);
-                    intent.putExtra("EXTRA_URL", url);
-                    intent.setFlags(0);
-                    notification.contentIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0);
                     break;
                 default:
                     title = r.getString(R.string.network_available_sign_in, 0);
                     details = r.getString(R.string.network_available_sign_in_detailed,
                             extraInfo);
                     icon = R.drawable.stat_notify_rssi_in_range;
-                    intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
-                    intent.setFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT |
-                            Intent.FLAG_ACTIVITY_NEW_TASK);
-                    notification.contentIntent = PendingIntent.getActivity(mContext, 0, intent, 0);
                     break;
             }
 
@@ -5090,6 +5034,7 @@
             notification.flags = Notification.FLAG_AUTO_CANCEL;
             notification.tickerText = title;
             notification.setLatestEventInfo(mContext, title, details, notification.contentIntent);
+            notification.contentIntent = intent;
 
             try {
                 notificationManager.notify(NOTIFICATION_ID, networkType, notification);
@@ -5263,9 +5208,8 @@
                 loge("Starting user already has a VPN");
                 return;
             }
-            userVpn = new Vpn(mContext, mVpnCallback, mNetd, this, userId);
+            userVpn = new Vpn(mHandler.getLooper(), mContext, mNetd, this, userId);
             mVpns.put(userId, userVpn);
-            userVpn.startMonitoring(mContext, mTrackerHandler);
         }
     }
 
@@ -5565,7 +5509,9 @@
     private void handleRegisterNetworkAgent(NetworkAgentInfo na) {
         if (VDBG) log("Got NetworkAgent Messenger");
         mNetworkAgentInfos.put(na.messenger, na);
-        mNetworkForNetId.put(na.network.netId, na);
+        synchronized (mNetworkForNetId) {
+            mNetworkForNetId.put(na.network.netId, na);
+        }
         na.asyncChannel.connect(mContext, mTrackerHandler, na.messenger);
         NetworkInfo networkInfo = na.networkInfo;
         na.networkInfo = null;
@@ -5710,7 +5656,9 @@
             NetworkCapabilities networkCapabilities) {
         // TODO - what else here?  Verify still satisfies everybody?
         // Check if satisfies somebody new?  call callbacks?
-        networkAgent.networkCapabilities = networkCapabilities;
+        synchronized (networkAgent) {
+            networkAgent.networkCapabilities = networkCapabilities;
+        }
     }
 
     private void sendUpdatedScoreToFactories(NetworkRequest networkRequest, int score) {
@@ -5783,7 +5731,7 @@
             loge("Unknown NetworkAgentInfo in handleConnectionValidated");
             return;
         }
-        boolean keep = false;
+        boolean keep = newNetwork.isVPN();
         boolean isNewDefault = false;
         if (DBG) log("handleConnectionValidated for "+newNetwork.name());
         // check if any NetworkRequest wants this NetworkAgent
@@ -5845,8 +5793,8 @@
             }
         }
         for (NetworkAgentInfo nai : affectedNetworks) {
-            boolean teardown = true;
-            for (int i = 0; i < nai.networkRequests.size(); i++) {
+            boolean teardown = !nai.isVPN();
+            for (int i = 0; i < nai.networkRequests.size() && teardown; i++) {
                 NetworkRequest nr = nai.networkRequests.valueAt(i);
                 try {
                 if (mNetworkRequests.get(nr).isRequest) {
@@ -5924,8 +5872,14 @@
 
     private void updateNetworkInfo(NetworkAgentInfo networkAgent, NetworkInfo newInfo) {
         NetworkInfo.State state = newInfo.getState();
-        NetworkInfo oldInfo = networkAgent.networkInfo;
-        networkAgent.networkInfo = newInfo;
+        NetworkInfo oldInfo = null;
+        synchronized (networkAgent) {
+            oldInfo = networkAgent.networkInfo;
+            networkAgent.networkInfo = newInfo;
+        }
+        if (networkAgent.isVPN() && mLockdownTracker != null) {
+            mLockdownTracker.onVpnStateChanged(newInfo);
+        }
 
         if (oldInfo != null && oldInfo.getState() == state) {
             if (VDBG) log("ignoring duplicate network state non-change");
@@ -5944,7 +5898,12 @@
                 // CONNECTING and back (like wifi on DHCP renew).
                 // TODO: keep track of which networks we've created, or ask netd
                 // to tell us whether we've already created this network or not.
-                mNetd.createNetwork(networkAgent.network.netId);
+                if (networkAgent.isVPN()) {
+                    mNetd.createVirtualNetwork(networkAgent.network.netId,
+                            !networkAgent.linkProperties.getDnsServers().isEmpty());
+                } else {
+                    mNetd.createPhysicalNetwork(networkAgent.network.netId);
+                }
             } catch (Exception e) {
                 loge("Error creating network " + networkAgent.network.netId + ": "
                         + e.getMessage());
@@ -5954,9 +5913,31 @@
             updateLinkProperties(networkAgent, null);
             notifyNetworkCallbacks(networkAgent, ConnectivityManager.CALLBACK_PRECHECK);
             networkAgent.networkMonitor.sendMessage(NetworkMonitor.CMD_NETWORK_CONNECTED);
+            if (networkAgent.isVPN()) {
+                // Temporarily disable the default proxy (not global).
+                synchronized (mProxyLock) {
+                    if (!mDefaultProxyDisabled) {
+                        mDefaultProxyDisabled = true;
+                        if (mGlobalProxy == null && mDefaultProxy != null) {
+                            sendProxyBroadcast(null);
+                        }
+                    }
+                }
+                // TODO: support proxy per network.
+            }
         } else if (state == NetworkInfo.State.DISCONNECTED ||
                 state == NetworkInfo.State.SUSPENDED) {
             networkAgent.asyncChannel.disconnect();
+            if (networkAgent.isVPN()) {
+                synchronized (mProxyLock) {
+                    if (mDefaultProxyDisabled) {
+                        mDefaultProxyDisabled = false;
+                        if (mGlobalProxy == null && mDefaultProxy != null) {
+                            sendProxyBroadcast(mDefaultProxy);
+                        }
+                    }
+                }
+            }
         }
     }
 
@@ -6054,9 +6035,12 @@
 
     private LinkProperties getLinkPropertiesForTypeInternal(int networkType) {
         NetworkAgentInfo nai = mLegacyTypeTracker.getNetworkForType(networkType);
-        return (nai != null) ?
-                new LinkProperties(nai.linkProperties) :
-                new LinkProperties();
+        if (nai != null) {
+            synchronized (nai) {
+                return new LinkProperties(nai.linkProperties);
+            }
+        }
+        return new LinkProperties();
     }
 
     private NetworkInfo getNetworkInfoForType(int networkType) {
@@ -6075,8 +6059,11 @@
 
     private NetworkCapabilities getNetworkCapabilitiesForType(int networkType) {
         NetworkAgentInfo nai = mLegacyTypeTracker.getNetworkForType(networkType);
-        return (nai != null) ?
-                new NetworkCapabilities(nai.networkCapabilities) :
-                new NetworkCapabilities();
+        if (nai != null) {
+            synchronized (nai) {
+                return new NetworkCapabilities(nai.networkCapabilities);
+            }
+        }
+        return new NetworkCapabilities();
     }
 }
diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java
index f9c7a78..c9f40cf 100644
--- a/services/core/java/com/android/server/NetworkManagementService.java
+++ b/services/core/java/com/android/server/NetworkManagementService.java
@@ -46,6 +46,7 @@
 import android.net.NetworkStats;
 import android.net.NetworkUtils;
 import android.net.RouteInfo;
+import android.net.UidRange;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiConfiguration.KeyMgmt;
 import android.os.BatteryStats;
@@ -90,6 +91,7 @@
 import java.net.NetworkInterface;
 import java.net.SocketException;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -116,6 +118,8 @@
     private static final String DEFAULT = "default";
     private static final String SECONDARY = "secondary";
 
+    private static final int MAX_UID_RANGES_PER_COMMAND = 10;
+
     /**
      * Name representing {@link #setGlobalAlert(long)} limit when delivered to
      * {@link INetworkManagementEventObserver#limitReached(String, String)}.
@@ -1702,44 +1706,46 @@
     }
 
     @Override
-    public void setUidRangeRoute(String iface, int uid_start, int uid_end, boolean forward_dns) {
+    public void addVpnUidRanges(int netId, UidRange[] ranges) {
         mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
-        try {
-            mConnector.execute("interface", "fwmark",
-                    "uid", "add", iface, uid_start, uid_end, forward_dns ? 1 : 0);
-        } catch (NativeDaemonConnectorException e) {
-            throw e.rethrowAsParcelableException();
+        Object[] argv = new Object[3 + MAX_UID_RANGES_PER_COMMAND];
+        argv[0] = "users";
+        argv[1] = "add";
+        argv[2] = netId;
+        int argc = 3;
+        // Avoid overly long commands by limiting number of UID ranges per command.
+        for (int i = 0; i < ranges.length; i++) {
+            argv[argc++] = ranges[i].toString();
+            if (i == (ranges.length - 1) || argc == argv.length) {
+                try {
+                    mConnector.execute("network", Arrays.copyOf(argv, argc));
+                } catch (NativeDaemonConnectorException e) {
+                    throw e.rethrowAsParcelableException();
+                }
+                argc = 3;
+            }
         }
     }
 
     @Override
-    public void clearUidRangeRoute(String iface, int uid_start, int uid_end) {
+    public void removeVpnUidRanges(int netId, UidRange[] ranges) {
         mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
-        try {
-            mConnector.execute("interface", "fwmark",
-                    "uid", "remove", iface, uid_start, uid_end, 0);
-        } catch (NativeDaemonConnectorException e) {
-            throw e.rethrowAsParcelableException();
-        }
-    }
-
-    @Override
-    public void setMarkedForwarding(String iface) {
-        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
-        try {
-            mConnector.execute("interface", "fwmark", "rule", "add", iface);
-        } catch (NativeDaemonConnectorException e) {
-            throw e.rethrowAsParcelableException();
-        }
-    }
-
-    @Override
-    public void clearMarkedForwarding(String iface) {
-        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
-        try {
-            mConnector.execute("interface", "fwmark", "rule", "remove", iface);
-        } catch (NativeDaemonConnectorException e) {
-            throw e.rethrowAsParcelableException();
+        Object[] argv = new Object[3 + MAX_UID_RANGES_PER_COMMAND];
+        argv[0] = "users";
+        argv[1] = "remove";
+        argv[2] = netId;
+        int argc = 3;
+        // Avoid overly long commands by limiting number of UID ranges per command.
+        for (int i = 0; i < ranges.length; i++) {
+            argv[argc++] = ranges[i].toString();
+            if (i == (ranges.length - 1) || argc == argv.length) {
+                try {
+                    mConnector.execute("network", Arrays.copyOf(argv, argc));
+                } catch (NativeDaemonConnectorException e) {
+                    throw e.rethrowAsParcelableException();
+                }
+                argc = 3;
+            }
         }
     }
 
@@ -2015,7 +2021,7 @@
     }
 
     @Override
-    public void createNetwork(int netId) {
+    public void createPhysicalNetwork(int netId) {
         mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
 
         try {
@@ -2026,6 +2032,17 @@
     }
 
     @Override
+    public void createVirtualNetwork(int netId, boolean hasDNS) {
+        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
+
+        try {
+            mConnector.execute("network", "create", netId, "vpn", hasDNS ? "1" : "0");
+        } catch (NativeDaemonConnectorException e) {
+            throw e.rethrowAsParcelableException();
+        }
+    }
+
+    @Override
     public void removeNetwork(int netId) {
         mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
 
@@ -2143,4 +2160,27 @@
             throw e.rethrowAsParcelableException();
         }
     }
+
+    @Override
+    public void allowProtect(int uid) {
+        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
+
+        try {
+            mConnector.execute("network", "protect", "allow", uid);
+        } catch (NativeDaemonConnectorException e) {
+            throw e.rethrowAsParcelableException();
+        }
+    }
+
+    @Override
+    public void denyProtect(int uid) {
+        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
+
+        try {
+            mConnector.execute("network", "protect", "deny", uid);
+        } catch (NativeDaemonConnectorException e) {
+            throw e.rethrowAsParcelableException();
+        }
+    }
+
 }
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index 88598c9..a19eb15 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -47,8 +47,10 @@
 import android.telephony.PreciseDataConnectionState;
 import android.telephony.PreciseDisconnectCause;
 import android.text.TextUtils;
+import android.text.format.Time;
 
 import java.util.ArrayList;
+import java.util.Calendar;
 import java.util.List;
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -354,10 +356,12 @@
                 }
                 int phoneId = SubscriptionManager.getPhoneId(subId);
                 r.events = events;
-                if (true/*DBG*/) log("listen: set events record=" + r);
+                if (true/*DBG*/) log("listen: set events record=" + r + " subId=" + subId + " phoneId=" + phoneId);
+                toStringLogSSC("listen");
                 if (notifyNow && validatePhoneId(phoneId)) {
                     if ((events & PhoneStateListener.LISTEN_SERVICE_STATE) != 0) {
                         try {
+                            log("listen: call onSSC state=" + mServiceState[phoneId]);
                             r.callback.onServiceStateChanged(
                                     new ServiceState(mServiceState[phoneId]));
                         } catch (RemoteException ex) {
@@ -550,14 +554,17 @@
             subId = mDefaultSubId;
             log("notifyServiceStateUsingSubId: using mDefaultSubId=" + mDefaultSubId);
         }
-        if (true/*VDBG*/) {
-            log("notifyServiceStateUsingSubId: subId=" + subId
-                + " state=" + state);
-        }
         synchronized (mRecords) {
             int phoneId = SubscriptionManager.getPhoneId(subId);
+            if (true/*VDBG*/) {
+                log("notifyServiceStateUsingSubId: subId=" + subId + " phoneId=" + phoneId
+                    + " state=" + state);
+            }
             if (validatePhoneId(phoneId)) {
                 mServiceState[phoneId] = state;
+                logServiceStateChanged("notifyServiceStateUsingSubId", subId, phoneId, state);
+                toStringLogSSC("notifyServiceStateUsingSubId");
+
                 for (Record r : mRecords) {
                     log("notifyServiceStateUsingSubId: r.events=0x" + Integer.toHexString(r.events) + " r.subId=" + r.subId + " subId=" + subId + " state=" + state);
                     // FIXME: use DEFAULT_SUB_ID instead??
@@ -591,6 +598,7 @@
         if (true/*VDBG*/) {
             log("notifySignalStrengthUsingSubId: subId=" + subId
                 + " signalStrength=" + signalStrength);
+            toStringLogSSC("notifySignalStrengthUsingSubId");
         }
         synchronized (mRecords) {
             int phoneId = SubscriptionManager.getPhoneId(subId);
@@ -1295,4 +1303,59 @@
     private static void log(String s) {
         Rlog.d(TAG, s);
     }
+
+    private static class LogSSC {
+        private Time mTime;
+        private String mS;
+        private long mSubId;
+        private int mPhoneId;
+        private ServiceState mState;
+
+        public void set(Time t, String s, long subId, int phoneId, ServiceState state) {
+            mTime = t; mS = s; mSubId = subId; mPhoneId = phoneId; mState = state;
+        }
+
+        @Override
+        public String toString() {
+            return mS + " " + mTime.toString() + " " + mSubId + " " + mPhoneId + " " + mState;
+        }
+    }
+
+    private LogSSC logSSC [] = new LogSSC[10];
+    private int next = 0;
+
+    private void logServiceStateChanged(String s, long subId, int phoneId, ServiceState state) {
+        if (logSSC == null || logSSC.length == 0) {
+            return;
+        }
+        if (logSSC[next] == null) {
+            logSSC[next] = new LogSSC();
+        }
+        Time t = new Time();
+        t.setToNow();
+        logSSC[next].set(t, s, subId, phoneId, state);
+        if (++next >= logSSC.length) {
+            next = 0;
+        }
+    }
+
+    private void toStringLogSSC(String prompt) {
+        if (logSSC == null || logSSC.length == 0 || (next == 0 && logSSC[next] == null)) {
+            log(prompt + ": logSSC is empty");
+        } else {
+            // There is at least one element
+            log(prompt + ": logSSC.length=" + logSSC.length + " next=" + next);
+            int i = next;
+            if (logSSC[i] == null) {
+                // logSSC is not full so back to the beginning
+                i = 0;
+            }
+            do {
+                log(logSSC[i].toString());
+                if (++i >= logSSC.length) {
+                    i = 0;
+                }
+            } while (i != next);
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index c7eabe8..e8e1536 100755
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -2236,6 +2236,7 @@
     }
 
     private void start() {
+        Process.removeAllProcessGroups();
         mProcessCpuThread.start();
 
         mBatteryStatsService.publish(mContext);
@@ -2827,6 +2828,7 @@
             // An application record is attached to a previous process,
             // clean it up now.
             if (DEBUG_PROCESSES || DEBUG_CLEANUP) Slog.v(TAG, "App died: " + app);
+            Process.killProcessGroup(app.info.uid, app.pid);
             handleAppDiedLocked(app, true, true);
         }
 
@@ -4069,6 +4071,8 @@
             stats.noteProcessDiedLocked(app.info.uid, pid);
         }
 
+        Process.killProcessGroup(app.info.uid, pid);
+
         // Clean up already done if the process has been re-started.
         if (app.pid == pid && app.thread != null &&
                 app.thread.asBinder() == thread.asBinder()) {
@@ -4307,7 +4311,10 @@
             try {
                 // 0 == continue, -1 = kill process immediately
                 int res = mController.appEarlyNotResponding(app.processName, app.pid, annotation);
-                if (res < 0 && app.pid != MY_PID) Process.killProcess(app.pid);
+                if (res < 0 && app.pid != MY_PID) {
+                    Process.killProcess(app.pid);
+                    Process.killProcessGroup(app.info.uid, app.pid);
+                }
             } catch (RemoteException e) {
                 mController = null;
                 Watchdog.getInstance().setActivityController(null);
@@ -4413,6 +4420,7 @@
                 if (res != 0) {
                     if (res < 0 && app.pid != MY_PID) {
                         Process.killProcess(app.pid);
+                        Process.killProcessGroup(app.info.uid, app.pid);
                     } else {
                         synchronized (this) {
                             mServices.scheduleServiceTimeoutLocked(app);
@@ -5122,6 +5130,7 @@
                 mBatteryStatsService.removeIsolatedUid(app.uid, app.info.uid);
             }
             killUnneededProcessLocked(app, reason);
+            Process.killProcessGroup(app.info.uid, app.pid);
             handleAppDiedLocked(app, true, allowRestart);
             removeLruProcessLocked(app);
 
@@ -5210,6 +5219,7 @@
             EventLog.writeEvent(EventLogTags.AM_DROP_PROCESS, pid);
             if (pid > 0 && pid != MY_PID) {
                 Process.killProcessQuiet(pid);
+                //TODO: Process.killProcessGroup(app.info.uid, pid);
             } else {
                 try {
                     thread.scheduleExit();
@@ -7409,6 +7419,7 @@
                     pr.processName, pr.setAdj, reason);
             pr.killedByAm = true;
             Process.killProcessQuiet(pr.pid);
+            Process.killProcessGroup(pr.info.uid, pr.pid);
         }
     }
 
@@ -10749,11 +10760,15 @@
                 try {
                     String name = r != null ? r.processName : null;
                     int pid = r != null ? r.pid : Binder.getCallingPid();
+                    int uid = r != null ? r.info.uid : Binder.getCallingUid();
                     if (!mController.appCrashed(name, pid,
                             shortMsg, longMsg, timeMillis, crashInfo.stackTrace)) {
                         Slog.w(TAG, "Force-killing crashed app " + name
                                 + " at watcher's request");
                         Process.killProcess(pid);
+                        if (r != null) {
+                            Process.killProcessGroup(uid, pid);
+                        }
                         return;
                     }
                 } catch (RemoteException e) {
@@ -16587,6 +16602,7 @@
                                 app.processName, app.setAdj, "empty");
                         app.killedByAm = true;
                         Process.killProcessQuiet(app.pid);
+                        Process.killProcessGroup(app.info.uid, app.pid);
                     } else {
                         try {
                             app.thread.scheduleExit();
@@ -17031,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/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
index 1332898..10bdba0 100644
--- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
+++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
@@ -69,6 +69,10 @@
         networkRequests.put(networkRequest.requestId, networkRequest);
     }
 
+    public boolean isVPN() {
+        return networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_VPN);
+    }
+
     public String toString() {
         return "NetworkAgentInfo{ ni{" + networkInfo + "}  network{" +
                 network + "}  lp{" +
diff --git a/services/core/java/com/android/server/connectivity/NetworkMonitor.java b/services/core/java/com/android/server/connectivity/NetworkMonitor.java
index 47789b1..6fb8570 100644
--- a/services/core/java/com/android/server/connectivity/NetworkMonitor.java
+++ b/services/core/java/com/android/server/connectivity/NetworkMonitor.java
@@ -16,17 +16,26 @@
 
 package com.android.server.connectivity;
 
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
 import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.ConnectivityManager;
+import android.net.Network;
 import android.net.NetworkCapabilities;
 import android.net.NetworkInfo;
 import android.os.Handler;
 import android.os.Message;
 import android.os.SystemProperties;
+import android.os.UserHandle;
 import android.provider.Settings;
 
 import com.android.internal.util.Protocol;
 import com.android.internal.util.State;
 import com.android.internal.util.StateMachine;
+import com.android.server.ConnectivityService;
 import com.android.server.connectivity.NetworkAgentInfo;
 
 import java.io.BufferedReader;
@@ -34,6 +43,7 @@
 import java.io.IOException;
 import java.io.OutputStreamWriter;
 import java.net.HttpURLConnection;
+import java.net.InetAddress;
 import java.net.InetSocketAddress;
 import java.net.Socket;
 import java.net.URL;
@@ -47,6 +57,19 @@
     private static final String DEFAULT_SERVER = "clients3.google.com";
     private static final int SOCKET_TIMEOUT_MS = 10000;
 
+    // Intent broadcast when user selects sign-in notification.
+    private static final String ACTION_SIGN_IN_REQUESTED =
+            "android.net.netmon.sign_in_requested";
+
+    // Keep these in sync with CaptivePortalLoginActivity.java.
+    // Intent broadcast from CaptivePortalLogin indicating sign-in is complete.
+    // Extras:
+    //     EXTRA_TEXT       = netId
+    //     LOGGED_IN_RESULT = "1" if we should use network, "0" if not.
+    private static final String ACTION_CAPTIVE_PORTAL_LOGGED_IN =
+            "android.net.netmon.captive_portal_logged_in";
+    private static final String LOGGED_IN_RESULT = "result";
+
     private static final int BASE = Protocol.BASE_NETWORK_MONITOR;
 
     /**
@@ -87,35 +110,63 @@
     public static final int EVENT_NETWORK_LINGER_COMPLETE = BASE + 5;
 
     /**
-     * Message to self indicating it's time to check for a captive portal again.
-     * TODO - Remove this once broadcast intents are used to communicate with
-     * apps to log into captive portals.
-     * arg1 = Token to ignore old messages.
-     */
-    private static final int CMD_CAPTIVE_PORTAL_REEVALUATE = BASE + 6;
-
-    /**
      * Message to self indicating it's time to evaluate a network's connectivity.
      * arg1 = Token to ignore old messages.
      */
-    private static final int CMD_REEVALUATE = BASE + 7;
+    private static final int CMD_REEVALUATE = BASE + 6;
 
     /**
      * Message to self indicating network evaluation is complete.
      * arg1 = Token to ignore old messages.
      * arg2 = HTTP response code of network evaluation.
      */
-    private static final int EVENT_REEVALUATION_COMPLETE = BASE + 8;
+    private static final int EVENT_REEVALUATION_COMPLETE = BASE + 7;
 
     /**
      * Inform NetworkMonitor that the network has disconnected.
      */
-    public static final int CMD_NETWORK_DISCONNECTED = BASE + 9;
+    public static final int CMD_NETWORK_DISCONNECTED = BASE + 8;
 
     /**
      * Force evaluation even if it has succeeded in the past.
      */
-    public static final int CMD_FORCE_REEVALUATION = BASE + 10;
+    public static final int CMD_FORCE_REEVALUATION = BASE + 9;
+
+    /**
+     * Message to self indicating captive portal login is complete.
+     * arg1 = Token to ignore old messages.
+     * arg2 = 1 if we should use this network, 0 otherwise.
+     */
+    private static final int CMD_CAPTIVE_PORTAL_LOGGED_IN = BASE + 10;
+
+    /**
+     * Message to self indicating user desires to log into captive portal.
+     * arg1 = Token to ignore old messages.
+     */
+    private static final int CMD_USER_WANTS_SIGN_IN = BASE + 11;
+
+    /**
+     * Request ConnectivityService display provisioning notification.
+     * arg1    = Whether to make the notification visible.
+     * obj     = Intent to be launched when notification selected by user.
+     * replyTo = NetworkAgentInfo.messenger so ConnectivityService can identify sender.
+     */
+    public static final int EVENT_PROVISIONING_NOTIFICATION = BASE + 12;
+
+    /**
+     * Message to self indicating sign-in app bypassed captive portal.
+     */
+    private static final int EVENT_APP_BYPASSED_CAPTIVE_PORTAL = BASE + 13;
+
+    /**
+     * Message to self indicating no sign-in app responded.
+     */
+    private static final int EVENT_NO_APP_RESPONSE = BASE + 14;
+
+    /**
+     * Message to self indicating sign-in app indicates sign-in is not possible.
+     */
+    private static final int EVENT_APP_INDICATES_SIGN_IN_IMPOSSIBLE = BASE + 15;
 
     private static final String LINGER_DELAY_PROPERTY = "persist.netmon.linger";
     // Default to 30s linger time-out.
@@ -123,16 +174,17 @@
     private final int mLingerDelayMs;
     private int mLingerToken = 0;
 
-    private static final int CAPTIVE_PORTAL_REEVALUATE_DELAY_MS = 5000;
-    private int mCaptivePortalReevaluateToken = 0;
-
     // Negative values disable reevaluation.
     private static final String REEVALUATE_DELAY_PROPERTY = "persist.netmon.reeval_delay";
     // Default to 5s reevaluation delay.
     private static final int DEFAULT_REEVALUATE_DELAY_MS = 5000;
+    private static final int MAX_RETRIES = 10;
     private final int mReevaluateDelayMs;
     private int mReevaluateToken = 0;
 
+    private int mCaptivePortalLoggedInToken = 0;
+    private int mUserPromptedToken = 0;
+
     private final Context mContext;
     private final Handler mConnectivityServiceHandler;
     private final NetworkAgentInfo mNetworkAgentInfo;
@@ -144,6 +196,9 @@
     private State mOfflineState = new OfflineState();
     private State mValidatedState = new ValidatedState();
     private State mEvaluatingState = new EvaluatingState();
+    private State mUninteractiveAppsPromptedState = new UninteractiveAppsPromptedState();
+    private State mUserPromptedState = new UserPromptedState();
+    private State mInteractiveAppsPromptedState = new InteractiveAppsPromptedState();
     private State mCaptivePortalState = new CaptivePortalState();
     private State mLingeringState = new LingeringState();
 
@@ -159,6 +214,9 @@
         addState(mOfflineState, mDefaultState);
         addState(mValidatedState, mDefaultState);
         addState(mEvaluatingState, mDefaultState);
+        addState(mUninteractiveAppsPromptedState, mDefaultState);
+        addState(mUserPromptedState, mDefaultState);
+        addState(mInteractiveAppsPromptedState, mDefaultState);
         addState(mCaptivePortalState, mDefaultState);
         addState(mLingeringState, mDefaultState);
         setInitialState(mOfflineState);
@@ -171,9 +229,8 @@
         mReevaluateDelayMs = SystemProperties.getInt(REEVALUATE_DELAY_PROPERTY,
                 DEFAULT_REEVALUATE_DELAY_MS);
 
-        // TODO: Enable this when we're ready.
-        // mIsCaptivePortalCheckEnabled = Settings.Global.getInt(mContext.getContentResolver(),
-        //        Settings.Global.CAPTIVE_PORTAL_DETECTION_ENABLED, 1) == 1;
+        mIsCaptivePortalCheckEnabled = Settings.Global.getInt(mContext.getContentResolver(),
+                Settings.Global.CAPTIVE_PORTAL_DETECTION_ENABLED, 1) == 1;
 
         start();
     }
@@ -237,6 +294,8 @@
     }
 
     private class EvaluatingState extends State {
+        private int mRetries;
+
         private class EvaluateInternetConnectivity extends Thread {
             private int mToken;
             EvaluateInternetConnectivity(int token) {
@@ -249,6 +308,7 @@
 
         @Override
         public void enter() {
+            mRetries = 0;
             sendMessage(CMD_REEVALUATE, ++mReevaluateToken, 0);
         }
 
@@ -259,8 +319,11 @@
                 case CMD_REEVALUATE:
                     if (message.arg1 != mReevaluateToken)
                         break;
+                    if (mNetworkAgentInfo.isVPN()) {
+                        transitionTo(mValidatedState);
+                    }
                     // If network provides no internet connectivity adjust evaluation.
-                    if (mNetworkAgentInfo.networkCapabilities.hasCapability(
+                    if (!mNetworkAgentInfo.networkCapabilities.hasCapability(
                             NetworkCapabilities.NET_CAPABILITY_INTERNET)) {
                         // TODO: Try to verify something works.  Do all gateways respond to pings?
                         transitionTo(mValidatedState);
@@ -276,12 +339,12 @@
                     if (httpResponseCode == 204) {
                         transitionTo(mValidatedState);
                     } else if (httpResponseCode >= 200 && httpResponseCode <= 399) {
-                        transitionTo(mCaptivePortalState);
-                    } else {
-                        if (mReevaluateDelayMs >= 0) {
-                            Message msg = obtainMessage(CMD_REEVALUATE, ++mReevaluateToken, 0);
-                            sendMessageDelayed(msg, mReevaluateDelayMs);
-                        }
+                        transitionTo(mUninteractiveAppsPromptedState);
+                    } else if (++mRetries > MAX_RETRIES) {
+                        transitionTo(mOfflineState);
+                    } else if (mReevaluateDelayMs >= 0) {
+                        Message msg = obtainMessage(CMD_REEVALUATE, ++mReevaluateToken, 0);
+                        sendMessageDelayed(msg, mReevaluateDelayMs);
                     }
                     break;
                 default:
@@ -291,30 +354,223 @@
         }
     }
 
-    // TODO: Until we add an intent from the app handling captive portal
-    // login we'll just re-evaluate after a delay.
-    private class CaptivePortalState extends State {
+    private class AppRespondedBroadcastReceiver extends BroadcastReceiver {
+        private static final int CAPTIVE_PORTAL_UNINITIALIZED_RETURN_CODE = 0;
+        private boolean mCanceled;
+        AppRespondedBroadcastReceiver() {
+            mCanceled = false;
+        }
+        public void send(String action) {
+            Intent intent = new Intent(action);
+            intent.putExtra(ConnectivityManager.EXTRA_NETWORK, mNetworkAgentInfo.network);
+            mContext.sendOrderedBroadcastAsUser(intent, UserHandle.ALL, null, this, getHandler(),
+                    CAPTIVE_PORTAL_UNINITIALIZED_RETURN_CODE, null, null);
+        }
+        public void cancel() {
+            mCanceled = true;
+        }
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (!mCanceled) {
+                cancel();
+                switch (getResultCode()) {
+                    case ConnectivityManager.CAPTIVE_PORTAL_SIGNED_IN:
+                        sendMessage(EVENT_APP_BYPASSED_CAPTIVE_PORTAL);
+                        break;
+                    case ConnectivityManager.CAPTIVE_PORTAL_DISCONNECT:
+                        sendMessage(EVENT_APP_INDICATES_SIGN_IN_IMPOSSIBLE);
+                        break;
+                    // NOTE: This case label makes compiler enforce no overlap between result codes.
+                    case CAPTIVE_PORTAL_UNINITIALIZED_RETURN_CODE:
+                    default:
+                        sendMessage(EVENT_NO_APP_RESPONSE);
+                        break;
+                }
+            }
+        }
+    }
+
+    private class UninteractiveAppsPromptedState extends State {
+        private AppRespondedBroadcastReceiver mReceiver;
         @Override
         public void enter() {
-            Message message = obtainMessage(CMD_CAPTIVE_PORTAL_REEVALUATE,
-                    ++mCaptivePortalReevaluateToken, 0);
-            sendMessageDelayed(message, CAPTIVE_PORTAL_REEVALUATE_DELAY_MS);
+            mReceiver = new AppRespondedBroadcastReceiver();
+            mReceiver.send(ConnectivityManager.ACTION_CAPTIVE_PORTAL_DETECTED);
+        }
+        @Override
+        public boolean processMessage(Message message) {
+            if (DBG) log(getName() + message.toString());
+            switch (message.what) {
+                case EVENT_APP_BYPASSED_CAPTIVE_PORTAL:
+                    transitionTo(mValidatedState);
+                    break;
+                case EVENT_APP_INDICATES_SIGN_IN_IMPOSSIBLE:
+                    transitionTo(mOfflineState);
+                    break;
+                case EVENT_NO_APP_RESPONSE:
+                    transitionTo(mUserPromptedState);
+                    break;
+                default:
+                    return NOT_HANDLED;
+            }
+            return HANDLED;
+        }
+        public void exit() {
+            mReceiver.cancel();
+        }
+    }
+
+    private class UserPromptedState extends State {
+        private class UserRespondedBroadcastReceiver extends BroadcastReceiver {
+            private final int mToken;
+            UserRespondedBroadcastReceiver(int token) {
+                mToken = token;
+            }
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                if (Integer.parseInt(intent.getStringExtra(Intent.EXTRA_TEXT)) ==
+                        mNetworkAgentInfo.network.netId) {
+                    sendMessage(obtainMessage(CMD_USER_WANTS_SIGN_IN, mToken));
+                }
+            }
+        }
+
+        private UserRespondedBroadcastReceiver mUserRespondedBroadcastReceiver;
+
+        @Override
+        public void enter() {
+            // Wait for user to select sign-in notifcation.
+            mUserRespondedBroadcastReceiver = new UserRespondedBroadcastReceiver(
+                    ++mUserPromptedToken);
+            IntentFilter filter = new IntentFilter(ACTION_SIGN_IN_REQUESTED);
+            mContext.registerReceiver(mUserRespondedBroadcastReceiver, filter);
+            // Initiate notification to sign-in.
+            Intent intent = new Intent(ACTION_SIGN_IN_REQUESTED);
+            intent.putExtra(Intent.EXTRA_TEXT, String.valueOf(mNetworkAgentInfo.network.netId));
+            Message message = obtainMessage(EVENT_PROVISIONING_NOTIFICATION, 1, 0,
+                    PendingIntent.getBroadcast(mContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT));
+            message.replyTo = mNetworkAgentInfo.messenger;
+            mConnectivityServiceHandler.sendMessage(message);
         }
 
         @Override
         public boolean processMessage(Message message) {
             if (DBG) log(getName() + message.toString());
             switch (message.what) {
-                case CMD_CAPTIVE_PORTAL_REEVALUATE:
-                    if (message.arg1 != mCaptivePortalReevaluateToken)
+                case CMD_USER_WANTS_SIGN_IN:
+                    if (message.arg1 != mUserPromptedToken)
                         break;
-                    transitionTo(mEvaluatingState);
+                    transitionTo(mInteractiveAppsPromptedState);
                     break;
                 default:
                     return NOT_HANDLED;
             }
             return HANDLED;
         }
+
+        @Override
+        public void exit() {
+            Message message = obtainMessage(EVENT_PROVISIONING_NOTIFICATION, 0, 0, null);
+            message.replyTo = mNetworkAgentInfo.messenger;
+            mConnectivityServiceHandler.sendMessage(message);
+            mContext.unregisterReceiver(mUserRespondedBroadcastReceiver);
+            mUserRespondedBroadcastReceiver = null;
+        }
+    }
+
+    private class InteractiveAppsPromptedState extends State {
+        private AppRespondedBroadcastReceiver mReceiver;
+        @Override
+        public void enter() {
+            mReceiver = new AppRespondedBroadcastReceiver();
+            mReceiver.send(ConnectivityManager.ACTION_CAPTIVE_PORTAL_SIGN_IN);
+        }
+        @Override
+        public boolean processMessage(Message message) {
+            if (DBG) log(getName() + message.toString());
+            switch (message.what) {
+                case EVENT_APP_BYPASSED_CAPTIVE_PORTAL:
+                    transitionTo(mValidatedState);
+                    break;
+                case EVENT_APP_INDICATES_SIGN_IN_IMPOSSIBLE:
+                    transitionTo(mOfflineState);
+                    break;
+                case EVENT_NO_APP_RESPONSE:
+                    transitionTo(mCaptivePortalState);
+                    break;
+                default:
+                    return NOT_HANDLED;
+            }
+            return HANDLED;
+        }
+        public void exit() {
+            mReceiver.cancel();
+        }
+    }
+
+    private class CaptivePortalState extends State {
+        private class CaptivePortalLoggedInBroadcastReceiver extends BroadcastReceiver {
+            private final int mToken;
+
+            CaptivePortalLoggedInBroadcastReceiver(int token) {
+                mToken = token;
+            }
+
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                if (Integer.parseInt(intent.getStringExtra(Intent.EXTRA_TEXT)) ==
+                        mNetworkAgentInfo.network.netId) {
+                    sendMessage(obtainMessage(CMD_CAPTIVE_PORTAL_LOGGED_IN, mToken,
+                            Integer.parseInt(intent.getStringExtra(LOGGED_IN_RESULT))));
+                }
+            }
+        }
+
+        private CaptivePortalLoggedInBroadcastReceiver mCaptivePortalLoggedInBroadcastReceiver;
+
+        @Override
+        public void enter() {
+            Intent intent = new Intent(Intent.ACTION_SEND);
+            intent.putExtra(Intent.EXTRA_TEXT, String.valueOf(mNetworkAgentInfo.network.netId));
+            intent.setType("text/plain");
+            intent.setComponent(new ComponentName("com.android.captiveportallogin",
+                    "com.android.captiveportallogin.CaptivePortalLoginActivity"));
+            intent.setFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT | Intent.FLAG_ACTIVITY_NEW_TASK);
+
+            // Wait for result.
+            mCaptivePortalLoggedInBroadcastReceiver = new CaptivePortalLoggedInBroadcastReceiver(
+                    ++mCaptivePortalLoggedInToken);
+            IntentFilter filter = new IntentFilter(ACTION_CAPTIVE_PORTAL_LOGGED_IN);
+            mContext.registerReceiver(mCaptivePortalLoggedInBroadcastReceiver, filter);
+            // Initiate app to log in.
+            mContext.startActivityAsUser(intent, UserHandle.CURRENT);
+        }
+
+        @Override
+        public boolean processMessage(Message message) {
+            if (DBG) log(getName() + message.toString());
+            switch (message.what) {
+                case CMD_CAPTIVE_PORTAL_LOGGED_IN:
+                    if (message.arg1 != mCaptivePortalLoggedInToken)
+                        break;
+                    if (message.arg2 == 0) {
+                        // TODO: Should teardown network.
+                        transitionTo(mOfflineState);
+                    } else {
+                        transitionTo(mValidatedState);
+                    }
+                    break;
+                default:
+                    return NOT_HANDLED;
+            }
+            return HANDLED;
+        }
+
+        @Override
+        public void exit() {
+            mContext.unregisterReceiver(mCaptivePortalLoggedInBroadcastReceiver);
+            mCaptivePortalLoggedInBroadcastReceiver = null;
+        }
     }
 
     private class LingeringState extends State {
@@ -353,10 +609,12 @@
         if (!mIsCaptivePortalCheckEnabled) return 204;
 
         String urlString = "http://" + mServer + "/generate_204";
-        if (DBG) log("Checking " + urlString);
+        if (DBG) {
+            log("Checking " + urlString + " on " + mNetworkAgentInfo.networkInfo.getExtraInfo());
+        }
         HttpURLConnection urlConnection = null;
         Socket socket = null;
-        int httpResponseCode = 500;
+        int httpResponseCode = 599;
         try {
             URL url = new URL(urlString);
             if (false) {
@@ -369,25 +627,65 @@
                 urlConnection.getInputStream();
                 httpResponseCode = urlConnection.getResponseCode();
             } else {
-                socket = new Socket();
-                // TODO: setNetworkForSocket(socket, mNetworkAgentInfo.network.netId);
-                InetSocketAddress address = new InetSocketAddress(url.getHost(), 80);
-                // TODO: address = new InetSocketAddress(
-                //               getByNameOnNetwork(mNetworkAgentInfo.network, url.getHost()), 80);
-                socket.connect(address);
+                socket = mNetworkAgentInfo.network.getSocketFactory().createSocket();
+                socket.setSoTimeout(SOCKET_TIMEOUT_MS);
+                // Lookup addresses only on this Network.
+                InetAddress[] hostAddresses = mNetworkAgentInfo.network.getAllByName(url.getHost());
+                // Try all addresses.
+                for (int i = 0; i < hostAddresses.length; i++) {
+                    if (DBG) log("Connecting to " + hostAddresses[i]);
+                    try {
+                        socket.connect(new InetSocketAddress(hostAddresses[i],
+                                url.getDefaultPort()), SOCKET_TIMEOUT_MS);
+                        break;
+                    } catch (IOException e) {
+                        // Ignore exceptions on all but the last.
+                        if (i == (hostAddresses.length - 1)) throw e;
+                    }
+                }
+                if (DBG) log("Requesting " + url.getFile());
                 BufferedReader reader = new BufferedReader(
                         new InputStreamReader(socket.getInputStream()));
                 OutputStreamWriter writer = new OutputStreamWriter(socket.getOutputStream());
-                writer.write("GET " + url.getFile() + " HTTP/1.1\r\n\n");
+                writer.write("GET " + url.getFile() + " HTTP/1.1\r\nHost: " + url.getHost() +
+                        "\r\nConnection: close\r\n\r\n");
                 writer.flush();
                 String response = reader.readLine();
-                if (response.startsWith("HTTP/1.1 ")) {
+                if (DBG) log("Received \"" + response + "\"");
+                if (response != null && (response.startsWith("HTTP/1.1 ") ||
+                        // NOTE: We may want to consider an "HTTP/1.0 204" response to be a captive
+                        // portal.  The only example of this seen so far was a captive portal.  For
+                        // the time being go with prior behavior of assuming it's not a captive
+                        // portal.  If it is considered a captive portal, a different sign-in URL
+                        // is needed (i.e. can't browse a 204).  This could be the result of an HTTP
+                        // proxy server.
+                        response.startsWith("HTTP/1.0 "))) {
+                    // NOTE: We may want to consider an "200" response with "Content-length=0" to
+                    // not be a captive portal. This could be the result of an HTTP proxy server.
+                    // See b/9972012.
                     httpResponseCode = Integer.parseInt(response.substring(9, 12));
+                } else {
+                    // A response was received but not understood.  The fact that a
+                    // response was sent indicates there's some kind of responsive network
+                    // out there so put up the notification to the user to log into the network
+                    // so the user can have the final say as to whether the network is useful.
+                    httpResponseCode = 399;
+                    while (DBG && response != null && !response.isEmpty()) {
+                        try {
+                            response = reader.readLine();
+                        } catch (IOException e) {
+                            break;
+                        }
+                        log("Received \"" + response + "\"");
+                    }
                 }
             }
             if (DBG) log("isCaptivePortal: ret=" + httpResponseCode);
         } catch (IOException e) {
             if (DBG) log("Probably not a portal: exception " + e);
+            if (httpResponseCode == 599) {
+                // TODO: Ping gateway and DNS server and log results.
+            }
         } finally {
             if (urlConnection != null) {
                 urlConnection.disconnect();
diff --git a/services/core/java/com/android/server/connectivity/Tethering.java b/services/core/java/com/android/server/connectivity/Tethering.java
index ec3389b..01a2fc2 100644
--- a/services/core/java/com/android/server/connectivity/Tethering.java
+++ b/services/core/java/com/android/server/connectivity/Tethering.java
@@ -109,13 +109,14 @@
     // Wifi is 192.168.43.1 and 255.255.255.0
     // BT is limited to max default of 5 connections. 192.168.44.1 to 192.168.48.1
     // with 255.255.255.0
+    // P2P is 192.168.49.1 and 255.255.255.0
 
     private String[] mDhcpRange;
     private static final String[] DHCP_DEFAULT_RANGE = {
         "192.168.42.2", "192.168.42.254", "192.168.43.2", "192.168.43.254",
         "192.168.44.2", "192.168.44.254", "192.168.45.2", "192.168.45.254",
         "192.168.46.2", "192.168.46.254", "192.168.47.2", "192.168.47.254",
-        "192.168.48.2", "192.168.48.254",
+        "192.168.48.2", "192.168.48.254", "192.168.49.2", "192.168.49.254",
     };
 
     private String[] mDefaultDnsServers;
@@ -701,6 +702,10 @@
         return retVal;
     }
 
+    public String[] getTetheredDhcpRanges() {
+        return mDhcpRange;
+    }
+
     public String[] getErroredIfaces() {
         ArrayList<String> list = new ArrayList<String>();
         synchronized (mPublicSync) {
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index df12995..d15254b 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -30,6 +30,7 @@
 import android.content.ServiceConnection;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.ResolveInfo;
 import android.content.pm.UserInfo;
 import android.graphics.Bitmap;
@@ -43,14 +44,18 @@
 import android.net.LinkProperties;
 import android.net.LocalSocket;
 import android.net.LocalSocketAddress;
+import android.net.NetworkAgent;
+import android.net.NetworkCapabilities;
 import android.net.NetworkInfo;
+import android.net.NetworkInfo.DetailedState;
 import android.net.NetworkUtils;
 import android.net.RouteInfo;
-import android.net.NetworkInfo.DetailedState;
+import android.net.UidRange;
 import android.os.Binder;
 import android.os.FileUtils;
 import android.os.IBinder;
 import android.os.INetworkManagementService;
+import android.os.Looper;
 import android.os.Parcel;
 import android.os.ParcelFileDescriptor;
 import android.os.Process;
@@ -69,7 +74,6 @@
 import com.android.internal.net.LegacyVpnInfo;
 import com.android.internal.net.VpnConfig;
 import com.android.internal.net.VpnProfile;
-import com.android.server.ConnectivityService.VpnCallback;
 import com.android.server.net.BaseNetworkObserver;
 
 import java.io.File;
@@ -78,7 +82,9 @@
 import java.net.InetAddress;
 import java.net.Inet4Address;
 import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.List;
 import java.util.concurrent.atomic.AtomicInteger;
 
 import libcore.io.IoUtils;
@@ -86,16 +92,18 @@
 /**
  * @hide
  */
-public class Vpn extends BaseNetworkStateTracker {
+public class Vpn {
+    private static final String NETWORKTYPE = "VPN";
     private static final String TAG = "Vpn";
     private static final boolean LOGD = true;
-    
+
     // TODO: create separate trackers for each unique VPN to support
     // automated reconnection
 
-    private final VpnCallback mCallback;
-
-    private String mPackage = VpnConfig.LEGACY_VPN;
+    private Context mContext;
+    private NetworkInfo mNetworkInfo;
+    private String mPackage;
+    private int mOwnerUID;
     private String mInterface;
     private Connection mConnection;
     private LegacyVpnRunner mLegacyVpnRunner;
@@ -103,22 +111,29 @@
     private volatile boolean mEnableNotif = true;
     private volatile boolean mEnableTeardown = true;
     private final IConnectivityManager mConnService;
+    private final INetworkManagementService mNetd;
     private VpnConfig mConfig;
+    private NetworkAgent mNetworkAgent;
+    private final Looper mLooper;
+    private final NetworkCapabilities mNetworkCapabilities;
 
     /* list of users using this VPN. */
     @GuardedBy("this")
-    private SparseBooleanArray mVpnUsers = null;
+    private List<UidRange> mVpnUsers = null;
     private BroadcastReceiver mUserIntentReceiver = null;
 
     private final int mUserId;
 
-    public Vpn(Context context, VpnCallback callback, INetworkManagementService netService,
+    public Vpn(Looper looper, Context context, INetworkManagementService netService,
             IConnectivityManager connService, int userId) {
-        super(ConnectivityManager.TYPE_VPN);
         mContext = context;
-        mCallback = callback;
+        mNetd = netService;
         mConnService = connService;
         mUserId = userId;
+        mLooper = looper;
+
+        mPackage = VpnConfig.LEGACY_VPN;
+        mOwnerUID = getAppUid(mPackage);
 
         try {
             netService.registerObserver(mObserver);
@@ -149,6 +164,12 @@
             mContext.registerReceiverAsUser(
                     mUserIntentReceiver, UserHandle.ALL, intentFilter, null, null);
         }
+
+        mNetworkInfo = new NetworkInfo(ConnectivityManager.TYPE_VPN, 0, NETWORKTYPE, "");
+        // TODO: Copy metered attribute and bandwidths from physical transport, b/16207332
+        mNetworkCapabilities = new NetworkCapabilities();
+        mNetworkCapabilities.addTransportType(NetworkCapabilities.TRANSPORT_VPN);
+        mNetworkCapabilities.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN);
     }
 
     /**
@@ -168,35 +189,15 @@
         mEnableTeardown = enableTeardown;
     }
 
-    @Override
-    protected void startMonitoringInternal() {
-        // Ignored; events are sent through callbacks for now
-    }
-
-    @Override
-    public boolean teardown() {
-        // TODO: finish migration to unique tracker for each VPN
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public boolean reconnect() {
-        // TODO: finish migration to unique tracker for each VPN
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public String getTcpBufferSizesPropName() {
-        return PROP_TCP_BUFFER_UNKNOWN;
-    }
-
     /**
      * Update current state, dispaching event to listeners.
      */
     private void updateState(DetailedState detailedState, String reason) {
         if (LOGD) Log.d(TAG, "setting state=" + detailedState + ", reason=" + reason);
         mNetworkInfo.setDetailedState(detailedState, reason, null);
-        mCallback.onStateChanged(new NetworkInfo(mNetworkInfo));
+        if (mNetworkAgent != null) {
+            mNetworkAgent.sendNetworkInfo(mNetworkInfo);
+        }
     }
 
     /**
@@ -234,22 +235,10 @@
 
         // Reset the interface and hide the notification.
         if (mInterface != null) {
-            final long token = Binder.clearCallingIdentity();
-            try {
-                mCallback.restore();
-                final int size = mVpnUsers.size();
-                final boolean forwardDns = (mConfig.dnsServers != null &&
-                        mConfig.dnsServers.size() != 0);
-                for (int i = 0; i < size; i++) {
-                    int user = mVpnUsers.keyAt(i);
-                    mCallback.clearUserForwarding(mInterface, user, forwardDns);
-                    hideNotification(user);
-                }
-
-                mCallback.clearMarkedForwarding(mInterface);
-            } finally {
-                Binder.restoreCallingIdentity(token);
+            for (UidRange uidRange : mVpnUsers) {
+                hideNotification(uidRange.getStartUser());
             }
+            agentDisconnect();
             jniReset(mInterface);
             mInterface = null;
             mVpnUsers = null;
@@ -270,34 +259,125 @@
             mLegacyVpnRunner = null;
         }
 
+        long token = Binder.clearCallingIdentity();
+        try {
+            mNetd.denyProtect(mOwnerUID);
+        } catch (Exception e) {
+            Log.wtf(TAG, "Failed to disallow UID " + mOwnerUID + " to call protect() " + e);
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+
         Log.i(TAG, "Switched from " + mPackage + " to " + newPackage);
         mPackage = newPackage;
+        mOwnerUID = getAppUid(newPackage);
+        token = Binder.clearCallingIdentity();
+        try {
+            mNetd.allowProtect(mOwnerUID);
+        } catch (Exception e) {
+            Log.wtf(TAG, "Failed to allow UID " + mOwnerUID + " to call protect() " + e);
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
         mConfig = null;
         updateState(DetailedState.IDLE, "prepare");
         return true;
     }
 
-    /**
-     * Protect a socket from VPN rules by binding it to the main routing table.
-     * The socket is NOT closed by this method.
-     *
-     * @param socket The socket to be bound.
-     */
-    public void protect(ParcelFileDescriptor socket) throws Exception {
-
-        PackageManager pm = mContext.getPackageManager();
-        int appUid = pm.getPackageUid(mPackage, mUserId);
-        if (Binder.getCallingUid() != appUid) {
-            throw new SecurityException("Unauthorized Caller");
+    private int getAppUid(String app) {
+        if (app == VpnConfig.LEGACY_VPN) {
+            return Process.myUid();
         }
-        // protect the socket from routing rules
-        final long token = Binder.clearCallingIdentity();
+        PackageManager pm = mContext.getPackageManager();
+        int result;
         try {
-            mCallback.protect(socket);
+            result = pm.getPackageUid(app, mUserId);
+        } catch (NameNotFoundException e) {
+            result = -1;
+        }
+        return result;
+    }
+
+    public NetworkInfo getNetworkInfo() {
+        return mNetworkInfo;
+    }
+
+    private void agentConnect() {
+        LinkProperties lp = new LinkProperties();
+        lp.setInterfaceName(mInterface);
+        boolean hasDefaultRoute = false;
+        for (RouteInfo route : mConfig.routes) {
+            lp.addRoute(route);
+            if (route.isDefaultRoute()) hasDefaultRoute = true;
+        }
+        if (hasDefaultRoute) {
+            mNetworkCapabilities.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
+        } else {
+            mNetworkCapabilities.removeCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
+        }
+        if (mConfig.dnsServers != null) {
+            for (String dnsServer : mConfig.dnsServers) {
+                lp.addDnsServer(InetAddress.parseNumericAddress(dnsServer));
+            }
+        }
+        // Concatenate search domains into a string.
+        StringBuilder buffer = new StringBuilder();
+        if (mConfig.searchDomains != null) {
+            for (String domain : mConfig.searchDomains) {
+                buffer.append(domain).append(' ');
+            }
+        }
+        lp.setDomains(buffer.toString().trim());
+        mNetworkInfo.setIsAvailable(true);
+        mNetworkInfo.setDetailedState(DetailedState.CONNECTED, null, null);
+        long token = Binder.clearCallingIdentity();
+        try {
+            mNetworkAgent = new NetworkAgent(mLooper, mContext, NETWORKTYPE,
+                    mNetworkInfo, mNetworkCapabilities, lp, 0) {
+                            public void unwanted() {
+                                // We are user controlled, not driven by NetworkRequest.
+                            };
+                        };
         } finally {
             Binder.restoreCallingIdentity(token);
         }
+        addVpnUserLocked(mUserId);
+        // If we are owner assign all Restricted Users to this VPN
+        if (mUserId == UserHandle.USER_OWNER) {
+            token = Binder.clearCallingIdentity();
+            List<UserInfo> users;
+            try {
+                users = UserManager.get(mContext).getUsers();
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+            for (UserInfo user : users) {
+                if (user.isRestricted()) {
+                    addVpnUserLocked(user.id);
+                }
+            }
+        }
+        mNetworkAgent.addUidRanges(mVpnUsers.toArray(new UidRange[mVpnUsers.size()]));
+    }
 
+    private void agentDisconnect(NetworkInfo networkInfo, NetworkAgent networkAgent) {
+        networkInfo.setIsAvailable(false);
+        networkInfo.setDetailedState(DetailedState.DISCONNECTED, null, null);
+        if (networkAgent != null) {
+            networkAgent.sendNetworkInfo(networkInfo);
+        }
+    }
+
+    private void agentDisconnect(NetworkAgent networkAgent) {
+        NetworkInfo networkInfo = new NetworkInfo(mNetworkInfo);
+        agentDisconnect(networkInfo, networkAgent);
+    }
+
+    private void agentDisconnect() {
+        if (mNetworkInfo.isConnected()) {
+            agentDisconnect(mNetworkInfo, mNetworkAgent);
+            mNetworkAgent = null;
+        }
     }
 
     /**
@@ -311,14 +391,7 @@
     public synchronized ParcelFileDescriptor establish(VpnConfig config) {
         // Check if the caller is already prepared.
         UserManager mgr = UserManager.get(mContext);
-        PackageManager pm = mContext.getPackageManager();
-        ApplicationInfo app = null;
-        try {
-            app = AppGlobals.getPackageManager().getApplicationInfo(mPackage, 0, mUserId);
-            if (Binder.getCallingUid() != app.uid) {
-                return null;
-            }
-        } catch (Exception e) {
+        if (Binder.getCallingUid() != mOwnerUID) {
             return null;
         }
         // Check if the service is properly declared.
@@ -350,7 +423,9 @@
         VpnConfig oldConfig = mConfig;
         String oldInterface = mInterface;
         Connection oldConnection = mConnection;
-        SparseBooleanArray oldUsers = mVpnUsers;
+        NetworkAgent oldNetworkAgent = mNetworkAgent;
+        mNetworkAgent = null;
+        List<UidRange> oldUsers = mVpnUsers;
 
         // Configure the interface. Abort if any of these steps fails.
         ParcelFileDescriptor tun = ParcelFileDescriptor.adoptFd(jniCreate(config.mtu));
@@ -382,67 +457,27 @@
             mConfig = config;
 
             // Set up forwarding and DNS rules.
-            mVpnUsers = new SparseBooleanArray();
-            token = Binder.clearCallingIdentity();
-            try {
-                mCallback.setMarkedForwarding(mInterface);
-                mCallback.setRoutes(mInterface, config.routes);
-                mCallback.override(mInterface, config.dnsServers, config.searchDomains);
-                addVpnUserLocked(mUserId);
-                // If we are owner assign all Restricted Users to this VPN
-                if (mUserId == UserHandle.USER_OWNER) {
-                    for (UserInfo user : mgr.getUsers()) {
-                        if (user.isRestricted()) {
-                            try {
-                                addVpnUserLocked(user.id);
-                            } catch (Exception e) {
-                                Log.wtf(TAG, "Failed to add user " + user.id + " to owner's VPN");
-                            }
-                        }
-                    }
-                }
-            } finally {
-                Binder.restoreCallingIdentity(token);
-            }
+            mVpnUsers = new ArrayList<UidRange>();
+            agentConnect();
 
             if (oldConnection != null) {
                 mContext.unbindService(oldConnection);
             }
+            // Remove the old tun's user forwarding rules
+            // The new tun's user rules have already been added so they will take over
+            // as rules are deleted. This prevents data leakage as the rules are moved over.
+            agentDisconnect(oldNetworkAgent);
             if (oldInterface != null && !oldInterface.equals(interfaze)) {
-                // Remove the old tun's user forwarding rules
-                // The new tun's user rules have already been added so they will take over
-                // as rules are deleted. This prevents data leakage as the rules are moved over.
-                token = Binder.clearCallingIdentity();
-                try {
-                        final int size = oldUsers.size();
-                        final boolean forwardDns = (oldConfig.dnsServers != null &&
-                                oldConfig.dnsServers.size() != 0);
-                        for (int i = 0; i < size; i++) {
-                            int user = oldUsers.keyAt(i);
-                            mCallback.clearUserForwarding(oldInterface, user, forwardDns);
-                        }
-                        mCallback.clearMarkedForwarding(oldInterface);
-                } finally {
-                    Binder.restoreCallingIdentity(token);
-                }
                 jniReset(oldInterface);
             }
         } catch (RuntimeException e) {
-            updateState(DetailedState.FAILED, "establish");
             IoUtils.closeQuietly(tun);
-            // make sure marked forwarding is cleared if it was set
-            token = Binder.clearCallingIdentity();
-            try {
-                mCallback.clearMarkedForwarding(mInterface);
-            } catch (Exception ingored) {
-                // ignored
-            } finally {
-                Binder.restoreCallingIdentity(token);
-            }
+            agentDisconnect();
             // restore old state
             mConfig = oldConfig;
             mConnection = oldConnection;
             mVpnUsers = oldUsers;
+            mNetworkAgent = oldNetworkAgent;
             mInterface = oldInterface;
             throw e;
         }
@@ -469,29 +504,27 @@
         return mVpnUsers != null;
     }
 
+    // Note: This function adds to mVpnUsers but does not publish list to NetworkAgent.
     private void addVpnUserLocked(int user) {
-        enforceControlPermission();
-
         if (!isRunningLocked()) {
             throw new IllegalStateException("VPN is not active");
         }
 
-        final boolean forwardDns = (mConfig.dnsServers != null &&
-                mConfig.dnsServers.size() != 0);
-
         // add the user
-        mCallback.addUserForwarding(mInterface, user, forwardDns);
-        mVpnUsers.put(user, true);
+        mVpnUsers.add(UidRange.createForUser(user));
 
         // show the notification
         if (!mPackage.equals(VpnConfig.LEGACY_VPN)) {
             // Load everything for the user's notification
             PackageManager pm = mContext.getPackageManager();
             ApplicationInfo app = null;
+            final long token = Binder.clearCallingIdentity();
             try {
                 app = AppGlobals.getPackageManager().getApplicationInfo(mPackage, 0, mUserId);
             } catch (RemoteException e) {
                 throw new IllegalStateException("Invalid application");
+            } finally {
+                Binder.restoreCallingIdentity(token);
             }
             String label = app.loadLabel(pm).toString();
             // Load the icon and convert it into a bitmap.
@@ -515,15 +548,14 @@
     }
 
     private void removeVpnUserLocked(int user) {
-            enforceControlPermission();
-
             if (!isRunningLocked()) {
                 throw new IllegalStateException("VPN is not active");
             }
-            final boolean forwardDns = (mConfig.dnsServers != null &&
-                    mConfig.dnsServers.size() != 0);
-            mCallback.clearUserForwarding(mInterface, user, forwardDns);
-            mVpnUsers.delete(user);
+            UidRange uidRange = UidRange.createForUser(user);
+            if (mNetworkAgent != null) {
+                mNetworkAgent.removeUidRanges(new UidRange[] { uidRange });
+            }
+            mVpnUsers.remove(uidRange);
             hideNotification(user);
     }
 
@@ -535,6 +567,10 @@
             if (user.isRestricted()) {
                 try {
                     addVpnUserLocked(userId);
+                    if (mNetworkAgent != null) {
+                        UidRange uidRange = UidRange.createForUser(userId);
+                        mNetworkAgent.addUidRanges(new UidRange[] { uidRange });
+                    }
                 } catch (Exception e) {
                     Log.wtf(TAG, "Failed to add restricted user to owner", e);
                 }
@@ -588,28 +624,15 @@
         public void interfaceRemoved(String interfaze) {
             synchronized (Vpn.this) {
                 if (interfaze.equals(mInterface) && jniCheck(interfaze) == 0) {
-                    final long token = Binder.clearCallingIdentity();
-                    try {
-                        final int size = mVpnUsers.size();
-                        final boolean forwardDns = (mConfig.dnsServers != null &&
-                                mConfig.dnsServers.size() != 0);
-                        for (int i = 0; i < size; i++) {
-                            int user = mVpnUsers.keyAt(i);
-                            mCallback.clearUserForwarding(mInterface, user, forwardDns);
-                            hideNotification(user);
-                        }
-                        mVpnUsers = null;
-                        mCallback.clearMarkedForwarding(mInterface);
-
-                        mCallback.restore();
-                    } finally {
-                        Binder.restoreCallingIdentity(token);
+                    for (UidRange uidRange : mVpnUsers) {
+                        hideNotification(uidRange.getStartUser());
                     }
+                    mVpnUsers = null;
                     mInterface = null;
                     if (mConnection != null) {
                         mContext.unbindService(mConnection);
                         mConnection = null;
-                        updateState(DetailedState.DISCONNECTED, "interfaceRemoved");
+                        agentDisconnect();
                     } else if (mLegacyVpnRunner != null) {
                         mLegacyVpnRunner.exit();
                         mLegacyVpnRunner = null;
@@ -658,27 +681,32 @@
 
     private void showNotification(String label, Bitmap icon, int user) {
         if (!mEnableNotif) return;
-        mStatusIntent = VpnConfig.getIntentForStatusPanel(mContext);
+        final long token = Binder.clearCallingIdentity();
+        try {
+            mStatusIntent = VpnConfig.getIntentForStatusPanel(mContext);
 
-        NotificationManager nm = (NotificationManager)
-                mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+            NotificationManager nm = (NotificationManager)
+                    mContext.getSystemService(Context.NOTIFICATION_SERVICE);
 
-        if (nm != null) {
-            String title = (label == null) ? mContext.getString(R.string.vpn_title) :
-                    mContext.getString(R.string.vpn_title_long, label);
-            String text = (mConfig.session == null) ? mContext.getString(R.string.vpn_text) :
-                    mContext.getString(R.string.vpn_text_long, mConfig.session);
+            if (nm != null) {
+                String title = (label == null) ? mContext.getString(R.string.vpn_title) :
+                        mContext.getString(R.string.vpn_title_long, label);
+                String text = (mConfig.session == null) ? mContext.getString(R.string.vpn_text) :
+                        mContext.getString(R.string.vpn_text_long, mConfig.session);
 
-            Notification notification = new Notification.Builder(mContext)
-                    .setSmallIcon(R.drawable.vpn_connected)
-                    .setLargeIcon(icon)
-                    .setContentTitle(title)
-                    .setContentText(text)
-                    .setContentIntent(mStatusIntent)
-                    .setDefaults(0)
-                    .setOngoing(true)
-                    .build();
-            nm.notifyAsUser(null, R.drawable.vpn_connected, notification, new UserHandle(user));
+                Notification notification = new Notification.Builder(mContext)
+                        .setSmallIcon(R.drawable.vpn_connected)
+                        .setLargeIcon(icon)
+                        .setContentTitle(title)
+                        .setContentText(text)
+                        .setContentIntent(mStatusIntent)
+                        .setDefaults(0)
+                        .setOngoing(true)
+                        .build();
+                nm.notifyAsUser(null, R.drawable.vpn_connected, notification, new UserHandle(user));
+            }
+        } finally {
+            Binder.restoreCallingIdentity(token);
         }
     }
 
@@ -690,14 +718,18 @@
                 mContext.getSystemService(Context.NOTIFICATION_SERVICE);
 
         if (nm != null) {
-            nm.cancelAsUser(null, R.drawable.vpn_connected, new UserHandle(user));
+            final long token = Binder.clearCallingIdentity();
+            try {
+                nm.cancelAsUser(null, R.drawable.vpn_connected, new UserHandle(user));
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
         }
     }
 
     private native int jniCreate(int mtu);
     private native String jniGetName(int tun);
     private native int jniSetAddresses(String interfaze, String addresses);
-    private native int jniSetRoutes(String interfaze, String routes);
     private native void jniReset(String interfaze);
     private native int jniCheck(String interfaze);
 
@@ -959,7 +991,7 @@
             for (LocalSocket socket : mSockets) {
                 IoUtils.closeQuietly(socket);
             }
-            updateState(DetailedState.DISCONNECTED, "exit");
+            agentDisconnect();
             try {
                 mContext.unregisterReceiver(mBroadcastReceiver);
             } catch (IllegalArgumentException e) {}
@@ -1018,7 +1050,7 @@
                     restart = restart || (arguments != null);
                 }
                 if (!restart) {
-                    updateState(DetailedState.DISCONNECTED, "execute");
+                    agentDisconnect();
                     return;
                 }
                 updateState(DetailedState.CONNECTING, "execute");
@@ -1129,15 +1161,6 @@
                     }
                 }
 
-                // Set the routes.
-                long token = Binder.clearCallingIdentity();
-                try {
-                    mCallback.setMarkedForwarding(mConfig.interfaze);
-                    mCallback.setRoutes(mConfig.interfaze, mConfig.routes);
-                } finally {
-                    Binder.restoreCallingIdentity(token);
-                }
-
                 // Here is the last step and it must be done synchronously.
                 synchronized (Vpn.this) {
                     // Set the start time
@@ -1153,44 +1176,14 @@
 
                     // Now INetworkManagementEventObserver is watching our back.
                     mInterface = mConfig.interfaze;
-                    mVpnUsers = new SparseBooleanArray();
+                    mVpnUsers = new ArrayList<UidRange>();
 
-                    token = Binder.clearCallingIdentity();
-                    try {
-                        mCallback.override(mInterface, mConfig.dnsServers, mConfig.searchDomains);
-                        addVpnUserLocked(mUserId);
-                    } finally {
-                        Binder.restoreCallingIdentity(token);
-                    }
+                    agentConnect();
 
-                    // Assign all restircted users to this VPN
-                    // (Legacy VPNs are Owner only)
-                    UserManager mgr = UserManager.get(mContext);
-                    token = Binder.clearCallingIdentity();
-                    try {
-                        for (UserInfo user : mgr.getUsers()) {
-                            if (user.isRestricted()) {
-                                try {
-                                    addVpnUserLocked(user.id);
-                                } catch (Exception e) {
-                                    Log.wtf(TAG, "Failed to add user " + user.id
-                                            + " to owner's VPN");
-                                }
-                            }
-                        }
-                    } finally {
-                        Binder.restoreCallingIdentity(token);
-                    }
                     Log.i(TAG, "Connected!");
-                    updateState(DetailedState.CONNECTED, "execute");
                 }
             } catch (Exception e) {
                 Log.i(TAG, "Aborting", e);
-                // make sure the routing is cleared
-                try {
-                    mCallback.clearMarkedForwarding(mConfig.interfaze);
-                } catch (Exception ignored) {
-                }
                 exit();
             } finally {
                 // Kill the daemons if they fail to stop.
@@ -1202,7 +1195,7 @@
 
                 // Do not leave an unstable state.
                 if (!initFinished || mNetworkInfo.getDetailedState() == DetailedState.CONNECTING) {
-                    updateState(DetailedState.FAILED, "execute");
+                    agentDisconnect();
                 }
             }
         }
@@ -1232,7 +1225,7 @@
                     SystemService.stop(daemon);
                 }
 
-                updateState(DetailedState.DISCONNECTED, "babysit");
+                agentDisconnect();
             }
         }
     }
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/location/FlpHardwareProvider.java b/services/core/java/com/android/server/location/FlpHardwareProvider.java
index 2699fea..495d3a9 100644
--- a/services/core/java/com/android/server/location/FlpHardwareProvider.java
+++ b/services/core/java/com/android/server/location/FlpHardwareProvider.java
@@ -420,9 +420,8 @@
                 return GeofenceHardware.GEOFENCE_SUCCESS;
             case FLP_RESULT_ERROR:
                 return GeofenceHardware.GEOFENCE_FAILURE;
-            // TODO: uncomment this once the ERROR definition is marked public
-            //case FLP_RESULT_INSUFFICIENT_MEMORY:
-            //    return GeofenceHardware.GEOFENCE_ERROR_INSUFFICIENT_MEMORY;
+            case FLP_RESULT_INSUFFICIENT_MEMORY:
+                return GeofenceHardware.GEOFENCE_ERROR_INSUFFICIENT_MEMORY;
             case FLP_RESULT_TOO_MANY_GEOFENCES:
                 return GeofenceHardware.GEOFENCE_ERROR_TOO_MANY_GEOFENCES;
             case FLP_RESULT_ID_EXISTS:
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 3a6cec2..892f61f 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -2301,7 +2301,7 @@
         int speedBumpIndex = -1;
         final int N = mNotificationList.size();
         ArrayList<String> keys = new ArrayList<String>(N);
-        ArrayList<String> dndKeys = new ArrayList<String>(N);
+        ArrayList<String> interceptedKeys = new ArrayList<String>(N);
         for (int i = 0; i < N; i++) {
             NotificationRecord record = mNotificationList.get(i);
             if (!info.enabledAndUserMatches(record.sbn.getUserId())) {
@@ -2309,7 +2309,7 @@
             }
             keys.add(record.sbn.getKey());
             if (record.isIntercepted()) {
-                dndKeys.add(record.sbn.getKey());
+                interceptedKeys.add(record.sbn.getKey());
             }
             if (speedBumpIndex == -1 &&
                     record.sbn.getNotification().priority == Notification.PRIORITY_MIN) {
@@ -2317,8 +2317,8 @@
             }
         }
         String[] keysAr = keys.toArray(new String[keys.size()]);
-        String[] dndKeysAr = dndKeys.toArray(new String[dndKeys.size()]);
-        return new NotificationRankingUpdate(keysAr, dndKeysAr, speedBumpIndex);
+        String[] interceptedKeysAr = interceptedKeys.toArray(new String[interceptedKeys.size()]);
+        return new NotificationRankingUpdate(keysAr, interceptedKeysAr, speedBumpIndex);
     }
 
     public class NotificationListeners extends ManagedServices {
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 5c05f45..53e20bb 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -4822,7 +4822,9 @@
         // Fix that up here.
         if (isSystemApp(pkg)) {
             PackageSetting ps = mSettings.mPackages.get(pkg.applicationInfo.packageName);
-            setBundledAppAbisAndRoots(pkg, ps);
+            if (!isUpdatedSystemApp(pkg)) {
+                setBundledAppAbisAndRoots(pkg, ps);
+            }
         }
 
         if (pkg.usesLibraries != null || pkg.usesOptionalLibraries != null) {
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/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index bda10de..8387b65 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -130,7 +130,8 @@
 
     // For debugging, this is the last information given to the surface flinger.
     boolean mSurfaceShown;
-    float mSurfaceX, mSurfaceY, mSurfaceW, mSurfaceH;
+    float mSurfaceX, mSurfaceY;
+    float mSurfaceW, mSurfaceH;
     int mSurfaceLayer;
     float mSurfaceAlpha;
 
@@ -666,117 +667,149 @@
     }
 
     SurfaceControl createSurfaceLocked() {
+        final WindowState w = mWin;
         if (mSurfaceControl == null) {
             if (DEBUG_ANIM || DEBUG_ORIENTATION) Slog.i(TAG,
                     "createSurface " + this + ": mDrawState=DRAW_PENDING");
             mDrawState = DRAW_PENDING;
-            if (mWin.mAppToken != null) {
-                if (mWin.mAppToken.mAppAnimator.animation == null) {
-                    mWin.mAppToken.allDrawn = false;
-                    mWin.mAppToken.deferClearAllDrawn = false;
+            if (w.mAppToken != null) {
+                if (w.mAppToken.mAppAnimator.animation == null) {
+                    w.mAppToken.allDrawn = false;
+                    w.mAppToken.deferClearAllDrawn = false;
                 } else {
                     // Currently animating, persist current state of allDrawn until animation
                     // is complete.
-                    mWin.mAppToken.deferClearAllDrawn = true;
+                    w.mAppToken.deferClearAllDrawn = true;
                 }
             }
 
-            mService.makeWindowFreezingScreenIfNeededLocked(mWin);
+            mService.makeWindowFreezingScreenIfNeededLocked(w);
 
             int flags = SurfaceControl.HIDDEN;
-            final WindowManager.LayoutParams attrs = mWin.mAttrs;
+            final WindowManager.LayoutParams attrs = w.mAttrs;
 
             if ((attrs.flags&WindowManager.LayoutParams.FLAG_SECURE) != 0) {
                 flags |= SurfaceControl.SECURE;
             }
-            if (DEBUG_VISIBILITY) Slog.v(
-                TAG, "Creating surface in session "
-                + mSession.mSurfaceSession + " window " + this
-                + " w=" + mWin.mCompatFrame.width()
-                + " h=" + mWin.mCompatFrame.height() + " format="
-                + attrs.format + " flags=" + flags);
 
-            int w = mWin.mCompatFrame.width();
-            int h = mWin.mCompatFrame.height();
+            int width;
+            int height;
             if ((attrs.flags & LayoutParams.FLAG_SCALED) != 0) {
                 // for a scaled surface, we always want the requested
                 // size.
-                w = mWin.mRequestedWidth;
-                h = mWin.mRequestedHeight;
+                width = w.mRequestedWidth;
+                height = w.mRequestedHeight;
+            } else {
+                width = w.mCompatFrame.width();
+                height = w.mCompatFrame.height();
             }
 
             // Something is wrong and SurfaceFlinger will not like this,
             // try to revert to sane values
-            if (w <= 0) w = 1;
-            if (h <= 0) h = 1;
+            if (width <= 0) {
+                width = 1;
+            }
+            if (height <= 0) {
+                height = 1;
+            }
 
+            float left = w.mFrame.left + w.mXOffset;
+            float top = w.mFrame.top + w.mYOffset;
+
+            // Adjust for surface insets.
+            width += attrs.shadowInsets.left + attrs.shadowInsets.right;
+            height += attrs.shadowInsets.top + attrs.shadowInsets.bottom;
+            left -= attrs.shadowInsets.left;
+            top -= attrs.shadowInsets.top;
+
+            if (DEBUG_VISIBILITY) {
+                Slog.v(TAG, "Creating surface in session "
+                        + mSession.mSurfaceSession + " window " + this
+                        + " w=" + width + " h=" + height
+                        + " x=" + left + " y=" + top
+                        + " format=" + attrs.format + " flags=" + flags);
+            }
+
+            // We may abort, so initialize to defaults.
             mSurfaceShown = false;
             mSurfaceLayer = 0;
             mSurfaceAlpha = 0;
             mSurfaceX = 0;
             mSurfaceY = 0;
-            mSurfaceW = w;
-            mSurfaceH = h;
-            mWin.mLastSystemDecorRect.set(0, 0, 0, 0);
+            w.mLastSystemDecorRect.set(0, 0, 0, 0);
+
+            // Set up surface control with initial size.
             try {
+                mSurfaceW = width;
+                mSurfaceH = height;
+
                 final boolean isHwAccelerated = (attrs.flags &
                         WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED) != 0;
                 final int format = isHwAccelerated ? PixelFormat.TRANSLUCENT : attrs.format;
                 if (!PixelFormat.formatHasAlpha(attrs.format)) {
                     flags |= SurfaceControl.OPAQUE;
                 }
+
                 if (DEBUG_SURFACE_TRACE) {
                     mSurfaceControl = new SurfaceTrace(
                             mSession.mSurfaceSession,
                             attrs.getTitle().toString(),
-                            w, h, format, flags);
+                            width, height, format, flags);
                 } else {
                     mSurfaceControl = new SurfaceControl(
                         mSession.mSurfaceSession,
                         attrs.getTitle().toString(),
-                        w, h, format, flags);
+                        width, height, format, flags);
                 }
-                mWin.mHasSurface = true;
-                if (SHOW_TRANSACTIONS || SHOW_SURFACE_ALLOC) Slog.i(TAG,
-                        "  CREATE SURFACE "
-                        + mSurfaceControl + " IN SESSION "
-                        + mSession.mSurfaceSession
-                        + ": pid=" + mSession.mPid + " format="
-                        + attrs.format + " flags=0x"
-                        + Integer.toHexString(flags)
-                        + " / " + this);
+
+                w.mHasSurface = true;
+
+                if (SHOW_TRANSACTIONS || SHOW_SURFACE_ALLOC) {
+                    Slog.i(TAG, "  CREATE SURFACE "
+                            + mSurfaceControl + " IN SESSION "
+                            + mSession.mSurfaceSession
+                            + ": pid=" + mSession.mPid + " format="
+                            + attrs.format + " flags=0x"
+                            + Integer.toHexString(flags)
+                            + " / " + this);
+                }
             } catch (OutOfResourcesException e) {
-                mWin.mHasSurface = false;
+                w.mHasSurface = false;
                 Slog.w(TAG, "OutOfResourcesException creating surface");
                 mService.reclaimSomeSurfaceMemoryLocked(this, "create", true);
                 mDrawState = NO_SURFACE;
                 return null;
             } catch (Exception e) {
-                mWin.mHasSurface = false;
+                w.mHasSurface = false;
                 Slog.e(TAG, "Exception creating surface", e);
                 mDrawState = NO_SURFACE;
                 return null;
             }
 
-            if (WindowManagerService.localLOGV) Slog.v(
-                TAG, "Got surface: " + mSurfaceControl
-                + ", set left=" + mWin.mFrame.left + " top=" + mWin.mFrame.top
-                + ", animLayer=" + mAnimLayer);
+            if (WindowManagerService.localLOGV) {
+                Slog.v(TAG, "Got surface: " + mSurfaceControl
+                        + ", set left=" + w.mFrame.left + " top=" + w.mFrame.top
+                        + ", animLayer=" + mAnimLayer);
+            }
+
             if (SHOW_LIGHT_TRANSACTIONS) {
                 Slog.i(TAG, ">>> OPEN TRANSACTION createSurfaceLocked");
-                WindowManagerService.logSurface(mWin, "CREATE pos=("
-                        + mWin.mFrame.left + "," + mWin.mFrame.top + ") ("
-                        + mWin.mCompatFrame.width() + "x" + mWin.mCompatFrame.height()
+                WindowManagerService.logSurface(w, "CREATE pos=("
+                        + w.mFrame.left + "," + w.mFrame.top + ") ("
+                        + w.mCompatFrame.width() + "x" + w.mCompatFrame.height()
                         + "), layer=" + mAnimLayer + " HIDE", null);
             }
+
+            // Start a new transaction and apply position & offset.
             SurfaceControl.openTransaction();
             try {
+                mSurfaceX = left;
+                mSurfaceY = top;
+
                 try {
-                    mSurfaceX = mWin.mFrame.left + mWin.mXOffset;
-                    mSurfaceY = mWin.mFrame.top + mWin.mYOffset;
-                    mSurfaceControl.setPosition(mSurfaceX, mSurfaceY);
+                    mSurfaceControl.setPosition(left, top);
                     mSurfaceLayer = mAnimLayer;
-                    final DisplayContent displayContent = mWin.getDisplayContent();
+                    final DisplayContent displayContent = w.getDisplayContent();
                     if (displayContent != null) {
                         mSurfaceControl.setLayerStack(displayContent.getDisplay().getLayerStack());
                     }
@@ -1107,14 +1140,27 @@
 
     void applyDecorRect(final Rect decorRect) {
         final WindowState w = mWin;
+        int width = w.mFrame.width();
+        int height = w.mFrame.height();
+
         // Compute the offset of the window in relation to the decor rect.
-        final int offX = w.mXOffset + w.mFrame.left;
-        final int offY = w.mYOffset + w.mFrame.top;
+        int left = w.mXOffset + w.mFrame.left;
+        int top = w.mYOffset + w.mFrame.top;
+
+        // Adjust for surface insets.
+        final WindowManager.LayoutParams attrs = w.mAttrs;
+        width += attrs.shadowInsets.left + attrs.shadowInsets.right;
+        height += attrs.shadowInsets.top + attrs.shadowInsets.bottom;
+        left -= attrs.shadowInsets.left;
+        top -= attrs.shadowInsets.top;
+
         // Initialize the decor rect to the entire frame.
-        w.mSystemDecorRect.set(0, 0, w.mFrame.width(), w.mFrame.height());
+        w.mSystemDecorRect.set(0, 0, width, height);
+
         // Intersect with the decor rect, offsetted by window position.
-        w.mSystemDecorRect.intersect(decorRect.left-offX, decorRect.top-offY,
-                decorRect.right-offX, decorRect.bottom-offY);
+        w.mSystemDecorRect.intersect(decorRect.left - left, decorRect.top - top,
+                decorRect.right - left, decorRect.bottom - top);
+
         // If size compatibility is being applied to the window, the
         // surface is scaled relative to the screen.  Also apply this
         // scaling to the crop rect.  We aren't using the standard rect
@@ -1212,10 +1258,12 @@
 
     void setSurfaceBoundariesLocked(final boolean recoveringMemory) {
         final WindowState w = mWin;
-        int width, height;
+
+        int width;
+        int height;
         if ((w.mAttrs.flags & LayoutParams.FLAG_SCALED) != 0) {
-            // for a scaled surface, we just want to use
-            // the requested size.
+            // for a scaled surface, we always want the requested
+            // size.
             width  = w.mRequestedWidth;
             height = w.mRequestedHeight;
         } else {
@@ -1223,42 +1271,52 @@
             height = w.mCompatFrame.height();
         }
 
+        // Something is wrong and SurfaceFlinger will not like this,
+        // try to revert to sane values
         if (width < 1) {
             width = 1;
         }
         if (height < 1) {
             height = 1;
         }
-        final boolean surfaceResized = mSurfaceW != width || mSurfaceH != height;
-        if (surfaceResized) {
-            mSurfaceW = width;
-            mSurfaceH = height;
-        }
 
-        final float left = w.mShownFrame.left;
-        final float top = w.mShownFrame.top;
-        if (mSurfaceX != left || mSurfaceY != top) {
+        float left = w.mShownFrame.left;
+        float top = w.mShownFrame.top;
+
+        // Adjust for surface insets.
+        final LayoutParams attrs = w.getAttrs();
+        width += attrs.shadowInsets.left + attrs.shadowInsets.right;
+        height += attrs.shadowInsets.top + attrs.shadowInsets.bottom;
+        left -= attrs.shadowInsets.left;
+        top -= attrs.shadowInsets.top;
+
+        final boolean surfaceMoved = mSurfaceX != left || mSurfaceY != top;
+        if (surfaceMoved) {
+            mSurfaceX = left;
+            mSurfaceY = top;
+
             try {
                 if (WindowManagerService.SHOW_TRANSACTIONS) WindowManagerService.logSurface(w,
                         "POS " + left + ", " + top, null);
-                mSurfaceX = left;
-                mSurfaceY = top;
                 mSurfaceControl.setPosition(left, top);
             } catch (RuntimeException e) {
                 Slog.w(TAG, "Error positioning surface of " + w
-                        + " pos=(" + left
-                        + "," + top + ")", e);
+                        + " pos=(" + left + "," + top + ")", e);
                 if (!recoveringMemory) {
                     mService.reclaimSomeSurfaceMemoryLocked(this, "position", true);
                 }
             }
         }
 
+        final boolean surfaceResized = mSurfaceW != width || mSurfaceH != height;
         if (surfaceResized) {
+            mSurfaceW = width;
+            mSurfaceH = height;
+            mSurfaceResized = true;
+
             try {
                 if (WindowManagerService.SHOW_TRANSACTIONS) WindowManagerService.logSurface(w,
                         "SIZE " + width + "x" + height, null);
-                mSurfaceResized = true;
                 mSurfaceControl.setSize(width, height);
                 mAnimator.setPendingLayoutChanges(w.getDisplayId(),
                         WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER);
diff --git a/services/core/jni/com_android_server_connectivity_Vpn.cpp b/services/core/jni/com_android_server_connectivity_Vpn.cpp
index bf34a74..6031906 100644
--- a/services/core/jni/com_android_server_connectivity_Vpn.cpp
+++ b/services/core/jni/com_android_server_connectivity_Vpn.cpp
@@ -187,96 +187,6 @@
     return count;
 }
 
-static int set_routes(const char *name, const char *routes)
-{
-    int index = get_interface_index(name);
-    if (index < 0) {
-        return index;
-    }
-
-    rtentry rt4;
-    memset(&rt4, 0, sizeof(rt4));
-    rt4.rt_dev = (char *)name;
-    rt4.rt_flags = RTF_UP;
-    rt4.rt_dst.sa_family = AF_INET;
-    rt4.rt_genmask.sa_family = AF_INET;
-
-    in6_rtmsg rt6;
-    memset(&rt6, 0, sizeof(rt6));
-    rt6.rtmsg_ifindex = index;
-    rt6.rtmsg_flags = RTF_UP;
-
-    char address[65];
-    int prefix;
-    int chars;
-    int count = 0;
-
-    while (sscanf(routes, " %64[^/]/%d %n", address, &prefix, &chars) == 2) {
-        routes += chars;
-
-        if (strchr(address, ':')) {
-            // Add an IPv6 route.
-            if (inet_pton(AF_INET6, address, &rt6.rtmsg_dst) != 1 ||
-                    prefix < 0 || prefix > 128) {
-                count = BAD_ARGUMENT;
-                break;
-            }
-
-            rt6.rtmsg_dst_len = prefix ? prefix : 1;
-            if (ioctl(inet6, SIOCADDRT, &rt6) && errno != EEXIST) {
-                count = (errno == EINVAL) ? BAD_ARGUMENT : SYSTEM_ERROR;
-                break;
-            }
-
-            if (!prefix) {
-                // Split the route instead of replacing the default route.
-                rt6.rtmsg_dst.s6_addr[0] ^= 0x80;
-                if (ioctl(inet6, SIOCADDRT, &rt6) && errno != EEXIST) {
-                    count = SYSTEM_ERROR;
-                    break;
-                }
-            }
-        } else {
-            // Add an IPv4 route.
-            if (inet_pton(AF_INET, address, as_in_addr(&rt4.rt_dst)) != 1 ||
-                    prefix < 0 || prefix > 32) {
-                count = BAD_ARGUMENT;
-                break;
-            }
-
-            in_addr_t mask = prefix ? (~0 << (32 - prefix)) : 0x80000000;
-            *as_in_addr(&rt4.rt_genmask) = htonl(mask);
-            if (ioctl(inet4, SIOCADDRT, &rt4) && errno != EEXIST) {
-                count = (errno == EINVAL) ? BAD_ARGUMENT : SYSTEM_ERROR;
-                break;
-            }
-
-            if (!prefix) {
-                // Split the route instead of replacing the default route.
-                *as_in_addr(&rt4.rt_dst) ^= htonl(0x80000000);
-                if (ioctl(inet4, SIOCADDRT, &rt4) && errno != EEXIST) {
-                    count = SYSTEM_ERROR;
-                    break;
-                }
-            }
-        }
-        ALOGD("Route added on %s: %s/%d", name, address, prefix);
-        ++count;
-    }
-
-    if (count == BAD_ARGUMENT) {
-        ALOGE("Invalid route: %s/%d", address, prefix);
-    } else if (count == SYSTEM_ERROR) {
-        ALOGE("Cannot add route: %s/%d: %s",
-                address, prefix, strerror(errno));
-    } else if (*routes) {
-        ALOGE("Invalid route: %s", routes);
-        count = BAD_ARGUMENT;
-    }
-
-    return count;
-}
-
 static int reset_interface(const char *name)
 {
     ifreq ifr4;
@@ -366,39 +276,6 @@
     return count;
 }
 
-static jint setRoutes(JNIEnv *env, jobject thiz, jstring jName,
-        jstring jRoutes)
-{
-    const char *name = NULL;
-    const char *routes = NULL;
-    int count = -1;
-
-    name = jName ? env->GetStringUTFChars(jName, NULL) : NULL;
-    if (!name) {
-        jniThrowNullPointerException(env, "name");
-        goto error;
-    }
-    routes = jRoutes ? env->GetStringUTFChars(jRoutes, NULL) : NULL;
-    if (!routes) {
-        jniThrowNullPointerException(env, "routes");
-        goto error;
-    }
-    count = set_routes(name, routes);
-    if (count < 0) {
-        throwException(env, count, "Cannot set route");
-        count = -1;
-    }
-
-error:
-    if (name) {
-        env->ReleaseStringUTFChars(jName, name);
-    }
-    if (routes) {
-        env->ReleaseStringUTFChars(jRoutes, routes);
-    }
-    return count;
-}
-
 static void reset(JNIEnv *env, jobject thiz, jstring jName)
 {
     const char *name = jName ? env->GetStringUTFChars(jName, NULL) : NULL;
@@ -430,7 +307,6 @@
     {"jniCreate", "(I)I", (void *)create},
     {"jniGetName", "(I)Ljava/lang/String;", (void *)getName},
     {"jniSetAddresses", "(Ljava/lang/String;Ljava/lang/String;)I", (void *)setAddresses},
-    {"jniSetRoutes", "(Ljava/lang/String;Ljava/lang/String;)I", (void *)setRoutes},
     {"jniReset", "(Ljava/lang/String;)V", (void *)reset},
     {"jniCheck", "(Ljava/lang/String;)I", (void *)check},
 };
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/telecomm/java/android/telecomm/CallCapabilities.java b/telecomm/java/android/telecomm/CallCapabilities.java
index 2e0152a..924526e 100644
--- a/telecomm/java/android/telecomm/CallCapabilities.java
+++ b/telecomm/java/android/telecomm/CallCapabilities.java
@@ -42,6 +42,12 @@
     /** Call supports generic conference mode. */
     public static final int GENERIC_CONFERENCE = 0x00000080;
 
+    /** Local device supports video telephony. */
+    public static final int SUPPORTS_VT_LOCAL  = 0x00000100;
+
+    /** Remote device supports video telephony. */
+    public static final int SUPPORTS_VT_REMOTE = 0x00000200;
+
     public static final int ALL = HOLD | SUPPORT_HOLD | MERGE_CALLS | SWAP_CALLS | ADD_CALL
             | RESPOND_VIA_TEXT | MUTE | GENERIC_CONFERENCE;
 
@@ -72,6 +78,12 @@
         if ((capabilities & GENERIC_CONFERENCE) != 0) {
             builder.append(" GENERIC_CONFERENCE");
         }
+        if ((capabilities & SUPPORTS_VT_LOCAL) != 0) {
+            builder.append(" SUPPORTS_VT_LOCAL");
+        }
+        if ((capabilities & SUPPORTS_VT_REMOTE) != 0) {
+            builder.append(" SUPPORTS_VT_REMOTE");
+        }
         builder.append("]");
         return builder.toString();
     }
diff --git a/telecomm/java/android/telecomm/Connection.java b/telecomm/java/android/telecomm/Connection.java
index 2fd001a..05b0062 100644
--- a/telecomm/java/android/telecomm/Connection.java
+++ b/telecomm/java/android/telecomm/Connection.java
@@ -112,6 +112,7 @@
     private boolean mRequestingRingback = false;
     private int mCallCapabilities;
     private Connection mParentConnection;
+    private CallVideoProvider mCallVideoProvider;
     private boolean mAudioModeIsVoip;
     private StatusHints mStatusHints;
 
@@ -349,11 +350,16 @@
      * @param callVideoProvider The call video provider.
      */
     public final void setCallVideoProvider(CallVideoProvider callVideoProvider) {
+        mCallVideoProvider = callVideoProvider;
         for (Listener l : mListeners) {
             l.onSetCallVideoProvider(this, callVideoProvider);
         }
     }
 
+    public final CallVideoProvider getCallVideoProvider() {
+        return mCallVideoProvider;
+    }
+
     /**
      * Sets state to disconnected.
      *
diff --git a/telecomm/java/android/telecomm/ConnectionService.java b/telecomm/java/android/telecomm/ConnectionService.java
index 1bc184b..d0ad0cc 100644
--- a/telecomm/java/android/telecomm/ConnectionService.java
+++ b/telecomm/java/android/telecomm/ConnectionService.java
@@ -25,6 +25,7 @@
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
+import android.telecomm.CallVideoProvider;
 
 import com.android.internal.os.SomeArgs;
 import com.android.internal.telecomm.IConnectionService;
@@ -688,6 +689,12 @@
         mIdByConnection.put(connection, callId);
         connection.addConnectionListener(mConnectionListener);
         onConnectionAdded(connection);
+
+        // Trigger listeners for properties set before connection listener was added.
+        CallVideoProvider callVideoProvider = connection.getCallVideoProvider();
+        if (callVideoProvider != null) {
+            connection.setCallVideoProvider(callVideoProvider);
+        }
     }
 
     private void removeConnection(Connection connection) {
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
diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl
index e83eed7..22ba924 100644
--- a/wifi/java/android/net/wifi/IWifiManager.aidl
+++ b/wifi/java/android/net/wifi/IWifiManager.aidl
@@ -18,6 +18,7 @@
 
 import android.net.wifi.BatchedScanResult;
 import android.net.wifi.BatchedScanSettings;
+import android.net.wifi.WifiAdapter;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiInfo;
 import android.net.wifi.ScanSettings;
@@ -25,6 +26,7 @@
 import android.net.wifi.ScanResult;
 import android.net.DhcpInfo;
 
+
 import android.os.Messenger;
 import android.os.WorkSource;
 
@@ -35,6 +37,8 @@
  */
 interface IWifiManager
 {
+    List<WifiAdapter> getAdaptors();
+
     List<WifiConfiguration> getConfiguredNetworks();
 
     int addOrUpdateNetwork(in WifiConfiguration config);
diff --git a/wifi/java/android/net/wifi/WifiAdapter.aidl b/wifi/java/android/net/wifi/WifiAdapter.aidl
new file mode 100644
index 0000000..931da92
--- /dev/null
+++ b/wifi/java/android/net/wifi/WifiAdapter.aidl
@@ -0,0 +1,19 @@
+/**
+ * Copyright (c) 2008, 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.net.wifi;
+
+parcelable WifiAdapter;
diff --git a/wifi/java/android/net/wifi/WifiAdapter.java b/wifi/java/android/net/wifi/WifiAdapter.java
new file mode 100644
index 0000000..f6ee730
--- /dev/null
+++ b/wifi/java/android/net/wifi/WifiAdapter.java
@@ -0,0 +1,135 @@
+/*
+ * 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.net.wifi;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/** @hide */
+public class WifiAdapter implements Parcelable {
+
+    /* Keep this list in sync with wifi_hal.h */
+    public static final int WIFI_FEATURE_INFRA            = 0x0001;  // Basic infrastructure mode
+    public static final int WIFI_FEATURE_INFRA_5G         = 0x0002;  // Support for 5 GHz Band
+    public static final int WIFI_FEATURE_PASSPOINT        = 0x0004;  // Support for GAS/ANQP
+    public static final int WIFI_FEATURE_P2P              = 0x0008;  // Wifi-Direct
+    public static final int WIFI_FEATURE_MOBILE_HOTSPOT   = 0x0010;  // Soft AP
+    public static final int WIFI_FEATURE_SCANNER          = 0x0020;  // WifiScanner APIs
+    public static final int WIFI_FEATURE_NAN              = 0x0040;  // Neighbor Awareness Networking
+    public static final int WIFI_FEATURE_D2D_RTT          = 0x0080;  // Device-to-device RTT
+    public static final int WIFI_FEATURE_D2AP_RTT         = 0x0100;  // Device-to-AP RTT
+    public static final int WIFI_FEATURE_BATCH_SCAN       = 0x0200;  // Batched Scan (deprecated)
+    public static final int WIFI_FEATURE_PNO              = 0x0400;  // Preferred network offload
+    public static final int WIFI_FEATURE_ADDITIONAL_STA   = 0x0800;  // Support for two STAs
+    public static final int WIFI_FEATURE_TDLS             = 0x1000;  // Tunnel directed link setup
+    public static final int WIFI_FEATURE_TDLS_OFFCHANNEL  = 0x2000;  // Support for TDLS off channel
+    public static final int WIFI_FEATURE_EPR              = 0x4000;  // Enhanced power reporting
+
+    private String name;
+    private int    supportedFeatures;
+
+    public WifiAdapter(String name, int supportedFeatures) {
+        this.name = name;
+        this.supportedFeatures = supportedFeatures;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    private int getSupportedFeatures() {
+        return supportedFeatures;
+    }
+
+    private boolean isFeatureSupported(int feature) {
+        return (supportedFeatures & feature) == feature;
+    }
+
+    public boolean isPasspointSupported() {
+        return isFeatureSupported(WIFI_FEATURE_PASSPOINT);
+    }
+
+    public boolean isWifiDirectSupported() {
+        return isFeatureSupported(WIFI_FEATURE_P2P);
+    }
+
+    public boolean isMobileHotstpoSupported() {
+        return isFeatureSupported(WIFI_FEATURE_MOBILE_HOTSPOT);
+    }
+
+    public boolean isWifiScannerSupported() {
+        return isFeatureSupported(WIFI_FEATURE_SCANNER);
+    }
+
+    public boolean isNanSupported() {
+        return isFeatureSupported(WIFI_FEATURE_NAN);
+    }
+
+    public boolean isDeviceToDeviceRttSupported() {
+        return isFeatureSupported(WIFI_FEATURE_D2D_RTT);
+    }
+
+    public boolean isDeviceToApRttSupported() {
+        return isFeatureSupported(WIFI_FEATURE_D2AP_RTT);
+    }
+
+    public boolean isPreferredNetworkOffloadSupported() {
+        return isFeatureSupported(WIFI_FEATURE_PNO);
+    }
+
+    public boolean isAdditionalStaSupported() {
+        return isFeatureSupported(WIFI_FEATURE_ADDITIONAL_STA);
+    }
+
+    public boolean isTdlsSupported() {
+        return isFeatureSupported(WIFI_FEATURE_TDLS);
+    }
+
+    public boolean isOffChannelTdlsSupported() {
+        return isFeatureSupported(WIFI_FEATURE_TDLS_OFFCHANNEL);
+    }
+
+    public boolean isEnhancedPowerReportingSupported() {
+        return isFeatureSupported(WIFI_FEATURE_EPR);
+    }
+
+    /* Parcelable implementation */
+
+    /** Implement the Parcelable interface {@hide} */
+    public int describeContents() {
+        return 0;
+    }
+
+    /** Implement the Parcelable interface {@hide} */
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeString(name);
+        dest.writeInt(supportedFeatures);
+    }
+
+    /** Implement the Parcelable interface {@hide} */
+    public static final Creator<WifiAdapter> CREATOR =
+            new Creator<WifiAdapter>() {
+                public WifiAdapter createFromParcel(Parcel in) {
+                    WifiAdapter adaptor = new WifiAdapter(in.readString(), in.readInt());
+                    return adaptor;
+                }
+
+                public WifiAdapter[] newArray(int size) {
+                    return new WifiAdapter[size];
+                }
+            };
+}
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index e99ea35..f9a9e7d 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -568,6 +568,17 @@
     }
 
     /**
+     * @hide
+     */
+    public List<WifiAdapter> getAdaptors() {
+        try {
+            return mService.getAdaptors();
+        } catch (RemoteException e) {
+            return null;
+        }
+    }
+
+    /**
      * Return a list of all the networks configured in the supplicant.
      * Not all fields of WifiConfiguration are returned. Only the following
      * fields are filled in:
diff --git a/wifi/java/android/net/wifi/WifiScanner.java b/wifi/java/android/net/wifi/WifiScanner.java
index 21b700d..f3294bb 100644
--- a/wifi/java/android/net/wifi/WifiScanner.java
+++ b/wifi/java/android/net/wifi/WifiScanner.java
@@ -16,6 +16,7 @@
 
 package android.net.wifi;
 
+import android.annotation.SystemApi;
 import android.content.Context;
 import android.os.Handler;
 import android.os.HandlerThread;
@@ -42,6 +43,7 @@
  * .WIFI_SCANNING_SERVICE)}.
  * @hide
  */
+@SystemApi
 public class WifiScanner {
 
     /** no band specified; use channel list instead */
@@ -61,7 +63,7 @@
     public static final int WIFI_BAND_BOTH_WITH_DFS = 7;    /* both bands with DFS channels */
 
     /** Minimum supported scanning period */
-    public static final int MIN_SCAN_PERIOD_MS = 2000;      /* minimum supported period */
+    public static final int MIN_SCAN_PERIOD_MS = 1000;      /* minimum supported period */
     /** Maximum supported scanning period */
     public static final int MAX_SCAN_PERIOD_MS = 1024000;   /* maximum supported period */
 
@@ -78,6 +80,7 @@
      * Generic action callback invocation interface
      *  @hide
      */
+    @SystemApi
     public static interface ActionListener {
         public void onSuccess();
         public void onFailure(int reason, String description);
@@ -138,7 +141,7 @@
         public int band;
         /** list of channels; used when band is set to WIFI_BAND_UNSPECIFIED */
         public ChannelSpec[] channels;
-        /** period of background scan; in millisecond */
+        /** period of background scan; in millisecond, 0 => single shot scan */
         public int periodInMs;
         /** must have a valid REPORT_EVENT value */
         public int reportEvents;
@@ -267,6 +270,7 @@
     /** @hide */
     public void scan(ScanSettings settings, ScanListener listener) {
         validateChannel();
+        settings.periodInMs = 0;
         sAsyncChannel.sendMessage(CMD_SCAN, 0, putListener(listener), settings);
     }
 
@@ -313,6 +317,7 @@
     }
 
     /** @hide */
+    @SystemApi
     public static class WifiChangeSettings implements Parcelable {
         public int rssiSampleSize;                          /* sample size for RSSI averaging */
         public int lostApSampleSize;                        /* samples to confirm AP's loss */
@@ -443,6 +448,7 @@
     }
 
     /** @hide */
+    @SystemApi
     public void configureWifiChange(WifiChangeSettings settings) {
         validateChannel();
         sAsyncChannel.sendMessage(CMD_CONFIGURE_WIFI_CHANGE, 0, 0, settings);
@@ -457,6 +463,7 @@
     }
 
     /** @hide */
+    @SystemApi
     public static class HotlistSettings implements Parcelable {
         public HotspotInfo[] hotspotInfos;
         public int apLostThreshold;