Add jni interface for the ArmNN Delegate

 * adds a version script to hide away all symbols that
   are not required to use the interface
 * the main purpose of the jni interface is to enable the
   delegate to be used in android apps
 * Add static building to patch - see patch 7 for JNI patch only

Signed-off-by: Jan Eilers <jan.eilers@arm.com>
Signed-off-by: Keith Davis <keith.davis@arm.com>
Change-Id: I9bb2d698b5fdb0d1b30cf79e6f19746310cd61b2
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 1fcadb0..c112acc 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -60,13 +60,6 @@
 add_subdirectory(src/armnnDeserializer)
 add_subdirectory(src/armnnTestUtils)
 
-
-if (BUILD_ARMNN_TFLITE_DELEGATE)
-    set(ARMNN_SUB_PROJECT ON)
-    add_subdirectory(delegate)
-    add_definitions(-DARMNN_TF_LITE_DELEGATE)
-endif()
-
 if (BUILD_TESTS)
     add_subdirectory(tests)
 endif()
@@ -443,20 +436,35 @@
     list(APPEND armnn_sources $<TARGET_OBJECTS:${lib}>)
 endforeach()
 
+# The delegate needs to be placed after armnnLibraries has been fully populated. The armnn_delegate_jni library
+# requires a static armnn build.
+# Explanation:
+# Because backends are added as object libraries they won't be linked to armnn when building armnn statically.
+# A target that uses a static armnn library has to link to the object libraries in the variable armnnLibraries
+# manually to include all symbols from backends.
+if (BUILD_ARMNN_TFLITE_DELEGATE)
+    set(ARMNN_SUB_PROJECT ON)
+    set(ARMNN_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR})
+    add_subdirectory(delegate)
+    add_definitions(-DARMNN_TF_LITE_DELEGATE)
+endif()
+
 if(BUILD_BARE_METAL)
     add_library_ex(armnn STATIC ${armnn_sources})
 else()
-    add_library_ex(armnn SHARED ${armnn_sources})
+    if (BUILD_SHARED_LIBS)
+        add_library_ex(armnn SHARED ${armnn_sources})
+    else()
+        add_library(armnn ${armnn_sources})
+    endif()
 endif()
 
 target_compile_definitions(armnn PRIVATE "ARMNN_COMPILING_DLL")
 
 # Generate a map file for debug mode only
-if(CMAKE_BUILD_TYPE MATCHES debug)
-    set_property(TARGET armnn APPEND_STRING PROPERTY
+set_property(TARGET armnn APPEND_STRING PROPERTY
         LINK_FLAGS " -Wl,-Map=libarmnnMapFile.map")
-    message(STATUS "Linker will generate mapfile " )
-endif()
+message(STATUS "Linker will generate mapfile ")
 
 target_include_directories(armnn
     PUBLIC
diff --git a/cmake/GlobalConfig.cmake b/cmake/GlobalConfig.cmake
index a5c17d5..d5cdca2 100644
--- a/cmake/GlobalConfig.cmake
+++ b/cmake/GlobalConfig.cmake
@@ -35,6 +35,9 @@
 option(BUILD_ARMNN_TFLITE_DELEGATE "Build the Arm NN TfLite delegate" OFF)
 option(BUILD_MEMORY_STRATEGY_BENCHMARK "Build the MemoryBenchmark" OFF)
 option(BUILD_BARE_METAL "Disable features requiring operating system support" OFF)
+option(BUILD_SHARED_LIBS "Determines if Armnn will be built statically or dynamically.
+                          This is an experimental feature and not fully supported.
+                          Only the ArmNN core and the Delegate can be built statically." ON)
 
 include(SelectLibraryConfigurations)
 
diff --git a/delegate/CMakeLists.txt b/delegate/CMakeLists.txt
index 523214b..01dde4e 100644
--- a/delegate/CMakeLists.txt
+++ b/delegate/CMakeLists.txt
@@ -54,7 +54,7 @@
         src/Unpack.hpp
         src/Transpose.hpp)
 
-add_library(armnnDelegate SHARED ${armnnDelegate_sources})
+add_library(armnnDelegate ${armnnDelegate_sources})
 
 target_include_directories(armnnDelegate
         PUBLIC
@@ -254,6 +254,12 @@
 
 endif()
 
+option(BUILD_DELEGATE_JNI_INTERFACE "Builds a library to allow accessing the Arm NN delegate from Java code.
+                                     This is an experimental feature." Off)
+if(BUILD_DELEGATE_JNI_INTERFACE)
+    add_subdirectory(armnnDelegateJNI)
+endif()
+
 ####################################################
 ## Export targets
 set(armnn_delegate_export_targets)
diff --git a/delegate/armnnDelegateJNI/CMakeLists.txt b/delegate/armnnDelegateJNI/CMakeLists.txt
new file mode 100644
index 0000000..06eb3bf
--- /dev/null
+++ b/delegate/armnnDelegateJNI/CMakeLists.txt
@@ -0,0 +1,62 @@
+#
+# Copyright © 2022 Arm Ltd and Contributors. All rights reserved.
+# SPDX-License-Identifier: MIT
+#
+
+cmake_minimum_required(VERSION 3.7.0)
+
+project("armnn_delegate_jni")
+
+# JNI is needed for jni calls
+find_package(JNI)
+
+list(APPEND jni_delegate_sources
+        src/armnn_delegate_jni.cpp)
+
+# the backends under src/backends extend the list of
+# object libs armnn to include in the build
+# If armnn is a static library (which it should be to make armnn_delegate_jni a stand alone library) then
+# the object libraries of the backends need to be linked manually
+include(${ARMNN_SOURCE_DIR}/src/backends/backends.cmake)
+foreach(lib ${armnnLibraries})
+    message(STATUS "Adding object library dependency to armnn_delegate_jni: ${lib}")
+    list(APPEND jni_delegate_sources $<TARGET_OBJECTS:${lib}>)
+endforeach()
+
+if (JNI_FOUND)
+    message (STATUS "JNI_INCLUDE_DIRS=${JNI_INCLUDE_DIRS}")
+    message (STATUS "JNI_LIBRARIES=${JNI_LIBRARIES}")
+else()
+    message (FATAL_ERROR "JNI library could not be found")
+endif()
+include_directories(${JNI_INCLUDE_DIRS})
+
+add_library(armnn_delegate_jni SHARED ${jni_delegate_sources})
+
+target_include_directories(
+        armnn_delegate_jni
+        PUBLIC
+        ${ARMNN_INCLUDE_DIR}
+        ${ARMNN_DELEGATE_INCLUDE_DIR}
+        ${ARMCOMPUTE_INCLUDE}
+        )
+
+target_link_libraries(
+        armnn_delegate_jni
+        PRIVATE
+        Armnn::Armnn
+        ArmnnDelegate::ArmnnDelegate
+        )
+
+# A version script is used to hide all symbols that are not required to use the jni interface
+# This is mostly required to avoid symbol conflicts between libc++_shared used to compile armnn
+# and an eventual other version used somewhere else: https://developer.android.com/ndk/guides/cpp-support
+#    This also requires to tell the compiler to link to the static version of libc++_shared. This can be accomplished
+#    by adding -DCMAKE_ANDROID_STL_TYPE=c++_static to the cmake command when building for android
+set(version_script "${CMAKE_CURRENT_SOURCE_DIR}/version_script")
+
+# Generate a map file for debug mode only
+set_property(TARGET armnn_delegate_jni APPEND_STRING PROPERTY
+        LINK_FLAGS " -Wl,--version-script=${version_script},-Map=mapfile.map")
+
+set_target_properties(armnn_delegate_jni PROPERTIES LINK_DEPENDS ${version_script})
diff --git a/delegate/armnnDelegateJNI/README.md b/delegate/armnnDelegateJNI/README.md
new file mode 100644
index 0000000..98ddd54
--- /dev/null
+++ b/delegate/armnnDelegateJNI/README.md
@@ -0,0 +1,15 @@
+# The Arm NN TensorFlow Lite delegate JNI (Experimental)
+
+NOTE: This library is an experimental feature. We cannot guarentee full support for this.
+
+'armnnDelegateJNI' is a library for accelerating certain TensorFlow Lite operators on Arm hardware specifically through Android
+applications. Each release is packaged in an AAR which can be found on Maven Central.
+The pre-built library contains the ArmNN Core, ArmNN Utils, Neon backend, CL Backend, and the ArmNN Delegate.
+It is essential to only build these. The backends you choose are optional.
+
+It requires a static build which can be switched on through setting BUILD_SHARED_LIBS=OFF. You will also have to set
+CMAKE_ANDROID_STL_TYPE=c++_static when building ArmNN.
+
+BUILD_DELEGATE_JNI_INTERFACE will also have to be set to true.
+
+To download the prebuilt ArmNN Delegate JNI AAR from Maven Central, please go to [ArmNN Maven Central Release Page](https://search.maven.org/artifact/io.github.arm-software/armnn.delegate).
diff --git a/delegate/armnnDelegateJNI/README.md.license b/delegate/armnnDelegateJNI/README.md.license
new file mode 100644
index 0000000..ac6973d
--- /dev/null
+++ b/delegate/armnnDelegateJNI/README.md.license
@@ -0,0 +1,4 @@
+#
+# Copyright © 2022 ARM Ltd and Contributors. All rights reserved.
+# SPDX-License-Identifier: MIT
+#
\ No newline at end of file
diff --git a/delegate/armnnDelegateJNI/src/armnn_delegate_jni.cpp b/delegate/armnnDelegateJNI/src/armnn_delegate_jni.cpp
new file mode 100644
index 0000000..55cc066
--- /dev/null
+++ b/delegate/armnnDelegateJNI/src/armnn_delegate_jni.cpp
@@ -0,0 +1,98 @@
+//
+// Copyright © 2022 Arm Ltd and Contributors. All rights reserved.
+// SPDX-License-Identifier: MIT
+//
+
+#include <armnn_delegate.hpp>
+#include <DelegateOptions.hpp>
+
+#if defined(ARMCOMPUTECL_ENABLED)
+#include <arm_compute/core/CL/OpenCL.h>
+#endif
+
+#include <jni.h>
+#include <string>
+
+extern "C" {
+
+/// Creates an Arm NN Delegate object.
+/// Options are passed in form of String arrays. For details about what options_keys and option_values
+/// are supported please see:
+//  armnnDelegate::DelegateOptions::DelegateOptions(char const* const*, char const* const*,size_t,void (*)(const char*))
+JNIEXPORT jlong
+JNICALL Java_com_arm_armnn_delegate_ArmnnDelegate_createDelegate(JNIEnv* env,
+                                                                 jclass clazz,
+                                                                 jobjectArray optionKeys,
+                                                                 jobjectArray optionValues)
+{
+    int numOptions = env->GetArrayLength(optionKeys);
+    const char* nativeOptionKeys[numOptions];
+    const char* nativeOptionValues[numOptions];
+
+    jstring jKeyStrings[numOptions];
+    jstring jValueStrings[numOptions];
+
+    // Convert java array of string into char so we can make use of it in cpp code
+    for (int i = 0; i < numOptions; i++)
+    {
+        jKeyStrings[i] = static_cast<jstring>(env->GetObjectArrayElement(optionKeys, i));
+        jValueStrings[i] = static_cast<jstring>(env->GetObjectArrayElement(optionValues, i));
+
+        nativeOptionKeys[i] = env->GetStringUTFChars(jKeyStrings[i], 0);
+        nativeOptionValues[i] = env->GetStringUTFChars(jValueStrings[i], 0);
+    }
+
+    armnnDelegate::DelegateOptions delegateOptions(nativeOptionKeys,
+                                                   nativeOptionValues,
+                                                   numOptions,
+                                                   nullptr);
+
+    // Release jni memory. After the delegate options are created there is no need to hold on to it anymore.
+    for (int i = 0; i < numOptions; i++)
+    {
+        env->ReleaseStringUTFChars(jKeyStrings[i], nativeOptionKeys[i]);
+        env->ReleaseStringUTFChars(jValueStrings[i], nativeOptionValues[i]);
+    }
+
+    return reinterpret_cast<jlong>(armnnDelegate::TfLiteArmnnDelegateCreate(delegateOptions));
+}
+
+/// Destroys a given Arm NN Delegate object
+JNIEXPORT void
+JNICALL Java_com_arm_armnn_delegate_ArmnnDelegate_deleteDelegate(JNIEnv* env, jclass clazz, jlong delegate)
+{
+    armnnDelegate::TfLiteArmnnDelegateDelete(reinterpret_cast<TfLiteDelegate*>(delegate));
+}
+
+/// Returns true if a Arm Mali GPU is detected.
+/// Can be used to ensure that GpuAcc is supported on a device.
+JNIEXPORT jboolean
+JNICALL Java_com_arm_armnn_delegate_ArmnnUtils_IsGpuAccSupported(JNIEnv* env, jclass clazz)
+{
+#if defined(ARMCOMPUTECL_ENABLED)
+    cl::Device device = cl::Device::getDefault();
+    char device_name[32];
+    cl_int err = clGetDeviceInfo(device.get(), CL_DEVICE_NAME, sizeof(device_name), &device_name, NULL);
+    if (err != CL_SUCCESS)
+    {
+        return false;
+    }
+    // search for "Mali" in the devices name
+    if (strstr(device_name, "Mali"))
+    {
+        return true;
+    }
+#endif
+    return false;
+}
+
+/// Returns true if the current device supports Neon instructions.
+/// Can be used to ensure the CpuAcc backend is supported.
+JNIEXPORT jboolean
+JNICALL Java_com_arm_armnn_delegate_ArmnnUtils_IsNeonDetected(JNIEnv* env, jclass clazz)
+{
+    return armnn::NeonDetected();
+}
+
+}
+
diff --git a/delegate/armnnDelegateJNI/version_script b/delegate/armnnDelegateJNI/version_script
new file mode 100644
index 0000000..8dc044e
--- /dev/null
+++ b/delegate/armnnDelegateJNI/version_script
@@ -0,0 +1,15 @@
+/*
+ Copyright © 2022 Arm Ltd and Contributors. All rights reserved.
+ SPDX-License-Identifier: MIT
+*/
+
+LIBARMNN_DELEGATE_JNI {
+global:
+    Java_com_arm_armnn_delegate_ArmnnDelegate_createDelegate;
+    Java_com_arm_armnn_delegate_ArmnnDelegate_deleteDelegate;
+    Java_com_arm_armnn_delegate_ArmnnUtils_IsGpuAccSupported;
+    Java_com_arm_armnn_delegate_ArmnnUtils_IsNeonDetected;
+local:
+    *;
+
+};
\ No newline at end of file