6package org.qtproject.qt.android;
8import android.annotation.SuppressLint;
9import android.app.Activity;
10import android.app.Service;
11import android.content.Intent;
12import android.content.ComponentName;
13import android.content.Context;
14import android.content.ContextWrapper;
15import android.content.pm.ApplicationInfo;
16import android.content.pm.PackageManager;
17import android.content.pm.ComponentInfo;
18import android.content.pm.PackageManager.NameNotFoundException;
19import android.content.res.Resources;
20import android.os.Build;
21import android.os.Bundle;
22import android.os.Debug;
23import android.os.Process;
24import android.system.Os;
25import android.util.Log;
28import java.lang.IllegalArgumentException;
29import java.util.ArrayList;
30import java.util.Collections;
31import java.util.HashMap;
32import java.util.Objects;
33import java.util.HashSet;
36import dalvik.system.DexClassLoader;
38abstract class QtLoader {
40 protected static final String QtTAG =
"QtLoader";
42 private final Resources m_resources;
43 private final String m_packageName;
44 private final String m_preferredAbi;
45 private String m_extractedNativeLibsDir =
null;
47 private ClassLoader m_classLoader;
49 protected ComponentInfo m_contextInfo;
51 protected String m_mainLibPath;
52 protected String m_mainLibName;
53 protected String m_applicationParameters =
"";
54 protected final HashMap<String, String> m_environmentVariables =
new HashMap<>();
56 protected static QtLoader m_instance =
null;
57 protected boolean m_librariesLoaded;
69 QtLoader(ContextWrapper
context)
throws IllegalArgumentException {
70 m_resources =
context.getResources();
71 m_packageName =
context.getPackageName();
73 if (!(baseContext instanceof Activity || baseContext instanceof Service)) {
74 throw new IllegalArgumentException(
"QtLoader: Context is not an instance of " +
75 "Activity or Service");
78 initClassLoader(baseContext);
80 initContextInfo(baseContext);
81 }
catch (NameNotFoundException e) {
82 throw new IllegalArgumentException(
"QtLoader: No ComponentInfo found for given " +
85 m_preferredAbi = resolvePreferredAbi();
93 protected void initContextInfo(
Context context)
throws NameNotFoundException {
94 if (
context instanceof Activity) {
95 m_contextInfo =
context.getPackageManager().getActivityInfo(
96 ((Activity)
context).getComponentName(), PackageManager.GET_META_DATA);
97 }
else if (
context instanceof Service) {
98 m_contextInfo =
context.getPackageManager().getServiceInfo(
100 PackageManager.GET_META_DATA);
110 setEnvironmentVariable(
"QT_ANDROID_FONTS",
"Roboto;Droid Sans;Droid Sans Fallback");
111 String monospaceFonts =
"Droid Sans Mono;Droid Sans;Droid Sans Fallback";
112 setEnvironmentVariable(
"QT_ANDROID_FONTS_MONOSPACE", monospaceFonts);
113 setEnvironmentVariable(
"QT_ANDROID_FONTS_SERIF",
"Droid Serif");
114 setEnvironmentVariable(
"HOME",
context.getFilesDir().getAbsolutePath());
115 setEnvironmentVariable(
"TMPDIR",
context.getCacheDir().getAbsolutePath());
116 setEnvironmentVariable(
"QT_BLOCK_EVENT_LOOPS_WHEN_SUSPENDED", isBackgroundRunningBlocked());
117 setEnvironmentVariable(
"QTRACE_LOCATION", getMetaData(
"android.app.trace_location"));
118 appendApplicationParameters(getMetaData(
"android.app.arguments"));
120 if (
context instanceof Activity) {
121 Intent intent = ((Activity)
context).getIntent();
123 appendApplicationParameters(intent.getStringExtra(
"applicationArguments"));
127 private String isBackgroundRunningBlocked() {
128 final String backgroundRunning = getMetaData(
"android.app.background_running");
129 if (backgroundRunning.compareTo(
"true") == 0)
134 private ArrayList<String> preferredAbiLibs(
String[] libs) {
135 ArrayList<String> abiLibs =
new ArrayList<>();
138 String[] splits = lib.split(
";", 2);
140 if (splits ==
null || splits.length < 2)
144 if (!splits[0].equals(m_preferredAbi) || splits[1].isEmpty())
147 abiLibs.add(splits[1]);
153 @SuppressLint(
"DiscouragedApi")
157 int id = m_resources.getIdentifier(
"qt_libs",
"array", m_packageName);
158 String[] libs = m_resources.getStringArray(
id);
159 Set<String> uniqueAbis =
new HashSet<>();
162 String[] splits = lib.split(
";", 2);
164 if (splits.length < 2)
167 uniqueAbis.add(splits[0].
trim());
170 String fallbackAbi =
null;
171 boolean is64Bit = Process.is64Bit();
172 for (
String abi : Build.SUPPORTED_ABIS) {
173 if (!uniqueAbis.contains(abi))
176 if (abi.contains(
"64") == is64Bit)
179 if (fallbackAbi ==
null)
183 if (fallbackAbi !=
null)
185 }
catch (Resources.NotFoundException ignored) { }
187 return Build.SUPPORTED_ABIS[0];
199 m_classLoader =
new DexClassLoader(sourceDir, outDexPath,
null,
context.getClassLoader());
200 QtNative.setClassLoader(m_classLoader);
207 public String getMainLibraryPath() {
208 return m_mainLibPath;
218 public void setMainLibraryName(
String libName) {
219 m_mainLibName = libName;
227 public String getApplicationParameters() {
228 return m_applicationParameters;
240 if (!m_applicationParameters.isEmpty())
241 m_applicationParameters +=
" ";
242 m_applicationParameters +=
params;
251 android.system.Os.setenv(
key,
value,
true);
252 m_environmentVariables.put(
key,
value);
253 }
catch (Exception e) {
254 Log.e(QtTAG,
"Could not set environment variable:" +
key +
"=" +
value);
264 public void setEnvironmentVariables(
String environmentVariables)
266 if (environmentVariables ==
null || environmentVariables.isEmpty())
269 environmentVariables = environmentVariables.replaceAll(
"\t",
" ");
273 if (keyValue.length < 2 || keyValue[0].isEmpty())
276 setEnvironmentVariable(keyValue[0], keyValue[1]);
287 private void parseNativeLibrariesDir() {
288 if (m_contextInfo ==
null)
290 if (isBundleQtLibs()) {
291 String nativeLibraryPrefix = m_contextInfo.applicationInfo.nativeLibraryDir +
"/";
292 File nativeLibraryDir =
new File(nativeLibraryPrefix);
293 if (nativeLibraryDir.exists()) {
295 if (nativeLibraryDir.isDirectory() &&
list !=
null &&
list.
length > 0) {
296 m_extractedNativeLibsDir = nativeLibraryPrefix;
301 String systemLibsPrefix = getApplicationMetaData(
"android.app.system_libs_prefix");
304 if (systemLibsPrefix.isEmpty())
305 systemLibsPrefix = getSystemLibsPrefix();
307 if (systemLibsPrefix.isEmpty()) {
308 final String SYSTEM_LIB_PATH =
"/system/lib/";
309 systemLibsPrefix = SYSTEM_LIB_PATH;
310 Log.e(QtTAG,
"Using " + SYSTEM_LIB_PATH +
" as default libraries path. "
311 +
"It looks like the app is deployed using Unbundled "
312 +
"deployment. It may be necessary to specify the path to "
313 +
"the directory where Qt libraries are installed using either "
314 +
"android.app.system_libs_prefix metadata variable in your "
315 +
"AndroidManifest.xml or QT_ANDROID_SYSTEM_LIBS_PATH in your "
319 File systemLibraryDir =
new File(systemLibsPrefix);
321 if (systemLibraryDir.exists()) {
322 if (systemLibraryDir.isDirectory() &&
list !=
null &&
list.
length > 0)
323 m_extractedNativeLibsDir = systemLibsPrefix;
325 Log.e(QtTAG,
"System library directory " + systemLibsPrefix +
" is empty.");
327 Log.e(QtTAG,
"System library directory " + systemLibsPrefix +
" does not exist.");
331 if (m_extractedNativeLibsDir !=
null && !m_extractedNativeLibsDir.endsWith(
"/"))
332 m_extractedNativeLibsDir +=
"/";
340 ApplicationInfo applicationInfo = m_contextInfo.applicationInfo;
341 if (applicationInfo ==
null)
344 Bundle metadata = applicationInfo.metaData;
345 if (metadata ==
null || !metadata.containsKey(
key))
348 return metadata.getString(
key);
355 if (m_contextInfo ==
null)
358 Bundle metadata = m_contextInfo.metaData;
359 if (metadata ==
null || !metadata.containsKey(
key))
362 return String.valueOf(metadata.get(
key));
365 @SuppressLint(
"DiscouragedApi")
368 int id = m_resources.getIdentifier(
"qt_libs",
"array", m_packageName);
369 return preferredAbiLibs(m_resources.getStringArray(
id));
370 }
catch (Resources.NotFoundException ignored) {
371 return new ArrayList<>();
375 @SuppressLint(
"DiscouragedApi")
376 private boolean useLocalQtLibs() {
378 int id = m_resources.getIdentifier(
"use_local_qt_libs",
"string", m_packageName);
379 return Integer.parseInt(m_resources.getString(
id)) == 1;
380 }
catch (Resources.NotFoundException ignored) {
385 @SuppressLint(
"DiscouragedApi")
386 private boolean isBundleQtLibs() {
388 int id = m_resources.getIdentifier(
"bundle_local_qt_libs",
"string", m_packageName);
389 return Integer.parseInt(m_resources.getString(
id)) == 1;
390 }
catch (Resources.NotFoundException ignored) {
395 @SuppressLint(
"DiscouragedApi")
398 int id = m_resources.getIdentifier(
"system_libs_prefix",
"string", m_packageName);
399 return m_resources.getString(
id);
400 }
catch (Resources.NotFoundException ignored) {
405 @SuppressLint(
"DiscouragedApi")
407 ArrayList<String> localLibs =
new ArrayList<>();
409 int id = m_resources.getIdentifier(
"load_local_libs",
"array", m_packageName);
410 for (
String arrayItem : preferredAbiLibs(m_resources.getStringArray(
id))) {
411 Collections.addAll(localLibs, arrayItem.split(
":"));
413 }
catch (Resources.NotFoundException ignored) { }
417 @SuppressLint(
"DiscouragedApi")
420 int id = m_resources.getIdentifier(
"bundled_libs",
"array", m_packageName);
421 return m_resources.getStringArray(
id);
422 }
catch (Resources.NotFoundException ignored) {
423 return new String[0];
430 private static boolean isUncompressedNativeLibs()
432 int flags = QtNative.getContext().getApplicationInfo().flags;
433 return (
flags & ApplicationInfo.FLAG_EXTRACT_NATIVE_LIBS) == 0;
439 private String getApkNativeLibrariesDir()
441 return QtApkFileEngine.getAppApkFilePath() +
"!/lib/" + m_preferredAbi +
"/";
450 if (m_librariesLoaded)
453 if (!useLocalQtLibs()) {
454 Log.w(QtTAG,
"Use local Qt libs is false");
458 if (isUncompressedNativeLibs()) {
459 String apkLibPath = getApkNativeLibrariesDir();
460 setEnvironmentVariable(
"QT_PLUGIN_PATH", apkLibPath);
461 setEnvironmentVariable(
"QML_PLUGIN_PATH", apkLibPath);
463 parseNativeLibrariesDir();
464 if (m_extractedNativeLibsDir ==
null || m_extractedNativeLibsDir.isEmpty()) {
465 Log.e(QtTAG,
"The native libraries directory is null or empty");
468 setEnvironmentVariable(
"QT_PLUGIN_PATH", m_extractedNativeLibsDir);
469 setEnvironmentVariable(
"QML_PLUGIN_PATH", m_extractedNativeLibsDir);
473 ArrayList<String> nativeLibraries = getQtLibrariesList();
474 nativeLibraries.addAll(getLocalLibrariesList());
476 if (
Debug.isDebuggerConnected()) {
477 final String debuggerSleepEnvVarName =
"QT_ANDROID_DEBUGGER_MAIN_THREAD_SLEEP_MS";
478 int debuggerSleepMs = 3000;
479 if (Os.getenv(debuggerSleepEnvVarName) !=
null) {
481 debuggerSleepMs =
Integer.parseInt(Os.getenv(debuggerSleepEnvVarName));
482 }
catch (NumberFormatException ignored) {
486 if (debuggerSleepMs > 0) {
487 Log.i(QtTAG,
"Sleeping for " + debuggerSleepMs +
488 "ms, helping the native debugger to settle. " +
489 "Use the env " + debuggerSleepEnvVarName +
490 " variable to change this value.");
491 QtNative.getQtThread().sleep(debuggerSleepMs);
495 if (!loadLibraries(nativeLibraries)) {
496 Log.e(QtTAG,
"Loading Qt native libraries failed");
501 ArrayList<String> bundledLibraries =
new ArrayList<>(preferredAbiLibs(getBundledLibs()));
502 if (!loadLibraries(bundledLibraries)) {
503 Log.e(QtTAG,
"Loading Qt bundled libraries failed");
507 if (m_mainLibName ==
null)
508 m_mainLibName = getMetaData(
"android.app.lib_name");
510 if (m_mainLibName ==
null || m_mainLibName.isEmpty()) {
511 Log.e(QtTAG,
"The main library name is null or empty.");
516 if (!loadMainLibrary(m_mainLibName +
"_" + m_preferredAbi)) {
517 Log.e(QtTAG,
"Loading main library failed");
520 m_librariesLoaded =
true;
526 @SuppressLint(
"UnsafeDynamicallyLoadedCode")
532 if (library.startsWith(
"/")) {
533 if (libFile.exists()) {
537 Log.e(QtTAG,
"Can't find '" + library +
"'");
540 System.loadLibrary(library);
543 }
catch (Exception e) {
544 Log.e(QtTAG,
"Can't load '" + library +
"'", e);
553 private ArrayList<String> getLibrariesFullPaths(
final ArrayList<String> libraries)
555 if (libraries ==
null)
558 ArrayList<String> absolutePathLibraries =
new ArrayList<>();
559 for (
String libName : libraries) {
562 if (isUncompressedNativeLibs()) {
563 if (libName.endsWith(
".so"))
564 libName = libName.substring(3, libName.length() - 3);
565 absolutePathLibraries.add(libName);
567 if (!libName.endsWith(
".so"))
568 libName =
"lib" + libName +
".so";
569 File file =
new File(m_extractedNativeLibsDir + libName);
570 absolutePathLibraries.add(
file.getAbsolutePath());
574 return absolutePathLibraries;
583 private boolean loadMainLibrary(
String mainLibName)
585 ArrayList<String> oneEntryArray =
new ArrayList<>(Collections.singletonList(mainLibName));
586 String mainLibPath = getLibrariesFullPaths(oneEntryArray).get(0);
587 final boolean[] success = {
true};
588 QtNative.getQtThread().run(() -> {
589 m_mainLibPath = loadLibraryHelper(mainLibPath);
590 if (m_mainLibPath ==
null)
592 else if (isUncompressedNativeLibs())
593 m_mainLibPath = getApkNativeLibrariesDir() +
"lib" + m_mainLibPath +
".so";
604 @SuppressWarnings(
"BooleanMethodIsAlwaysInverted")
605 private boolean loadLibraries(final ArrayList<
String> libraries)
607 if (libraries ==
null)
610 ArrayList<String> fullPathLibs = getLibrariesFullPaths(libraries);
612 if (libraries.size() != fullPathLibs.size()) {
613 Log.e(QtTAG,
"Failed to get full paths of libraries.");
617 final boolean[] success = {
true};
618 QtNative.getQtThread().run(() -> {
619 for (
int i = 0;
i < fullPathLibs.size(); ++
i) {
620 String libName = fullPathLibs.get(i);
621 if (loadLibraryHelper(libName) ==
null) {
constexpr qsizetype length() const noexcept
static const QString context()
QMap< Name, StatePointer > Bundle
std::string trim(std::string const &str)
Returns a new string without whitespace at the start/end.
EGLOutputLayerEXT EGLint EGLAttrib value
[3]
decltype(openFileForWriting({})) File