Merge "Extract DSP NNAPI SL libraries to disk"
diff --git a/Android.mk b/Android.mk
index 51797d8..1e64c39 100644
--- a/Android.mk
+++ b/Android.mk
@@ -36,7 +36,7 @@
 endif
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src/com/android/nn/benchmark)
-LOCAL_JNI_SHARED_LIBRARIES := libnnbenchmark_jni $(SL_LIBS)
+LOCAL_JNI_SHARED_LIBRARIES := libnnbenchmark_jni libsupport_library_jni $(SL_LIBS)
 
 # need fread_unlocked in version 28
 LOCAL_SDK_VERSION := 28
@@ -71,7 +71,7 @@
 LOCAL_COMPATIBILITY_SUITE += device-tests
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
-LOCAL_JNI_SHARED_LIBRARIES := libnnbenchmark_jni librandom_graph_test_jni $(SL_LIBS)
+LOCAL_JNI_SHARED_LIBRARIES := libnnbenchmark_jni libsupport_library_jni librandom_graph_test_jni $(SL_LIBS)
 
 # need fread_unlocked in version 28
 LOCAL_SDK_VERSION := 28
diff --git a/crashtest/Android.mk b/crashtest/Android.mk
index 8b93d8b..29aa86e 100644
--- a/crashtest/Android.mk
+++ b/crashtest/Android.mk
@@ -32,7 +32,7 @@
     $(call all-java-files-under, ../src/com/android/nn/crashtest/core) \
     ../src/com/android/nn/benchmark/app/AcceleratorSpecificTestSupport.java
 
-LOCAL_JNI_SHARED_LIBRARIES := libnnbenchmark_jni
+LOCAL_JNI_SHARED_LIBRARIES := libnnbenchmark_jni libsupport_library_jni
 
 LOCAL_SDK_VERSION := 27
 LOCAL_ASSET_DIR := $(LOCAL_PATH)/../../models/assets
diff --git a/dogfood/Android.mk b/dogfood/Android.mk
index 52d4633..6cd07d8 100644
--- a/dogfood/Android.mk
+++ b/dogfood/Android.mk
@@ -29,7 +29,7 @@
     $(call all-java-files-under, ../src/com/android/nn/benchmark/evaluators) \
     $(call all-java-files-under, ../src/com/android/nn/benchmark/imageprocessors) \
     $(call all-java-files-under, ../src/com/android/nn/benchmark/util)
-LOCAL_JNI_SHARED_LIBRARIES := libnnbenchmark_jni
+LOCAL_JNI_SHARED_LIBRARIES := libnnbenchmark_jni libsupport_library_jni
 
 LOCAL_SDK_VERSION := 28
 LOCAL_ASSET_DIR := $(LOCAL_PATH)/../../models/assets
diff --git a/jni/Android.bp b/jni/Android.bp
index 0be00ec..1dc575c 100644
--- a/jni/Android.bp
+++ b/jni/Android.bp
@@ -97,3 +97,31 @@
         keep_symbols: true,
     },
 }
+
+cc_library {
+    name: "libsupport_library_jni",
+    sdk_version: "current",
+    min_sdk_version: "29",
+    srcs: [
+        "support_library_jni.cpp",
+    ],
+    header_libs: [
+        "flatbuffer_headers",
+        "jni_headers",
+        "tensorflow_headers",
+    ],
+    shared_libs: [
+        "liblog",
+    ],
+    static_libs: [
+        "libtflite_static",
+    ],
+    cflags: [
+        "-Wno-sign-compare",
+        "-Wno-unused-parameter",
+    ],
+    stl: "libc++_static",
+    strip: {
+        keep_symbols: true,
+    },
+}
\ No newline at end of file
diff --git a/jni/benchmark_jni.cpp b/jni/benchmark_jni.cpp
index 075aeb2..c4f114a 100644
--- a/jni/benchmark_jni.cpp
+++ b/jni/benchmark_jni.cpp
@@ -26,9 +26,6 @@
 #include <android/log.h>
 #include <android/sharedmem.h>
 #include <sys/mman.h>
-#include "tensorflow/lite/nnapi/nnapi_implementation.h"
-
-#define LOG_TAG "NN_BENCHMARK"
 
 extern "C" JNIEXPORT jboolean JNICALL
 Java_com_android_nn_benchmark_core_NNTestBase_hasNnApiDevice(
@@ -104,30 +101,7 @@
     return (jlong)(uintptr_t)handle;
 }
 
-// This method loads the NNAPI SL from the given path.
-// Is called by a synchronized method in NNTestBase that will cache the
-// result. We expect this to be called only once per JVM and the handle
-// to be released when the JVM is shut down.
-extern "C" JNIEXPORT jlong JNICALL
-Java_com_android_nn_benchmark_core_NNTestBase_loadNnApiSlHandle(
-    JNIEnv *env, jobject /* clazz */, jstring _nnapiSlDriverPath) {
-  if (_nnapiSlDriverPath != NULL) {
-    const char *nnapiSlDriverPath =
-        env->GetStringUTFChars(_nnapiSlDriverPath, NULL);
-    std::unique_ptr<const tflite::nnapi::NnApiSupportLibrary> tmp =
-        tflite::nnapi::loadNnApiSupportLibrary(nnapiSlDriverPath);
-    if (!tmp) {
-      __android_log_print(ANDROID_LOG_ERROR, LOG_TAG,
-                          "Failed to load NNAPI SL driver from '%s'",
-                          nnapiSlDriverPath);
-      return false;
-    }
-    __android_log_print(ANDROID_LOG_INFO, LOG_TAG, "Loaded NNAPI SL");
-    return (jlong)(uintptr_t)tmp.release();
-  }
 
-  return 0l;
-}
 
 extern "C"
 JNIEXPORT void
diff --git a/jni/support_library_jni.cpp b/jni/support_library_jni.cpp
new file mode 100644
index 0000000..a8eb1cf
--- /dev/null
+++ b/jni/support_library_jni.cpp
@@ -0,0 +1,55 @@
+/**
+ * Copyright 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.
+ */
+
+#include "run_tflite.h"
+
+#include <jni.h>
+#include <string>
+#include <iomanip>
+#include <sstream>
+#include <fcntl.h>
+
+#include <android/log.h>
+
+#include "tensorflow/lite/nnapi/nnapi_implementation.h"
+
+#define LOG_TAG "NN_BENCHMARK"
+
+
+// This method loads the NNAPI SL from the given path.
+// Is called by a synchronized method in NNTestBase that will cache the
+// result. We expect this to be called only once per JVM and the handle
+// to be released when the JVM is shut down.
+extern "C" JNIEXPORT jlong JNICALL
+Java_com_android_nn_benchmark_core_sl_SupportLibraryDriverHandler_loadNnApiSlHandle(
+    JNIEnv *env, jobject /* clazz */, jstring _nnapiSlDriverPath) {
+  if (_nnapiSlDriverPath != NULL) {
+    const char *nnapiSlDriverPath =
+        env->GetStringUTFChars(_nnapiSlDriverPath, NULL);
+    std::unique_ptr<const tflite::nnapi::NnApiSupportLibrary> tmp =
+        tflite::nnapi::loadNnApiSupportLibrary(nnapiSlDriverPath);
+    if (!tmp) {
+      __android_log_print(ANDROID_LOG_ERROR, LOG_TAG,
+                          "Failed to load NNAPI SL driver from '%s'",
+                          nnapiSlDriverPath);
+      return false;
+    }
+    __android_log_print(ANDROID_LOG_INFO, LOG_TAG, "Loaded NNAPI SL");
+    return (jlong)(uintptr_t)tmp.release();
+  }
+
+  return 0l;
+}
\ No newline at end of file
diff --git a/sl_prebuilt/assets/dsp_loaded_libraries.txt b/sl_prebuilt/assets/dsp_loaded_libraries.txt
new file mode 100644
index 0000000..6c11b96
--- /dev/null
+++ b/sl_prebuilt/assets/dsp_loaded_libraries.txt
@@ -0,0 +1 @@
+libhexagon_nn_skel.so
\ No newline at end of file
diff --git a/src/com/android/nn/benchmark/core/NNTestBase.java b/src/com/android/nn/benchmark/core/NNTestBase.java
index f8454f0..31feea3 100644
--- a/src/com/android/nn/benchmark/core/NNTestBase.java
+++ b/src/com/android/nn/benchmark/core/NNTestBase.java
@@ -20,13 +20,17 @@
 import android.content.Context;
 import android.content.res.AssetManager;
 import android.os.Build;
+import android.system.Os;
+import android.system.ErrnoException;
 import android.util.Log;
 import android.util.Pair;
 import android.widget.TextView;
 import androidx.test.InstrumentationRegistry;
+import com.android.nn.benchmark.core.sl.QualcommSupportLibraryDriverHandler;
+import com.android.nn.benchmark.core.sl.SupportLibraryDriverHandler;
 import java.io.BufferedReader;
 import java.io.File;
-import java.io.FileInputStream;
+import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
@@ -37,7 +41,6 @@
 import java.util.List;
 import java.util.Optional;
 import java.util.Random;
-import java.util.concurrent.atomic.AtomicReference;
 import java.util.stream.Collectors;
 import dalvik.system.BaseDexClassLoader;
 import android.content.res.AssetFileDescriptor;
@@ -49,8 +52,6 @@
 public class NNTestBase implements AutoCloseable {
     protected static final String TAG = "NN_TESTBASE";
 
-    private static final String NNAPI_SL_LIBRARIES_LIST_ASSET_PATH = "sl_prebuilt_filelist.txt";
-
     // Used to load the 'native-lib' library on application startup.
     static {
         System.loadLibrary("nnbenchmark_jni");
@@ -68,103 +69,6 @@
     public static native boolean getAcceleratorNames(List<String> resultList);
     public static native boolean hasNnApiDevice(String nnApiDeviceName);
 
-    private static native long loadNnApiSlHandle(String nnApiSlPath);
-
-    private static long nnapiSlHandle = 0;
-    public static synchronized long getOrLoadNnApiSlHandle(Context context, boolean extractNnApiSupportLibrary)
-        throws IOException {
-      if (nnapiSlHandle == 0) {
-        Log.i(TAG, "Initializing NNAPI SL.");
-
-        String nnSupportLibFilePath = null;
-        Log.i(TAG, "Preparing NNAPI SL");
-        if (extractNnApiSupportLibrary) {
-            nnSupportLibFilePath = extractAllAndGetNnApiSlPath(context, NNAPI_SL_LIB_NAME);
-        } else {
-            nnSupportLibFilePath = getNnApiSlPathFromApkLibraries(context, NNAPI_SL_LIB_NAME);
-        }
-
-        if (nnSupportLibFilePath != null) {
-          nnapiSlHandle = loadNnApiSlHandle(nnSupportLibFilePath);
-          if (nnapiSlHandle == 0) {
-            Log.e(TAG, String
-                .format("Unable load NNAPI SL from '%s'.", nnSupportLibFilePath));
-          } else {
-              Log.i(TAG, String
-                  .format("Successfully loaded NNAPI SL from '%s'.", nnSupportLibFilePath));
-          }
-        } else {
-          Log.e(TAG, String
-              .format("Unable to find NNAPI SL entry point '%s' in embedded libraries path.",
-                  NNAPI_SL_LIB_NAME));
-        }
-      }
-      return nnapiSlHandle;
-    }
-
-    private static InputStream getInputStreamFromApk(String apkPath, String filePath) throws IOException {
-        Log.i(TAG, String.format("Getting input stream from APK '%s' and file '%s'.", apkPath, filePath));
-
-        JarFile jarFile = new JarFile(apkPath);
-        JarEntry jarEntry = jarFile.getJarEntry(filePath);
-        return jarFile.getInputStream(jarEntry);
-    }
-
-    private static String extractAllAndGetNnApiSlPath(Context context, String entryPointName)
-        throws IOException {
-        try {
-            BufferedReader slLibraryListReader
-                = new BufferedReader(
-                    new InputStreamReader(
-                        context.getAssets().open(NNAPI_SL_LIBRARIES_LIST_ASSET_PATH)));
-            String result = null;
-            final String nnLibTargetFolder = context.getCodeCacheDir().toString();
-            for (final String libraryFile : slLibraryListReader.lines().collect(Collectors.toList())) {
-                String sourcePath = getNnApiSlPathFromApkLibraries(context, libraryFile);
-                if (sourcePath == null) {
-                    Log.w(TAG, String.format("Unable to find SL library '%s' to extract assuming is not part of this chipset distribution.", libraryFile));
-                    continue;
-                }
-
-                String[] apkAndLibraryPaths = sourcePath.split("!");
-                if (apkAndLibraryPaths.length != 2) {
-                    Log.e(TAG, String.format("Unable to extract %s.", sourcePath));
-                    return null;
-                }
-
-                File targetPath = new File(nnLibTargetFolder, libraryFile);
-                try(InputStream in = getInputStreamFromApk(apkAndLibraryPaths[0],
-                    // Removing leading '/'
-                    apkAndLibraryPaths[1].substring(1));
-                    OutputStream out = new FileOutputStream(targetPath)
-                ) {
-                    copyFull(in, out);
-                }
-
-                Log.i(TAG, String.format("Copied '%s' to '%s'.", sourcePath, targetPath));
-
-                if (libraryFile.equals(entryPointName)) {
-                    result = targetPath.getAbsolutePath();
-                }
-            }
-            return result;
-        } catch (IOException e) {
-            Log.e(TAG, "Unable to find list of SL libraries under assets.", e);
-            throw e;
-        }
-    }
-
-    private static String getNnApiSlPathFromApkLibraries(Context context, String resourceName) {
-        BaseDexClassLoader dexClassLoader = (BaseDexClassLoader) context.getClassLoader();
-        // Removing the "lib" prefix and ".so" suffix.
-        String libShortName = resourceName.substring(3, resourceName.length() - 3);
-        String result = dexClassLoader.findLibrary(libShortName);
-        if (result != null) {
-            return result;
-        }
-        return dexClassLoader.findLibrary(resourceName);
-    }
-
     private synchronized native long initModel(
             String modelFileName,
             int tfliteBackend,
@@ -220,8 +124,6 @@
     /** Collect only 1 benchmark result every 10 **/
     public static final int FLAG_SAMPLE_BENCHMARK_RESULTS = 1 << 2;
 
-    private static final String NNAPI_SL_LIB_NAME = "libnnapi_sl_driver.so";
-
     protected Context mContext;
     protected TextView mText;
     private final String mModelName;
@@ -323,14 +225,16 @@
         mContext = ipcxt;
         long nnApiLibHandle = 0;
         if (mUseNnApiSupportLibrary) {
-          nnApiLibHandle = getOrLoadNnApiSlHandle(mContext, mExtractNnApiSupportLibrary);
+          // TODO: support different drivers providers maybe with a flag
+          QualcommSupportLibraryDriverHandler qcSlhandler = new QualcommSupportLibraryDriverHandler();
+          nnApiLibHandle = qcSlhandler.getOrLoadNnApiSlHandle(mContext, mExtractNnApiSupportLibrary);
           if (nnApiLibHandle == 0) {
             Log.e(TAG, String
                 .format("Unable to find NNAPI SL entry point '%s' in embedded libraries path.",
-                    NNAPI_SL_LIB_NAME));
+                    SupportLibraryDriverHandler.NNAPI_SL_LIB_NAME));
             throw new NnApiDelegationFailure(String
                 .format("Unable to find NNAPI SL entry point '%s' in embedded libraries path.",
-                    NNAPI_SL_LIB_NAME));
+                    SupportLibraryDriverHandler.NNAPI_SL_LIB_NAME));
           }
         }
         if (mTemporaryModelFilePath != null) {
@@ -577,7 +481,7 @@
         }
     }
 
-    private static void copyFull(InputStream in, OutputStream out) throws IOException {
+    public static void copyFull(InputStream in, OutputStream out) throws IOException {
         byte[] byteBuffer = new byte[1024];
         int readBytes = -1;
         while ((readBytes = in.read(byteBuffer)) != -1) {
diff --git a/src/com/android/nn/benchmark/core/sl/QualcommSupportLibraryDriverHandler.java b/src/com/android/nn/benchmark/core/sl/QualcommSupportLibraryDriverHandler.java
new file mode 100644
index 0000000..3edaef1
--- /dev/null
+++ b/src/com/android/nn/benchmark/core/sl/QualcommSupportLibraryDriverHandler.java
@@ -0,0 +1,67 @@
+package com.android.nn.benchmark.core.sl;
+
+import android.content.Context;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.util.Log;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class QualcommSupportLibraryDriverHandler extends SupportLibraryDriverHandler {
+  private static final String NNAPI_DSP_SL_LIBRARIES_ASSET_PATH = "dsp_loaded_libraries.txt";
+  private static final String DSP_LOAD_PATH_ENV_VAR = "ADSP_LIBRARY_PATH";
+
+  @Override
+  public void prepareDriver(Context context, String nnSupportLibFilePath) throws IOException {
+    boolean isApkPath = nnSupportLibFilePath.contains("apk!");
+
+    String dspLibsFolder = null;
+    Log.i(TAG, "Preparing NNAPI SL");
+    if (isApkPath) {
+      dspLibsFolder = extractDSPLibraries(context);
+    } else {
+      dspLibsFolder = new File(nnSupportLibFilePath).getParent();
+    }
+
+    if (dspLibsFolder != null) {
+      try {
+        Os.setenv(DSP_LOAD_PATH_ENV_VAR, dspLibsFolder, /*overwrite=*/true);
+        Log.i(TAG, String.format("Overwritten system env variable %s with %s",
+            DSP_LOAD_PATH_ENV_VAR, dspLibsFolder));
+      } catch (ErrnoException errnoException) {
+        throw new IOException(String.format("Unable to overwrite system env variable %s with %s",
+            DSP_LOAD_PATH_ENV_VAR, dspLibsFolder), errnoException);
+      }
+    }
+  }
+
+  private String extractDSPLibraries(Context context)
+      throws IOException {
+    try {
+      BufferedReader slLibraryListReader
+          = new BufferedReader(
+          new InputStreamReader(
+              context.getAssets().open(NNAPI_DSP_SL_LIBRARIES_ASSET_PATH)));
+      final String nnLibTargetFolder = context.getCodeCacheDir().toString();
+      final List<String> libsToExtract = slLibraryListReader.lines().collect(Collectors.toList());
+      if (libsToExtract.isEmpty()) {
+        Log.i(TAG, "No SL library to extract.");
+        return null;
+      }
+      for (final String libraryFile : libsToExtract) {
+        if (!extractNnApiSlLibTo(context, libraryFile, nnLibTargetFolder)) {
+          throw new FileNotFoundException(String.format("Unable to extract file %s", libraryFile));
+        }
+      }
+      return nnLibTargetFolder;
+    } catch (IOException e) {
+      Log.e(TAG, "Unable to find list of SL libraries to extract from APK under assets.", e);
+      throw e;
+    }
+  }
+}
diff --git a/src/com/android/nn/benchmark/core/sl/SupportLibraryDriverHandler.java b/src/com/android/nn/benchmark/core/sl/SupportLibraryDriverHandler.java
new file mode 100644
index 0000000..5c2f232
--- /dev/null
+++ b/src/com/android/nn/benchmark/core/sl/SupportLibraryDriverHandler.java
@@ -0,0 +1,147 @@
+package com.android.nn.benchmark.core.sl;
+
+import android.content.Context;
+import android.util.Log;
+import com.android.nn.benchmark.core.NNTestBase;
+import dalvik.system.BaseDexClassLoader;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+import java.util.stream.Collectors;
+
+/**
+ * Abstracts the initialization required to enable a NNAPI Support Library for a given vendor.
+ **/
+public abstract class SupportLibraryDriverHandler {
+
+  static {
+    System.loadLibrary("support_library_jni");
+  }
+
+  protected static final String TAG = "NN_TESTBASE";
+
+  private static final String NNAPI_SL_LIBRARIES_LIST_ASSET_PATH = "sl_prebuilt_filelist.txt";
+  public static final String NNAPI_SL_LIB_NAME = "libnnapi_sl_driver.so";
+
+  private static native long loadNnApiSlHandle(String nnApiSlPath);
+
+  // Guarded by this
+  private static long nnapiSlHandle = 0;
+
+  public synchronized long getOrLoadNnApiSlHandle(Context context, boolean extractNnApiSupportLibrary)
+      throws IOException {
+    if (nnapiSlHandle == 0) {
+      Log.i(TAG, "Initializing NNAPI SL.");
+
+      String nnSupportLibFilePath = null;
+      Log.i(TAG, "Preparing NNAPI SL");
+      if (extractNnApiSupportLibrary) {
+        nnSupportLibFilePath = extractAllAndGetNnApiSlPath(context, NNAPI_SL_LIB_NAME);
+      } else {
+        nnSupportLibFilePath = getNnApiSlPathFromApkLibraries(context, NNAPI_SL_LIB_NAME);
+      }
+
+      prepareDriver(context, nnSupportLibFilePath);
+
+      if (nnSupportLibFilePath != null) {
+        nnapiSlHandle = loadNnApiSlHandle(nnSupportLibFilePath);
+        if (nnapiSlHandle == 0) {
+          Log.e(TAG, String
+              .format("Unable load NNAPI SL from '%s'.", nnSupportLibFilePath));
+        } else {
+          Log.i(TAG, String
+              .format("Successfully loaded NNAPI SL from '%s'.", nnSupportLibFilePath));
+        }
+      } else {
+        Log.e(TAG, String
+            .format("Unable to find NNAPI SL entry point '%s' in embedded libraries path.",
+                NNAPI_SL_LIB_NAME));
+      }
+    }
+    return nnapiSlHandle;
+  }
+
+  private static InputStream getInputStreamFromApk(String apkPath, String filePath) throws IOException {
+    Log.i(TAG, String.format("Getting input stream from APK '%s' and file '%s'.", apkPath, filePath));
+
+    JarFile jarFile = new JarFile(apkPath);
+    JarEntry jarEntry = jarFile.getJarEntry(filePath);
+    return jarFile.getInputStream(jarEntry);
+  }
+
+  private static String extractAllAndGetNnApiSlPath(Context context, String entryPointName)
+      throws IOException {
+    try {
+      BufferedReader slLibraryListReader
+          = new BufferedReader(
+          new InputStreamReader(
+              context.getAssets().open(NNAPI_SL_LIBRARIES_LIST_ASSET_PATH)));
+      String result = null;
+      final String nnLibTargetFolder = context.getCodeCacheDir().toString();
+      for (final String libraryFile : slLibraryListReader.lines().collect(Collectors.toList())) {
+        try {
+          boolean copied = extractNnApiSlLibTo(context, libraryFile, nnLibTargetFolder);
+          if (copied && libraryFile.equals(entryPointName)) {
+            result = new File(nnLibTargetFolder, libraryFile).getAbsolutePath();
+          }
+        } catch (FileNotFoundException unableToExtractFile) {
+          return null;
+        }
+      }
+      return result;
+    } catch (IOException e) {
+      Log.e(TAG, "Unable to find list of SL libraries under assets.", e);
+      throw e;
+    }
+  }
+
+  protected static boolean extractNnApiSlLibTo(Context context, String libraryFile, String targetFolder)
+      throws IOException {
+    String sourcePath = getNnApiSlPathFromApkLibraries(context, libraryFile);
+    if (sourcePath == null) {
+      Log.w(TAG, String.format("Unable to find SL library '%s' to extract assuming is not part of this chipset distribution.", libraryFile));
+      return false;
+    }
+
+    String[] apkAndLibraryPaths = sourcePath.split("!");
+    if (apkAndLibraryPaths.length != 2) {
+      final String errorMsg = String.format("Unable to extract %s.", sourcePath);
+      Log.e(TAG, errorMsg);
+      throw new FileNotFoundException(errorMsg);
+    }
+
+    File targetPath = new File(targetFolder, libraryFile);
+    try(InputStream in = getInputStreamFromApk(apkAndLibraryPaths[0],
+        // Removing leading '/'
+        apkAndLibraryPaths[1].substring(1));
+        OutputStream out = new FileOutputStream(targetPath)
+    ) {
+      NNTestBase.copyFull(in, out);
+    }
+
+    Log.i(TAG, String.format("Copied '%s' to '%s'.", sourcePath, targetPath));
+
+    return true;
+  }
+
+  private static String getNnApiSlPathFromApkLibraries(Context context, String resourceName) {
+    BaseDexClassLoader dexClassLoader = (BaseDexClassLoader) context.getClassLoader();
+    // Removing the "lib" prefix and ".so" suffix.
+    String libShortName = resourceName.substring(3, resourceName.length() - 3);
+    String result = dexClassLoader.findLibrary(libShortName);
+    if (result != null) {
+      return result;
+    }
+    return dexClassLoader.findLibrary(resourceName);
+  }
+
+  // Vendor-specifi preparation steps
+  protected abstract void prepareDriver(Context context, String nnSupportLibFilePath) throws IOException;
+}