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 String[] splits = lib.split(";", 2);
138 // Ensure we have both abi and lib name parts
139 if (splits == null || splits.length < 2)
140 continue;
141
142 if (!splits[0].equals(m_preferredAbi))
143 continue;
144
145 abiLibs.add(splits[1]);
146 }
147
148 return abiLibs;
149 }
150
151 @SuppressLint("DiscouragedApi")
152 private String resolvePreferredAbi()
153 {
154 int id = m_resources.getIdentifier("qt_libs", "array", m_packageName);
155 String[] libs = m_resources.getStringArray(id);
156 Set<String> uniqueAbis = new HashSet<>();
157
158 for (String lib : libs) {
159 String[] splits = lib.split(";", 2);
160 // Ensure we have both abi and lib name parts
161 if (splits == null || splits.length < 2)
162 continue;
163
164 uniqueAbis.add(splits[0]);
165 }
166
167 boolean is64Bit = Process.is64Bit();
168 for (String abi : Build.SUPPORTED_ABIS) {
169 if (uniqueAbis.contains(abi) && abi.contains("64") == is64Bit)
170 return abi;
171 }
172
173 return Build.SUPPORTED_ABIS[0];
174 }
175
180 private void initClassLoader(Context context)
181 {
182 // directory where optimized DEX files should be written.
183 String outDexPath = context.getDir("outdex", Context.MODE_PRIVATE).getAbsolutePath();
184 String sourceDir = context.getApplicationInfo().sourceDir;
185 m_classLoader = new DexClassLoader(sourceDir, outDexPath, null, context.getClassLoader());
186 QtNative.setClassLoader(m_classLoader);
187 }
188
193 public String getMainLibraryPath() {
194 return m_mainLibPath;
195 }
196
204 public void setMainLibraryName(String libName) {
205 m_mainLibName = libName;
206 }
207
213 public String getApplicationParameters() {
214 return m_applicationParameters;
215 }
216
221 public void appendApplicationParameters(String params)
222 {
223 if (params == null || params.isEmpty())
224 return;
225
226 if (!m_applicationParameters.isEmpty())
227 m_applicationParameters += " ";
228 m_applicationParameters += params;
229 }
230
234 public void setEnvironmentVariable(String key, String value)
235 {
236 try {
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);
241 e.printStackTrace();
242 }
243 }
244
250 public void setEnvironmentVariables(String environmentVariables)
251 {
252 if (environmentVariables == null || environmentVariables.isEmpty())
253 return;
254
255 environmentVariables = environmentVariables.replaceAll("\t", " ");
256
257 for (String variable : environmentVariables.split(" ")) {
258 String[] keyValue = variable.split("=", 2);
259 if (keyValue.length < 2 || keyValue[0].isEmpty())
260 continue;
261
262 setEnvironmentVariable(keyValue[0], keyValue[1]);
263 }
264 }
265
273 private void parseNativeLibrariesDir() {
274 if (m_contextInfo == null)
275 return;
276 if (isBundleQtLibs()) {
277 String nativeLibraryPrefix = m_contextInfo.applicationInfo.nativeLibraryDir + "/";
278 File nativeLibraryDir = new File(nativeLibraryPrefix);
279 if (nativeLibraryDir.exists()) {
280 String[] list = nativeLibraryDir.list();
281 if (nativeLibraryDir.isDirectory() && list != null && list.length > 0) {
282 m_extractedNativeLibsDir = nativeLibraryPrefix;
283 }
284 }
285 } else {
286 // First check if user has provided system libs prefix in AndroidManifest
287 String systemLibsPrefix = getApplicationMetaData("android.app.system_libs_prefix");
288
289 // If not, check if it's provided by androiddeployqt in libs.xml
290 if (systemLibsPrefix.isEmpty())
291 systemLibsPrefix = getSystemLibsPrefix();
292
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 "
302 + "CMakeLists.txt");
303 }
304
305 File systemLibraryDir = new File(systemLibsPrefix);
306 String[] list = systemLibraryDir.list();
307 if (systemLibraryDir.exists()) {
308 if (systemLibraryDir.isDirectory() && list != null && list.length > 0)
309 m_extractedNativeLibsDir = systemLibsPrefix;
310 else
311 Log.e(QtTAG, "System library directory " + systemLibsPrefix + " is empty.");
312 } else {
313 Log.e(QtTAG, "System library directory " + systemLibsPrefix + " does not exist.");
314 }
315 }
316
317 if (m_extractedNativeLibsDir != null && !m_extractedNativeLibsDir.endsWith("/"))
318 m_extractedNativeLibsDir += "/";
319 }
320
325 private String getApplicationMetaData(String key) {
326 ApplicationInfo applicationInfo = m_contextInfo.applicationInfo;
327 if (applicationInfo == null)
328 return "";
329
330 Bundle metadata = applicationInfo.metaData;
331 if (metadata == null || !metadata.containsKey(key))
332 return "";
333
334 return metadata.getString(key);
335 }
336
340 protected String getMetaData(String key) {
341 if (m_contextInfo == null)
342 return "";
343
344 Bundle metadata = m_contextInfo.metaData;
345 if (metadata == null || !metadata.containsKey(key))
346 return "";
347
348 return String.valueOf(metadata.get(key));
349 }
350
351 @SuppressLint("DiscouragedApi")
352 private ArrayList<String> getQtLibrariesList() {
353 int id = m_resources.getIdentifier("qt_libs", "array", m_packageName);
354 return preferredAbiLibs(m_resources.getStringArray(id));
355 }
356
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;
361 }
362
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;
367 }
368
369 @SuppressLint("DiscouragedApi")
370 private String getSystemLibsPrefix() {
371 int id = m_resources.getIdentifier("system_libs_prefix", "string", m_packageName);
372 return m_resources.getString(id);
373 }
374
375 @SuppressLint("DiscouragedApi")
376 private ArrayList<String> getLocalLibrariesList() {
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(":"));
381 }
382 return localLibs;
383 }
384
385 @SuppressLint("DiscouragedApi")
386 private String[] getBundledLibs() {
387 int id = m_resources.getIdentifier("bundled_libs", "array", m_packageName);
388 return m_resources.getStringArray(id);
389 }
390
394 private static boolean isUncompressedNativeLibs()
395 {
396 int flags = QtNative.getContext().getApplicationInfo().flags;
397 return (flags & ApplicationInfo.FLAG_EXTRACT_NATIVE_LIBS) == 0;
398 }
399
403 private String getApkNativeLibrariesDir()
404 {
405 return QtApkFileEngine.getAppApkFilePath() + "!/lib/" + m_preferredAbi + "/";
406 }
407
413 public LoadingResult loadQtLibraries() {
414 if (m_librariesLoaded)
416
417 if (!useLocalQtLibs()) {
418 Log.w(QtTAG, "Use local Qt libs is false");
419 return LoadingResult.Failed;
420 }
421
422 if (m_extractedNativeLibsDir == null)
423 parseNativeLibrariesDir();
424
425 if (isUncompressedNativeLibs()) {
426 String apkLibPath = getApkNativeLibrariesDir();
427 setEnvironmentVariable("QT_PLUGIN_PATH", apkLibPath);
428 setEnvironmentVariable("QML_PLUGIN_PATH", apkLibPath);
429 } else {
430 if (m_extractedNativeLibsDir == null || m_extractedNativeLibsDir.isEmpty()) {
431 Log.e(QtTAG, "The native libraries directory is null or empty");
432 return LoadingResult.Failed;
433 }
434 setEnvironmentVariable("QT_PLUGIN_PATH", m_extractedNativeLibsDir);
435 setEnvironmentVariable("QML_PLUGIN_PATH", m_extractedNativeLibsDir);
436 }
437
438 // Load native Qt APK libraries
439 ArrayList<String> nativeLibraries = getQtLibrariesList();
440 nativeLibraries.addAll(getLocalLibrariesList());
441
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) {
446 try {
447 debuggerSleepMs = Integer.parseInt(Os.getenv(debuggerSleepEnvVarName));
448 } catch (NumberFormatException ignored) {
449 }
450 }
451
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);
458 }
459 }
460
461 if (!loadLibraries(nativeLibraries)) {
462 Log.e(QtTAG, "Loading Qt native libraries failed");
463 return LoadingResult.Failed;
464 }
465
466 // add all bundled Qt libs to loader params
467 ArrayList<String> bundledLibraries = new ArrayList<>(preferredAbiLibs(getBundledLibs()));
468 if (!loadLibraries(bundledLibraries)) {
469 Log.e(QtTAG, "Loading Qt bundled libraries failed");
470 return LoadingResult.Failed;
471 }
472
473 if (m_mainLibName == null)
474 m_mainLibName = getMetaData("android.app.lib_name");
475 // Load main lib
476 if (!loadMainLibrary(m_mainLibName + "_" + m_preferredAbi)) {
477 Log.e(QtTAG, "Loading main library failed");
478 return LoadingResult.Failed;
479 }
480 m_librariesLoaded = true;
482 }
483
484 // Loading libraries using System.load() uses full lib paths
485 // or System.loadLibrary() for uncompressed libs
486 @SuppressLint("UnsafeDynamicallyLoadedCode")
487 private String loadLibraryHelper(String library)
488 {
489 String loadedLib = null;
490 try {
491 File libFile = new File(library);
492 if (library.startsWith("/")) {
493 if (libFile.exists()) {
494 System.load(library);
495 loadedLib = library;
496 } else {
497 Log.e(QtTAG, "Can't find '" + library + "'");
498 }
499 } else {
500 System.loadLibrary(library);
501 loadedLib = library;
502 }
503 } catch (Exception e) {
504 Log.e(QtTAG, "Can't load '" + library + "'", e);
505 }
506
507 return loadedLib;
508 }
509
513 private ArrayList<String> getLibrariesFullPaths(final ArrayList<String> libraries)
514 {
515 if (libraries == null)
516 return null;
517
518 ArrayList<String> absolutePathLibraries = new ArrayList<>();
519 for (String libName : libraries) {
520 // Add lib and .so to the lib name only if it doesn't already end with .so,
521 // this means some names don't necessarily need to have the lib prefix
522 if (isUncompressedNativeLibs()) {
523 if (libName.endsWith(".so"))
524 libName = libName.substring(3, libName.length() - 3);
525 absolutePathLibraries.add(libName);
526 } else {
527 if (!libName.endsWith(".so"))
528 libName = "lib" + libName + ".so";
529 File file = new File(m_extractedNativeLibsDir + libName);
530 absolutePathLibraries.add(file.getAbsolutePath());
531 }
532 }
533
534 return absolutePathLibraries;
535 }
536
543 private boolean loadMainLibrary(String mainLibName)
544 {
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)
551 success[0] = false;
552 else if (isUncompressedNativeLibs())
553 m_mainLibPath = getApkNativeLibrariesDir() + "lib" + m_mainLibPath + ".so";
554 });
555
556 return success[0];
557 }
558
564 @SuppressWarnings("BooleanMethodIsAlwaysInverted")
565 private boolean loadLibraries(final ArrayList<String> libraries)
566 {
567 if (libraries == null)
568 return false;
569
570 ArrayList<String> fullPathLibs = getLibrariesFullPaths(libraries);
571
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) {
577 success[0] = false;
578 break;
579 }
580 }
581 });
582
583 return success[0];
584 }
585}
constexpr qsizetype length() const noexcept
Definition qlist.h:456
QPainter Context
static const QString context()
Definition java.cpp:398
QMap< Name, StatePointer > Bundle
Definition lalr.h:46
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]