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) {
122 final boolean isDebuggable = (
flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
123 Intent intent = ((Activity)
context).getIntent();
124 if (isDebuggable && intent !=
null && intent.hasExtra(
"applicationArguments"))
125 appendApplicationParameters(intent.getStringExtra(
"applicationArguments"));
129 private String isBackgroundRunningBlocked() {
130 final String backgroundRunning = getMetaData(
"android.app.background_running");
131 if (backgroundRunning.compareTo(
"true") == 0)
136 private ArrayList<String> preferredAbiLibs(
String[] libs) {
137 ArrayList<String> abiLibs =
new ArrayList<>();
140 String[] splits = lib.split(
";", 2);
142 if (splits ==
null || splits.length < 2)
146 if (!splits[0].equals(m_preferredAbi) || splits[1].isEmpty())
149 abiLibs.add(splits[1]);
155 @SuppressLint(
"DiscouragedApi")
159 int id = m_resources.getIdentifier(
"qt_libs",
"array", m_packageName);
160 String[] libs = m_resources.getStringArray(
id);
161 Set<String> uniqueAbis =
new HashSet<>();
164 String[] splits = lib.split(
";", 2);
166 if (splits.length < 2)
169 uniqueAbis.add(splits[0].
trim());
172 String fallbackAbi =
null;
173 boolean is64Bit = Process.is64Bit();
174 for (
String abi : Build.SUPPORTED_ABIS) {
175 if (!uniqueAbis.contains(abi))
178 if (abi.contains(
"64") == is64Bit)
181 if (fallbackAbi ==
null)
185 if (fallbackAbi !=
null)
188 final String packagedAbis =
"[" +
String.join(
", ", uniqueAbis) +
"]";
189 final String deviceAbis =
"[" +
String.join(
", ", Build.SUPPORTED_ABIS) +
"]";
190 Log.w(QtTAG,
"No packaged library ABIs " + packagedAbis +
" match the device ABIs "
191 + deviceAbis +
", falling back to " + Build.SUPPORTED_ABIS[0] +
".");
192 }
catch (Resources.NotFoundException ignored) { }
194 return Build.SUPPORTED_ABIS[0];
206 m_classLoader =
new DexClassLoader(sourceDir, outDexPath,
null,
context.getClassLoader());
207 QtNative.setClassLoader(m_classLoader);
214 public String getMainLibraryPath() {
215 return m_mainLibPath;
225 public void setMainLibraryName(
String libName) {
226 m_mainLibName = libName;
234 public String getApplicationParameters() {
235 return m_applicationParameters;
247 if (!m_applicationParameters.isEmpty())
248 m_applicationParameters +=
" ";
249 m_applicationParameters +=
params;
258 android.system.Os.setenv(
key,
value,
true);
259 m_environmentVariables.put(
key,
value);
260 }
catch (Exception e) {
261 Log.e(QtTAG,
"Could not set environment variable:" +
key +
"=" +
value);
270 public void setEnvironmentVariables(
String environmentVariables)
272 if (environmentVariables ==
null || environmentVariables.isEmpty())
277 if (keyValue.length < 2 || keyValue[0].isEmpty())
280 setEnvironmentVariable(keyValue[0], keyValue[1]);
291 private void parseNativeLibrariesDir() {
292 if (m_contextInfo ==
null)
294 if (isBundleQtLibs()) {
295 String nativeLibraryPrefix = m_contextInfo.applicationInfo.nativeLibraryDir +
"/";
296 File nativeLibraryDir =
new File(nativeLibraryPrefix);
297 if (nativeLibraryDir.exists()) {
299 if (nativeLibraryDir.isDirectory() &&
list !=
null &&
list.
length > 0) {
300 m_extractedNativeLibsDir = nativeLibraryPrefix;
305 String systemLibsPrefix = getApplicationMetaData(
"android.app.system_libs_prefix");
308 if (systemLibsPrefix.isEmpty())
309 systemLibsPrefix = getSystemLibsPrefix();
311 if (systemLibsPrefix.isEmpty()) {
312 final String SYSTEM_LIB_PATH =
"/system/lib/";
313 systemLibsPrefix = SYSTEM_LIB_PATH;
314 Log.e(QtTAG,
"Using " + SYSTEM_LIB_PATH +
" as default libraries path. "
315 +
"It looks like the app is deployed using Unbundled "
316 +
"deployment. It may be necessary to specify the path to "
317 +
"the directory where Qt libraries are installed using either "
318 +
"android.app.system_libs_prefix metadata variable in your "
319 +
"AndroidManifest.xml or QT_ANDROID_SYSTEM_LIBS_PATH in your "
323 File systemLibraryDir =
new File(systemLibsPrefix);
325 if (systemLibraryDir.exists()) {
326 if (systemLibraryDir.isDirectory() &&
list !=
null &&
list.
length > 0)
327 m_extractedNativeLibsDir = systemLibsPrefix;
329 Log.e(QtTAG,
"System library directory " + systemLibsPrefix +
" is empty.");
331 Log.e(QtTAG,
"System library directory " + systemLibsPrefix +
" does not exist.");
335 if (m_extractedNativeLibsDir !=
null && !m_extractedNativeLibsDir.endsWith(
"/"))
336 m_extractedNativeLibsDir +=
"/";
344 ApplicationInfo applicationInfo = m_contextInfo.applicationInfo;
345 if (applicationInfo ==
null)
348 Bundle metadata = applicationInfo.metaData;
349 if (metadata ==
null || !metadata.containsKey(
key))
352 return metadata.getString(
key);
359 if (m_contextInfo ==
null)
362 Bundle metadata = m_contextInfo.metaData;
363 if (metadata ==
null || !metadata.containsKey(
key))
366 return String.valueOf(metadata.get(
key));
369 @SuppressLint(
"DiscouragedApi")
372 int id = m_resources.getIdentifier(
"qt_libs",
"array", m_packageName);
373 return preferredAbiLibs(m_resources.getStringArray(
id));
374 }
catch (Resources.NotFoundException ignored) {
375 return new ArrayList<>();
379 @SuppressLint(
"DiscouragedApi")
380 private boolean useLocalQtLibs() {
382 int id = m_resources.getIdentifier(
"use_local_qt_libs",
"string", m_packageName);
383 return Integer.parseInt(m_resources.getString(
id)) == 1;
384 }
catch (Resources.NotFoundException ignored) {
389 @SuppressLint(
"DiscouragedApi")
390 private boolean isBundleQtLibs() {
392 int id = m_resources.getIdentifier(
"bundle_local_qt_libs",
"string", m_packageName);
393 return Integer.parseInt(m_resources.getString(
id)) == 1;
394 }
catch (Resources.NotFoundException ignored) {
399 @SuppressLint(
"DiscouragedApi")
402 int id = m_resources.getIdentifier(
"system_libs_prefix",
"string", m_packageName);
403 return m_resources.getString(
id);
404 }
catch (Resources.NotFoundException ignored) {
409 @SuppressLint(
"DiscouragedApi")
411 ArrayList<String> localLibs =
new ArrayList<>();
413 int id = m_resources.getIdentifier(
"load_local_libs",
"array", m_packageName);
414 for (
String arrayItem : preferredAbiLibs(m_resources.getStringArray(
id))) {
415 Collections.addAll(localLibs, arrayItem.split(
":"));
417 }
catch (Resources.NotFoundException ignored) { }
421 @SuppressLint(
"DiscouragedApi")
424 int id = m_resources.getIdentifier(
"bundled_libs",
"array", m_packageName);
425 return m_resources.getStringArray(
id);
426 }
catch (Resources.NotFoundException ignored) {
427 return new String[0];
434 private static boolean isUncompressedNativeLibs()
438 Log.w(QtTAG,
"isUncompressedNativeLibs() called before a valid context was set.");
442 return (
flags & ApplicationInfo.FLAG_EXTRACT_NATIVE_LIBS) == 0;
449 private String getApkNativeLibrariesDir()
451 String apkFilePath = QtApkFileEngine.getAppApkFilePath();
452 if (apkFilePath ==
null)
454 return apkFilePath +
"!/lib/" + m_preferredAbi +
"/";
463 if (m_librariesLoaded)
466 if (!useLocalQtLibs()) {
467 Log.w(QtTAG,
"Use local Qt libs is false");
471 if (isUncompressedNativeLibs()) {
472 String apkLibPath = getApkNativeLibrariesDir();
473 if (apkLibPath ==
null) {
474 Log.e(QtTAG,
"Failed to resolve the APK native libraries directory");
477 setEnvironmentVariable(
"QT_PLUGIN_PATH", apkLibPath);
478 setEnvironmentVariable(
"QML_PLUGIN_PATH", apkLibPath);
480 parseNativeLibrariesDir();
481 if (m_extractedNativeLibsDir ==
null || m_extractedNativeLibsDir.isEmpty()) {
482 Log.e(QtTAG,
"The native libraries directory is null or empty");
485 setEnvironmentVariable(
"QT_PLUGIN_PATH", m_extractedNativeLibsDir);
486 setEnvironmentVariable(
"QML_PLUGIN_PATH", m_extractedNativeLibsDir);
490 ArrayList<String> nativeLibraries = getQtLibrariesList();
491 nativeLibraries.addAll(getLocalLibrariesList());
493 if (
Debug.isDebuggerConnected()) {
494 final String debuggerSleepEnvVarName =
"QT_ANDROID_DEBUGGER_MAIN_THREAD_SLEEP_MS";
495 int debuggerSleepMs = 3000;
496 if (Os.getenv(debuggerSleepEnvVarName) !=
null) {
498 debuggerSleepMs =
Integer.parseInt(Os.getenv(debuggerSleepEnvVarName));
499 }
catch (NumberFormatException ignored) {
503 if (debuggerSleepMs > 0) {
504 Log.i(QtTAG,
"Sleeping for " + debuggerSleepMs +
505 "ms, helping the native debugger to settle. " +
506 "Use the env " + debuggerSleepEnvVarName +
507 " variable to change this value.");
508 QtNative.getQtThread().sleep(debuggerSleepMs);
512 if (!loadLibraries(nativeLibraries)) {
513 Log.e(QtTAG,
"Loading Qt native libraries failed");
518 ArrayList<String> bundledLibraries =
new ArrayList<>(preferredAbiLibs(getBundledLibs()));
519 if (!loadLibraries(bundledLibraries)) {
520 Log.e(QtTAG,
"Loading Qt bundled libraries failed");
524 if (m_mainLibName ==
null)
525 m_mainLibName = getMetaData(
"android.app.lib_name");
527 if (m_mainLibName ==
null || m_mainLibName.isEmpty()) {
528 Log.e(QtTAG,
"The main library name is null or empty.");
533 if (!loadMainLibrary(m_mainLibName +
"_" + m_preferredAbi)) {
534 Log.e(QtTAG,
"Loading main library failed");
537 m_librariesLoaded =
true;
543 @SuppressLint(
"UnsafeDynamicallyLoadedCode")
549 if (library.startsWith(
"/")) {
550 if (libFile.exists()) {
554 Log.e(QtTAG,
"Can't find '" + library +
"'");
557 System.loadLibrary(library);
560 }
catch (Exception | UnsatisfiedLinkError e) {
561 Log.e(QtTAG,
"Can't load '" + library +
"'", e);
570 private ArrayList<String> getLibrariesFullPaths(
final ArrayList<String> libraries)
572 if (libraries ==
null)
575 ArrayList<String> absolutePathLibraries =
new ArrayList<>();
576 for (
String libName : libraries) {
579 if (isUncompressedNativeLibs()) {
580 if (libName.endsWith(
".so"))
581 libName = libName.substring(3, libName.length() - 3);
582 absolutePathLibraries.add(libName);
584 if (!libName.endsWith(
".so"))
585 libName =
"lib" + libName +
".so";
586 File file =
new File(m_extractedNativeLibsDir + libName);
587 absolutePathLibraries.add(
file.getAbsolutePath());
591 return absolutePathLibraries;
600 private boolean loadMainLibrary(
String mainLibName)
602 ArrayList<String> oneEntryArray =
new ArrayList<>(Collections.singletonList(mainLibName));
603 String mainLibPath = getLibrariesFullPaths(oneEntryArray).get(0);
604 QtNative.getQtThread().run(() -> {
605 m_mainLibPath = loadLibraryHelper(mainLibPath);
606 if (m_mainLibPath !=
null && isUncompressedNativeLibs())
607 m_mainLibPath = getApkNativeLibrariesDir() +
"lib" + m_mainLibPath +
".so";
610 return m_mainLibPath !=
null;
618 @SuppressWarnings(
"BooleanMethodIsAlwaysInverted")
619 private boolean loadLibraries(final ArrayList<
String> libraries)
621 if (libraries ==
null)
624 ArrayList<String> fullPathLibs = getLibrariesFullPaths(libraries);
626 if (libraries.size() != fullPathLibs.size()) {
627 Log.e(QtTAG,
"Failed to get full paths of libraries.");
631 final boolean[] success = {
true};
632 QtNative.getQtThread().run(() -> {
633 for (
int i = 0;
i < fullPathLibs.size(); ++
i) {
634 String libName = fullPathLibs.get(i);
635 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