Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
QtLoader.java
Go to the documentation of this file.
1// Copyright (C) 2023 The Qt Company Ltd.
2// Copyright (c) 2019, BogDan Vatra <bogdan@kde.org>
3// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
4// Qt-Security score:significant reason:trusted-data-only
5
6package org.qtproject.qt.android;
7
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;
26
27import java.io.File;
28import java.lang.IllegalArgumentException;
29import java.util.ArrayList;
30import java.util.Collections;
31import java.util.HashMap;
32import java.util.Objects;
33import java.util.HashSet;
34import java.util.Set;
35
36import dalvik.system.DexClassLoader;
37
38abstract class QtLoader {
39
40 protected static final String QtTAG = "QtLoader";
41
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;
48
49 protected ComponentInfo m_contextInfo;
50
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<>();
55
56 protected static QtLoader m_instance = null;
57 protected boolean m_librariesLoaded;
58
60
69 QtLoader(ContextWrapper context) throws IllegalArgumentException {
70 m_resources = context.getResources();
71 m_packageName = context.getPackageName();
72 final Context baseContext = context.getBaseContext();
73 if (!(baseContext instanceof Activity || baseContext instanceof Service)) {
74 throw new IllegalArgumentException("QtLoader: Context is not an instance of " +
75 "Activity or Service");
76 }
77
78 initClassLoader(baseContext);
79 try {
80 initContextInfo(baseContext);
81 } catch (NameNotFoundException e) {
82 throw new IllegalArgumentException("QtLoader: No ComponentInfo found for given " +
83 "Context", e);
84 }
85 m_preferredAbi = resolvePreferredAbi();
86 }
87
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(
99 new ComponentName(context, context.getClass()),
100 PackageManager.GET_META_DATA);
101 }
102 }
103
109 protected void extractContextMetaData(Context context) {
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"));
119
120 if (context instanceof Activity) {
121 Intent intent = ((Activity) context).getIntent();
122 if (intent != null)
123 appendApplicationParameters(intent.getStringExtra("applicationArguments"));
124 }
125 }
126
127 private String isBackgroundRunningBlocked() {
128 final String backgroundRunning = getMetaData("android.app.background_running");
129 if (backgroundRunning.compareTo("true") == 0)
130 return "0";
131 return "1";
132 }
133
134 private ArrayList<String> preferredAbiLibs(String[] libs) {
135 ArrayList<String> abiLibs = new ArrayList<>();
136 for (String lib : libs) {
137 // We expect each line to be in the form "<abi>;<libName>"
138 String[] splits = lib.split(";", 2);
139 // Ensure we have both abi and lib name parts
140 if (splits == null || splits.length < 2)
141 continue;
142
143 // Ensure the lib name for the preferred abi is not empty
144 if (!splits[0].equals(m_preferredAbi) || splits[1].isEmpty())
145 continue;
146
147 abiLibs.add(splits[1]);
148 }
149
150 return abiLibs;
151 }
152
153 @SuppressLint("DiscouragedApi")
154 private String resolvePreferredAbi()
155 {
156 try {
157 int id = m_resources.getIdentifier("qt_libs", "array", m_packageName);
158 String[] libs = m_resources.getStringArray(id);
159 Set<String> uniqueAbis = new HashSet<>();
160
161 for (String lib : libs) {
162 String[] splits = lib.split(";", 2);
163 // Ensure we have both abi and lib name parts
164 if (splits.length < 2)
165 continue;
166
167 uniqueAbis.add(splits[0].trim());
168 }
169
170 String fallbackAbi = null;
171 boolean is64Bit = Process.is64Bit();
172 for (String abi : Build.SUPPORTED_ABIS) {
173 if (!uniqueAbis.contains(abi))
174 continue;
175
176 if (abi.contains("64") == is64Bit) // best match
177 return abi;
178
179 if (fallbackAbi == null)
180 fallbackAbi = abi;
181 }
182
183 if (fallbackAbi != null)
184 return fallbackAbi;
185 } catch (Resources.NotFoundException ignored) { }
186
187 return Build.SUPPORTED_ABIS[0];
188 }
189
194 private void initClassLoader(Context context)
195 {
196 // directory where optimized DEX files should be written.
197 String outDexPath = context.getDir("outdex", Context.MODE_PRIVATE).getAbsolutePath();
198 String sourceDir = context.getApplicationInfo().sourceDir;
199 m_classLoader = new DexClassLoader(sourceDir, outDexPath, null, context.getClassLoader());
200 QtNative.setClassLoader(m_classLoader);
201 }
202
207 public String getMainLibraryPath() {
208 return m_mainLibPath;
209 }
210
218 public void setMainLibraryName(String libName) {
219 m_mainLibName = libName;
220 }
221
227 public String getApplicationParameters() {
228 return m_applicationParameters;
229 }
230
235 public void appendApplicationParameters(String params)
236 {
237 if (params == null || params.isEmpty())
238 return;
239
240 if (!m_applicationParameters.isEmpty())
241 m_applicationParameters += " ";
242 m_applicationParameters += params;
243 }
244
248 public void setEnvironmentVariable(String key, String value)
249 {
250 try {
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);
255 e.printStackTrace();
256 }
257 }
258
264 public void setEnvironmentVariables(String environmentVariables)
265 {
266 if (environmentVariables == null || environmentVariables.isEmpty())
267 return;
268
269 environmentVariables = environmentVariables.replaceAll("\t", " ");
270
271 for (String variable : environmentVariables.split(" ")) {
272 String[] keyValue = variable.split("=", 2);
273 if (keyValue.length < 2 || keyValue[0].isEmpty())
274 continue;
275
276 setEnvironmentVariable(keyValue[0], keyValue[1]);
277 }
278 }
279
287 private void parseNativeLibrariesDir() {
288 if (m_contextInfo == null)
289 return;
290 if (isBundleQtLibs()) {
291 String nativeLibraryPrefix = m_contextInfo.applicationInfo.nativeLibraryDir + "/";
292 File nativeLibraryDir = new File(nativeLibraryPrefix);
293 if (nativeLibraryDir.exists()) {
294 String[] list = nativeLibraryDir.list();
295 if (nativeLibraryDir.isDirectory() && list != null && list.length > 0) {
296 m_extractedNativeLibsDir = nativeLibraryPrefix;
297 }
298 }
299 } else {
300 // First check if user has provided system libs prefix in AndroidManifest
301 String systemLibsPrefix = getApplicationMetaData("android.app.system_libs_prefix");
302
303 // If not, check if it's provided by androiddeployqt in libs.xml
304 if (systemLibsPrefix.isEmpty())
305 systemLibsPrefix = getSystemLibsPrefix();
306
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 "
316 + "CMakeLists.txt");
317 }
318
319 File systemLibraryDir = new File(systemLibsPrefix);
320 String[] list = systemLibraryDir.list();
321 if (systemLibraryDir.exists()) {
322 if (systemLibraryDir.isDirectory() && list != null && list.length > 0)
323 m_extractedNativeLibsDir = systemLibsPrefix;
324 else
325 Log.e(QtTAG, "System library directory " + systemLibsPrefix + " is empty.");
326 } else {
327 Log.e(QtTAG, "System library directory " + systemLibsPrefix + " does not exist.");
328 }
329 }
330
331 if (m_extractedNativeLibsDir != null && !m_extractedNativeLibsDir.endsWith("/"))
332 m_extractedNativeLibsDir += "/";
333 }
334
339 private String getApplicationMetaData(String key) {
340 ApplicationInfo applicationInfo = m_contextInfo.applicationInfo;
341 if (applicationInfo == null)
342 return "";
343
344 Bundle metadata = applicationInfo.metaData;
345 if (metadata == null || !metadata.containsKey(key))
346 return "";
347
348 return metadata.getString(key);
349 }
350
354 protected String getMetaData(String key) {
355 if (m_contextInfo == null)
356 return "";
357
358 Bundle metadata = m_contextInfo.metaData;
359 if (metadata == null || !metadata.containsKey(key))
360 return "";
361
362 return String.valueOf(metadata.get(key));
363 }
364
365 @SuppressLint("DiscouragedApi")
366 private ArrayList<String> getQtLibrariesList() {
367 try {
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<>();
372 }
373 }
374
375 @SuppressLint("DiscouragedApi")
376 private boolean useLocalQtLibs() {
377 try {
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) {
381 return false;
382 }
383 }
384
385 @SuppressLint("DiscouragedApi")
386 private boolean isBundleQtLibs() {
387 try {
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) {
391 return false;
392 }
393 }
394
395 @SuppressLint("DiscouragedApi")
396 private String getSystemLibsPrefix() {
397 try {
398 int id = m_resources.getIdentifier("system_libs_prefix", "string", m_packageName);
399 return m_resources.getString(id);
400 } catch (Resources.NotFoundException ignored) {
401 return "";
402 }
403 }
404
405 @SuppressLint("DiscouragedApi")
406 private ArrayList<String> getLocalLibrariesList() {
407 ArrayList<String> localLibs = new ArrayList<>();
408 try {
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(":"));
412 }
413 } catch (Resources.NotFoundException ignored) { }
414 return localLibs;
415 }
416
417 @SuppressLint("DiscouragedApi")
418 private String[] getBundledLibs() {
419 try {
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];
424 }
425 }
426
430 private static boolean isUncompressedNativeLibs()
431 {
432 int flags = QtNative.getContext().getApplicationInfo().flags;
433 return (flags & ApplicationInfo.FLAG_EXTRACT_NATIVE_LIBS) == 0;
434 }
435
439 private String getApkNativeLibrariesDir()
440 {
441 return QtApkFileEngine.getAppApkFilePath() + "!/lib/" + m_preferredAbi + "/";
442 }
443
449 public LoadingResult loadQtLibraries() {
450 if (m_librariesLoaded)
452
453 if (!useLocalQtLibs()) {
454 Log.w(QtTAG, "Use local Qt libs is false");
455 return LoadingResult.Failed;
456 }
457
458 if (isUncompressedNativeLibs()) {
459 String apkLibPath = getApkNativeLibrariesDir();
460 setEnvironmentVariable("QT_PLUGIN_PATH", apkLibPath);
461 setEnvironmentVariable("QML_PLUGIN_PATH", apkLibPath);
462 } else {
463 parseNativeLibrariesDir();
464 if (m_extractedNativeLibsDir == null || m_extractedNativeLibsDir.isEmpty()) {
465 Log.e(QtTAG, "The native libraries directory is null or empty");
466 return LoadingResult.Failed;
467 }
468 setEnvironmentVariable("QT_PLUGIN_PATH", m_extractedNativeLibsDir);
469 setEnvironmentVariable("QML_PLUGIN_PATH", m_extractedNativeLibsDir);
470 }
471
472 // Load native Qt APK libraries
473 ArrayList<String> nativeLibraries = getQtLibrariesList();
474 nativeLibraries.addAll(getLocalLibrariesList());
475
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) {
480 try {
481 debuggerSleepMs = Integer.parseInt(Os.getenv(debuggerSleepEnvVarName));
482 } catch (NumberFormatException ignored) {
483 }
484 }
485
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);
492 }
493 }
494
495 if (!loadLibraries(nativeLibraries)) {
496 Log.e(QtTAG, "Loading Qt native libraries failed");
497 return LoadingResult.Failed;
498 }
499
500 // add all bundled Qt libs to loader params
501 ArrayList<String> bundledLibraries = new ArrayList<>(preferredAbiLibs(getBundledLibs()));
502 if (!loadLibraries(bundledLibraries)) {
503 Log.e(QtTAG, "Loading Qt bundled libraries failed");
504 return LoadingResult.Failed;
505 }
506
507 if (m_mainLibName == null)
508 m_mainLibName = getMetaData("android.app.lib_name");
509
510 if (m_mainLibName == null || m_mainLibName.isEmpty()) {
511 Log.e(QtTAG, "The main library name is null or empty.");
512 return LoadingResult.Failed;
513 }
514
515 // Load main lib
516 if (!loadMainLibrary(m_mainLibName + "_" + m_preferredAbi)) {
517 Log.e(QtTAG, "Loading main library failed");
518 return LoadingResult.Failed;
519 }
520 m_librariesLoaded = true;
522 }
523
524 // Loading libraries using System.load() uses full lib paths
525 // or System.loadLibrary() for uncompressed libs
526 @SuppressLint("UnsafeDynamicallyLoadedCode")
527 private String loadLibraryHelper(String library)
528 {
529 String loadedLib = null;
530 try {
531 File libFile = new File(library);
532 if (library.startsWith("/")) {
533 if (libFile.exists()) {
534 System.load(library);
535 loadedLib = library;
536 } else {
537 Log.e(QtTAG, "Can't find '" + library + "'");
538 }
539 } else {
540 System.loadLibrary(library);
541 loadedLib = library;
542 }
543 } catch (Exception e) {
544 Log.e(QtTAG, "Can't load '" + library + "'", e);
545 }
546
547 return loadedLib;
548 }
549
553 private ArrayList<String> getLibrariesFullPaths(final ArrayList<String> libraries)
554 {
555 if (libraries == null)
556 return null;
557
558 ArrayList<String> absolutePathLibraries = new ArrayList<>();
559 for (String libName : libraries) {
560 // Add lib and .so to the lib name only if it doesn't already end with .so,
561 // this means some names don't necessarily need to have the lib prefix
562 if (isUncompressedNativeLibs()) {
563 if (libName.endsWith(".so"))
564 libName = libName.substring(3, libName.length() - 3);
565 absolutePathLibraries.add(libName);
566 } else {
567 if (!libName.endsWith(".so"))
568 libName = "lib" + libName + ".so";
569 File file = new File(m_extractedNativeLibsDir + libName);
570 absolutePathLibraries.add(file.getAbsolutePath());
571 }
572 }
573
574 return absolutePathLibraries;
575 }
576
583 private boolean loadMainLibrary(String mainLibName)
584 {
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)
591 success[0] = false;
592 else if (isUncompressedNativeLibs())
593 m_mainLibPath = getApkNativeLibrariesDir() + "lib" + m_mainLibPath + ".so";
594 });
595
596 return success[0];
597 }
598
604 @SuppressWarnings("BooleanMethodIsAlwaysInverted")
605 private boolean loadLibraries(final ArrayList<String> libraries)
606 {
607 if (libraries == null)
608 return false;
609
610 ArrayList<String> fullPathLibs = getLibrariesFullPaths(libraries);
611
612 if (libraries.size() != fullPathLibs.size()) {
613 Log.e(QtTAG, "Failed to get full paths of libraries.");
614 return false;
615 }
616
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) {
622 success[0] = false;
623 break;
624 }
625 }
626 });
627
628 return success[0];
629 }
630}
constexpr qsizetype length() const noexcept
Definition qlist.h:464
QPainter Context
static const QString context()
Definition java.cpp:396
QMap< Name, StatePointer > Bundle
Definition lalr.h:46
std::string trim(std::string const &str)
Returns a new string without whitespace at the start/end.
EGLOutputLayerEXT EGLint EGLAttrib value
[3]
GLuint64 key
GLbitfield flags
void ** params
GLenum GLenum variable
decltype(openFileForWriting({})) File
Definition main.cpp:76
QList< int > list
[14]