5package org.qtproject.qt.android;
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;
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;
29import dalvik.system.DexClassLoader;
31abstract class QtLoader {
33 protected static final String QtTAG =
"QtLoader";
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;
41 protected final ContextWrapper m_context;
42 protected ComponentInfo m_contextInfo;
44 protected String m_mainLibPath;
45 protected String m_mainLibName;
46 protected String m_applicationParameters =
"";
47 protected HashMap<String, String> m_environmentVariables =
new HashMap<>();
49 protected int m_debuggerSleepMs = 0;
57 public QtLoader(ContextWrapper
context) {
59 m_resources = m_context.getResources();
60 m_packageName = m_context.getPackageName();
71 abstract protected void finish();
78 protected void initContextInfo() {
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(
87 PackageManager.GET_META_DATA);
89 Log.w(QtTAG,
"Context is not an instance of Activity or Service, could not get " +
90 "context info for it");
92 }
catch (Exception e) {
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"));
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))
122 if (!abiLibs.containsKey(archLib[0]))
123 abiLibs.put(archLib[0],
new ArrayList<>());
124 Objects.requireNonNull(abiLibs.get(archLib[0])).add(archLib[1]);
127 if (m_preferredAbi !=
null) {
128 if (abiLibs.containsKey(m_preferredAbi)) {
129 return abiLibs.get(m_preferredAbi);
131 return new ArrayList<>();
134 for (String abi : Build.SUPPORTED_ABIS) {
135 if (abiLibs.containsKey(abi)) {
136 m_preferredAbi = abi;
137 return abiLibs.get(abi);
140 return new ArrayList<>();
143 private void initStaticClasses() {
144 Context
context = m_context.getBaseContext();
145 boolean isActivity =
context instanceof Activity;
146 for (String
className : getStaticInitClasses()) {
148 Class<?> initClass = m_classLoader.loadClass(
className);
149 Object staticInitDataObject = initClass.newInstance();
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");
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");
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");
177 }
catch (IllegalAccessException | ClassNotFoundException | InstantiationException e) {
178 Log.d(QtTAG,
"Could not instantiate class " +
className +
", " + e);
187 private void initClassLoader()
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);
200 public String getMainLibraryPath() {
201 return m_mainLibPath;
211 public void setMainLibraryName(String libName) {
212 m_mainLibName = libName;
220 public String getApplicationParameters() {
221 return m_applicationParameters;
228 public void appendApplicationParameters(String
params)
233 if (!m_applicationParameters.isEmpty())
234 m_applicationParameters +=
" ";
235 m_applicationParameters +=
params;
241 public void setEnvironmentVariable(String
key, String
value)
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);
257 public void setEnvironmentVariables(String environmentVariables)
259 if (environmentVariables ==
null || environmentVariables.isEmpty())
262 environmentVariables = environmentVariables.replaceAll(
"\t",
" ");
265 String[] keyValue =
variable.split(
"=", 2);
266 if (keyValue.length < 2 || keyValue[0].isEmpty())
269 setEnvironmentVariable(keyValue[0], keyValue[1]);
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;
292 String systemLibsPrefix = getApplicationMetaData(
"android.app.system_libs_prefix");
295 if (systemLibsPrefix.isEmpty())
296 systemLibsPrefix = getSystemLibsPrefix();
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 "
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;
316 Log.e(QtTAG,
"System library directory " + systemLibsPrefix +
" is empty.");
318 Log.e(QtTAG,
"System library directory " + systemLibsPrefix +
" does not exist.");
322 if (m_nativeLibrariesDir !=
null && !m_nativeLibrariesDir.endsWith(
"/"))
323 m_nativeLibrariesDir +=
"/";
330 private String getApplicationMetaData(String
key) {
331 if (m_contextInfo ==
null)
334 ApplicationInfo applicationInfo = m_contextInfo.applicationInfo;
335 if (applicationInfo ==
null)
338 Bundle metadata = applicationInfo.metaData;
339 if (metadata ==
null || !metadata.containsKey(
key))
342 return metadata.getString(
key);
348 protected String getMetaData(String
key) {
349 if (m_contextInfo ==
null)
352 Bundle metadata = m_contextInfo.metaData;
353 if (metadata ==
null || !metadata.containsKey(
key))
356 return String.valueOf(metadata.get(
key));
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));
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;
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;
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);
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(
":"));
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);
406 @SuppressLint(
"DiscouragedApi")
407 private String[] getBundledLibs() {
408 int id = m_resources.getIdentifier(
"bundled_libs",
"array", m_packageName);
409 return m_resources.getStringArray(
id);
415 public void loadQtLibraries() {
416 if (!useLocalQtLibs()) {
417 Log.w(QtTAG,
"Use local Qt libs is false");
422 if (m_nativeLibrariesDir ==
null)
423 parseNativeLibrariesDir();
425 if (m_nativeLibrariesDir ==
null || m_nativeLibrariesDir.isEmpty()) {
426 Log.e(QtTAG,
"The native libraries directory is null or empty");
431 setEnvironmentVariable(
"QT_PLUGIN_PATH", m_nativeLibrariesDir);
432 setEnvironmentVariable(
"QML_PLUGIN_PATH", m_nativeLibrariesDir);
435 ArrayList<String> nativeLibraries = getQtLibrariesList();
436 nativeLibraries.addAll(getLocalLibrariesList());
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);
445 if (!loadLibraries(nativeLibraries)) {
446 Log.e(QtTAG,
"Loading Qt native libraries failed");
452 ArrayList<String> bundledLibraries =
new ArrayList<>(preferredAbiLibs(getBundledLibs()));
453 if (!loadLibraries(bundledLibraries)) {
454 Log.e(QtTAG,
"Loading Qt bundled libraries failed");
459 if (m_mainLibName ==
null)
460 m_mainLibName = getMetaData(
"android.app.lib_name");
462 if (!loadMainLibrary(m_mainLibName +
"_" + m_preferredAbi)) {
463 Log.e(QtTAG,
"Loading main library failed");
469 @SuppressLint(
"UnsafeDynamicallyLoadedCode")
470 private String loadLibraryHelper(String library)
472 String loadedLib =
null;
474 File libFile =
new File(library);
475 if (libFile.exists()) {
479 Log.e(QtTAG,
"Can't find '" + library +
"'");
481 }
catch (Exception e) {
482 Log.e(QtTAG,
"Can't load '" + library +
"'", e);
491 private ArrayList<String> getLibrariesFullPaths(
final ArrayList<String> libraries)
493 if (libraries ==
null)
496 ArrayList<String> absolutePathLibraries =
new ArrayList<>();
497 for (String libName : libraries) {
500 if (!libName.endsWith(
".so")) {
501 libName = libName +
".so";
502 libName =
"lib" + libName;
505 File
file =
new File(m_nativeLibrariesDir + libName);
506 absolutePathLibraries.add(
file.getAbsolutePath());
509 return absolutePathLibraries;
518 private boolean loadMainLibrary(String mainLibName)
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)
537 @SuppressWarnings(
"BooleanMethodIsAlwaysInverted")
538 private boolean loadLibraries(final ArrayList<String> libraries)
540 if (libraries ==
null)
543 ArrayList<String> fullPathLibs = getLibrariesFullPaths(libraries);
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) {
qsizetype length() const noexcept
EGLOutputLayerEXT EGLint EGLAttrib value
[5]
static void split(QT_FT_Vector *b)
decltype(openFileForWriting({})) File
const char className[16]
[1]