diff options
Diffstat (limited to 'src/android/jar/src/org/qtproject/qt/android/QtLoader.java')
-rw-r--r-- | src/android/jar/src/org/qtproject/qt/android/QtLoader.java | 669 |
1 files changed, 418 insertions, 251 deletions
diff --git a/src/android/jar/src/org/qtproject/qt/android/QtLoader.java b/src/android/jar/src/org/qtproject/qt/android/QtLoader.java index e7f260fd51..7570299e1a 100644 --- a/src/android/jar/src/org/qtproject/qt/android/QtLoader.java +++ b/src/android/jar/src/org/qtproject/qt/android/QtLoader.java @@ -4,12 +4,10 @@ package org.qtproject.qt.android; -import android.app.AlertDialog; -import android.app.Dialog; +import android.annotation.SuppressLint; import android.content.Context; import android.content.ContextWrapper; -import android.content.DialogInterface; -import android.content.Intent; +import android.content.pm.ApplicationInfo; import android.content.pm.ComponentInfo; import android.content.res.Resources; import android.os.Build; @@ -17,318 +15,487 @@ import android.os.Bundle; import android.util.Log; import java.io.File; -import java.io.FileOutputStream; -import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; -import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; -import java.util.List; +import java.util.Objects; import dalvik.system.DexClassLoader; -import static org.qtproject.qt.android.QtConstants.*; - public abstract class QtLoader { - static String QtTAG = "Qt"; - - // These parameters matter in case of deploying application as system (embedded into firmware) - public static final String SYSTEM_LIB_PATH = "/system/lib/"; + protected static final String QtTAG = "QtLoader"; - public String APPLICATION_PARAMETERS = null; - public String ENVIRONMENT_VARIABLES = "QT_USE_ANDROID_NATIVE_DIALOGS=1"; - public String[] QT_ANDROID_THEMES = null; - public String QT_ANDROID_DEFAULT_THEME = null; // sets the default theme. + private final Resources m_resources; + private final String m_packageName; + private String m_preferredAbi = null; + private String m_nativeLibrariesDir = null; + private ClassLoader m_classLoader; - public ArrayList<String> m_qtLibs = null; // required qt libs - public int m_displayDensity = -1; - private ContextWrapper m_context; + protected final ContextWrapper m_context; protected ComponentInfo m_contextInfo; + protected String m_mainLib; + protected ArrayList<String> m_applicationParameters = new ArrayList<>(); + protected HashMap<String, String> m_environmentVariables = new HashMap<>(); + + /** + * Sets and initialize the basic pieces. + * Initializes the class loader since it doesn't rely on anything + * other than the context. + * Also, we can already initialize the static classes contexts here. + **/ public QtLoader(ContextWrapper context) { m_context = context; + m_resources = m_context.getResources(); + m_packageName = m_context.getPackageName(); + + initClassLoader(); + initStaticClasses(); + initContextInfo(); } - public static void setQtTAG(String tag) - { - QtTAG = tag; + /** + * Initializes the context info instance which is used to retrieve + * the app metadata from the AndroidManifest.xml or other xml resources. + * Some values are dependent on the context being an Activity or Service. + **/ + abstract protected void initContextInfo(); + + /** + * Implements the logic for finish the extended context, mostly called + * in error cases. + **/ + abstract protected void finish(); + + /** + * Context specific (Activity/Service) implementation for static classes. + * The Activity and Service loaders call different methods. + **/ + abstract protected void initStaticClassesImpl(Class<?> initClass, Object staticInitDataObject); + + /** + * Extract the common metadata in the base implementation. And the extended methods + * call context specific metadata extraction. This also sets the various environment + * variables and application parameters. + **/ + protected void extractContextMetaData() { + setEnvironmentVariable("QT_ANDROID_FONTS", "Roboto;Droid Sans;Droid Sans Fallback"); + String monospaceFonts = "Droid Sans Mono;Droid Sans;Droid Sans Fallback"; + setEnvironmentVariable("QT_ANDROID_FONTS_MONOSPACE", monospaceFonts); + setEnvironmentVariable("QT_ANDROID_FONTS_SERIF", "Droid Serif"); + setEnvironmentVariable("HOME", m_context.getFilesDir().getAbsolutePath()); + setEnvironmentVariable("TMPDIR", m_context.getCacheDir().getAbsolutePath()); + String backgroundRunning = getMetaData("android.app.background_running"); + setEnvironmentVariable("QT_BLOCK_EVENT_LOOPS_WHEN_SUSPENDED", backgroundRunning); + setEnvironmentVariable("QTRACE_LOCATION", getMetaData("android.app.trace_location")); + setApplicationParameters(getMetaData("android.app.arguments")); } - // Implement in subclass - protected void finish() {} + private ArrayList<String> preferredAbiLibs(String[] libs) { + HashMap<String, ArrayList<String>> abiLibs = new HashMap<>(); + for (String lib : libs) { + String[] archLib = lib.split(";", 2); + if (m_preferredAbi != null && !archLib[0].equals(m_preferredAbi)) + continue; + if (!abiLibs.containsKey(archLib[0])) + abiLibs.put(archLib[0], new ArrayList<>()); + Objects.requireNonNull(abiLibs.get(archLib[0])).add(archLib[1]); + } + + if (m_preferredAbi != null) { + if (abiLibs.containsKey(m_preferredAbi)) { + return abiLibs.get(m_preferredAbi); + } + return new ArrayList<>(); + } - protected String getTitle() { - return "Qt"; + for (String abi : Build.SUPPORTED_ABIS) { + if (abiLibs.containsKey(abi)) { + m_preferredAbi = abi; + return abiLibs.get(abi); + } + } + return new ArrayList<>(); } - protected void runOnUiThread(Runnable run) { - run.run(); + private void initStaticClasses() { + for (String className : getStaticInitClasses()) { + try { + Class<?> initClass = m_classLoader.loadClass(className); + Object staticInitDataObject = initClass.newInstance(); // create an instance + initStaticClassesImpl(initClass, staticInitDataObject); + + // For modules that don't need/have setActivity/setService + Method m = initClass.getMethod("setContext", Context.class); + m.invoke(staticInitDataObject, m_context); + } catch (ClassNotFoundException | IllegalAccessException | InstantiationException | + NoSuchMethodException | InvocationTargetException e) { + Log.d(QtTAG, "Class " + className + " does not implement setContext method"); + } + } } - Intent getIntent() + /** + * Initialize the class loader instance and sets it via QtNative. + * This would also be used by QJniObject API. + **/ + private void initClassLoader() { - return null; + // directory where optimized DEX files should be written. + String outDexPath = m_context.getDir("outdex", Context.MODE_PRIVATE).getAbsolutePath(); + m_classLoader = new DexClassLoader("", outDexPath, null, m_context.getClassLoader()); + QtNative.setClassLoader(m_classLoader); } - // Implement in subclass - private final List<String> supportedAbis = Arrays.asList(Build.SUPPORTED_ABIS); - private String preferredAbi = null; + /** + * Returns the context's main library absolute path, + * or null if the library hasn't been loaded yet. + **/ + public String getMainLibrary() { + return m_mainLib; + } + + /** + * Returns the context's parameters that are used when calling + * the main library's main() function. This is assembled from + * a combination of static values and also metadata dependent values. + **/ + public ArrayList<String> getApplicationParameters() { + return m_applicationParameters; + } - private ArrayList<String> prefferedAbiLibs(String []libs) + /** + * Adds a parameter string to the internal array list of parameters. + **/ + public void setApplicationParameter(String param) { - HashMap<String, ArrayList<String>> abisLibs = new HashMap<>(); - for (String lib : libs) { - String[] archLib = lib.split(";", 2); - if (preferredAbi != null && !archLib[0].equals(preferredAbi)) - continue; - if (!abisLibs.containsKey(archLib[0])) - abisLibs.put(archLib[0], new ArrayList<String>()); - abisLibs.get(archLib[0]).add(archLib[1]); - } + if (param == null || param.isEmpty()) + return; + m_applicationParameters.add(param); + } - if (preferredAbi != null) { - if (abisLibs.containsKey(preferredAbi)) { - return abisLibs.get(preferredAbi); - } - return new ArrayList<String>(); - } + /** + * Adds a list of parameters to the internal array list of parameters. + * This expects the parameters separated by '\t'. + **/ + public void setApplicationParameters(String params) + { + if (params == null || params.isEmpty()) + return; - for (String abi: supportedAbis) { - if (abisLibs.containsKey(abi)) { - preferredAbi = abi; - return abisLibs.get(abi); - } - } - return new ArrayList<String>(); + for (String param : params.split("\t")) + setApplicationParameter(param); } - // this function is used to load and start the loader - private void loadApplication(Bundle loaderParams) + /** + * Sets a single key/value environment variable pair. + **/ + public void setEnvironmentVariable(String key, String value) { - final Resources resources = m_context.getResources(); - final String packageName = m_context.getPackageName(); try { - // add all bundled Qt libs to loader params - int id = resources.getIdentifier("bundled_libs", "array", packageName); - final String[] bundledLibs = resources.getStringArray(id); - ArrayList<String> libs = new ArrayList<>(prefferedAbiLibs(bundledLibs)); - - String libName = null; - if (m_contextInfo.metaData.containsKey("android.app.lib_name")) { - libName = m_contextInfo.metaData.getString("android.app.lib_name") + "_" + preferredAbi; - loaderParams.putString(MAIN_LIBRARY_KEY, libName); //main library contains main() function - } - - loaderParams.putStringArrayList(BUNDLED_LIBRARIES_KEY, libs); - - // load and start QtLoader class - DexClassLoader classLoader = new DexClassLoader(loaderParams.getString(DEX_PATH_KEY), // .jar/.apk files - m_context.getDir("outdex", Context.MODE_PRIVATE).getAbsolutePath(), // directory where optimized DEX files should be written. - loaderParams.containsKey(LIB_PATH_KEY) ? loaderParams.getString(LIB_PATH_KEY) : null, // libs folder (if exists) - m_context.getClassLoader()); // parent loader - - if (m_context instanceof QtActivityBase) { - QtActivityBase activityBase = (QtActivityBase)m_context; - QtActivityDelegate activityDelegate = activityBase.getActivityDelegate(); - if (!activityDelegate.loadApplication(activityBase, classLoader, loaderParams)) - throw new Exception(""); - if (!activityDelegate.startApplication()) - throw new Exception(""); - } else if (m_context instanceof QtServiceBase) { - QtServiceBase serviceBase = (QtServiceBase)m_context; - QtServiceDelegate serviceDelegate = serviceBase.getServiceDelegate(); - if (!serviceDelegate.loadApplication(serviceBase, classLoader, loaderParams)) - throw new Exception(""); - if (!serviceDelegate.startApplication()) - throw new Exception(""); - } + android.system.Os.setenv(key, value, true); + m_environmentVariables.put(key, value); } catch (Exception e) { + Log.e(QtTAG, "Could not set environment variable:" + key + "=" + value); e.printStackTrace(); - AlertDialog errorDialog = new AlertDialog.Builder(m_context).create(); - int id = resources.getIdentifier("fatal_error_msg", "string", - packageName); - errorDialog.setMessage(resources.getString(id)); - errorDialog.setButton(Dialog.BUTTON_POSITIVE, - resources.getString(android.R.string.ok), - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - finish(); - } - }); - errorDialog.show(); } } - public void startApp(final boolean firstStart) + /** + * Sets a list of keys/values string to as environment variables. + * This expects the key/value to be separated by '=', and parameters + * to be separated by '\t'. + * Ex: key1=val1\tkey2=val2\tkey3=val3 + **/ + public void setEnvironmentVariables(String environmentVariables) { - try { - final Resources resources = m_context.getResources(); - final String packageName = m_context.getPackageName(); - int id = resources.getIdentifier("qt_libs", "array", packageName); - m_qtLibs = prefferedAbiLibs(resources.getStringArray(id)); - - id = resources.getIdentifier("use_local_qt_libs", "string", packageName); - final int useLocalLibs = Integer.parseInt(resources.getString(id)); - - if (useLocalLibs == 1) { - ArrayList<String> libraryList = new ArrayList<>(); - String libsDir = null; - String bundledLibsDir = null; - - id = resources.getIdentifier("bundle_local_qt_libs", "string", packageName); - final int bundleLocalLibs = Integer.parseInt(resources.getString(id)); - if (bundleLocalLibs == 0) { - String systemLibsPrefix; - final String systemLibsKey = "android.app.system_libs_prefix"; - // First check if user has provided system libs prefix in AndroidManifest - if (m_contextInfo.applicationInfo.metaData != null && - m_contextInfo.applicationInfo.metaData.containsKey(systemLibsKey)) { - systemLibsPrefix = m_contextInfo.applicationInfo.metaData.getString(systemLibsKey); - } else { - // If not, check if it's provided by androiddeployqt in libs.xml - id = resources.getIdentifier("system_libs_prefix","string", - packageName); - systemLibsPrefix = resources.getString(id); - } - if (systemLibsPrefix.isEmpty()) { - systemLibsPrefix = SYSTEM_LIB_PATH; - Log.e(QtTAG, "It looks like app deployed using Unbundled " - + "deployment. It may be necessary to specify path to directory " - + "where Qt libraries are installed using either " - + "android.app.system_libs_prefix metadata variable in your " - + "AndroidManifest.xml or QT_ANDROID_SYSTEM_LIBS_PATH in your " - + "CMakeLists.txt"); - Log.e(QtTAG, "Using " + SYSTEM_LIB_PATH + " as default path"); - } + if (environmentVariables == null || environmentVariables.isEmpty()) + return; - File systemLibraryDir = new File(systemLibsPrefix); - if (systemLibraryDir.exists() && systemLibraryDir.isDirectory() - && systemLibraryDir.list().length > 0) { - libsDir = systemLibsPrefix; - bundledLibsDir = systemLibsPrefix; - } else { - Log.e(QtTAG, - "System library directory " + systemLibsPrefix - + " does not exist or is empty."); - } - } else { - String nativeLibraryPrefix = m_context.getApplicationInfo().nativeLibraryDir + "/"; - File nativeLibraryDir = new File(nativeLibraryPrefix); - if (nativeLibraryDir.exists() && nativeLibraryDir.isDirectory() && nativeLibraryDir.list().length > 0) { - libsDir = nativeLibraryPrefix; - bundledLibsDir = nativeLibraryPrefix; - } else { - Log.e(QtTAG, - "Native library directory " + nativeLibraryPrefix - + " does not exist or is empty."); - } + for (String variable : environmentVariables.split("\t")) { + String[] keyValue = variable.split("=", 2); + if (keyValue.length < 2 || keyValue[0].isEmpty()) + continue; + + setEnvironmentVariable(keyValue[0], keyValue[1]); + } + } + + /** + * Parses the native libraries dir. If the libraries are part of the APK, + * the path is set to the APK extracted libs path. + * Otherwise, it looks for the system level dir, that's either set in the Manifest, + * the deployment libs.xml. + * If none of the above are valid, it falls back to predefined system path. + **/ + private void parseNativeLibrariesDir() { + if (isBundleQtLibs()) { + String nativeLibraryPrefix = m_context.getApplicationInfo().nativeLibraryDir + "/"; + File nativeLibraryDir = new File(nativeLibraryPrefix); + if (nativeLibraryDir.exists()) { + String[] list = nativeLibraryDir.list(); + if (nativeLibraryDir.isDirectory() && list != null && list.length > 0) { + m_nativeLibrariesDir = nativeLibraryPrefix; } + } + } else { + // First check if user has provided system libs prefix in AndroidManifest + String systemLibsPrefix = getApplicationMetaData("android.app.system_libs_prefix"); + + // If not, check if it's provided by androiddeployqt in libs.xml + if (systemLibsPrefix.isEmpty()) + systemLibsPrefix = getSystemLibsPrefix(); + + if (systemLibsPrefix.isEmpty()) { + final String SYSTEM_LIB_PATH = "/system/lib/"; + systemLibsPrefix = SYSTEM_LIB_PATH; + Log.e(QtTAG, "Using " + SYSTEM_LIB_PATH + " as default libraries path. " + + "It looks like the app is deployed using Unbundled " + + "deployment. It may be necessary to specify the path to " + + "the directory where Qt libraries are installed using either " + + "android.app.system_libs_prefix metadata variable in your " + + "AndroidManifest.xml or QT_ANDROID_SYSTEM_LIBS_PATH in your " + + "CMakeLists.txt"); + } - if (libsDir == null) - throw new Exception(""); + File systemLibraryDir = new File(systemLibsPrefix); + String[] list = systemLibraryDir.list(); + if (systemLibraryDir.exists()) { + if (systemLibraryDir.isDirectory() && list != null && list.length > 0) + m_nativeLibrariesDir = systemLibsPrefix; + else + Log.e(QtTAG, "System library directory " + systemLibsPrefix + " is empty."); + } else { + Log.e(QtTAG, "System library directory " + systemLibsPrefix + " does not exist."); + } + } + if (m_nativeLibrariesDir != null && !m_nativeLibrariesDir.endsWith("/")) + m_nativeLibrariesDir += "/"; + } - if (m_qtLibs != null) { - String libPrefix = libsDir + "lib"; - for (String lib : m_qtLibs) - libraryList.add(libPrefix + lib + ".so"); - } + /** + * Returns the application level metadata. + **/ + private String getApplicationMetaData(String key) { + if (m_contextInfo == null) + return ""; - id = resources.getIdentifier("load_local_libs", "array", packageName); - ArrayList<String> localLibs = prefferedAbiLibs(resources.getStringArray(id)); - for (String libs : localLibs) { - for (String lib : libs.split(":")) { - if (!lib.isEmpty()) - libraryList.add(libsDir + lib); - } - } - if (bundledLibsDir != null) { - ENVIRONMENT_VARIABLES += "\tQT_PLUGIN_PATH=" + bundledLibsDir; - ENVIRONMENT_VARIABLES += "\tQML_PLUGIN_PATH=" + bundledLibsDir; - } + ApplicationInfo applicationInfo = m_contextInfo.applicationInfo; + if (applicationInfo == null) + return ""; - Bundle loaderParams = new Bundle(); - loaderParams.putString(DEX_PATH_KEY, new String()); + Bundle metadata = applicationInfo.metaData; + if (metadata == null || !metadata.containsKey(key)) + return ""; - id = resources.getIdentifier("static_init_classes", "string", packageName); - loaderParams.putStringArray(STATIC_INIT_CLASSES_KEY, resources.getString(id) - .split(":")); + return metadata.getString(key); + } - loaderParams.putStringArrayList(NATIVE_LIBRARIES_KEY, libraryList); + /** + * Returns the context level metadata. + **/ + protected String getMetaData(String key) { + if (m_contextInfo == null) + return ""; + Bundle metadata = m_contextInfo.metaData; + if (metadata == null || !metadata.containsKey(key)) + return ""; - String themePath = m_context.getApplicationInfo().dataDir + "/qt-reserved-files/android-style/"; - String stylePath = themePath + m_displayDensity + "/"; + return metadata.getString(key); + } - String extractOption = "default"; - if (m_contextInfo.metaData.containsKey("android.app.extract_android_style")) { - extractOption = m_contextInfo.metaData.getString("android.app.extract_android_style"); - if (!extractOption.equals("default") && !extractOption.equals("full") && !extractOption.equals("minimal") && !extractOption.equals("none")) { - Log.e(QtTAG, "Invalid extract_android_style option \"" + extractOption + "\", defaulting to \"default\""); - extractOption = "default"; - } - } + @SuppressLint("DiscouragedApi") + private ArrayList<String> getQtLibrariesList() { + int id = m_resources.getIdentifier("qt_libs", "array", m_packageName); + return preferredAbiLibs(m_resources.getStringArray(id)); + } - // QTBUG-69810: The extraction code will trigger compatibility warnings on Android SDK version >= 28 - // when the target SDK version is set to something lower then 28, so default to "none" and issue a warning - // if that is the case. - if (extractOption.equals("default")) { - final int targetSdkVersion = m_context.getApplicationInfo().targetSdkVersion; - if (targetSdkVersion < 28 && Build.VERSION.SDK_INT >= 28) { - Log.e(QtTAG, "extract_android_style option set to \"none\" when targetSdkVersion is less then 28"); - extractOption = "none"; - } - } + @SuppressLint("DiscouragedApi") + private boolean useLocalQtLibs() { + int id = m_resources.getIdentifier("use_local_qt_libs", "string", m_packageName); + return Integer.parseInt(m_resources.getString(id)) == 1; + } - if (!extractOption.equals("none")) { - loaderParams.putString(EXTRACT_STYLE_KEY, stylePath); - loaderParams.putBoolean(EXTRACT_STYLE_MINIMAL_KEY, extractOption.equals("minimal")); - } + @SuppressLint("DiscouragedApi") + private boolean isBundleQtLibs() { + int id = m_resources.getIdentifier("bundle_local_qt_libs", "string", m_packageName); + return Integer.parseInt(m_resources.getString(id)) == 1; + } - if (extractOption.equals("full")) - ENVIRONMENT_VARIABLES += "\tQT_USE_ANDROID_NATIVE_STYLE=1"; + @SuppressLint("DiscouragedApi") + private String getSystemLibsPrefix() { + int id = m_resources.getIdentifier("system_libs_prefix", "string", m_packageName); + return m_resources.getString(id); + } - ENVIRONMENT_VARIABLES += "\tANDROID_STYLE_PATH=" + stylePath; + @SuppressLint("DiscouragedApi") + private ArrayList<String> getLocalLibrariesList() { + int id = m_resources.getIdentifier("load_local_libs", "array", m_packageName); + return preferredAbiLibs(m_resources.getStringArray(id)); + } - if (m_contextInfo.metaData.containsKey("android.app.trace_location")) { - String loc = m_contextInfo.metaData.getString("android.app.trace_location"); - ENVIRONMENT_VARIABLES += "\tQTRACE_LOCATION="+loc; - } + @SuppressLint("DiscouragedApi") + private ArrayList<String> getStaticInitClasses() { + int id = m_resources.getIdentifier("static_init_classes", "string", m_packageName); + String[] classes = m_resources.getString(id).split(":"); + ArrayList<String> finalClasses = new ArrayList<>(); + for (String element : classes) { + if (!element.isEmpty()) { + finalClasses.add(element); + } + } + return finalClasses; + } - loaderParams.putString(ENVIRONMENT_VARIABLES_KEY, ENVIRONMENT_VARIABLES); + @SuppressLint("DiscouragedApi") + private String[] getBundledLibs() { + int id = m_resources.getIdentifier("bundled_libs", "array", m_packageName); + return m_resources.getStringArray(id); + } - String appParams = null; - if (APPLICATION_PARAMETERS != null) - appParams = APPLICATION_PARAMETERS; + /** + * Loads all Qt native bundled libraries and main library. + **/ + public void loadQtLibraries() { + if (!useLocalQtLibs()) { + Log.w(QtTAG, "Use local Qt libs is false"); + finish(); + return; + } - Intent intent = getIntent(); - if (intent != null) { - String parameters = intent.getStringExtra("applicationArguments"); - if (parameters != null) - if (appParams == null) - appParams = parameters; - else - appParams += '\t' + parameters; - } + if (m_nativeLibrariesDir == null) + parseNativeLibrariesDir(); - if (m_contextInfo.metaData.containsKey("android.app.arguments")) { - String parameters = m_contextInfo.metaData.getString("android.app.arguments"); - if (appParams == null) - appParams = parameters; - else - appParams += '\t' + parameters; - } + if (m_nativeLibrariesDir == null || m_nativeLibrariesDir.isEmpty()) { + Log.e(QtTAG, "The native libraries directory is null or empty"); + finish(); + return; + } + + setEnvironmentVariable("QT_PLUGIN_PATH", m_nativeLibrariesDir); + setEnvironmentVariable("QML_PLUGIN_PATH", m_nativeLibrariesDir); - if (appParams != null) - loaderParams.putString(APPLICATION_PARAMETERS_KEY, appParams); + // Load native Qt APK libraries + ArrayList<String> nativeLibraries = getQtLibrariesList(); + nativeLibraries.addAll(getLocalLibrariesList()); - loadApplication(loaderParams); - return; + if (!loadLibraries(nativeLibraries)) { + Log.e(QtTAG, "Loading Qt native libraries failed"); + finish(); + return; + } + + // add all bundled Qt libs to loader params + ArrayList<String> bundledLibraries = new ArrayList<>(preferredAbiLibs(getBundledLibs())); + if (!loadLibraries(bundledLibraries)) { + Log.e(QtTAG, "Loading Qt bundled libraries failed"); + finish(); + return; + } + + // Load main lib + if (!loadMainLibrary(getMetaData("android.app.lib_name") + "_" + m_preferredAbi)) { + Log.e(QtTAG, "Loading main library failed"); + finish(); + } + } + + // Loading libraries using System.load() uses full lib paths + @SuppressLint("UnsafeDynamicallyLoadedCode") + private String loadLibraryHelper(String library) + { + String loadedLib = null; + try { + File libFile = new File(library); + if (libFile.exists()) { + System.load(library); + loadedLib = library; + } else { + Log.i(QtTAG, "Can't find '" + library + "'"); } } catch (Exception e) { - Log.e(QtTAG, "Can't create main activity", e); + Log.i(QtTAG, "Can't load '" + library + "'", e); } + + return loadedLib; + } + + /** + * Returns an array with absolute library paths from a list of file names only. + **/ + private ArrayList<String> getLibrariesFullPaths(final ArrayList<String> libraries) + { + if (libraries == null) + return null; + + ArrayList<String> absolutePathLibraries = new ArrayList<>(); + for (String libName : libraries) { + if (!libName.startsWith("lib")) + libName = "lib" + libName; + if (!libName.endsWith(".so")) + libName = libName + ".so"; + File file = new File(m_nativeLibrariesDir + libName); + absolutePathLibraries.add(file.getAbsolutePath()); + } + + return absolutePathLibraries; + } + + /** + * Loads the main library. + * Returns true if loading was successful, and sets the absolute + * path to the main library. Otherwise, returns false and the path + * to the main library is null. + **/ + private boolean loadMainLibrary(String mainLibName) + { + ArrayList<String> oneEntryArray = new ArrayList<>(Collections.singletonList(mainLibName)); + String mainLibPath = getLibrariesFullPaths(oneEntryArray).get(0); + final boolean[] success = {true}; + QtNative.getQtThread().run(new Runnable() { + @Override + public void run() { + m_mainLib = loadLibraryHelper(mainLibPath); + if (m_mainLib == null) + success[0] = false; + } + }); + + return success[0]; + } + + /** + * Loads a list of libraries. + * Returns true if all libraries were loaded successfully, + * and false if any library failed. Stops loading at the first failure. + **/ + @SuppressWarnings("BooleanMethodIsAlwaysInverted") + private boolean loadLibraries(final ArrayList<String> libraries) + { + if (libraries == null) + return false; + + ArrayList<String> fullPathLibs = getLibrariesFullPaths(libraries); + + final boolean[] success = {true}; + QtNative.getQtThread().run(new Runnable() { + @Override + public void run() { + for (int i = 0; i < fullPathLibs.size(); ++i) { + String libName = fullPathLibs.get(i); + if (loadLibraryHelper(libName) == null) { + success[0] = false; + break; + } + } + } + }); + + return success[0]; } } |