Merge "Remove dead code 1." into lmp-dev
diff --git a/core/java/android/hardware/soundtrigger/IRecognitionStatusCallback.aidl b/core/java/android/hardware/soundtrigger/IRecognitionStatusCallback.aidl
index 038d7ef..0bf4f25 100644
--- a/core/java/android/hardware/soundtrigger/IRecognitionStatusCallback.aidl
+++ b/core/java/android/hardware/soundtrigger/IRecognitionStatusCallback.aidl
@@ -16,6 +16,8 @@
 
 package android.hardware.soundtrigger;
 
+import android.hardware.soundtrigger.SoundTrigger;
+
 /**
  * @hide
  */
@@ -27,7 +29,7 @@
      *        TODO: See if the data being passed in works well, if not use shared memory.
      *        This *MUST* not exceed 100K.
      */
-    void onDetected(in byte[] data);
+    void onDetected(in SoundTrigger.RecognitionEvent recognitionEvent);
     /**
      * Called when the detection for the associated keyphrase stops.
      */
diff --git a/core/java/android/hardware/soundtrigger/SoundTrigger.aidl b/core/java/android/hardware/soundtrigger/SoundTrigger.aidl
index 837691a..9adc6bc 100644
--- a/core/java/android/hardware/soundtrigger/SoundTrigger.aidl
+++ b/core/java/android/hardware/soundtrigger/SoundTrigger.aidl
@@ -21,4 +21,5 @@
 parcelable SoundTrigger.KeyphraseRecognitionExtra;
 parcelable SoundTrigger.KeyphraseSoundModel;
 parcelable SoundTrigger.ModuleProperties;
-parcelable SoundTrigger.RecognitionConfig;
\ No newline at end of file
+parcelable SoundTrigger.RecognitionConfig;
+parcelable SoundTrigger.RecognitionEvent;
\ No newline at end of file
diff --git a/core/java/android/hardware/soundtrigger/SoundTrigger.java b/core/java/android/hardware/soundtrigger/SoundTrigger.java
index 9a5cd9b..3e84368 100644
--- a/core/java/android/hardware/soundtrigger/SoundTrigger.java
+++ b/core/java/android/hardware/soundtrigger/SoundTrigger.java
@@ -347,12 +347,7 @@
 
         private static KeyphraseSoundModel fromParcel(Parcel in) {
             UUID uuid = UUID.fromString(in.readString());
-            byte[] data = null;
-            int dataLength = in.readInt();
-            if (dataLength >= 0) {
-                data = new byte[dataLength];
-                in.readByteArray(data);
-            }
+            byte[] data = in.readBlob();
             Keyphrase[] keyphrases = in.createTypedArray(Keyphrase.CREATOR);
             return new KeyphraseSoundModel(uuid, data, keyphrases);
         }
@@ -365,12 +360,7 @@
         @Override
         public void writeToParcel(Parcel dest, int flags) {
             dest.writeString(uuid.toString());
-            if (data != null) {
-                dest.writeInt(data.length);
-                dest.writeByteArray(data);
-            } else {
-                dest.writeInt(-1);
-            }
+            dest.writeBlob(data);
             dest.writeTypedArray(keyphrases, 0);
         }
 
@@ -406,7 +396,7 @@
      *  {@link StatusListener#onRecognition(RecognitionEvent)}
      *  callback upon recognition success or failure.
      */
-    public static class RecognitionEvent {
+    public static class RecognitionEvent implements Parcelable {
         /** Recognition status e.g {@link #RECOGNITION_STATUS_SUCCESS} */
         public final int status;
         /** Sound Model corresponding to this event callback */
@@ -425,7 +415,7 @@
          * typically during enrollment. */
         public final byte[] data;
 
-        RecognitionEvent(int status, int soundModelHandle, boolean captureAvailable,
+        public RecognitionEvent(int status, int soundModelHandle, boolean captureAvailable,
                 int captureSession, int captureDelayMs, int capturePreambleMs, byte[] data) {
             this.status = status;
             this.soundModelHandle = soundModelHandle;
@@ -435,6 +425,85 @@
             this.capturePreambleMs = capturePreambleMs;
             this.data = data;
         }
+
+        public static final Parcelable.Creator<RecognitionEvent> CREATOR
+                = new Parcelable.Creator<RecognitionEvent>() {
+            public RecognitionEvent createFromParcel(Parcel in) {
+                return RecognitionEvent.fromParcel(in);
+            }
+
+            public RecognitionEvent[] newArray(int size) {
+                return new RecognitionEvent[size];
+            }
+        };
+
+        private static RecognitionEvent fromParcel(Parcel in) {
+            int status = in.readInt();
+            int soundModelHandle = in.readInt();
+            boolean captureAvailable = in.readByte() == 1;
+            int captureSession = in.readInt();
+            int captureDelayMs = in.readInt();
+            int capturePreambleMs = in.readInt();
+            byte[] data = in.readBlob();
+            return new RecognitionEvent(status, soundModelHandle, captureAvailable, captureSession,
+                    captureDelayMs, capturePreambleMs, data);
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeInt(status);
+            dest.writeInt(soundModelHandle);
+            dest.writeByte((byte) (captureAvailable ? 1 : 0));
+            dest.writeInt(captureSession);
+            dest.writeInt(captureDelayMs);
+            dest.writeInt(capturePreambleMs);
+            dest.writeBlob(data);
+        }
+
+        @Override
+        public int hashCode() {
+            final int prime = 31;
+            int result = 1;
+            result = prime * result + (captureAvailable ? 1231 : 1237);
+            result = prime * result + captureDelayMs;
+            result = prime * result + capturePreambleMs;
+            result = prime * result + captureSession;
+            result = prime * result + Arrays.hashCode(data);
+            result = prime * result + soundModelHandle;
+            result = prime * result + status;
+            return result;
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj)
+                return true;
+            if (obj == null)
+                return false;
+            if (getClass() != obj.getClass())
+                return false;
+            RecognitionEvent other = (RecognitionEvent) obj;
+            if (captureAvailable != other.captureAvailable)
+                return false;
+            if (captureDelayMs != other.captureDelayMs)
+                return false;
+            if (capturePreambleMs != other.capturePreambleMs)
+                return false;
+            if (captureSession != other.captureSession)
+                return false;
+            if (!Arrays.equals(data, other.data))
+                return false;
+            if (soundModelHandle != other.soundModelHandle)
+                return false;
+            if (status != other.status)
+                return false;
+            return true;
+        }
     }
 
     /**
@@ -475,12 +544,7 @@
             boolean captureRequested = in.readByte() == 1;
             KeyphraseRecognitionExtra[] keyphrases =
                     in.createTypedArray(KeyphraseRecognitionExtra.CREATOR);
-            byte[] data = null;
-            int dataLength = in.readInt();
-            if (dataLength >= 0) {
-                data = new byte[dataLength];
-                in.readByteArray(data);
-            }
+            byte[] data = in.readBlob();
             return new RecognitionConfig(captureRequested, keyphrases, data);
         }
 
@@ -488,12 +552,7 @@
         public void writeToParcel(Parcel dest, int flags) {
             dest.writeByte((byte) (captureRequested ? 1 : 0));
             dest.writeTypedArray(keyphrases, 0);
-            if (data != null) {
-                dest.writeInt(data.length);
-                dest.writeByteArray(data);
-            } else {
-                dest.writeInt(-1);
-            }
+            dest.writeBlob(data);
         }
 
         @Override
diff --git a/core/java/android/net/NetworkAgent.java b/core/java/android/net/NetworkAgent.java
index a365af0..22da90e 100644
--- a/core/java/android/net/NetworkAgent.java
+++ b/core/java/android/net/NetworkAgent.java
@@ -106,6 +106,20 @@
      */
     public static final int EVENT_UID_RANGES_REMOVED = BASE + 6;
 
+    /**
+     * Sent by the NetworkAgent to ConnectivityService to block all routes for a certain address
+     * family (AF_INET or AF_INET6) on this Network. For VPNs only.
+     * obj = Integer representing the family (AF_INET or AF_INET6)
+     */
+    public static final int EVENT_BLOCK_ADDRESS_FAMILY = BASE + 7;
+
+    /**
+     * Sent by the NetworkAgent to ConnectivityService to unblock routes for a certain address
+     * family (AF_INET or AF_INET6) on this Network. For VPNs only.
+     * obj = Integer representing the family (AF_INET or AF_INET6)
+     */
+    public static final int EVENT_UNBLOCK_ADDRESS_FAMILY = BASE + 8;
+
     public NetworkAgent(Looper looper, Context context, String logTag, NetworkInfo ni,
             NetworkCapabilities nc, LinkProperties lp, int score) {
         this(looper, context, logTag, ni, nc, lp, score, null);
@@ -229,6 +243,21 @@
     }
 
     /**
+     * Called by the VPN code when it wants to block an address family from being routed, typically
+     * because the VPN network doesn't support that family.
+     */
+    public void blockAddressFamily(int family) {
+        queueOrSendMessage(EVENT_BLOCK_ADDRESS_FAMILY, family);
+    }
+
+    /**
+     * Called by the VPN code when it wants to unblock an address family from being routed.
+     */
+    public void unblockAddressFamily(int family) {
+        queueOrSendMessage(EVENT_UNBLOCK_ADDRESS_FAMILY, family);
+    }
+
+    /**
      * 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/VpnService.java b/core/java/android/net/VpnService.java
index 4b07e3f..9b66997 100644
--- a/core/java/android/net/VpnService.java
+++ b/core/java/android/net/VpnService.java
@@ -373,6 +373,7 @@
                 throw new IllegalArgumentException("Bad address");
             }
             mAddresses.add(new LinkAddress(address, prefixLength));
+            mConfig.updateAllowedFamilies(address);
             return this;
         }
 
@@ -413,6 +414,7 @@
                 }
             }
             mRoutes.add(new RouteInfo(new LinkAddress(address, prefixLength), null));
+            mConfig.updateAllowedFamilies(address);
             return this;
         }
 
@@ -497,7 +499,14 @@
          * @return this {@link Builder} object to facilitate chaining of method calls.
          */
         public Builder allowFamily(int family) {
-            // TODO
+            if (family == AF_INET) {
+                mConfig.allowIPv4 = true;
+            } else if (family == AF_INET6) {
+                mConfig.allowIPv6 = true;
+            } else {
+                throw new IllegalArgumentException(family + " is neither " + AF_INET + " nor " +
+                        AF_INET6);
+            }
             return this;
         }
 
diff --git a/core/java/android/os/INetworkManagementService.aidl b/core/java/android/os/INetworkManagementService.aidl
index c467d4a..d6ee8e0 100644
--- a/core/java/android/os/INetworkManagementService.aidl
+++ b/core/java/android/os/INetworkManagementService.aidl
@@ -404,4 +404,7 @@
 
     void addInterfaceToLocalNetwork(String iface, in List<RouteInfo> routes);
     void removeInterfaceFromLocalNetwork(String iface);
+
+    void blockAddressFamily(int family, int netId, String iface);
+    void unblockAddressFamily(int family, int netId, String iface);
 }
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index 59cd97c..a1c2aa1 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -485,6 +485,7 @@
      * growing {@link #dataCapacity} if needed.
      * @param b Bytes to place into the parcel.
      * {@hide}
+     * {@SystemApi}
      */
     public final void writeBlob(byte[] b) {
         nativeWriteBlob(mNativePtr, b, 0, (b != null) ? b.length : 0);
@@ -1714,6 +1715,7 @@
     /**
      * Read a blob of data from the parcel and return it as a byte array.
      * {@hide}
+     * {@SystemApi}
      */
     public final byte[] readBlob() {
         return nativeReadBlob(mNativePtr);
diff --git a/core/java/android/service/voice/AlwaysOnHotwordDetector.java b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
index d077a17..a8c08d55 100644
--- a/core/java/android/service/voice/AlwaysOnHotwordDetector.java
+++ b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
@@ -27,6 +27,7 @@
 import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel;
 import android.hardware.soundtrigger.SoundTrigger.ModuleProperties;
 import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig;
+import android.hardware.soundtrigger.SoundTrigger.RecognitionEvent;
 import android.os.AsyncTask;
 import android.os.Handler;
 import android.os.Message;
@@ -380,10 +381,10 @@
         }
 
         @Override
-        public void onDetected(byte[] data) {
+        public void onDetected(RecognitionEvent recognitionEvent) {
             Slog.i(TAG, "onDetected");
             Message message = Message.obtain(mHandler, MSG_HOTWORD_DETECTED);
-            message.obj = data;
+            message.obj = recognitionEvent.data;
             message.sendToTarget();
         }
 
diff --git a/core/java/com/android/internal/net/VpnConfig.java b/core/java/com/android/internal/net/VpnConfig.java
index dac59f9..0099269 100644
--- a/core/java/com/android/internal/net/VpnConfig.java
+++ b/core/java/com/android/internal/net/VpnConfig.java
@@ -27,6 +27,7 @@
 import android.net.RouteInfo;
 import android.net.LinkAddress;
 
+import java.net.Inet4Address;
 import java.net.InetAddress;
 import java.util.List;
 import java.util.ArrayList;
@@ -75,6 +76,16 @@
     public boolean legacy;
     public boolean blocking;
     public boolean allowBypass;
+    public boolean allowIPv4;
+    public boolean allowIPv6;
+
+    public void updateAllowedFamilies(InetAddress address) {
+        if (address instanceof Inet4Address) {
+            allowIPv4 = true;
+        } else {
+            allowIPv6 = true;
+        }
+    }
 
     public void addLegacyRoutes(String routesStr) {
         if (routesStr.trim().equals("")) {
@@ -87,6 +98,7 @@
             RouteInfo info = new RouteInfo(new LinkAddress
                     (InetAddress.parseNumericAddress(split[0]), Integer.parseInt(split[1])), null);
             this.routes.add(info);
+            updateAllowedFamilies(info.getDestination().getAddress());
         }
     }
 
@@ -101,6 +113,7 @@
             LinkAddress addr = new LinkAddress(InetAddress.parseNumericAddress(split[0]),
                     Integer.parseInt(split[1]));
             this.addresses.add(addr);
+            updateAllowedFamilies(addr.getAddress());
         }
     }
 
@@ -124,6 +137,8 @@
         out.writeInt(legacy ? 1 : 0);
         out.writeInt(blocking ? 1 : 0);
         out.writeInt(allowBypass ? 1 : 0);
+        out.writeInt(allowIPv4 ? 1 : 0);
+        out.writeInt(allowIPv6 ? 1 : 0);
     }
 
     public static final Parcelable.Creator<VpnConfig> CREATOR =
@@ -144,6 +159,8 @@
             config.legacy = in.readInt() != 0;
             config.blocking = in.readInt() != 0;
             config.allowBypass = in.readInt() != 0;
+            config.allowIPv4 = in.readInt() != 0;
+            config.allowIPv6 = in.readInt() != 0;
             return config;
         }
 
diff --git a/core/jni/android_os_Parcel.cpp b/core/jni/android_os_Parcel.cpp
index 3ba481e..44863cc 100644
--- a/core/jni/android_os_Parcel.cpp
+++ b/core/jni/android_os_Parcel.cpp
@@ -194,6 +194,14 @@
         return;
     }
 
+    if (data == NULL) {
+        const status_t err = parcel->writeInt32(-1);
+        if (err != NO_ERROR) {
+            signalExceptionForError(env, clazz, err);
+        }
+        return;
+    }
+
     const status_t err = parcel->writeInt32(length);
     if (err != NO_ERROR) {
         signalExceptionForError(env, clazz, err);
diff --git a/graphics/java/android/graphics/ImageFormat.java b/graphics/java/android/graphics/ImageFormat.java
index ab4258d..3efb9c0 100644
--- a/graphics/java/android/graphics/ImageFormat.java
+++ b/graphics/java/android/graphics/ImageFormat.java
@@ -236,17 +236,17 @@
      * Android 10-bit raw format
      * </p>
      * <p>
-     * This is a single-plane, 10-bit per pixel, densely packed, unprocessed
-     * format, usually representing raw Bayer-pattern images coming from an image
-     * sensor.
+     * This is a single-plane, 10-bit per pixel, densely packed (in each row),
+     * unprocessed format, usually representing raw Bayer-pattern images coming
+     * from an image sensor.
      * </p>
      * <p>
-     * In an image buffer with this format, starting from the first pixel, each
-     * 4 consecutive pixels are packed into 5 bytes (40 bits). Each one of the
-     * first 4 bytes contains the top 8 bits of each pixel, The fifth byte
-     * contains the 2 least significant bits of the 4 pixels, the exact layout
-     * data for each 4 consecutive pixels is illustrated below (Pi[j] stands for
-     * the jth bit of the ith pixel):
+     * In an image buffer with this format, starting from the first pixel of
+     * each row, each 4 consecutive pixels are packed into 5 bytes (40 bits).
+     * Each one of the first 4 bytes contains the top 8 bits of each pixel, The
+     * fifth byte contains the 2 least significant bits of the 4 pixels, the
+     * exact layout data for each 4 consecutive pixels is illustrated below
+     * ({@code Pi[j]} stands for the jth bit of the ith pixel):
      * </p>
      * <table>
      * <thead>
@@ -327,23 +327,26 @@
      * </ul>
      * </p>
      *
-     * <pre>
-     * size = width * height * 10 / 8
-     * </pre>
-     * <p>
-     * Since this is a densely packed format, the pixel and row stride are always
-     * 0. The application must use the pixel data layout defined in above table
-     * to access data.
-     * </p>
+     * <pre>size = row stride * height</pre> where the row stride is in <em>bytes</em>,
+     * not pixels.
      *
      * <p>
+     * Since this is a densely packed format, the pixel stride is always 0. The
+     * application must use the pixel data layout defined in above table to
+     * access each row data. When row stride is equal to {@code width * (10 / 8)}, there
+     * will be no padding bytes at the end of each row, the entire image data is
+     * densely packed. When stride is larger than {@code width * (10 / 8)}, padding
+     * bytes will be present at the end of each row.
+     * </p>
+     * <p>
      * For example, the {@link android.media.Image} object can provide data in
-     * this format from a {@link android.hardware.camera2.CameraDevice} (if supported)
-     * through a {@link android.media.ImageReader} object. The
+     * this format from a {@link android.hardware.camera2.CameraDevice} (if
+     * supported) through a {@link android.media.ImageReader} object. The
      * {@link android.media.Image#getPlanes() Image#getPlanes()} will return a
-     * single plane containing the pixel data. The pixel stride and row stride
-     * are always 0 in {@link android.media.Image.Plane#getPixelStride()} and
-     * {@link android.media.Image.Plane#getRowStride()} respectively.
+     * single plane containing the pixel data. The pixel stride is always 0 in
+     * {@link android.media.Image.Plane#getPixelStride()}, and the
+     * {@link android.media.Image.Plane#getRowStride()} describes the vertical
+     * neighboring pixel distance (in bytes) between adjacent rows.
      * </p>
      *
      * @see android.media.Image
diff --git a/media/jni/android_media_ImageReader.cpp b/media/jni/android_media_ImageReader.cpp
index 35317e1..fa4439d 100644
--- a/media/jni/android_media_ImageReader.cpp
+++ b/media/jni/android_media_ImageReader.cpp
@@ -420,8 +420,11 @@
                                 "Width is not multiple of 4 %d", buffer->width);
             LOG_ALWAYS_FATAL_IF(buffer->height % 2,
                                 "Height is not even %d", buffer->height);
+            LOG_ALWAYS_FATAL_IF(buffer->stride < (buffer->width * 10 / 8),
+                                "stride (%d) should be at least %d",
+                                buffer->stride, buffer->width * 10 / 8);
             pData = buffer->data;
-            dataSize = buffer->width * buffer->height * 10 / 8;
+            dataSize = buffer->stride * buffer->height;
             break;
         case HAL_PIXEL_FORMAT_RGBA_8888:
         case HAL_PIXEL_FORMAT_RGBX_8888:
@@ -535,12 +538,15 @@
             rowStride = (idx == 0) ? buffer->stride : ALIGN(buffer->stride / 2, 16);
             break;
         case HAL_PIXEL_FORMAT_BLOB:
-        case HAL_PIXEL_FORMAT_RAW10:
             // Blob is used for JPEG data, RAW10 is used for 10-bit raw data, they are
             // single plane, row and pixel strides are 0.
             ALOG_ASSERT(idx == 0, "Wrong index: %d", idx);
             rowStride = 0;
             break;
+        case HAL_PIXEL_FORMAT_RAW10:
+            ALOG_ASSERT(idx == 0, "Wrong index: %d", idx);
+            rowStride = buffer->stride;
+            break;
         case HAL_PIXEL_FORMAT_Y8:
             ALOG_ASSERT(idx == 0, "Wrong index: %d", idx);
             LOG_ALWAYS_FATAL_IF(buffer->stride % 16,
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 3d9307d..7349ebf 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -2580,7 +2580,9 @@
                     }
                     try {
                         mNetd.addVpnUidRanges(nai.network.netId, (UidRange[])msg.obj);
-                    } catch (RemoteException e) {
+                    } catch (Exception e) {
+                        // Never crash!
+                        loge("Exception in addVpnUidRanges: " + e);
                     }
                     break;
                 }
@@ -2592,7 +2594,39 @@
                     }
                     try {
                         mNetd.removeVpnUidRanges(nai.network.netId, (UidRange[])msg.obj);
-                    } catch (RemoteException e) {
+                    } catch (Exception e) {
+                        // Never crash!
+                        loge("Exception in removeVpnUidRanges: " + e);
+                    }
+                    break;
+                }
+                case NetworkAgent.EVENT_BLOCK_ADDRESS_FAMILY: {
+                    NetworkAgentInfo nai = mNetworkAgentInfos.get(msg.replyTo);
+                    if (nai == null) {
+                        loge("EVENT_BLOCK_ADDRESS_FAMILY from unknown NetworkAgent");
+                        break;
+                    }
+                    try {
+                        mNetd.blockAddressFamily((Integer) msg.obj, nai.network.netId,
+                                nai.linkProperties.getInterfaceName());
+                    } catch (Exception e) {
+                        // Never crash!
+                        loge("Exception in blockAddressFamily: " + e);
+                    }
+                    break;
+                }
+                case NetworkAgent.EVENT_UNBLOCK_ADDRESS_FAMILY: {
+                    NetworkAgentInfo nai = mNetworkAgentInfos.get(msg.replyTo);
+                    if (nai == null) {
+                        loge("EVENT_UNBLOCK_ADDRESS_FAMILY from unknown NetworkAgent");
+                        break;
+                    }
+                    try {
+                        mNetd.unblockAddressFamily((Integer) msg.obj, nai.network.netId,
+                                nai.linkProperties.getInterfaceName());
+                    } catch (Exception e) {
+                        // Never crash!
+                        loge("Exception in blockAddressFamily: " + e);
                     }
                     break;
                 }
diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java
index 75a7878..362a745 100644
--- a/services/core/java/com/android/server/NetworkManagementService.java
+++ b/services/core/java/com/android/server/NetworkManagementService.java
@@ -25,6 +25,8 @@
 import static android.net.NetworkStats.TAG_NONE;
 import static android.net.NetworkStats.UID_ALL;
 import static android.net.TrafficStats.UID_TETHERING;
+import static android.system.OsConstants.AF_INET;
+import static android.system.OsConstants.AF_INET6;
 import static com.android.server.NetworkManagementService.NetdResponseCode.ClatdStatusResult;
 import static com.android.server.NetworkManagementService.NetdResponseCode.InterfaceGetCfgResult;
 import static com.android.server.NetworkManagementService.NetdResponseCode.InterfaceListResult;
@@ -85,6 +87,7 @@
 import java.io.InputStreamReader;
 import java.io.PrintWriter;
 import java.net.Inet4Address;
+import java.net.Inet6Address;
 import java.net.InetAddress;
 import java.net.InterfaceAddress;
 import java.net.NetworkInterface;
@@ -2102,4 +2105,36 @@
     public void removeInterfaceFromLocalNetwork(String iface) {
         modifyInterfaceInNetwork("remove", "local", iface);
     }
+
+    @Override
+    public void blockAddressFamily(int family, int netId, String iface) {
+        modifyAddressFamily("add", family, netId, iface);
+    }
+
+    @Override
+    public void unblockAddressFamily(int family, int netId, String iface) {
+        modifyAddressFamily("remove", family, netId, iface);
+    }
+
+    private void modifyAddressFamily(String action, int family, int netId, String iface) {
+        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
+
+        final Command cmd = new Command("network", "route", action, netId, iface);
+
+        if (family == AF_INET) {
+            cmd.appendArg(Inet4Address.ANY.getHostAddress() + "/0");
+        } else if (family == AF_INET6) {
+            cmd.appendArg(Inet6Address.ANY.getHostAddress() + "/0");
+        } else {
+            throw new IllegalStateException(family + " is neither " + AF_INET + " nor " + AF_INET6);
+        }
+
+        cmd.appendArg("unreachable");
+
+        try {
+            mConnector.execute(cmd);
+        } catch (NativeDaemonConnectorException e) {
+            throw e.rethrowAsParcelableException();
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index 0305424..0a8ca6a 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -17,6 +17,8 @@
 package com.android.server.connectivity;
 
 import static android.Manifest.permission.BIND_VPN_SERVICE;
+import static android.system.OsConstants.AF_INET;
+import static android.system.OsConstants.AF_INET6;
 
 import android.app.AppGlobals;
 import android.app.Notification;
@@ -107,6 +109,8 @@
     private String mPackage;
     private int mOwnerUID;
     private String mInterface;
+    private boolean mAllowIPv4;
+    private boolean mAllowIPv6;
     private Connection mConnection;
     private LegacyVpnRunner mLegacyVpnRunner;
     private PendingIntent mStatusIntent;
@@ -307,6 +311,7 @@
     private void agentConnect() {
         LinkProperties lp = new LinkProperties();
         lp.setInterfaceName(mInterface);
+
         boolean hasDefaultRoute = false;
         for (RouteInfo route : mConfig.routes) {
             lp.addRoute(route);
@@ -317,11 +322,19 @@
         } else {
             mNetworkCapabilities.removeCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
         }
+
         if (mConfig.dnsServers != null) {
             for (String dnsServer : mConfig.dnsServers) {
-                lp.addDnsServer(InetAddress.parseNumericAddress(dnsServer));
+                InetAddress address = InetAddress.parseNumericAddress(dnsServer);
+                lp.addDnsServer(address);
+                if (address instanceof Inet4Address) {
+                    mAllowIPv4 = true;
+                } else {
+                    mAllowIPv6 = true;
+                }
             }
         }
+
         // Concatenate search domains into a string.
         StringBuilder buffer = new StringBuilder();
         if (mConfig.searchDomains != null) {
@@ -330,12 +343,13 @@
             }
         }
         lp.setDomains(buffer.toString().trim());
+
         mNetworkInfo.setIsAvailable(true);
         mNetworkInfo.setDetailedState(DetailedState.CONNECTED, null, null);
+
         NetworkMisc networkMisc = new NetworkMisc();
-        if (mConfig.allowBypass) {
-            networkMisc.allowBypass = true;
-        }
+        networkMisc.allowBypass = mConfig.allowBypass;
+
         long token = Binder.clearCallingIdentity();
         try {
             mNetworkAgent = new NetworkAgent(mLooper, mContext, NETWORKTYPE,
@@ -347,6 +361,14 @@
         } finally {
             Binder.restoreCallingIdentity(token);
         }
+
+        if (!mAllowIPv4) {
+            mNetworkAgent.blockAddressFamily(AF_INET);
+        }
+        if (!mAllowIPv6) {
+            mNetworkAgent.blockAddressFamily(AF_INET6);
+        }
+
         addVpnUserLocked(mUserId);
         // If we are owner assign all Restricted Users to this VPN
         if (mUserId == UserHandle.USER_OWNER) {
@@ -432,6 +454,8 @@
         NetworkAgent oldNetworkAgent = mNetworkAgent;
         mNetworkAgent = null;
         List<UidRange> oldUsers = mVpnUsers;
+        boolean oldAllowIPv4 = mAllowIPv4;
+        boolean oldAllowIPv6 = mAllowIPv6;
 
         // Configure the interface. Abort if any of these steps fails.
         ParcelFileDescriptor tun = ParcelFileDescriptor.adoptFd(jniCreate(config.mtu));
@@ -464,6 +488,9 @@
 
             // Set up forwarding and DNS rules.
             mVpnUsers = new ArrayList<UidRange>();
+            mAllowIPv4 = mConfig.allowIPv4;
+            mAllowIPv6 = mConfig.allowIPv6;
+
             agentConnect();
 
             if (oldConnection != null) {
@@ -492,6 +519,8 @@
             mVpnUsers = oldUsers;
             mNetworkAgent = oldNetworkAgent;
             mInterface = oldInterface;
+            mAllowIPv4 = oldAllowIPv4;
+            mAllowIPv6 = oldAllowIPv6;
             throw e;
         }
         Log.i(TAG, "Established by " + config.user + " on " + mInterface);
@@ -1175,6 +1204,8 @@
                     // Now INetworkManagementEventObserver is watching our back.
                     mInterface = mConfig.interfaze;
                     mVpnUsers = new ArrayList<UidRange>();
+                    mAllowIPv4 = mConfig.allowIPv4;
+                    mAllowIPv6 = mConfig.allowIPv6;
 
                     agentConnect();
 
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/SoundTriggerHelper.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/SoundTriggerHelper.java
index 86dca79..1e4a518 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/SoundTriggerHelper.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/SoundTriggerHelper.java
@@ -238,20 +238,13 @@
 
         switch (event.status) {
             case SoundTrigger.RECOGNITION_STATUS_SUCCESS:
-                // TODO: Pass the captured audio back.
                 try {
-                    listener.onDetected(null);
+                    listener.onDetected(event);
                 } catch (RemoteException e) {
                     Slog.w(TAG, "RemoteException in onDetected");
                 }
                 break;
-            case SoundTrigger.RECOGNITION_STATUS_ABORT:
-                try {
-                    listener.onDetectionStopped();
-                } catch (RemoteException e) {
-                    Slog.w(TAG, "RemoteException in onDetectionStopped");
-                }
-                break;
+            case SoundTrigger.RECOGNITION_STATUS_ABORT: // fall-through
             case SoundTrigger.RECOGNITION_STATUS_FAILURE:
                 try {
                     listener.onDetectionStopped();
diff --git a/tests/SoundTriggerTests/Android.mk b/tests/SoundTriggerTests/Android.mk
new file mode 100644
index 0000000..407a9d7
--- /dev/null
+++ b/tests/SoundTriggerTests/Android.mk
@@ -0,0 +1,27 @@
+#
+# 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.
+#
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+LOCAL_JAVA_LIBRARIES := android.test.runner
+
+LOCAL_PACKAGE_NAME := SoundTriggerTests
+
+include $(BUILD_PACKAGE)
diff --git a/tests/SoundTriggerTests/AndroidManifest.xml b/tests/SoundTriggerTests/AndroidManifest.xml
new file mode 100644
index 0000000..5e5a108
--- /dev/null
+++ b/tests/SoundTriggerTests/AndroidManifest.xml
@@ -0,0 +1,25 @@
+<?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="android.hardware.soundtrigger">
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation android:name="android.test.InstrumentationTestRunner"
+                     android:targetPackage="android.hardware.soundtrigger"
+                     android:label="Tests for android.hardware.soundtrigger" />
+</manifest>
diff --git a/tests/SoundTriggerTests/src/android/hardware/soundtrigger/SoundTriggerTest.java b/tests/SoundTriggerTests/src/android/hardware/soundtrigger/SoundTriggerTest.java
new file mode 100644
index 0000000..5d32c66
--- /dev/null
+++ b/tests/SoundTriggerTests/src/android/hardware/soundtrigger/SoundTriggerTest.java
@@ -0,0 +1,256 @@
+/*
+ * 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.soundtrigger;
+
+import android.hardware.soundtrigger.SoundTrigger;
+import android.hardware.soundtrigger.SoundTrigger.Keyphrase;
+import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel;
+import android.hardware.soundtrigger.SoundTrigger.RecognitionEvent;
+import android.os.Parcel;
+import android.test.InstrumentationTestCase;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import java.util.Arrays;
+import java.util.Random;
+import java.util.UUID;
+
+public class SoundTriggerTest extends InstrumentationTestCase {
+    private Random mRandom = new Random();
+
+    @SmallTest
+    public void testKeyphraseParcelUnparcel_noUsers() throws Exception {
+        Keyphrase keyphrase = new Keyphrase(1, 0, "en-US", "hello", null);
+
+        // Write to a parcel
+        Parcel parcel = Parcel.obtain();
+        keyphrase.writeToParcel(parcel, 0);
+
+        // Read from it
+        parcel.setDataPosition(0);
+        Keyphrase unparceled = Keyphrase.CREATOR.createFromParcel(parcel);
+
+        // Verify that they are the same
+        assertEquals(keyphrase.id, unparceled.id);
+        assertNull(unparceled.users);
+        assertEquals(keyphrase.locale, unparceled.locale);
+        assertEquals(keyphrase.text, unparceled.text);
+    }
+
+    @SmallTest
+    public void testKeyphraseParcelUnparcel_zeroUsers() throws Exception {
+        Keyphrase keyphrase = new Keyphrase(1, 0, "en-US", "hello", new int[0]);
+
+        // Write to a parcel
+        Parcel parcel = Parcel.obtain();
+        keyphrase.writeToParcel(parcel, 0);
+
+        // Read from it
+        parcel.setDataPosition(0);
+        Keyphrase unparceled = Keyphrase.CREATOR.createFromParcel(parcel);
+
+        // Verify that they are the same
+        assertEquals(keyphrase.id, unparceled.id);
+        assertTrue(Arrays.equals(keyphrase.users, unparceled.users));
+        assertEquals(keyphrase.locale, unparceled.locale);
+        assertEquals(keyphrase.text, unparceled.text);
+    }
+
+    @SmallTest
+    public void testKeyphraseParcelUnparcel_pos() throws Exception {
+        Keyphrase keyphrase = new Keyphrase(1, 0, "en-US", "hello", new int[] {1, 2, 3, 4, 5});
+
+        // Write to a parcel
+        Parcel parcel = Parcel.obtain();
+        keyphrase.writeToParcel(parcel, 0);
+
+        // Read from it
+        parcel.setDataPosition(0);
+        Keyphrase unparceled = Keyphrase.CREATOR.createFromParcel(parcel);
+
+        // Verify that they are the same
+        assertEquals(keyphrase.id, unparceled.id);
+        assertTrue(Arrays.equals(keyphrase.users, unparceled.users));
+        assertEquals(keyphrase.locale, unparceled.locale);
+        assertEquals(keyphrase.text, unparceled.text);
+    }
+
+    @SmallTest
+    public void testKeyphraseSoundModelParcelUnparcel_noData() throws Exception {
+        Keyphrase[] keyphrases = new Keyphrase[2];
+        keyphrases[0] = new Keyphrase(1, 0, "en-US", "hello", new int[] {0});
+        keyphrases[1] = new Keyphrase(2, 0, "fr-FR", "there", new int[] {1, 2});
+        KeyphraseSoundModel ksm = new KeyphraseSoundModel(UUID.randomUUID(), null, keyphrases);
+
+        // Write to a parcel
+        Parcel parcel = Parcel.obtain();
+        ksm.writeToParcel(parcel, 0);
+
+        // Read from it
+        parcel.setDataPosition(0);
+        KeyphraseSoundModel unparceled = KeyphraseSoundModel.CREATOR.createFromParcel(parcel);
+
+        // Verify that they are the same
+        assertEquals(ksm.uuid, unparceled.uuid);
+        assertNull(unparceled.data);
+        assertEquals(ksm.type, unparceled.type);
+        assertTrue(Arrays.equals(keyphrases, unparceled.keyphrases));
+    }
+
+    @SmallTest
+    public void testKeyphraseSoundModelParcelUnparcel_zeroData() throws Exception {
+        Keyphrase[] keyphrases = new Keyphrase[2];
+        keyphrases[0] = new Keyphrase(1, 0, "en-US", "hello", new int[] {0});
+        keyphrases[1] = new Keyphrase(2, 0, "fr-FR", "there", new int[] {1, 2});
+        KeyphraseSoundModel ksm = new KeyphraseSoundModel(UUID.randomUUID(), new byte[0],
+                keyphrases);
+
+        // Write to a parcel
+        Parcel parcel = Parcel.obtain();
+        ksm.writeToParcel(parcel, 0);
+
+        // Read from it
+        parcel.setDataPosition(0);
+        KeyphraseSoundModel unparceled = KeyphraseSoundModel.CREATOR.createFromParcel(parcel);
+
+        // Verify that they are the same
+        assertEquals(ksm.uuid, unparceled.uuid);
+        assertEquals(ksm.type, unparceled.type);
+        assertTrue(Arrays.equals(ksm.keyphrases, unparceled.keyphrases));
+        assertTrue(Arrays.equals(ksm.data, unparceled.data));
+    }
+
+    @SmallTest
+    public void testKeyphraseSoundModelParcelUnparcel_noKeyphrases() throws Exception {
+        byte[] data = new byte[10];
+        mRandom.nextBytes(data);
+        KeyphraseSoundModel ksm = new KeyphraseSoundModel(UUID.randomUUID(), data, null);
+
+        // Write to a parcel
+        Parcel parcel = Parcel.obtain();
+        ksm.writeToParcel(parcel, 0);
+
+        // Read from it
+        parcel.setDataPosition(0);
+        KeyphraseSoundModel unparceled = KeyphraseSoundModel.CREATOR.createFromParcel(parcel);
+
+        // Verify that they are the same
+        assertEquals(ksm.uuid, unparceled.uuid);
+        assertEquals(ksm.type, unparceled.type);
+        assertNull(unparceled.keyphrases);
+        assertTrue(Arrays.equals(ksm.data, unparceled.data));
+    }
+
+    @SmallTest
+    public void testKeyphraseSoundModelParcelUnparcel_zeroKeyphrases() throws Exception {
+        byte[] data = new byte[10];
+        mRandom.nextBytes(data);
+        KeyphraseSoundModel ksm = new KeyphraseSoundModel(UUID.randomUUID(), data,
+                new Keyphrase[0]);
+
+        // Write to a parcel
+        Parcel parcel = Parcel.obtain();
+        ksm.writeToParcel(parcel, 0);
+
+        // Read from it
+        parcel.setDataPosition(0);
+        KeyphraseSoundModel unparceled = KeyphraseSoundModel.CREATOR.createFromParcel(parcel);
+
+        // Verify that they are the same
+        assertEquals(ksm.uuid, unparceled.uuid);
+        assertEquals(ksm.type, unparceled.type);
+        assertTrue(Arrays.equals(ksm.keyphrases, unparceled.keyphrases));
+        assertTrue(Arrays.equals(ksm.data, unparceled.data));
+    }
+
+    @LargeTest
+    public void testKeyphraseSoundModelParcelUnparcel_largeData() throws Exception {
+        Keyphrase[] keyphrases = new Keyphrase[2];
+        keyphrases[0] = new Keyphrase(1, 0, "en-US", "hello", new int[] {0});
+        keyphrases[1] = new Keyphrase(2, 0, "fr-FR", "there", new int[] {1, 2});
+        byte[] data = new byte[200 * 1024];
+        mRandom.nextBytes(data);
+        KeyphraseSoundModel ksm = new KeyphraseSoundModel(UUID.randomUUID(), data, keyphrases);
+
+        // Write to a parcel
+        Parcel parcel = Parcel.obtain();
+        ksm.writeToParcel(parcel, 0);
+
+        // Read from it
+        parcel.setDataPosition(0);
+        KeyphraseSoundModel unparceled = KeyphraseSoundModel.CREATOR.createFromParcel(parcel);
+
+        // Verify that they are the same
+        assertEquals(ksm.uuid, unparceled.uuid);
+        assertEquals(ksm.type, unparceled.type);
+        assertTrue(Arrays.equals(ksm.data, unparceled.data));
+        assertTrue(Arrays.equals(ksm.keyphrases, unparceled.keyphrases));
+    }
+
+    @SmallTest
+    public void testRecognitionEventParcelUnparcel_noData() throws Exception {
+        RecognitionEvent re = new RecognitionEvent(SoundTrigger.RECOGNITION_STATUS_SUCCESS, 1,
+                true, 2, 3, 4, null);
+
+        // Write to a parcel
+        Parcel parcel = Parcel.obtain();
+        re.writeToParcel(parcel, 0);
+
+        // Read from it
+        parcel.setDataPosition(0);
+        RecognitionEvent unparceled = RecognitionEvent.CREATOR.createFromParcel(parcel);
+
+        // Verify that they are the same
+        assertEquals(re, unparceled);
+    }
+
+    @SmallTest
+    public void testRecognitionEventParcelUnparcel_zeroData() throws Exception {
+        RecognitionEvent re = new RecognitionEvent(SoundTrigger.RECOGNITION_STATUS_FAILURE, 1,
+                true, 2, 3, 4, new byte[1]);
+
+        // Write to a parcel
+        Parcel parcel = Parcel.obtain();
+        re.writeToParcel(parcel, 0);
+
+        // Read from it
+        parcel.setDataPosition(0);
+        RecognitionEvent unparceled = RecognitionEvent.CREATOR.createFromParcel(parcel);
+
+        // Verify that they are the same
+        assertEquals(re, unparceled);
+    }
+
+    @SmallTest
+    public void testRecognitionEventParcelUnparcel_largeData() throws Exception {
+        byte[] data = new byte[200 * 1024];
+        mRandom.nextBytes(data);
+        RecognitionEvent re = new RecognitionEvent(SoundTrigger.RECOGNITION_STATUS_ABORT, 1,
+                false, 2, 3, 4, data);
+
+        // Write to a parcel
+        Parcel parcel = Parcel.obtain();
+        re.writeToParcel(parcel, 0);
+
+        // Read from it
+        parcel.setDataPosition(0);
+        RecognitionEvent unparceled = RecognitionEvent.CREATOR.createFromParcel(parcel);
+
+        // Verify that they are the same
+        assertEquals(re, unparceled);
+    }
+}