Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
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
5package org.qtproject.qt.android;
6
7import android.annotation.SuppressLint;
8import android.app.Activity;
9import android.app.Service;
10import android.content.ComponentName;
11import android.content.Context;
12import android.content.ContextWrapper;
13import android.content.pm.ApplicationInfo;
14import android.content.pm.PackageManager;
15import android.content.pm.ComponentInfo;
16import android.content.res.Resources;
17import android.os.Build;
18import android.os.Bundle;
19import android.util.Log;
20
21import java.io.File;
22import java.lang.reflect.InvocationTargetException;
23import java.lang.reflect.Method;
24import java.util.ArrayList;
25import java.util.Collections;
26import java.util.HashMap;
27import java.util.Objects;
28
29import dalvik.system.DexClassLoader;
30
31abstract class QtLoader {
32
33 protected static final String QtTAG = "QtLoader";
34
35 private final Resources m_resources;
36 private final String m_packageName;
37 private String m_preferredAbi = null;
38 private String m_nativeLibrariesDir = null;
39 private ClassLoader m_classLoader;
40
41 protected final ContextWrapper m_context;
42 protected ComponentInfo m_contextInfo;
43
44 protected String m_mainLibPath;
45 protected String m_mainLibName;
46 protected String m_applicationParameters = "";
47 protected HashMap<String, String> m_environmentVariables = new HashMap<>();
48
49 protected int m_debuggerSleepMs = 0;
50
57 public QtLoader(ContextWrapper context) {
58 m_context = context;
59 m_resources = m_context.getResources();
60 m_packageName = m_context.getPackageName();
61
62 initClassLoader();
63 initStaticClasses();
64 initContextInfo();
65 }
66
71 abstract protected void finish();
72
78 protected void initContextInfo() {
79 try {
80 Context context = m_context.getBaseContext();
81 if (context instanceof Activity) {
82 m_contextInfo = context.getPackageManager().getActivityInfo(
83 ((Activity)context).getComponentName(), PackageManager.GET_META_DATA);
84 } else if (context instanceof Service) {
85 m_contextInfo = context.getPackageManager().getServiceInfo(
86 new ComponentName(context, context.getClass()),
87 PackageManager.GET_META_DATA);
88 } else {
89 Log.w(QtTAG, "Context is not an instance of Activity or Service, could not get " +
90 "context info for it");
91 }
92 } catch (Exception e) {
93 e.printStackTrace();
94 finish();
95 }
96 }
97
103 protected void extractContextMetaData() {
104 setEnvironmentVariable("QT_ANDROID_FONTS", "Roboto;Droid Sans;Droid Sans Fallback");
105 String monospaceFonts = "Droid Sans Mono;Droid Sans;Droid Sans Fallback";
106 setEnvironmentVariable("QT_ANDROID_FONTS_MONOSPACE", monospaceFonts);
107 setEnvironmentVariable("QT_ANDROID_FONTS_SERIF", "Droid Serif");
108 setEnvironmentVariable("HOME", m_context.getFilesDir().getAbsolutePath());
109 setEnvironmentVariable("TMPDIR", m_context.getCacheDir().getAbsolutePath());
110 String backgroundRunning = getMetaData("android.app.background_running");
111 setEnvironmentVariable("QT_BLOCK_EVENT_LOOPS_WHEN_SUSPENDED", backgroundRunning);
112 setEnvironmentVariable("QTRACE_LOCATION", getMetaData("android.app.trace_location"));
113 appendApplicationParameters(getMetaData("android.app.arguments"));
114 }
115
116 private ArrayList<String> preferredAbiLibs(String[] libs) {
117 HashMap<String, ArrayList<String>> abiLibs = new HashMap<>();
118 for (String lib : libs) {
119 String[] archLib = lib.split(";", 2);
120 if (m_preferredAbi != null && !archLib[0].equals(m_preferredAbi))
121 continue;
122 if (!abiLibs.containsKey(archLib[0]))
123 abiLibs.put(archLib[0], new ArrayList<>());
124 Objects.requireNonNull(abiLibs.get(archLib[0])).add(archLib[1]);
125 }
126
127 if (m_preferredAbi != null) {
128 if (abiLibs.containsKey(m_preferredAbi)) {
129 return abiLibs.get(m_preferredAbi);
130 }
131 return new ArrayList<>();
132 }
133
134 for (String abi : Build.SUPPORTED_ABIS) {
135 if (abiLibs.containsKey(abi)) {
136 m_preferredAbi = abi;
137 return abiLibs.get(abi);
138 }
139 }
140 return new ArrayList<>();
141 }
142
143 private void initStaticClasses() {
144 Context context = m_context.getBaseContext();
145 boolean isActivity = context instanceof Activity;
146 for (String className : getStaticInitClasses()) {
147 try {
148 Class<?> initClass = m_classLoader.loadClass(className);
149 Object staticInitDataObject = initClass.newInstance(); // create an instance
150
151 if (isActivity) {
152 try {
153 Method m = initClass.getMethod("setActivity", Activity.class, Object.class);
154 m.invoke(staticInitDataObject, (Activity) context, this);
155 } catch (InvocationTargetException | NoSuchMethodException e) {
156 Log.d(QtTAG, "Class " + initClass.getName() + " does not implement " +
157 "setActivity method");
158 }
159 } else {
160 try {
161 Method m = initClass.getMethod("setService", Service.class, Object.class);
162 m.invoke(staticInitDataObject, (Service) context, this);
163 } catch (InvocationTargetException | NoSuchMethodException e) {
164 Log.d(QtTAG, "Class " + initClass.getName() + " does not implement " +
165 "setService method");
166 }
167 }
168
169 try {
170 // For modules that don't need/have setActivity/setService
171 Method m = initClass.getMethod("setContext", Context.class);
172 m.invoke(staticInitDataObject, context);
173 } catch (InvocationTargetException | NoSuchMethodException e) {
174 Log.d(QtTAG, "Class " + initClass.getName() + " does not implement " +
175 "setContext method");
176 }
177 } catch (IllegalAccessException | ClassNotFoundException | InstantiationException e) {
178 Log.d(QtTAG, "Could not instantiate class " + className + ", " + e);
179 }
180 }
181 }
182
187 private void initClassLoader()
188 {
189 // directory where optimized DEX files should be written.
190 String outDexPath = m_context.getDir("outdex", Context.MODE_PRIVATE).getAbsolutePath();
191 String sourceDir = m_context.getApplicationInfo().sourceDir;
192 m_classLoader = new DexClassLoader(sourceDir, outDexPath, null, m_context.getClassLoader());
193 QtNative.setClassLoader(m_classLoader);
194 }
195
200 public String getMainLibraryPath() {
201 return m_mainLibPath;
202 }
203
211 public void setMainLibraryName(String libName) {
212 m_mainLibName = libName;
213 }
214
220 public String getApplicationParameters() {
221 return m_applicationParameters;
222 }
223
228 public void appendApplicationParameters(String params)
229 {
230 if (params == null || params.isEmpty())
231 return;
232
233 if (!m_applicationParameters.isEmpty())
234 m_applicationParameters += " ";
235 m_applicationParameters += params;
236 }
237
241 public void setEnvironmentVariable(String key, String value)
242 {
243 try {
244 android.system.Os.setenv(key, value, true);
245 m_environmentVariables.put(key, value);
246 } catch (Exception e) {
247 Log.e(QtTAG, "Could not set environment variable:" + key + "=" + value);
248 e.printStackTrace();
249 }
250 }
251
257 public void setEnvironmentVariables(String environmentVariables)
258 {
259 if (environmentVariables == null || environmentVariables.isEmpty())
260 return;
261
262 environmentVariables = environmentVariables.replaceAll("\t", " ");
263
264 for (String variable : environmentVariables.split(" ")) {
265 String[] keyValue = variable.split("=", 2);
266 if (keyValue.length < 2 || keyValue[0].isEmpty())
267 continue;
268
269 setEnvironmentVariable(keyValue[0], keyValue[1]);
270 }
271 }
272
280 private void parseNativeLibrariesDir() {
281 if (isBundleQtLibs()) {
282 String nativeLibraryPrefix = m_context.getApplicationInfo().nativeLibraryDir + "/";
283 File nativeLibraryDir = new File(nativeLibraryPrefix);
284 if (nativeLibraryDir.exists()) {
285 String[] list = nativeLibraryDir.list();
286 if (nativeLibraryDir.isDirectory() && list != null && list.length > 0) {
287 m_nativeLibrariesDir = nativeLibraryPrefix;
288 }
289 }
290 } else {
291 // First check if user has provided system libs prefix in AndroidManifest
292 String systemLibsPrefix = getApplicationMetaData("android.app.system_libs_prefix");
293
294 // If not, check if it's provided by androiddeployqt in libs.xml
295 if (systemLibsPrefix.isEmpty())
296 systemLibsPrefix = getSystemLibsPrefix();
297
298 if (systemLibsPrefix.isEmpty()) {
299 final String SYSTEM_LIB_PATH = "/system/lib/";
300 systemLibsPrefix = SYSTEM_LIB_PATH;
301 Log.e(QtTAG, "Using " + SYSTEM_LIB_PATH + " as default libraries path. "
302 + "It looks like the app is deployed using Unbundled "
303 + "deployment. It may be necessary to specify the path to "
304 + "the directory where Qt libraries are installed using either "
305 + "android.app.system_libs_prefix metadata variable in your "
306 + "AndroidManifest.xml or QT_ANDROID_SYSTEM_LIBS_PATH in your "
307 + "CMakeLists.txt");
308 }
309
310 File systemLibraryDir = new File(systemLibsPrefix);
311 String[] list = systemLibraryDir.list();
312 if (systemLibraryDir.exists()) {
313 if (systemLibraryDir.isDirectory() && list != null && list.length > 0)
314 m_nativeLibrariesDir = systemLibsPrefix;
315 else
316 Log.e(QtTAG, "System library directory " + systemLibsPrefix + " is empty.");
317 } else {
318 Log.e(QtTAG, "System library directory " + systemLibsPrefix + " does not exist.");
319 }
320 }
321
322 if (m_nativeLibrariesDir != null && !m_nativeLibrariesDir.endsWith("/"))
323 m_nativeLibrariesDir += "/";
324 }
325
330 private String getApplicationMetaData(String key) {
331 if (m_contextInfo == null)
332 return "";
333
334 ApplicationInfo applicationInfo = m_contextInfo.applicationInfo;
335 if (applicationInfo == null)
336 return "";
337
338 Bundle metadata = applicationInfo.metaData;
339 if (metadata == null || !metadata.containsKey(key))
340 return "";
341
342 return metadata.getString(key);
343 }
344
348 protected String getMetaData(String key) {
349 if (m_contextInfo == null)
350 return "";
351
352 Bundle metadata = m_contextInfo.metaData;
353 if (metadata == null || !metadata.containsKey(key))
354 return "";
355
356 return String.valueOf(metadata.get(key));
357 }
358
359 @SuppressLint("DiscouragedApi")
360 private ArrayList<String> getQtLibrariesList() {
361 int id = m_resources.getIdentifier("qt_libs", "array", m_packageName);
362 return preferredAbiLibs(m_resources.getStringArray(id));
363 }
364
365 @SuppressLint("DiscouragedApi")
366 private boolean useLocalQtLibs() {
367 int id = m_resources.getIdentifier("use_local_qt_libs", "string", m_packageName);
368 return Integer.parseInt(m_resources.getString(id)) == 1;
369 }
370
371 @SuppressLint("DiscouragedApi")
372 private boolean isBundleQtLibs() {
373 int id = m_resources.getIdentifier("bundle_local_qt_libs", "string", m_packageName);
374 return Integer.parseInt(m_resources.getString(id)) == 1;
375 }
376
377 @SuppressLint("DiscouragedApi")
378 private String getSystemLibsPrefix() {
379 int id = m_resources.getIdentifier("system_libs_prefix", "string", m_packageName);
380 return m_resources.getString(id);
381 }
382
383 @SuppressLint("DiscouragedApi")
384 private ArrayList<String> getLocalLibrariesList() {
385 int id = m_resources.getIdentifier("load_local_libs", "array", m_packageName);
386 ArrayList<String> localLibs = new ArrayList<>();
387 for (String arrayItem : preferredAbiLibs(m_resources.getStringArray(id))) {
388 Collections.addAll(localLibs, arrayItem.split(":"));
389 }
390 return localLibs;
391 }
392
393 @SuppressLint("DiscouragedApi")
394 private ArrayList<String> getStaticInitClasses() {
395 int id = m_resources.getIdentifier("static_init_classes", "string", m_packageName);
396 String[] classes = m_resources.getString(id).split(":");
397 ArrayList<String> finalClasses = new ArrayList<>();
398 for (String element : classes) {
399 if (!element.isEmpty()) {
400 finalClasses.add(element);
401 }
402 }
403 return finalClasses;
404 }
405
406 @SuppressLint("DiscouragedApi")
407 private String[] getBundledLibs() {
408 int id = m_resources.getIdentifier("bundled_libs", "array", m_packageName);
409 return m_resources.getStringArray(id);
410 }
411
415 public void loadQtLibraries() {
416 if (!useLocalQtLibs()) {
417 Log.w(QtTAG, "Use local Qt libs is false");
418 finish();
419 return;
420 }
421
422 if (m_nativeLibrariesDir == null)
423 parseNativeLibrariesDir();
424
425 if (m_nativeLibrariesDir == null || m_nativeLibrariesDir.isEmpty()) {
426 Log.e(QtTAG, "The native libraries directory is null or empty");
427 finish();
428 return;
429 }
430
431 setEnvironmentVariable("QT_PLUGIN_PATH", m_nativeLibrariesDir);
432 setEnvironmentVariable("QML_PLUGIN_PATH", m_nativeLibrariesDir);
433
434 // Load native Qt APK libraries
435 ArrayList<String> nativeLibraries = getQtLibrariesList();
436 nativeLibraries.addAll(getLocalLibrariesList());
437
438 if (m_debuggerSleepMs > 0) {
439 Log.i(QtTAG, "Sleeping for " + m_debuggerSleepMs +
440 "ms, helping the native debugger to settle. " +
441 "Use the env QT_ANDROID_DEBUGGER_MAIN_THREAD_SLEEP_MS variable to change this value.");
442 QtNative.getQtThread().sleep(m_debuggerSleepMs);
443 }
444
445 if (!loadLibraries(nativeLibraries)) {
446 Log.e(QtTAG, "Loading Qt native libraries failed");
447 finish();
448 return;
449 }
450
451 // add all bundled Qt libs to loader params
452 ArrayList<String> bundledLibraries = new ArrayList<>(preferredAbiLibs(getBundledLibs()));
453 if (!loadLibraries(bundledLibraries)) {
454 Log.e(QtTAG, "Loading Qt bundled libraries failed");
455 finish();
456 return;
457 }
458
459 if (m_mainLibName == null)
460 m_mainLibName = getMetaData("android.app.lib_name");
461 // Load main lib
462 if (!loadMainLibrary(m_mainLibName + "_" + m_preferredAbi)) {
463 Log.e(QtTAG, "Loading main library failed");
464 finish();
465 }
466 }
467
468 // Loading libraries using System.load() uses full lib paths
469 @SuppressLint("UnsafeDynamicallyLoadedCode")
470 private String loadLibraryHelper(String library)
471 {
472 String loadedLib = null;
473 try {
474 File libFile = new File(library);
475 if (libFile.exists()) {
476 System.load(library);
477 loadedLib = library;
478 } else {
479 Log.e(QtTAG, "Can't find '" + library + "'");
480 }
481 } catch (Exception e) {
482 Log.e(QtTAG, "Can't load '" + library + "'", e);
483 }
484
485 return loadedLib;
486 }
487
491 private ArrayList<String> getLibrariesFullPaths(final ArrayList<String> libraries)
492 {
493 if (libraries == null)
494 return null;
495
496 ArrayList<String> absolutePathLibraries = new ArrayList<>();
497 for (String libName : libraries) {
498 // Add lib and .so to the lib name only if it doesn't already end with .so,
499 // this means some names don't necessarily need to have the lib prefix
500 if (!libName.endsWith(".so")) {
501 libName = libName + ".so";
502 libName = "lib" + libName;
503 }
504
505 File file = new File(m_nativeLibrariesDir + libName);
506 absolutePathLibraries.add(file.getAbsolutePath());
507 }
508
509 return absolutePathLibraries;
510 }
511
518 private boolean loadMainLibrary(String mainLibName)
519 {
520 ArrayList<String> oneEntryArray = new ArrayList<>(Collections.singletonList(mainLibName));
521 String mainLibPath = getLibrariesFullPaths(oneEntryArray).get(0);
522 final boolean[] success = {true};
523 QtNative.getQtThread().run(() -> {
524 m_mainLibPath = loadLibraryHelper(mainLibPath);
525 if (m_mainLibPath == null)
526 success[0] = false;
527 });
528
529 return success[0];
530 }
531
537 @SuppressWarnings("BooleanMethodIsAlwaysInverted")
538 private boolean loadLibraries(final ArrayList<String> libraries)
539 {
540 if (libraries == null)
541 return false;
542
543 ArrayList<String> fullPathLibs = getLibrariesFullPaths(libraries);
544
545 final boolean[] success = {true};
546 QtNative.getQtThread().run(() -> {
547 for (int i = 0; i < fullPathLibs.size(); ++i) {
548 String libName = fullPathLibs.get(i);
549 if (loadLibraryHelper(libName) == null) {
550 success[0] = false;
551 break;
552 }
553 }
554 });
555
556 return success[0];
557 }
558}
Definition main.cpp:8
qsizetype length() const noexcept
Definition qlist.h:399
static void * context
EGLOutputLayerEXT EGLint EGLAttrib value
[5]
const GLfloat * m
GLuint64 key
GLenum GLuint id
[7]
void ** params
GLenum GLenum variable
static void split(QT_FT_Vector *b)
decltype(openFileForWriting({})) File
Definition main.cpp:76
const char className[16]
[1]
Definition qwizard.cpp:100
QList< int > list
[14]
QFile file
[0]