Pass models to soundtrigger middleware with shared memory

This both avoids the need to make extra copied for the models and
allows exceeding the parcel size limits.

Bug: 150100907
Test: Manual testing of basic sound trigger functionality
Change-Id: Ic4c5c1a9de3e29b1b6fa82442254e1afe7daec19
diff --git a/core/java/android/hardware/soundtrigger/ConversionUtil.java b/core/java/android/hardware/soundtrigger/ConversionUtil.java
index dbf33ca..3d763e6 100644
--- a/core/java/android/hardware/soundtrigger/ConversionUtil.java
+++ b/core/java/android/hardware/soundtrigger/ConversionUtil.java
@@ -32,7 +32,11 @@
 import android.media.soundtrigger_middleware.SoundModel;
 import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor;
 import android.media.soundtrigger_middleware.SoundTriggerModuleProperties;
+import android.os.SharedMemory;
+import android.system.ErrnoException;
 
+import java.io.FileDescriptor;
+import java.nio.ByteBuffer;
 import java.util.Arrays;
 import java.util.UUID;
 
@@ -105,7 +109,8 @@
         aidlModel.type = apiModel.type;
         aidlModel.uuid = api2aidlUuid(apiModel.uuid);
         aidlModel.vendorUuid = api2aidlUuid(apiModel.vendorUuid);
-        aidlModel.data = Arrays.copyOf(apiModel.data, apiModel.data.length);
+        aidlModel.data = byteArrayToSharedMemory(apiModel.data, "SoundTrigger SoundModel");
+        aidlModel.dataSize = apiModel.data.length;
         return aidlModel;
     }
 
@@ -352,4 +357,20 @@
         }
         return result;
     }
+
+    private static @Nullable FileDescriptor byteArrayToSharedMemory(byte[] data, String name) {
+        if (data.length == 0) {
+            return null;
+        }
+
+        try {
+            SharedMemory shmem = SharedMemory.create(name != null ? name : "", data.length);
+            ByteBuffer buffer = shmem.mapReadWrite();
+            buffer.put(data);
+            shmem.unmap(buffer);
+            return shmem.getFileDescriptor();
+        } catch (ErrnoException e) {
+            throw new RuntimeException(e);
+        }
+    }
 }
diff --git a/core/java/android/os/HidlMemoryUtil.java b/core/java/android/os/HidlMemoryUtil.java
index b08822dd..4252fe3 100644
--- a/core/java/android/os/HidlMemoryUtil.java
+++ b/core/java/android/os/HidlMemoryUtil.java
@@ -21,14 +21,13 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SuppressLint;
-import android.annotation.SystemApi;
-import android.annotation.TestApi;
 import android.system.ErrnoException;
 import android.system.Os;
 import android.util.Log;
 
 import com.android.internal.util.Preconditions;
 
+import java.io.FileDescriptor;
 import java.nio.ByteBuffer;
 import java.nio.DirectByteBuffer;
 import java.util.ArrayList;
@@ -82,8 +81,7 @@
             ByteBuffer buffer = shmem.mapReadWrite();
             buffer.put(input);
             shmem.unmap(buffer);
-            NativeHandle handle = new NativeHandle(shmem.getFileDescriptor(), true);
-            return new HidlMemory("ashmem", input.length, handle);
+            return sharedMemoryToHidlMemory(shmem);
         } catch (ErrnoException e) {
             throw new RuntimeException(e);
         }
@@ -128,8 +126,7 @@
                 buffer.put(b);
             }
             shmem.unmap(buffer);
-            NativeHandle handle = new NativeHandle(shmem.getFileDescriptor(), true);
-            return new HidlMemory("ashmem", input.size(), handle);
+            return sharedMemoryToHidlMemory(shmem);
         } catch (ErrnoException e) {
             throw new RuntimeException(e);
         }
@@ -189,6 +186,38 @@
         return result;
     }
 
+    /**
+     * Converts a SharedMemory to a HidlMemory without copying.
+     *
+     * @param shmem The shared memory object. Null means "empty" and will still result in a non-null
+     *              return value.
+     * @return The HidlMemory instance.
+     */
+    @NonNull public static HidlMemory sharedMemoryToHidlMemory(@Nullable SharedMemory shmem) {
+        if (shmem == null) {
+            return new HidlMemory("ashmem", 0, null);
+        }
+        return fileDescriptorToHidlMemory(shmem.getFileDescriptor(), shmem.getSize());
+    }
+
+    /**
+     * Converts a FileDescriptor to a HidlMemory without copying.
+     *
+     * @param fd   The FileDescriptor object. Null is allowed if size is 0 and will still result in
+     *             a non-null return value.
+     * @param size The size of the memory buffer.
+     * @return The HidlMemory instance.
+     */
+    @NonNull public static HidlMemory fileDescriptorToHidlMemory(@Nullable FileDescriptor fd,
+            int size) {
+        Preconditions.checkArgument(fd != null || size == 0);
+        if (fd == null) {
+            return new HidlMemory("ashmem", 0, null);
+        }
+        NativeHandle handle = new NativeHandle(fd, true);
+        return new HidlMemory("ashmem", size, handle);
+    }
+
     private static ByteBuffer getBuffer(@NonNull HidlMemory mem) {
         try {
             final int size = (int) mem.getSize();
diff --git a/media/java/android/media/soundtrigger_middleware/SoundModel.aidl b/media/java/android/media/soundtrigger_middleware/SoundModel.aidl
index fba1ee50..81d8291 100644
--- a/media/java/android/media/soundtrigger_middleware/SoundModel.aidl
+++ b/media/java/android/media/soundtrigger_middleware/SoundModel.aidl
@@ -32,5 +32,7 @@
      * was build for */
     String vendorUuid;
     /** Opaque data transparent to Android framework */
-    byte[] data;
+    FileDescriptor data;
+    /** Size of the above data, in bytes. */
+    int dataSize;
 }
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/ConversionUtil.java b/services/core/java/com/android/server/soundtrigger_middleware/ConversionUtil.java
index a641f06..1d31285 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/ConversionUtil.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/ConversionUtil.java
@@ -196,8 +196,8 @@
         hidlModel.header.type = aidl2hidlSoundModelType(aidlModel.type);
         hidlModel.header.uuid = aidl2hidlUuid(aidlModel.uuid);
         hidlModel.header.vendorUuid = aidl2hidlUuid(aidlModel.vendorUuid);
-        hidlModel.data = HidlMemoryUtil.byteArrayToHidlMemory(aidlModel.data,
-                "SoundTrigger SoundModel");
+        hidlModel.data = HidlMemoryUtil.fileDescriptorToHidlMemory(aidlModel.data,
+                aidlModel.dataSize);
         return hidlModel;
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java b/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java
index c56034a..06b5fe4 100644
--- a/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java
@@ -60,6 +60,8 @@
 import android.os.IHwBinder;
 import android.os.IHwInterface;
 import android.os.RemoteException;
+import android.os.SharedMemory;
+import android.system.ErrnoException;
 import android.util.Pair;
 
 import org.junit.Before;
@@ -71,6 +73,9 @@
 import org.mockito.Mockito;
 import org.mockito.stubbing.Answer;
 
+import java.io.FileDescriptor;
+import java.nio.ByteBuffer;
+
 @RunWith(Parameterized.class)
 public class SoundTriggerMiddlewareImplTest {
     private static final String TAG = "SoundTriggerMiddlewareImplTest";
@@ -104,12 +109,25 @@
         return createSoundModel(SoundModelType.GENERIC);
     }
 
+    private static FileDescriptor byteArrayToFileDescriptor(byte[] data) {
+        try {
+            SharedMemory shmem = SharedMemory.create("", data.length);
+            ByteBuffer buffer = shmem.mapReadWrite();
+            buffer.put(data);
+            return shmem.getFileDescriptor();
+        } catch (ErrnoException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
     private static SoundModel createSoundModel(int type) {
         SoundModel model = new SoundModel();
         model.type = type;
         model.uuid = "12345678-2345-3456-4567-abcdef987654";
         model.vendorUuid = "87654321-5432-6543-7654-456789fedcba";
-        model.data = new byte[]{91, 92, 93, 94, 95};
+        byte[] data = new byte[]{91, 92, 93, 94, 95};
+        model.data = byteArrayToFileDescriptor(data);
+        model.dataSize = data.length;
         return model;
     }