Use blob (shared memory) for large data in sound model/recognition event/config

Also add a missing null check in writeBlob

Bug: 16516353
Change-Id: Ie702f8daae541cab7c2cee6e13d49e7fc84c84e1
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/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/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/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..5d32c66e
--- /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);
+    }
+}