Add Dynamic Source Effects

Adds support for adding/removing effects to the list of
default effects for a given input source type.

Bug: 78527120
Test: Builds, manually tested with app that adds source effects,
additionally tested by Android Things integration tests making use
of this API.

Change-Id: Ibac324567e4df2888dccabd1e824c4cc51ad3644
diff --git a/media/java/android/media/audiofx/DefaultEffect.java b/media/java/android/media/audiofx/DefaultEffect.java
index a919868..ce087ad 100644
--- a/media/java/android/media/audiofx/DefaultEffect.java
+++ b/media/java/android/media/audiofx/DefaultEffect.java
@@ -24,6 +24,7 @@
  * <p>Applications should not use the DefaultEffect class directly but one of its derived classes
  * to control specific types of defaults:
  * <ul>
+ *   <li> {@link android.media.audiofx.SourceDefaultEffect}</li>
  *   <li> {@link android.media.audiofx.StreamDefaultEffect}</li>
  * </ul>
  * <p>Creating a DefaultEffect object will register the corresponding effect engine as a default
diff --git a/media/java/android/media/audiofx/SourceDefaultEffect.java b/media/java/android/media/audiofx/SourceDefaultEffect.java
new file mode 100644
index 0000000..d7a292e
--- /dev/null
+++ b/media/java/android/media/audiofx/SourceDefaultEffect.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2018 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.media.audiofx;
+
+import android.annotation.RequiresPermission;
+import android.app.ActivityThread;
+import android.util.Log;
+import java.util.UUID;
+
+/**
+ * SourceDefaultEffect is a default effect that attaches automatically to all AudioRecord and
+ * MediaRecorder instances of a given source type.
+ * <p>see {@link android.media.audiofx.DefaultEffect} class for more details on default effects.
+ * @hide
+ */
+
+public class SourceDefaultEffect extends DefaultEffect {
+    static {
+        System.loadLibrary("audioeffect_jni");
+    }
+
+    private final static String TAG = "SourceDefaultEffect-JAVA";
+
+    /**
+     * Class constructor.
+     *
+     * @param type type of effect engine to be default. This parameter is ignored if uuid is set,
+     *             and can be set to {@link android.media.audiofx.AudioEffect#EFFECT_TYPE_NULL}
+     *             in that case.
+     * @param uuid unique identifier of a particular effect implementation to be default. This
+     *             parameter can be set to
+     *             {@link android.media.audiofx.AudioEffect#EFFECT_TYPE_NULL}, in which case only
+     *             the type will be used to select the effect.
+     * @param priority the priority level requested by the application for controlling the effect
+     *             engine. As the same engine can be shared by several applications, this parameter
+     *             indicates how much the requesting application needs control of effect parameters.
+     *             The normal priority is 0, above normal is a positive number, below normal a
+     *             negative number.
+     * @param source a MediaRecorder.AudioSource.* constant from
+     *             {@link android.media.MediaRecorder.AudioSource} indicating
+     *             what sources the given effect should attach to by default. Note that similar
+     *             sources may share defaults.
+     *
+     * @throws java.lang.IllegalArgumentException
+     * @throws java.lang.UnsupportedOperationException
+     * @throws java.lang.RuntimeException
+     */
+    @RequiresPermission(value = android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS,
+                        conditional = true)  // Android Things uses an alternate permission.
+    public SourceDefaultEffect(UUID type, UUID uuid, int priority, int source) {
+        int[] id = new int[1];
+        int initResult = native_setup(type.toString(),
+                                      uuid.toString(),
+                                      priority,
+                                      source,
+                                      ActivityThread.currentOpPackageName(),
+                                      id);
+        if (initResult != AudioEffect.SUCCESS) {
+            Log.e(TAG, "Error code " + initResult + " when initializing SourceDefaultEffect");
+            switch (initResult) {
+                case AudioEffect.ERROR_BAD_VALUE:
+                    throw (new IllegalArgumentException(
+                            "Source, type uuid, or implementation uuid not supported."));
+                case AudioEffect.ERROR_INVALID_OPERATION:
+                    throw (new UnsupportedOperationException(
+                            "Effect library not loaded"));
+                default:
+                    throw (new RuntimeException(
+                            "Cannot initialize effect engine for type: " + type
+                            + " Error: " + initResult));
+            }
+        }
+
+        mId = id[0];
+    }
+
+
+    /**
+     * Releases the native SourceDefaultEffect resources. It is a good practice to
+     * release the default effect when done with use as control can be returned to
+     * other applications or the native resources released.
+     */
+    public void release() {
+        native_release(mId);
+    }
+
+    @Override
+    protected void finalize() {
+        release();
+    }
+
+    // ---------------------------------------------------------
+    // Native methods called from the Java side
+    // --------------------
+
+    private native final int native_setup(String type,
+                                          String uuid,
+                                          int priority,
+                                          int source,
+                                          String opPackageName,
+                                          int[] id);
+
+    private native final void native_release(int id);
+}
diff --git a/media/jni/audioeffect/Android.bp b/media/jni/audioeffect/Android.bp
index 0063c11..09c546a 100644
--- a/media/jni/audioeffect/Android.bp
+++ b/media/jni/audioeffect/Android.bp
@@ -3,6 +3,7 @@
 
     srcs: [
         "android_media_AudioEffect.cpp",
+        "android_media_SourceDefaultEffect.cpp",
         "android_media_StreamDefaultEffect.cpp",
         "android_media_Visualizer.cpp",
     ],
diff --git a/media/jni/audioeffect/android_media_AudioEffect.cpp b/media/jni/audioeffect/android_media_AudioEffect.cpp
index d3ba9f2..8c9025b 100644
--- a/media/jni/audioeffect/android_media_AudioEffect.cpp
+++ b/media/jni/audioeffect/android_media_AudioEffect.cpp
@@ -907,6 +907,7 @@
 
 // ----------------------------------------------------------------------------
 
+extern int register_android_media_SourceDefaultEffect(JNIEnv *env);
 extern int register_android_media_StreamDefaultEffect(JNIEnv *env);
 extern int register_android_media_visualizer(JNIEnv *env);
 
@@ -932,6 +933,11 @@
         goto bail;
     }
 
+    if (register_android_media_SourceDefaultEffect(env) < 0) {
+        ALOGE("ERROR: SourceDefaultEffect native registration failed\n");
+        goto bail;
+    }
+
     if (register_android_media_StreamDefaultEffect(env) < 0) {
         ALOGE("ERROR: StreamDefaultEffect native registration failed\n");
         goto bail;
diff --git a/media/jni/audioeffect/android_media_SourceDefaultEffect.cpp b/media/jni/audioeffect/android_media_SourceDefaultEffect.cpp
new file mode 100644
index 0000000..d244bcb7
--- /dev/null
+++ b/media/jni/audioeffect/android_media_SourceDefaultEffect.cpp
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2018 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_NDEBUG 0
+#define LOG_TAG "SourceDefaultEffect-JNI"
+
+#include <utils/Errors.h>
+#include <utils/Log.h>
+#include <jni.h>
+#include <nativehelper/JNIHelp.h>
+#include <android_runtime/AndroidRuntime.h>
+#include "media/AudioEffect.h"
+
+#include <nativehelper/ScopedUtfChars.h>
+
+#include "android_media_AudioEffect.h"
+
+using namespace android;
+
+static const char* const kClassPathName = "android/media/audiofx/SourceDefaultEffect";
+
+static jint android_media_SourceDefaultEffect_native_setup(JNIEnv *env,
+                                                           jobject /*thiz*/,
+                                                           jstring type,
+                                                           jstring uuid,
+                                                           jint priority,
+                                                           jint source,
+                                                           jstring opPackageName,
+                                                           jintArray jId)
+{
+    ALOGV("android_media_SourceDefaultEffect_native_setup");
+    status_t lStatus = NO_ERROR;
+    jint* nId = NULL;
+    const char *typeStr = NULL;
+    const char *uuidStr = NULL;
+
+    ScopedUtfChars opPackageNameStr(env, opPackageName);
+
+    if (type != NULL) {
+        typeStr = env->GetStringUTFChars(type, NULL);
+        if (typeStr == NULL) {  // Out of memory
+            lStatus = NO_MEMORY;
+            jniThrowException(env, "java/lang/RuntimeException", "Out of memory");
+            goto setup_exit;
+        }
+    }
+
+    if (uuid != NULL) {
+        uuidStr = env->GetStringUTFChars(uuid, NULL);
+        if (uuidStr == NULL) {  // Out of memory
+            lStatus = NO_MEMORY;
+            jniThrowException(env, "java/lang/RuntimeException", "Out of memory");
+            goto setup_exit;
+        }
+    }
+
+    if (typeStr == NULL && uuidStr == NULL) {
+        lStatus = BAD_VALUE;
+        goto setup_exit;
+    }
+
+    nId = reinterpret_cast<jint *>(env->GetPrimitiveArrayCritical(jId, NULL));
+    if (nId == NULL) {
+        ALOGE("setup: Error retrieving id pointer");
+        lStatus = BAD_VALUE;
+        goto setup_exit;
+    }
+
+    // create the native SourceDefaultEffect.
+    audio_unique_id_t id;
+    lStatus = AudioEffect::addSourceDefaultEffect(typeStr,
+                                                  String16(opPackageNameStr.c_str()),
+                                                  uuidStr,
+                                                  priority,
+                                                  static_cast<audio_source_t>(source),
+                                                  &id);
+    if (lStatus != NO_ERROR) {
+        ALOGE("setup: Error adding SourceDefaultEffect");
+        goto setup_exit;
+    }
+
+    nId[0] = static_cast<jint>(id);
+
+setup_exit:
+    // Final cleanup and return.
+
+    if (nId != NULL) {
+        env->ReleasePrimitiveArrayCritical(jId, nId, 0);
+        nId = NULL;
+    }
+
+    if (uuidStr != NULL) {
+        env->ReleaseStringUTFChars(uuid, uuidStr);
+        uuidStr = NULL;
+    }
+
+    if (typeStr != NULL) {
+        env->ReleaseStringUTFChars(type, typeStr);
+        typeStr = NULL;
+    }
+
+    return AudioEffectJni::translateNativeErrorToJava(lStatus);
+}
+
+static void android_media_SourceDefaultEffect_native_release(JNIEnv */*env*/,
+                                                             jobject /*thiz*/,
+                                                             jint id) {
+    status_t lStatus = AudioEffect::removeSourceDefaultEffect(id);
+    if (lStatus != NO_ERROR) {
+        ALOGW("Error releasing SourceDefaultEffect: %d", lStatus);
+    }
+}
+
+// ----------------------------------------------------------------------------
+
+// Dalvik VM type signatures
+static const JNINativeMethod gMethods[] = {
+    {"native_setup",         "(Ljava/lang/String;Ljava/lang/String;IILjava/lang/String;[I)I",
+                                         (void *)android_media_SourceDefaultEffect_native_setup},
+    {"native_release",       "(I)V",      (void *)android_media_SourceDefaultEffect_native_release},
+};
+
+
+// ----------------------------------------------------------------------------
+
+int register_android_media_SourceDefaultEffect(JNIEnv *env)
+{
+    return AndroidRuntime::registerNativeMethods(env, kClassPathName, gMethods, NELEM(gMethods));
+}