5package org.qtproject.qt.android.bindings;
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;
20import java.io.FileOutputStream;
21import java.lang.reflect.Method;
22import java.util.ArrayList;
23import java.util.Arrays;
24import java.util.HashMap;
27import dalvik.system.DexClassLoader;
43 private static final String EXTRACT_STYLE_MINIMAL_KEY =
"extract.android.style.option";
76 private ContextWrapper m_context;
78 private Class<?> m_delegateClass;
80 private static ArrayList<FileOutputStream> m_fileOutputStreams =
new ArrayList<FileOutputStream>();
85 m_delegateClass = clazz;
108 private final List<String> supportedAbis = Arrays.asList(Build.SUPPORTED_ABIS);
109 private String preferredAbi =
null;
111 private ArrayList<String> prefferedAbiLibs(String []libs)
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))
118 if (!abisLibs.containsKey(archLib[0]))
119 abisLibs.put(archLib[0],
new ArrayList<String>());
120 abisLibs.get(archLib[0]).add(archLib[1]);
123 if (preferredAbi !=
null) {
124 if (abisLibs.containsKey(preferredAbi)) {
125 return abisLibs.get(preferredAbi);
127 return new ArrayList<String>();
130 for (String abi: supportedAbis) {
131 if (abisLibs.containsKey(abi)) {
133 return abisLibs.get(abi);
136 return new ArrayList<String>();
140 private void loadApplication(
Bundle loaderParams)
142 final Resources resources = m_context.getResources();
143 final String packageName = m_context.getPackageName();
146 if (errorCode != 0) {
148 AlertDialog errorDialog =
new AlertDialog.Builder(m_context).create();
150 errorDialog.setButton(
Dialog.BUTTON_POSITIVE,
151 resources.getString(android.R.string.ok),
152 new DialogInterface.OnClickListener() {
154 public void onClick(DialogInterface dialog, int which) {
163 int id = resources.getIdentifier(
"bundled_libs",
"array", packageName);
164 final String[] bundledLibs = resources.getStringArray(
id);
165 ArrayList<String> libs =
new ArrayList<>(prefferedAbiLibs(bundledLibs));
168 if (
m_contextInfo.metaData.containsKey(
"android.app.lib_name")) {
169 libName =
m_contextInfo.metaData.getString(
"android.app.lib_name") +
"_" + preferredAbi;
177 m_context.getDir(
"outdex", Context.MODE_PRIVATE).getAbsolutePath(),
179 m_context.getClassLoader());
182 Object qtLoader = loaderClass.newInstance();
183 Method prepareAppMethod = qtLoader.getClass().getMethod(
"loadApplication",
187 if (!(
Boolean)prepareAppMethod.invoke(qtLoader, m_context, classLoader, loaderParams))
188 throw new Exception(
"");
190 QtApplication.setQtContextDelegate(m_delegateClass, qtLoader);
192 Method startAppMethod=qtLoader.getClass().getMethod(
"startApplication");
193 if (!(
Boolean)startAppMethod.invoke(qtLoader))
194 throw new Exception(
"");
196 }
catch (Exception
e) {
198 AlertDialog errorDialog =
new AlertDialog.Builder(m_context).create();
199 int id = resources.getIdentifier(
"fatal_error_msg",
"string",
201 errorDialog.setMessage(resources.getString(
id));
202 errorDialog.setButton(
Dialog.BUTTON_POSITIVE,
203 resources.getString(android.R.string.ok),
204 new DialogInterface.OnClickListener() {
206 public void onClick(DialogInterface dialog, int which) {
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));
222 id = resources.getIdentifier(
"use_local_qt_libs",
"string", packageName);
223 final int useLocalLibs =
Integer.parseInt(resources.getString(
id));
225 if (useLocalLibs == 1) {
226 ArrayList<String> libraryList =
new ArrayList<>();
227 String libsDir =
null;
228 String bundledLibsDir =
null;
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";
236 if (m_contextInfo.applicationInfo.metaData !=
null &&
237 m_contextInfo.applicationInfo.metaData.containsKey(systemLibsKey)) {
238 systemLibsPrefix = m_contextInfo.applicationInfo.metaData.getString(systemLibsKey);
241 id = resources.getIdentifier(
"system_libs_prefix",
"string",
243 systemLibsPrefix = resources.getString(
id);
245 if (systemLibsPrefix.isEmpty()) {
246 systemLibsPrefix = SYSTEM_LIB_PATH;
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 "
256 File systemLibraryDir =
new File(systemLibsPrefix);
257 if (systemLibraryDir.exists() && systemLibraryDir.isDirectory()
258 && systemLibraryDir.list().length > 0) {
259 libsDir = systemLibsPrefix;
260 bundledLibsDir = systemLibsPrefix;
263 "System library directory " + systemLibsPrefix
264 +
" does not exist or is empty.");
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;
274 "Native library directory " + nativeLibraryPrefix
275 +
" does not exist or is empty.");
280 throw new Exception(
"");
283 if (m_qtLibs !=
null) {
284 String libPrefix = libsDir +
"lib";
285 for (String lib : m_qtLibs)
286 libraryList.add(libPrefix + lib +
".so");
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(
":")) {
294 libraryList.add(libsDir + lib);
297 if (bundledLibsDir !=
null) {
298 ENVIRONMENT_VARIABLES +=
"\tQT_PLUGIN_PATH=" + bundledLibsDir;
299 ENVIRONMENT_VARIABLES +=
"\tQML_PLUGIN_PATH=" + bundledLibsDir;
303 loaderParams.putInt(ERROR_CODE_KEY, 0);
304 loaderParams.putString(DEX_PATH_KEY,
new String());
305 loaderParams.putString(LOADER_CLASS_NAME_KEY, loaderClassName());
307 id = resources.getIdentifier(
"static_init_classes",
"string", packageName);
308 loaderParams.putStringArray(STATIC_INIT_CLASSES_KEY, resources.getString(
id)
311 loaderParams.putStringArrayList(NATIVE_LIBRARIES_KEY, libraryList);
314 String themePath = m_context.getApplicationInfo().dataDir +
"/qt-reserved-files/android-style/";
315 String stylePath = themePath + m_displayDensity +
"/";
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";
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";
337 if (!extractOption.equals(
"none")) {
338 loaderParams.putString(EXTRACT_STYLE_KEY, stylePath);
339 loaderParams.putBoolean(EXTRACT_STYLE_MINIMAL_KEY, extractOption.equals(
"minimal"));
342 if (extractOption.equals(
"full"))
343 ENVIRONMENT_VARIABLES +=
"\tQT_USE_ANDROID_NATIVE_STYLE=1";
345 ENVIRONMENT_VARIABLES +=
"\tANDROID_STYLE_PATH=" + stylePath;
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;
352 loaderParams.putString(ENVIRONMENT_VARIABLES_KEY, ENVIRONMENT_VARIABLES);
354 String appParams =
null;
355 if (APPLICATION_PARAMETERS !=
null)
356 appParams = APPLICATION_PARAMETERS;
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;
365 appParams +=
'\t' + parameters;
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;
373 appParams +=
'\t' + parameters;
376 if (appParams !=
null)
377 loaderParams.putString(APPLICATION_PARAMETERS_KEY, appParams);
379 loadApplication(loaderParams);
382 }
catch (Exception
e) {
static final String QtTAG
void runOnUiThread(Runnable run)
static final String APPLICATION_PARAMETERS_KEY
static final String SYSTEM_LIB_PATH
static final String BUNDLED_LIBRARIES_KEY
String QT_ANDROID_DEFAULT_THEME
String[] QT_ANDROID_THEMES
static final String ENVIRONMENT_VARIABLES_KEY
void startApp(final boolean firstStart)
static final String EXTRACT_STYLE_KEY
static final String ERROR_CODE_KEY
static final String LIB_PATH_KEY
static final String LOADER_CLASS_NAME_KEY
static final String ERROR_MESSAGE_KEY
abstract String loaderClassName()
ArrayList< String > m_qtLibs
static final String DEX_PATH_KEY
ComponentInfo m_contextInfo
static final String STATIC_INIT_CLASSES_KEY
String APPLICATION_PARAMETERS
static final String MAIN_LIBRARY_KEY
abstract Class<?> contextClassName()
static final String NATIVE_LIBRARIES_KEY
String ENVIRONMENT_VARIABLES
QMap< Name, StatePointer > Bundle