Qt 6.x
The Qt SDK
Loading...
Searching...
No Matches
QtLoader.java
Go to the documentation of this file.
1// Copyright (C) 2022 The Qt Company Ltd.
2// Copyright (c) 2019, BogDan Vatra <bogdan@kde.org>
3// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
4
5package org.qtproject.qt.android.bindings;
6
7import android.app.AlertDialog;
8import android.app.Dialog;
9import android.content.Context;
10import android.content.ContextWrapper;
11import android.content.DialogInterface;
12import android.content.Intent;
13import android.content.pm.ComponentInfo;
14import android.content.res.Resources;
15import android.os.Build;
16import android.os.Bundle;
17import android.util.Log;
18
19import java.io.File;
20import java.io.FileOutputStream;
21import java.lang.reflect.Method;
22import java.util.ArrayList;
23import java.util.Arrays;
24import java.util.HashMap;
25import java.util.List;
26
27import dalvik.system.DexClassLoader;
28
29public abstract class QtLoader {
30
31 public static final String ERROR_CODE_KEY = "error.code";
32 public static final String ERROR_MESSAGE_KEY = "error.message";
33 public static final String DEX_PATH_KEY = "dex.path";
34 public static final String LIB_PATH_KEY = "lib.path";
35 public static final String LOADER_CLASS_NAME_KEY = "loader.class.name";
36 public static final String NATIVE_LIBRARIES_KEY = "native.libraries";
37 public static final String ENVIRONMENT_VARIABLES_KEY = "environment.variables";
38 public static final String APPLICATION_PARAMETERS_KEY = "application.parameters";
39 public static final String BUNDLED_LIBRARIES_KEY = "bundled.libraries";
40 public static final String MAIN_LIBRARY_KEY = "main.library";
41 public static final String STATIC_INIT_CLASSES_KEY = "static.init.classes";
42 public static final String EXTRACT_STYLE_KEY = "extract.android.style";
43 private static final String EXTRACT_STYLE_MINIMAL_KEY = "extract.android.style.option";
44
45 // These parameters matter in case of deploying application as system (embedded into firmware)
46 public static final String SYSTEM_LIB_PATH = "/system/lib/";
47
48 public String APPLICATION_PARAMETERS = null; // use this variable to pass any parameters to your application,
49 // the parameters must not contain any white spaces
50 // and must be separated with "\t"
51 // e.g "-param1\t-param2=value2\t-param3\tvalue3"
52
53 public String ENVIRONMENT_VARIABLES = "QT_USE_ANDROID_NATIVE_DIALOGS=1";
54 // use this variable to add any environment variables to your application.
55 // the env vars must be separated with "\t"
56 // e.g. "ENV_VAR1=1\tENV_VAR2=2\t"
57 // Currently the following vars are used by the android plugin:
58 // * QT_USE_ANDROID_NATIVE_DIALOGS -1 to use the android native dialogs.
59
60 public String[] QT_ANDROID_THEMES = null; // A list with all themes that your application want to use.
61 // The name of the theme must be the same with any theme from
62 // http://developer.android.com/reference/android/R.style.html
63 // The most used themes are:
64 // * "Theme" - (fallback) check http://developer.android.com/reference/android/R.style.html#Theme
65 // * "Theme_Black" - check http://developer.android.com/reference/android/R.style.html#Theme_Black
66 // * "Theme_Light" - (default for API <=10) check http://developer.android.com/reference/android/R.style.html#Theme_Light
67 // * "Theme_Holo" - check http://developer.android.com/reference/android/R.style.html#Theme_Holo
68 // * "Theme_Holo_Light" - (default for API 11-13) check http://developer.android.com/reference/android/R.style.html#Theme_Holo_Light
69 // * "Theme_DeviceDefault" - check http://developer.android.com/reference/android/R.style.html#Theme_DeviceDefault
70 // * "Theme_DeviceDefault_Light" - (default for API 14+) check http://developer.android.com/reference/android/R.style.html#Theme_DeviceDefault_Light
71
72 public String QT_ANDROID_DEFAULT_THEME = null; // sets the default theme.
73
74 public ArrayList<String> m_qtLibs = null; // required qt libs
75 public int m_displayDensity = -1;
76 private ContextWrapper m_context;
77 protected ComponentInfo m_contextInfo;
78 private Class<?> m_delegateClass;
79
80 private static ArrayList<FileOutputStream> m_fileOutputStreams = new ArrayList<FileOutputStream>();
81 // List of open file streams associated with files copied during installation.
82
83 QtLoader(ContextWrapper context, Class<?> clazz) {
84 m_context = context;
85 m_delegateClass = clazz;
86 }
87
88 // Implement in subclass
89 protected void finish() {}
90
91 protected String getTitle() {
92 return "Qt";
93 }
94
95 protected void runOnUiThread(Runnable run) {
96 run.run();
97 }
98
99 protected abstract String loaderClassName();
100 protected abstract Class<?> contextClassName();
101
102 Intent getIntent()
103 {
104 return null;
105 }
106 // Implement in subclass
107
108 private final List<String> supportedAbis = Arrays.asList(Build.SUPPORTED_ABIS);
109 private String preferredAbi = null;
110
111 private ArrayList<String> prefferedAbiLibs(String []libs)
112 {
113 HashMap<String, ArrayList<String>> abisLibs = new HashMap<>();
114 for (String lib : libs) {
115 String[] archLib = lib.split(";", 2);
116 if (preferredAbi != null && !archLib[0].equals(preferredAbi))
117 continue;
118 if (!abisLibs.containsKey(archLib[0]))
119 abisLibs.put(archLib[0], new ArrayList<String>());
120 abisLibs.get(archLib[0]).add(archLib[1]);
121 }
122
123 if (preferredAbi != null) {
124 if (abisLibs.containsKey(preferredAbi)) {
125 return abisLibs.get(preferredAbi);
126 }
127 return new ArrayList<String>();
128 }
129
130 for (String abi: supportedAbis) {
131 if (abisLibs.containsKey(abi)) {
132 preferredAbi = abi;
133 return abisLibs.get(abi);
134 }
135 }
136 return new ArrayList<String>();
137 }
138
139 // this function is used to load and start the loader
140 private void loadApplication(Bundle loaderParams)
141 {
142 final Resources resources = m_context.getResources();
143 final String packageName = m_context.getPackageName();
144 try {
145 final int errorCode = loaderParams.getInt(ERROR_CODE_KEY);
146 if (errorCode != 0) {
147 // fatal error, show the error and quit
148 AlertDialog errorDialog = new AlertDialog.Builder(m_context).create();
149 errorDialog.setMessage(loaderParams.getString(ERROR_MESSAGE_KEY));
150 errorDialog.setButton(Dialog.BUTTON_POSITIVE,
151 resources.getString(android.R.string.ok),
152 new DialogInterface.OnClickListener() {
153 @Override
154 public void onClick(DialogInterface dialog, int which) {
155 finish();
156 }
157 });
158 errorDialog.show();
159 return;
160 }
161
162 // add all bundled Qt libs to loader params
163 int id = resources.getIdentifier("bundled_libs", "array", packageName);
164 final String[] bundledLibs = resources.getStringArray(id);
165 ArrayList<String> libs = new ArrayList<>(prefferedAbiLibs(bundledLibs));
166
167 String libName = null;
168 if (m_contextInfo.metaData.containsKey("android.app.lib_name")) {
169 libName = m_contextInfo.metaData.getString("android.app.lib_name") + "_" + preferredAbi;
170 loaderParams.putString(MAIN_LIBRARY_KEY, libName); //main library contains main() function
171 }
172
173 loaderParams.putStringArrayList(BUNDLED_LIBRARIES_KEY, libs);
174
175 // load and start QtLoader class
176 DexClassLoader classLoader = new DexClassLoader(loaderParams.getString(DEX_PATH_KEY), // .jar/.apk files
177 m_context.getDir("outdex", Context.MODE_PRIVATE).getAbsolutePath(), // directory where optimized DEX files should be written.
178 loaderParams.containsKey(LIB_PATH_KEY) ? loaderParams.getString(LIB_PATH_KEY) : null, // libs folder (if exists)
179 m_context.getClassLoader()); // parent loader
180
181 Class<?> loaderClass = classLoader.loadClass(loaderParams.getString(LOADER_CLASS_NAME_KEY)); // load QtLoader class
182 Object qtLoader = loaderClass.newInstance(); // create an instance
183 Method prepareAppMethod = qtLoader.getClass().getMethod("loadApplication",
185 ClassLoader.class,
186 Bundle.class);
187 if (!(Boolean)prepareAppMethod.invoke(qtLoader, m_context, classLoader, loaderParams))
188 throw new Exception("");
189
190 QtApplication.setQtContextDelegate(m_delegateClass, qtLoader);
191
192 Method startAppMethod=qtLoader.getClass().getMethod("startApplication");
193 if (!(Boolean)startAppMethod.invoke(qtLoader))
194 throw new Exception("");
195
196 } catch (Exception e) {
197 e.printStackTrace();
198 AlertDialog errorDialog = new AlertDialog.Builder(m_context).create();
199 int id = resources.getIdentifier("fatal_error_msg", "string",
200 packageName);
201 errorDialog.setMessage(resources.getString(id));
202 errorDialog.setButton(Dialog.BUTTON_POSITIVE,
203 resources.getString(android.R.string.ok),
204 new DialogInterface.OnClickListener() {
205 @Override
206 public void onClick(DialogInterface dialog, int which) {
207 finish();
208 }
209 });
210 errorDialog.show();
211 }
212 }
213
214 public void startApp(final boolean firstStart)
215 {
216 try {
217 final Resources resources = m_context.getResources();
218 final String packageName = m_context.getPackageName();
219 int id = resources.getIdentifier("qt_libs", "array", packageName);
220 m_qtLibs = prefferedAbiLibs(resources.getStringArray(id));
221
222 id = resources.getIdentifier("use_local_qt_libs", "string", packageName);
223 final int useLocalLibs = Integer.parseInt(resources.getString(id));
224
225 if (useLocalLibs == 1) {
226 ArrayList<String> libraryList = new ArrayList<>();
227 String libsDir = null;
228 String bundledLibsDir = null;
229
230 id = resources.getIdentifier("bundle_local_qt_libs", "string", packageName);
231 final int bundleLocalLibs = Integer.parseInt(resources.getString(id));
232 if (bundleLocalLibs == 0) {
233 String systemLibsPrefix;
234 final String systemLibsKey = "android.app.system_libs_prefix";
235 // First check if user has provided system libs prefix in AndroidManifest
236 if (m_contextInfo.applicationInfo.metaData != null &&
237 m_contextInfo.applicationInfo.metaData.containsKey(systemLibsKey)) {
238 systemLibsPrefix = m_contextInfo.applicationInfo.metaData.getString(systemLibsKey);
239 } else {
240 // If not, check if it's provided by androiddeployqt in libs.xml
241 id = resources.getIdentifier("system_libs_prefix","string",
242 packageName);
243 systemLibsPrefix = resources.getString(id);
244 }
245 if (systemLibsPrefix.isEmpty()) {
246 systemLibsPrefix = SYSTEM_LIB_PATH;
247 Log.e(QtApplication.QtTAG, "It looks like app deployed using Unbundled "
248 + "deployment. It may be necessary to specify path to directory "
249 + "where Qt libraries are installed using either "
250 + "android.app.system_libs_prefix metadata variable in your "
251 + "AndroidManifest.xml or QT_ANDROID_SYSTEM_LIBS_PATH in your "
252 + "CMakeLists.txt");
253 Log.e(QtApplication.QtTAG, "Using " + SYSTEM_LIB_PATH + " as default path");
254 }
255
256 File systemLibraryDir = new File(systemLibsPrefix);
257 if (systemLibraryDir.exists() && systemLibraryDir.isDirectory()
258 && systemLibraryDir.list().length > 0) {
259 libsDir = systemLibsPrefix;
260 bundledLibsDir = systemLibsPrefix;
261 } else {
263 "System library directory " + systemLibsPrefix
264 + " does not exist or is empty.");
265 }
266 } else {
267 String nativeLibraryPrefix = m_context.getApplicationInfo().nativeLibraryDir + "/";
268 File nativeLibraryDir = new File(nativeLibraryPrefix);
269 if (nativeLibraryDir.exists() && nativeLibraryDir.isDirectory() && nativeLibraryDir.list().length > 0) {
270 libsDir = nativeLibraryPrefix;
271 bundledLibsDir = nativeLibraryPrefix;
272 } else {
274 "Native library directory " + nativeLibraryPrefix
275 + " does not exist or is empty.");
276 }
277 }
278
279 if (libsDir == null)
280 throw new Exception("");
281
282
283 if (m_qtLibs != null) {
284 String libPrefix = libsDir + "lib";
285 for (String lib : m_qtLibs)
286 libraryList.add(libPrefix + lib + ".so");
287 }
288
289 id = resources.getIdentifier("load_local_libs", "array", packageName);
290 ArrayList<String> localLibs = prefferedAbiLibs(resources.getStringArray(id));
291 for (String libs : localLibs) {
292 for (String lib : libs.split(":")) {
293 if (!lib.isEmpty())
294 libraryList.add(libsDir + lib);
295 }
296 }
297 if (bundledLibsDir != null) {
298 ENVIRONMENT_VARIABLES += "\tQT_PLUGIN_PATH=" + bundledLibsDir;
299 ENVIRONMENT_VARIABLES += "\tQML_PLUGIN_PATH=" + bundledLibsDir;
300 }
301
302 Bundle loaderParams = new Bundle();
303 loaderParams.putInt(ERROR_CODE_KEY, 0);
304 loaderParams.putString(DEX_PATH_KEY, new String());
305 loaderParams.putString(LOADER_CLASS_NAME_KEY, loaderClassName());
306
307 id = resources.getIdentifier("static_init_classes", "string", packageName);
308 loaderParams.putStringArray(STATIC_INIT_CLASSES_KEY, resources.getString(id)
309 .split(":"));
310
311 loaderParams.putStringArrayList(NATIVE_LIBRARIES_KEY, libraryList);
312
313
314 String themePath = m_context.getApplicationInfo().dataDir + "/qt-reserved-files/android-style/";
315 String stylePath = themePath + m_displayDensity + "/";
316
317 String extractOption = "default";
318 if (m_contextInfo.metaData.containsKey("android.app.extract_android_style")) {
319 extractOption = m_contextInfo.metaData.getString("android.app.extract_android_style");
320 if (!extractOption.equals("default") && !extractOption.equals("full") && !extractOption.equals("minimal") && !extractOption.equals("none")) {
321 Log.e(QtApplication.QtTAG, "Invalid extract_android_style option \"" + extractOption + "\", defaulting to \"default\"");
322 extractOption = "default";
323 }
324 }
325
326 // QTBUG-69810: The extraction code will trigger compatibility warnings on Android SDK version >= 28
327 // when the target SDK version is set to something lower then 28, so default to "none" and issue a warning
328 // if that is the case.
329 if (extractOption.equals("default")) {
330 final int targetSdkVersion = m_context.getApplicationInfo().targetSdkVersion;
331 if (targetSdkVersion < 28 && Build.VERSION.SDK_INT >= 28) {
332 Log.e(QtApplication.QtTAG, "extract_android_style option set to \"none\" when targetSdkVersion is less then 28");
333 extractOption = "none";
334 }
335 }
336
337 if (!extractOption.equals("none")) {
338 loaderParams.putString(EXTRACT_STYLE_KEY, stylePath);
339 loaderParams.putBoolean(EXTRACT_STYLE_MINIMAL_KEY, extractOption.equals("minimal"));
340 }
341
342 if (extractOption.equals("full"))
343 ENVIRONMENT_VARIABLES += "\tQT_USE_ANDROID_NATIVE_STYLE=1";
344
345 ENVIRONMENT_VARIABLES += "\tANDROID_STYLE_PATH=" + stylePath;
346
347 if (m_contextInfo.metaData.containsKey("android.app.trace_location")) {
348 String loc = m_contextInfo.metaData.getString("android.app.trace_location");
349 ENVIRONMENT_VARIABLES += "\tQTRACE_LOCATION="+loc;
350 }
351
352 loaderParams.putString(ENVIRONMENT_VARIABLES_KEY, ENVIRONMENT_VARIABLES);
353
354 String appParams = null;
355 if (APPLICATION_PARAMETERS != null)
356 appParams = APPLICATION_PARAMETERS;
357
358 Intent intent = getIntent();
359 if (intent != null) {
360 String parameters = intent.getStringExtra("applicationArguments");
361 if (parameters != null)
362 if (appParams == null)
363 appParams = parameters;
364 else
365 appParams += '\t' + parameters;
366 }
367
368 if (m_contextInfo.metaData.containsKey("android.app.arguments")) {
369 String parameters = m_contextInfo.metaData.getString("android.app.arguments");
370 if (appParams == null)
371 appParams = parameters;
372 else
373 appParams += '\t' + parameters;
374 }
375
376 if (appParams != null)
377 loaderParams.putString(APPLICATION_PARAMETERS_KEY, appParams);
378
379 loadApplication(loaderParams);
380 return;
381 }
382 } catch (Exception e) {
383 Log.e(QtApplication.QtTAG, "Can't create main activity", e);
384 }
385 }
386}
Definition main.cpp:8
static final String APPLICATION_PARAMETERS_KEY
Definition QtLoader.java:38
static final String ENVIRONMENT_VARIABLES_KEY
Definition QtLoader.java:37
void startApp(final boolean firstStart)
double e
QMap< Name, StatePointer > Bundle
Definition lalr.h:47
jobject classLoader()
@ Dialog
Definition qnamespace.h:207
static void * context