Effects factory: Implement xml config loading

For treble, files shared between vendor and system must have a schema.
As .conf did not support such feature, transition to an xml based
configuration file.

This new format has several changes:
 - library path are now relative to the standard effect directories
 - effect parameters are no longer supported (nobody used them)

Test: Check that the dumpsys is similar than with the legacy formal
Bug: 37492580
Change-Id: I01d79d016ec0bf52fbaf073f4452862214ab9344
Signed-off-by: Kevin Rocard <krocard@google.com>
diff --git a/media/libeffects/factory/Android.bp b/media/libeffects/factory/Android.bp
index 5ef875c..9c6b5b6 100644
--- a/media/libeffects/factory/Android.bp
+++ b/media/libeffects/factory/Android.bp
@@ -12,12 +12,15 @@
     vendor: true,
     srcs: ["EffectsFactory.c",
            "EffectsConfigLoader.c",
-           "EffectsFactoryState.c"],
+           "EffectsFactoryState.c",
+           "EffectsXmlConfigLoader.cpp",
+    ],
 
     shared_libs: [
         "libcutils",
         "liblog",
         "libdl",
+        "libeffectsconfig",
     ],
     cflags: ["-fvisibility=hidden"],
 
diff --git a/media/libeffects/factory/EffectsConfigLoader.h b/media/libeffects/factory/EffectsConfigLoader.h
index ac0d7ab..1dbf638 100644
--- a/media/libeffects/factory/EffectsConfigLoader.h
+++ b/media/libeffects/factory/EffectsConfigLoader.h
@@ -17,8 +17,20 @@
 #ifndef ANDROID_EFFECTSCONFIGLOADER_H
 #define ANDROID_EFFECTSCONFIGLOADER_H
 
+#include "EffectsFactoryState.h"
+
+#ifdef  __cplusplus
+extern "C" {
+#endif
+
+
 /** Parses the platform effect configuration
  * and stores its content in the global EffectFactoryState. */
 int loadEffectConfig();
 
+
+#ifdef  __cplusplus
+} // extern "C"
+#endif
+
 #endif  // ANDROID_EFFECTSCONFIGLOADER_H
diff --git a/media/libeffects/factory/EffectsFactory.c b/media/libeffects/factory/EffectsFactory.c
index a8a0fe6..a2fdcc5 100644
--- a/media/libeffects/factory/EffectsFactory.c
+++ b/media/libeffects/factory/EffectsFactory.c
@@ -27,6 +27,7 @@
 #include <media/EffectsFactoryApi.h>
 
 #include "EffectsConfigLoader.h"
+#include "EffectsXmlConfigLoader.h"
 #include "EffectsFactoryState.h"
 
 #include "EffectsFactory.h"
@@ -436,7 +437,13 @@
     if (ignoreFxConfFiles) {
         ALOGI("Audio effects in configuration files will be ignored");
     } else {
-        loadEffectConfig();
+        ssize_t loadResult = loadXmlEffectConfig(NULL);
+        if (loadResult < 0) {
+            ALOGW("Failed to load XML effect configuration, fallback to .conf");
+            loadEffectConfig();
+        } else if (loadResult > 0) {
+            ALOGE("Effect config is partially invalid, skipped %zd elements", loadResult);
+        }
     }
 
     updateNumEffects();
diff --git a/media/libeffects/factory/EffectsFactoryState.h b/media/libeffects/factory/EffectsFactoryState.h
index 697cf76..aef945e 100644
--- a/media/libeffects/factory/EffectsFactoryState.h
+++ b/media/libeffects/factory/EffectsFactoryState.h
@@ -19,6 +19,10 @@
 
 #include "EffectsFactory.h"
 
+#if __cplusplus
+extern "C" {
+#endif
+
 /** @file Contains the state shared with configuration loader of the Effect factory.
  *        This global state should probably be refactor in a structure
  *        provided by the config loader on EffectsFactory init.
@@ -26,7 +30,8 @@
  */
 
 extern list_elem_t *gLibraryList; // list of lib_entry_t: all currently loaded libraries
-extern list_elem_t *gSkippedEffects; // list of effects skipped because of duplicate uuid
+// list of effects skipped because of duplicate uuid or invalid version
+extern list_elem_t *gSkippedEffects;
 // list of effect_descriptor and list of sub effects : all currently loaded
 // It does not contain effects without sub effects.
 extern list_sub_elem_t *gSubEffectList;
@@ -48,4 +53,8 @@
 /** Used for debuging. */
 void dumpEffectDescriptor(effect_descriptor_t *desc, char *str, size_t len, int indent);
 
+#if __cplusplus
+} // extern "C"
+#endif
+
 #endif // ANDROID_EFFECTSFACTORYSTATE_H_
diff --git a/media/libeffects/factory/EffectsXmlConfigLoader.cpp b/media/libeffects/factory/EffectsXmlConfigLoader.cpp
new file mode 100644
index 0000000..6b12571
--- /dev/null
+++ b/media/libeffects/factory/EffectsXmlConfigLoader.cpp
@@ -0,0 +1,335 @@
+/*
+ * Copyright (C) 2017 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 "EffectsFactoryConfigLoader"
+//#define LOG_NDEBUG 0
+
+#include <dlfcn.h>
+#include <set>
+#include <stdlib.h>
+#include <string>
+
+#include <log/log.h>
+
+#include <media/EffectsConfig.h>
+
+#include "EffectsConfigLoader.h"
+#include "EffectsFactoryState.h"
+#include "EffectsXmlConfigLoader.h"
+
+namespace android {
+
+using namespace effectsConfig;
+
+/////////////////////////////////////////////////
+//      Local functions
+/////////////////////////////////////////////////
+
+namespace {
+
+/** Similarly to dlopen, looks for the provided path in LD_EFFECT_LIBRARY_PATH.
+ * @return true if the library is found and set resolvedPath to its absolute path.
+ *         false if not found
+ */
+bool resolveLibrary(const std::string& path, std::string* resolvedPath) {
+    for (auto* libraryDirectory : LD_EFFECT_LIBRARY_PATH) {
+        std::string candidatePath = std::string(libraryDirectory) + '/' + path;
+        if (access(candidatePath.c_str(), R_OK) == 0) {
+            *resolvedPath = std::move(candidatePath);
+            return true;
+        }
+    }
+    return false;
+}
+
+/** Loads a library given its relative path and stores the result in libEntry.
+ * @return true on success with libEntry's path, handle and desc filled
+ *         false on success with libEntry's path filled with the path of the failed lib
+ * The caller MUST free the resources path (free) and handle (dlclose) if filled.
+ */
+bool loadLibrary(const char* relativePath, lib_entry_t* libEntry) noexcept {
+
+    std::string absolutePath;
+    if (!resolveLibrary(relativePath, &absolutePath)) {
+        ALOGE("Could not find library in effect directories: %s", relativePath);
+        libEntry->path = strdup(relativePath);
+        return false;
+    }
+    const char* path = absolutePath.c_str();
+    libEntry->path = strdup(path);
+
+    // Make sure the lib is closed on early return
+    std::unique_ptr<void, decltype(dlclose)*> libHandle(dlopen(path, RTLD_NOW),
+                                                       dlclose);
+    if (libHandle == nullptr) {
+        ALOGE("Could not dlopen library %s: %s", path, dlerror());
+        return false;
+    }
+
+    auto* description = static_cast<audio_effect_library_t*>(
+          dlsym(libHandle.get(), AUDIO_EFFECT_LIBRARY_INFO_SYM_AS_STR));
+    if (description == nullptr) {
+        ALOGE("Invalid effect library, failed not find symbol '%s' in %s: %s",
+              AUDIO_EFFECT_LIBRARY_INFO_SYM_AS_STR, path, dlerror());
+        return false;
+    }
+
+    if (description->tag != AUDIO_EFFECT_LIBRARY_TAG) {
+        ALOGE("Bad tag %#08x in description structure, expected %#08x for library %s",
+              description->tag, AUDIO_EFFECT_LIBRARY_TAG, path);
+        return false;
+    }
+
+    uint32_t majorVersion = EFFECT_API_VERSION_MAJOR(description->version);
+    uint32_t expectedMajorVersion = EFFECT_API_VERSION_MAJOR(EFFECT_LIBRARY_API_VERSION);
+    if (majorVersion != expectedMajorVersion) {
+        ALOGE("Unsupported major version %#08x, expected %#08x for library %s",
+              majorVersion, expectedMajorVersion, path);
+        return false;
+    }
+
+    libEntry->handle = libHandle.release();
+    libEntry->desc = description;
+    return true;
+}
+
+/** Because the structures will be destroyed by c code, using new to allocate shared structure
+ * is not possible. Provide a equivalent of unique_ptr for malloc/freed structure to make sure
+ * they are not leaked in the c++ code.
+ @{ */
+struct FreeDeleter {
+    void operator()(void* p) {
+        free(p);
+    }
+};
+/** unique_ptr for object created with malloc. */
+template <class T>
+using UniqueCPtr = std::unique_ptr<T, FreeDeleter>;
+
+/** c version of std::make_unique. Uses malloc and free. */
+template <class T>
+UniqueCPtr<T> makeUniqueC() {
+    T* ptr = new (malloc(sizeof(T))) T{}; // Use placement new to initialize the structure
+    return UniqueCPtr<T>{ptr};
+}
+
+/** @} */
+
+/** Push an not owned element in a list_elem link list with an optional lock. */
+template <class T, class ListElem>
+void listPush(T* object, ListElem** list, pthread_mutex_t* mutex = nullptr) noexcept {
+    auto listElem = makeUniqueC<ListElem>();
+    listElem->object = object;
+    if (mutex != nullptr) {
+        pthread_mutex_lock(mutex);
+    }
+    listElem->next = *list;
+    *list = listElem.release();
+    if (mutex != nullptr) {
+        pthread_mutex_unlock(mutex);
+    }
+}
+
+/** Push an owned element in a list_elem link list with an optional lock. */
+template <class T, class ListElem>
+void listPush(UniqueCPtr<T>&& object, ListElem** list, pthread_mutex_t* mutex = nullptr) noexcept {
+    listPush(object.release(), list, mutex);
+}
+
+size_t loadLibraries(const effectsConfig::Libraries& libs,
+                     list_elem_t** libList, pthread_mutex_t* libListLock,
+                     list_elem_t** libFailedList)
+{
+    size_t nbSkippedElement = 0;
+    for (auto& library : libs) {
+
+        // Construct a lib entry
+        auto libEntry = makeUniqueC<lib_entry_t>();
+        libEntry->name = strdup(library.name.c_str());
+        libEntry->effects = nullptr;
+        pthread_mutex_init(&libEntry->lock, nullptr);
+
+        if (!loadLibrary(library.path.c_str(), libEntry.get())) {
+            // Register library load failure
+            listPush(std::move(libEntry), libFailedList);
+            ++nbSkippedElement;
+            continue;
+        }
+        listPush(std::move(libEntry), libList, libListLock);
+    }
+    return nbSkippedElement;
+}
+
+/** Find a library with the given name in the given list. */
+lib_entry_t* findLibrary(const char* name, list_elem_t* list) {
+
+    while (list != nullptr) {
+        auto* object = static_cast<lib_entry_t*>(list->object);
+        if (strcmp(object->name, name) == 0) {
+            return object;
+        }
+        list = list->next;
+    }
+    return nullptr;
+}
+
+struct UuidStr {
+    /** Length of an uuid represented as string. @TODO: use a constant instead of 40. */
+    char buff[40];
+};
+
+/** @return a string representing the provided uuid.
+ * By not providing an output buffer, it is implicitly created in the caller context.
+ * In such case the return pointer has the same lifetime as the expression containing uuidToString()
+ */
+char* uuidToString(const effect_uuid_t& uuid, UuidStr&& str = {}) {
+    uuidToString(&uuid, str.buff, sizeof(str.buff));
+    return str.buff;
+}
+
+struct LoadEffectResult {
+    /** true if the effect is usable (aka, existing lib, desc, right version, unique uuid) */
+    bool success = false;
+    /** Set if the effect lib was found*/
+    lib_entry_t* lib = nullptr;
+    //* Set if the description was successfuly retrieved from the lib */
+    UniqueCPtr<effect_descriptor_t> effectDesc;
+};
+
+LoadEffectResult loadEffect(const EffectImpl& effect, const std::string& name,
+                            list_elem_t* libList) {
+    LoadEffectResult result;
+
+    // Find the effect library
+    result.lib = findLibrary(effect.library->name.c_str(), libList);
+    if (result.lib == nullptr) {
+        ALOGE("Could not find library %s to load effect %s",
+              effect.library->name.c_str(), name.c_str());
+        return result;
+    }
+
+    result.effectDesc = makeUniqueC<effect_descriptor_t>();
+
+    // Get the effect descriptor
+    if (result.lib->desc->get_descriptor(&effect.uuid, result.effectDesc.get()) != 0) {
+        ALOGE("Error querying effect %s on lib %s",
+              uuidToString(effect.uuid), result.lib->name);
+        result.effectDesc.reset();
+        return result;
+    }
+
+    // Dump effect for debug
+#if (LOG_NDEBUG==0)
+    char s[512];
+    dumpEffectDescriptor(result.effectDesc.get(), s, sizeof(s), 0 /* indent */);
+    ALOGV("loadEffect() read descriptor %p:%s", result.effectDesc.get(), s);
+#endif
+
+    // Check effect is supported
+    uint32_t expectedMajorVersion = EFFECT_API_VERSION_MAJOR(EFFECT_CONTROL_API_VERSION);
+    if (EFFECT_API_VERSION_MAJOR(result.effectDesc->apiVersion) != expectedMajorVersion) {
+        ALOGE("Bad API version %#08x for effect %s in lib %s, expected major %#08x",
+              result.effectDesc->apiVersion, name.c_str(), result.lib->name, expectedMajorVersion);
+        return result;
+    }
+
+    lib_entry_t *_;
+    if (findEffect(nullptr, &effect.uuid, &_, nullptr) == 0) {
+        ALOGE("Effect %s uuid %s already exist", uuidToString(effect.uuid), name.c_str());
+        return result;
+    }
+
+    result.success = true;
+    return result;
+}
+
+size_t loadEffects(const Effects& effects, list_elem_t* libList, list_elem_t** skippedEffects,
+                   list_sub_elem_t** subEffectList) {
+    size_t nbSkippedElement = 0;
+
+    for (auto& effect : effects) {
+
+        auto effectLoadResult = loadEffect(effect, effect.name, libList);
+        if (!effectLoadResult.success) {
+            if (effectLoadResult.effectDesc != nullptr) {
+                listPush(std::move(effectLoadResult.effectDesc), skippedEffects);
+            }
+            ++nbSkippedElement;
+            continue;
+        }
+
+        if (effect.isProxy) {
+            auto swEffectLoadResult = loadEffect(effect.libSw, effect.name + " libsw", libList);
+            auto hwEffectLoadResult = loadEffect(effect.libHw, effect.name + " libhw", libList);
+            if (!swEffectLoadResult.success || !hwEffectLoadResult.success) {
+                // Push the main effect in the skipped list even if only a subeffect is invalid
+                // as the main effect is not usable without its subeffects.
+                listPush(std::move(effectLoadResult.effectDesc), skippedEffects);
+                ++nbSkippedElement;
+                continue;
+            }
+            listPush(&effectLoadResult.effectDesc, subEffectList);
+
+            // Since we return a dummy descriptor for the proxy during
+            // get_descriptor call, we replace it with the corresponding
+            // sw effect descriptor, but keep the Proxy UUID
+            *effectLoadResult.effectDesc = *swEffectLoadResult.effectDesc;
+            effectLoadResult.effectDesc->uuid = effect.uuid;
+
+            effectLoadResult.effectDesc->flags |= EFFECT_FLAG_OFFLOAD_SUPPORTED;
+
+            auto registerSubEffect = [subEffectList](auto&& result) {
+                auto entry = makeUniqueC<sub_effect_entry_t>();
+                entry->object = result.effectDesc.release();
+                // lib_entry_t is stored since the sub effects are not linked to the library
+                entry->lib = result.lib;
+                listPush(std::move(entry), &(*subEffectList)->sub_elem);
+            };
+            registerSubEffect(std::move(swEffectLoadResult));
+            registerSubEffect(std::move(hwEffectLoadResult));
+        }
+
+        listPush(std::move(effectLoadResult.effectDesc), &effectLoadResult.lib->effects);
+    }
+    return nbSkippedElement;
+}
+
+} // namespace
+
+/////////////////////////////////////////////////
+//      Interface function
+/////////////////////////////////////////////////
+
+extern "C" ssize_t loadXmlEffectConfig(const char* path)
+{
+    using effectsConfig::parse;
+    auto result = path ? parse(path) : parse();
+    if (result.parsedConfig == nullptr) {
+        ALOGE("Failed to parse XML configuration file");
+        return -1;
+    }
+    result.nbSkippedElement += loadLibraries(result.parsedConfig->libraries,
+                                             &gLibraryList, &gLibLock, &gLibraryFailedList) +
+                               loadEffects(result.parsedConfig->effects, gLibraryList,
+                                           &gSkippedEffects, &gSubEffectList);
+
+    ALOGE_IF(result.nbSkippedElement != 0, "%zu errors during loading of configuration: %s",
+             result.nbSkippedElement, path ?: effectsConfig::DEFAULT_PATH);
+
+    return result.nbSkippedElement;
+}
+
+} // namespace android
diff --git a/media/libeffects/factory/EffectsXmlConfigLoader.h b/media/libeffects/factory/EffectsXmlConfigLoader.h
new file mode 100644
index 0000000..9bf4d18
--- /dev/null
+++ b/media/libeffects/factory/EffectsXmlConfigLoader.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#ifndef ANDROID_EFFECTSXMLCONFIGLOADER_H
+#define ANDROID_EFFECTSXMLCONFIGLOADER_H
+
+#include <unistd.h>
+
+#if __cplusplus
+extern "C" {
+#endif
+
+/** Parses the platform effect xml configuration and stores its content in EffectFactoryState.
+ * @param[in] path of the configuration file or NULL to load the default one
+ * @return -1 on unrecoverable error (eg: no configuration file)
+ *         0 on success
+ *         the number of invalid elements (lib & effect) skipped if the config is partially invalid
+ */
+ssize_t loadXmlEffectConfig(const char* path);
+
+#if __cplusplus
+} // extern "C"
+#endif
+
+#endif  // ANDROID_EFFECTSXMLCONFIGLOADER_H