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<>();
137 String[] splits = lib.split(
";", 2);
139 if (splits ==
null || splits.length < 2)
142 if (!splits[0].equals(m_preferredAbi))
145 abiLibs.add(splits[1]);
151 @SuppressLint(
"DiscouragedApi")
154 int id = m_resources.getIdentifier(
"qt_libs",
"array", m_packageName);
155 String[] libs = m_resources.getStringArray(
id);
156 Set<String> uniqueAbis =
new HashSet<>();
159 String[] splits = lib.split(
";", 2);
161 if (splits ==
null || splits.length < 2)
164 uniqueAbis.add(splits[0]);
167 boolean is64Bit = Process.is64Bit();
168 for (
String abi : Build.SUPPORTED_ABIS) {
169 if (uniqueAbis.contains(abi) && abi.contains(
"64") == is64Bit)
173 return Build.SUPPORTED_ABIS[0];
185 m_classLoader =
new DexClassLoader(sourceDir, outDexPath,
null,
context.getClassLoader());
186 QtNative.setClassLoader(m_classLoader);
193 public String getMainLibraryPath() {
194 return m_mainLibPath;
204 public void setMainLibraryName(
String libName) {
205 m_mainLibName = libName;
213 public String getApplicationParameters() {
214 return m_applicationParameters;
226 if (!m_applicationParameters.isEmpty())
227 m_applicationParameters +=
" ";
228 m_applicationParameters +=
params;
237 android.system.Os.setenv(
key,
value,
true);
238 m_environmentVariables.put(
key,
value);
239 }
catch (Exception e) {
240 Log.e(QtTAG,
"Could not set environment variable:" +
key +
"=" +
value);
250 public void setEnvironmentVariables(
String environmentVariables)
252 if (environmentVariables ==
null || environmentVariables.isEmpty())
255 environmentVariables = environmentVariables.replaceAll(
"\t",
" ");
259 if (keyValue.length < 2 || keyValue[0].isEmpty())
262 setEnvironmentVariable(keyValue[0], keyValue[1]);
273 private void parseNativeLibrariesDir() {
274 if (m_contextInfo ==
null)
276 if (isBundleQtLibs()) {
277 String nativeLibraryPrefix = m_contextInfo.applicationInfo.nativeLibraryDir +
"/";
278 File nativeLibraryDir =
new File(nativeLibraryPrefix);
279 if (nativeLibraryDir.exists()) {
281 if (nativeLibraryDir.isDirectory() &&
list !=
null &&
list.
length > 0) {
282 m_extractedNativeLibsDir = nativeLibraryPrefix;
287 String systemLibsPrefix = getApplicationMetaData(
"android.app.system_libs_prefix");
290 if (systemLibsPrefix.isEmpty())
291 systemLibsPrefix = getSystemLibsPrefix();
293 if (systemLibsPrefix.isEmpty()) {
294 final String SYSTEM_LIB_PATH =
"/system/lib/";
295 systemLibsPrefix = SYSTEM_LIB_PATH;
296 Log.e(QtTAG,
"Using " + SYSTEM_LIB_PATH +
" as default libraries path. "
297 +
"It looks like the app is deployed using Unbundled "
298 +
"deployment. It may be necessary to specify the path to "
299 +
"the directory where Qt libraries are installed using either "
300 +
"android.app.system_libs_prefix metadata variable in your "
301 +
"AndroidManifest.xml or QT_ANDROID_SYSTEM_LIBS_PATH in your "
305 File systemLibraryDir =
new File(systemLibsPrefix);
307 if (systemLibraryDir.exists()) {
308 if (systemLibraryDir.isDirectory() &&
list !=
null &&
list.
length > 0)
309 m_extractedNativeLibsDir = systemLibsPrefix;
311 Log.e(QtTAG,
"System library directory " + systemLibsPrefix +
" is empty.");
313 Log.e(QtTAG,
"System library directory " + systemLibsPrefix +
" does not exist.");
317 if (m_extractedNativeLibsDir !=
null && !m_extractedNativeLibsDir.endsWith(
"/"))
318 m_extractedNativeLibsDir +=
"/";
326 ApplicationInfo applicationInfo = m_contextInfo.applicationInfo;
327 if (applicationInfo ==
null)
330 Bundle metadata = applicationInfo.metaData;
331 if (metadata ==
null || !metadata.containsKey(
key))
334 return metadata.getString(
key);
341 if (m_contextInfo ==
null)
344 Bundle metadata = m_contextInfo.metaData;
345 if (metadata ==
null || !metadata.containsKey(
key))
348 return String.valueOf(metadata.get(
key));
351 @SuppressLint(
"DiscouragedApi")
353 int id = m_resources.getIdentifier(
"qt_libs",
"array", m_packageName);
354 return preferredAbiLibs(m_resources.getStringArray(
id));
357 @SuppressLint(
"DiscouragedApi")
358 private boolean useLocalQtLibs() {
359 int id = m_resources.getIdentifier(
"use_local_qt_libs",
"string", m_packageName);
360 return Integer.parseInt(m_resources.getString(
id)) == 1;
363 @SuppressLint(
"DiscouragedApi")
364 private boolean isBundleQtLibs() {
365 int id = m_resources.getIdentifier(
"bundle_local_qt_libs",
"string", m_packageName);
366 return Integer.parseInt(m_resources.getString(
id)) == 1;
369 @SuppressLint(
"DiscouragedApi")
371 int id = m_resources.getIdentifier(
"system_libs_prefix",
"string", m_packageName);
372 return m_resources.getString(
id);
375 @SuppressLint(
"DiscouragedApi")
377 int id = m_resources.getIdentifier(
"load_local_libs",
"array", m_packageName);
378 ArrayList<String> localLibs =
new ArrayList<>();
379 for (
String arrayItem : preferredAbiLibs(m_resources.getStringArray(
id))) {
380 Collections.addAll(localLibs, arrayItem.split(
":"));
385 @SuppressLint(
"DiscouragedApi")
387 int id = m_resources.getIdentifier(
"bundled_libs",
"array", m_packageName);
388 return m_resources.getStringArray(
id);
394 private static boolean isUncompressedNativeLibs()
396 int flags = QtNative.getContext().getApplicationInfo().flags;
397 return (
flags & ApplicationInfo.FLAG_EXTRACT_NATIVE_LIBS) == 0;
403 private String getApkNativeLibrariesDir()
405 return QtApkFileEngine.getAppApkFilePath() +
"!/lib/" + m_preferredAbi +
"/";
414 if (m_librariesLoaded)
417 if (!useLocalQtLibs()) {
418 Log.w(QtTAG,
"Use local Qt libs is false");
422 if (m_extractedNativeLibsDir ==
null)
423 parseNativeLibrariesDir();
425 if (isUncompressedNativeLibs()) {
426 String apkLibPath = getApkNativeLibrariesDir();
427 setEnvironmentVariable(
"QT_PLUGIN_PATH", apkLibPath);
428 setEnvironmentVariable(
"QML_PLUGIN_PATH", apkLibPath);
430 if (m_extractedNativeLibsDir ==
null || m_extractedNativeLibsDir.isEmpty()) {
431 Log.e(QtTAG,
"The native libraries directory is null or empty");
434 setEnvironmentVariable(
"QT_PLUGIN_PATH", m_extractedNativeLibsDir);
435 setEnvironmentVariable(
"QML_PLUGIN_PATH", m_extractedNativeLibsDir);
439 ArrayList<String> nativeLibraries = getQtLibrariesList();
440 nativeLibraries.addAll(getLocalLibrariesList());
442 if (
Debug.isDebuggerConnected()) {
443 final String debuggerSleepEnvVarName =
"QT_ANDROID_DEBUGGER_MAIN_THREAD_SLEEP_MS";
444 int debuggerSleepMs = 3000;
445 if (Os.getenv(debuggerSleepEnvVarName) !=
null) {
447 debuggerSleepMs =
Integer.parseInt(Os.getenv(debuggerSleepEnvVarName));
448 }
catch (NumberFormatException ignored) {
452 if (debuggerSleepMs > 0) {
453 Log.i(QtTAG,
"Sleeping for " + debuggerSleepMs +
454 "ms, helping the native debugger to settle. " +
455 "Use the env " + debuggerSleepEnvVarName +
456 " variable to change this value.");
457 QtNative.getQtThread().sleep(debuggerSleepMs);
461 if (!loadLibraries(nativeLibraries)) {
462 Log.e(QtTAG,
"Loading Qt native libraries failed");
467 ArrayList<String> bundledLibraries =
new ArrayList<>(preferredAbiLibs(getBundledLibs()));
468 if (!loadLibraries(bundledLibraries)) {
469 Log.e(QtTAG,
"Loading Qt bundled libraries failed");
473 if (m_mainLibName ==
null)
474 m_mainLibName = getMetaData(
"android.app.lib_name");
476 if (!loadMainLibrary(m_mainLibName +
"_" + m_preferredAbi)) {
477 Log.e(QtTAG,
"Loading main library failed");
480 m_librariesLoaded =
true;
486 @SuppressLint(
"UnsafeDynamicallyLoadedCode")
492 if (library.startsWith(
"/")) {
493 if (libFile.exists()) {
497 Log.e(QtTAG,
"Can't find '" + library +
"'");
500 System.loadLibrary(library);
503 }
catch (Exception e) {
504 Log.e(QtTAG,
"Can't load '" + library +
"'", e);
513 private ArrayList<String> getLibrariesFullPaths(
final ArrayList<String> libraries)
515 if (libraries ==
null)
518 ArrayList<String> absolutePathLibraries =
new ArrayList<>();
519 for (
String libName : libraries) {
522 if (isUncompressedNativeLibs()) {
523 if (libName.endsWith(
".so"))
524 libName = libName.substring(3, libName.length() - 3);
525 absolutePathLibraries.add(libName);
527 if (!libName.endsWith(
".so"))
528 libName =
"lib" + libName +
".so";
529 File file =
new File(m_extractedNativeLibsDir + libName);
530 absolutePathLibraries.add(
file.getAbsolutePath());
534 return absolutePathLibraries;
543 private boolean loadMainLibrary(
String mainLibName)
545 ArrayList<String> oneEntryArray =
new ArrayList<>(Collections.singletonList(mainLibName));
546 String mainLibPath = getLibrariesFullPaths(oneEntryArray).get(0);
547 final boolean[] success = {
true};
548 QtNative.getQtThread().run(() -> {
549 m_mainLibPath = loadLibraryHelper(mainLibPath);
550 if (m_mainLibPath ==
null)
552 else if (isUncompressedNativeLibs())
553 m_mainLibPath = getApkNativeLibrariesDir() +
"lib" + m_mainLibPath +
".so";
564 @SuppressWarnings(
"BooleanMethodIsAlwaysInverted")
565 private boolean loadLibraries(final ArrayList<
String> libraries)
567 if (libraries ==
null)
570 ArrayList<String> fullPathLibs = getLibrariesFullPaths(libraries);
572 final boolean[] success = {
true};
573 QtNative.getQtThread().run(() -> {
574 for (
int i = 0;
i < fullPathLibs.size(); ++
i) {
575 String libName = fullPathLibs.get(i);
576 if (loadLibraryHelper(libName) ==
null) {
constexpr qsizetype length() const noexcept
static const QString context()
QMap< Name, StatePointer > Bundle
EGLOutputLayerEXT EGLint EGLAttrib value
[3]
decltype(openFileForWriting({})) File