Say hello to Android Services
This changeset enables running a QCoreApplication from within an Android Service. The Android Application running can now have a QtActivity or a QtService, but having both in the same process is not supported. This patch was based on Cory Slep's patch [ChangeLog][Android] Qt can now be used to easily create Android Services. Task-number: QTBUG-37221 Change-Id: I0fd693daaa85b991940ffe9cc41c483022677199 Reviewed-by: Eskil Abrahamsen Blomfeldt <eskil.abrahamsen-blomfeldt@theqtcompany.com>
This commit is contained in:
parent
4a7ccf74ff
commit
efcf1dec49
@ -16,7 +16,8 @@ JAVASOURCES += \
|
||||
$$PATHPREFIX/QtNative.java \
|
||||
$$PATHPREFIX/QtNativeLibrariesDir.java \
|
||||
$$PATHPREFIX/QtSurface.java \
|
||||
$$PATHPREFIX/ExtractStyle.java
|
||||
$$PATHPREFIX/ExtractStyle.java \
|
||||
$$PATHPREFIX/QtServiceDelegate.java
|
||||
|
||||
# install
|
||||
target.path = $$[QT_INSTALL_PREFIX]/jar
|
||||
|
@ -953,6 +953,7 @@ public class QtActivityDelegate
|
||||
{
|
||||
if (m_quitApp) {
|
||||
QtNative.terminateQt();
|
||||
QtNative.setActivity(null, null);
|
||||
if (m_debuggerProcess != null)
|
||||
m_debuggerProcess.destroy();
|
||||
System.exit(0);// FIXME remove it or find a better way
|
||||
|
@ -1,6 +1,6 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2014 BogDan Vatra <bogdan@kde.org>
|
||||
** Copyright (C) 2016 BogDan Vatra <bogdan@kde.org>
|
||||
** Copyright (C) 2016 The Qt Company Ltd.
|
||||
** Contact: https://www.qt.io/licensing/
|
||||
**
|
||||
@ -45,6 +45,7 @@ import java.util.ArrayList;
|
||||
import java.util.concurrent.Semaphore;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Service;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
@ -72,7 +73,9 @@ public class QtNative
|
||||
{
|
||||
private static Activity m_activity = null;
|
||||
private static boolean m_activityPaused = false;
|
||||
private static Service m_service = null;
|
||||
private static QtActivityDelegate m_activityDelegate = null;
|
||||
private static QtServiceDelegate m_serviceDelegate = null;
|
||||
public static Object m_mainActivityMutex = new Object(); // mutex used to synchronize runnable operations
|
||||
|
||||
public static final String QtTAG = "Qt JAVA"; // string used for Log.x
|
||||
@ -115,6 +118,14 @@ public class QtNative
|
||||
}
|
||||
}
|
||||
|
||||
public static Service service()
|
||||
{
|
||||
synchronized (m_mainActivityMutex) {
|
||||
return m_service;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static QtActivityDelegate activityDelegate()
|
||||
{
|
||||
synchronized (m_mainActivityMutex) {
|
||||
@ -122,6 +133,13 @@ public class QtNative
|
||||
}
|
||||
}
|
||||
|
||||
public static QtServiceDelegate serviceDelegate()
|
||||
{
|
||||
synchronized (m_mainActivityMutex) {
|
||||
return m_serviceDelegate;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean openURL(String url, String mime)
|
||||
{
|
||||
boolean ok = true;
|
||||
@ -186,6 +204,14 @@ public class QtNative
|
||||
}
|
||||
}
|
||||
|
||||
public static void setService(Service qtMainService, QtServiceDelegate qtServiceDelegate)
|
||||
{
|
||||
synchronized (m_mainActivityMutex) {
|
||||
m_service = qtMainService;
|
||||
m_serviceDelegate = qtServiceDelegate;
|
||||
}
|
||||
}
|
||||
|
||||
public static void setApplicationState(int state)
|
||||
{
|
||||
synchronized (m_mainActivityMutex) {
|
||||
@ -319,7 +345,11 @@ public class QtNative
|
||||
runAction(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
m_activity.finish();
|
||||
quitQtAndroidPlugin();
|
||||
if (m_activity != null)
|
||||
m_activity.finish();
|
||||
if (m_service != null)
|
||||
m_service.stopSelf();
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -452,7 +482,8 @@ public class QtNative
|
||||
runAction(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
m_activityDelegate.updateSelection(selStart, selEnd, candidatesStart, candidatesEnd);
|
||||
if (m_activityDelegate != null)
|
||||
m_activityDelegate.updateSelection(selStart, selEnd, candidatesStart, candidatesEnd);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -467,7 +498,8 @@ public class QtNative
|
||||
runAction(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
m_activityDelegate.showSoftwareKeyboard(x, y, width, height, inputHints, enterKeyType);
|
||||
if (m_activityDelegate != null)
|
||||
m_activityDelegate.showSoftwareKeyboard(x, y, width, height, inputHints, enterKeyType);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -477,7 +509,8 @@ public class QtNative
|
||||
runAction(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
m_activityDelegate.resetSoftwareKeyboard();
|
||||
if (m_activityDelegate != null)
|
||||
m_activityDelegate.resetSoftwareKeyboard();
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -487,7 +520,8 @@ public class QtNative
|
||||
runAction(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
m_activityDelegate.hideSoftwareKeyboard();
|
||||
if (m_activityDelegate != null)
|
||||
m_activityDelegate.hideSoftwareKeyboard();
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -497,7 +531,9 @@ public class QtNative
|
||||
runAction(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
m_activityDelegate.setFullScreen(fullScreen);
|
||||
if (m_activityDelegate != null) {
|
||||
m_activityDelegate.setFullScreen(fullScreen);
|
||||
}
|
||||
updateWindow();
|
||||
}
|
||||
});
|
||||
@ -505,34 +541,44 @@ public class QtNative
|
||||
|
||||
private static void registerClipboardManager()
|
||||
{
|
||||
final Semaphore semaphore = new Semaphore(0);
|
||||
runAction(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
m_clipboardManager = (android.text.ClipboardManager) m_activity.getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
semaphore.release();
|
||||
if (m_service == null || m_activity != null) { // Avoid freezing if only service
|
||||
final Semaphore semaphore = new Semaphore(0);
|
||||
runAction(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (m_activity != null)
|
||||
m_clipboardManager = (android.text.ClipboardManager) m_activity.getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
semaphore.release();
|
||||
}
|
||||
});
|
||||
try {
|
||||
semaphore.acquire();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
try {
|
||||
semaphore.acquire();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private static void setClipboardText(String text)
|
||||
{
|
||||
m_clipboardManager.setText(text);
|
||||
if (m_clipboardManager != null)
|
||||
m_clipboardManager.setText(text);
|
||||
}
|
||||
|
||||
private static boolean hasClipboardText()
|
||||
{
|
||||
return m_clipboardManager.hasText();
|
||||
if (m_clipboardManager != null)
|
||||
return m_clipboardManager.hasText();
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
private static String getClipboardText()
|
||||
{
|
||||
return m_clipboardManager.getText().toString();
|
||||
if (m_clipboardManager != null)
|
||||
return m_clipboardManager.getText().toString();
|
||||
else
|
||||
return "";
|
||||
}
|
||||
|
||||
private static void openContextMenu(final int x, final int y, final int w, final int h)
|
||||
@ -540,7 +586,8 @@ public class QtNative
|
||||
runAction(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
m_activityDelegate.openContextMenu(x, y, w, h);
|
||||
if (m_activityDelegate != null)
|
||||
m_activityDelegate.openContextMenu(x, y, w, h);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -550,7 +597,8 @@ public class QtNative
|
||||
runAction(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
m_activityDelegate.closeContextMenu();
|
||||
if (m_activityDelegate != null)
|
||||
m_activityDelegate.closeContextMenu();
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -560,7 +608,8 @@ public class QtNative
|
||||
runAction(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
m_activityDelegate.resetOptionsMenu();
|
||||
if (m_activityDelegate != null)
|
||||
m_activityDelegate.resetOptionsMenu();
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -570,7 +619,8 @@ public class QtNative
|
||||
runAction(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
m_activity.openOptionsMenu();
|
||||
if (m_activity != null)
|
||||
m_activity.openOptionsMenu();
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -607,7 +657,8 @@ public class QtNative
|
||||
runAction(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
m_activityDelegate.createSurface(id, onTop, x, y, w, h, imageDepth);
|
||||
if (m_activityDelegate != null)
|
||||
m_activityDelegate.createSurface(id, onTop, x, y, w, h, imageDepth);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -617,7 +668,8 @@ public class QtNative
|
||||
runAction(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
m_activityDelegate.insertNativeView(id, view, x, y, w, h);
|
||||
if (m_activityDelegate != null)
|
||||
m_activityDelegate.insertNativeView(id, view, x, y, w, h);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -627,7 +679,8 @@ public class QtNative
|
||||
runAction(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
m_activityDelegate.setSurfaceGeometry(id, x, y, w, h);
|
||||
if (m_activityDelegate != null)
|
||||
m_activityDelegate.setSurfaceGeometry(id, x, y, w, h);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -637,7 +690,8 @@ public class QtNative
|
||||
runAction(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
m_activityDelegate.bringChildToFront(id);
|
||||
if (m_activityDelegate != null)
|
||||
m_activityDelegate.bringChildToFront(id);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -647,7 +701,8 @@ public class QtNative
|
||||
runAction(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
m_activityDelegate.bringChildToBack(id);
|
||||
if (m_activityDelegate != null)
|
||||
m_activityDelegate.bringChildToBack(id);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -657,7 +712,8 @@ public class QtNative
|
||||
runAction(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
m_activityDelegate.destroySurface(id);
|
||||
if (m_activityDelegate != null)
|
||||
m_activityDelegate.destroySurface(id);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -737,4 +793,7 @@ public class QtNative
|
||||
public static native void onNewIntent(Intent data);
|
||||
|
||||
public static native void runPendingCppRunnables();
|
||||
|
||||
private static native void setNativeActivity(Activity activity);
|
||||
private static native void setNativeService(Service service);
|
||||
}
|
||||
|
@ -40,17 +40,17 @@
|
||||
|
||||
package org.qtproject.qt5.android;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager.NameNotFoundException;
|
||||
|
||||
public class QtNativeLibrariesDir {
|
||||
public static String nativeLibrariesDir(Activity activity)
|
||||
public static String nativeLibrariesDir(Context context)
|
||||
{
|
||||
String m_nativeLibraryDir = null;
|
||||
try {
|
||||
ApplicationInfo ai = activity.getPackageManager().getApplicationInfo(activity.getPackageName(), 0);
|
||||
m_nativeLibraryDir = ai.nativeLibraryDir+"/";
|
||||
ApplicationInfo ai = context.getPackageManager().getApplicationInfo(context.getPackageName(), 0);
|
||||
m_nativeLibraryDir = ai.nativeLibraryDir + "/";
|
||||
} catch (NameNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
@ -0,0 +1,182 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2016 BogDan Vatra <bogdan@kde.org>
|
||||
** Copyright (C) 2016 The Qt Company Ltd.
|
||||
** Contact: https://www.qt.io/licensing/
|
||||
**
|
||||
** This file is part of the Android port of the Qt Toolkit.
|
||||
**
|
||||
** $QT_BEGIN_LICENSE:LGPL$
|
||||
** Commercial License Usage
|
||||
** Licensees holding valid commercial Qt licenses may use this file in
|
||||
** accordance with the commercial license agreement provided with the
|
||||
** Software or, alternatively, in accordance with the terms contained in
|
||||
** a written agreement between you and The Qt Company. For licensing terms
|
||||
** and conditions see https://www.qt.io/terms-conditions. For further
|
||||
** information use the contact form at https://www.qt.io/contact-us.
|
||||
**
|
||||
** GNU Lesser General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU Lesser
|
||||
** General Public License version 3 as published by the Free Software
|
||||
** Foundation and appearing in the file LICENSE.LGPL3 included in the
|
||||
** packaging of this file. Please review the following information to
|
||||
** ensure the GNU Lesser General Public License version 3 requirements
|
||||
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
|
||||
**
|
||||
** GNU General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU
|
||||
** General Public License version 2.0 or (at your option) the GNU General
|
||||
** Public license version 3 or any later version approved by the KDE Free
|
||||
** Qt Foundation. The licenses are as published by the Free Software
|
||||
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
|
||||
** included in the packaging of this file. Please review the following
|
||||
** information to ensure the GNU General Public License requirements will
|
||||
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
|
||||
** https://www.gnu.org/licenses/gpl-3.0.html.
|
||||
**
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
package org.qtproject.qt5.android;
|
||||
|
||||
import android.app.Service;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ActivityInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.PackageManager.NameNotFoundException;
|
||||
import android.content.res.Configuration;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.net.LocalServerSocket;
|
||||
import android.net.LocalSocket;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.ResultReceiver;
|
||||
import android.text.method.MetaKeyKeyListener;
|
||||
import android.util.Base64;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.Log;
|
||||
import android.util.TypedValue;
|
||||
import android.view.ContextMenu;
|
||||
import android.view.ContextMenu.ContextMenuInfo;
|
||||
import android.view.KeyCharacterMap;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.Surface;
|
||||
import android.view.View;
|
||||
import android.view.ViewConfiguration;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.WindowManager;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
|
||||
public class QtServiceDelegate
|
||||
{
|
||||
private static final String NATIVE_LIBRARIES_KEY = "native.libraries";
|
||||
private static final String BUNDLED_LIBRARIES_KEY = "bundled.libraries";
|
||||
private static final String MAIN_LIBRARY_KEY = "main.library";
|
||||
private static final String ENVIRONMENT_VARIABLES_KEY = "environment.variables";
|
||||
private static final String APPLICATION_PARAMETERS_KEY = "application.parameters";
|
||||
private static final String STATIC_INIT_CLASSES_KEY = "static.init.classes";
|
||||
private static final String APP_DISPLAY_METRIC_SCREEN_DESKTOP_KEY = "display.screen.desktop";
|
||||
private static final String APP_DISPLAY_METRIC_SCREEN_XDPI_KEY = "display.screen.dpi.x";
|
||||
private static final String APP_DISPLAY_METRIC_SCREEN_YDPI_KEY = "display.screen.dpi.y";
|
||||
private static final String APP_DISPLAY_METRIC_SCREEN_DENSITY_KEY = "display.screen.density";
|
||||
|
||||
private Service m_service = null;
|
||||
private String m_mainLib;
|
||||
private static String m_environmentVariables = null;
|
||||
private static String m_applicationParameters = null;
|
||||
|
||||
public boolean loadApplication(Service service, ClassLoader classLoader, Bundle loaderParams)
|
||||
{
|
||||
/// check parameters integrity
|
||||
if (!loaderParams.containsKey(NATIVE_LIBRARIES_KEY)
|
||||
|| !loaderParams.containsKey(BUNDLED_LIBRARIES_KEY)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
m_service = service;
|
||||
QtNative.setService(m_service, this);
|
||||
QtNative.setClassLoader(classLoader);
|
||||
|
||||
QtNative.setApplicationDisplayMetrics(10, 10, 10, 10, 120, 120, 1.0, 1.0);
|
||||
|
||||
if (loaderParams.containsKey(STATIC_INIT_CLASSES_KEY)) {
|
||||
for (String className: loaderParams.getStringArray(STATIC_INIT_CLASSES_KEY)) {
|
||||
if (className.length() == 0)
|
||||
continue;
|
||||
|
||||
try {
|
||||
Class<?> initClass = classLoader.loadClass(className);
|
||||
Object staticInitDataObject = initClass.newInstance(); // create an instance
|
||||
Method m = initClass.getMethod("setService", Service.class, Object.class);
|
||||
m.invoke(staticInitDataObject, m_service, this);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
QtNative.loadQtLibraries(loaderParams.getStringArrayList(NATIVE_LIBRARIES_KEY));
|
||||
ArrayList<String> libraries = loaderParams.getStringArrayList(BUNDLED_LIBRARIES_KEY);
|
||||
QtNative.loadBundledLibraries(libraries, QtNativeLibrariesDir.nativeLibrariesDir(m_service));
|
||||
m_mainLib = loaderParams.getString(MAIN_LIBRARY_KEY);
|
||||
|
||||
m_environmentVariables = loaderParams.getString(ENVIRONMENT_VARIABLES_KEY);
|
||||
String additionalEnvironmentVariables = "QT_ANDROID_FONTS_MONOSPACE=Droid Sans Mono;Droid Sans;Droid Sans Fallback"
|
||||
+ "\tQT_ANDROID_FONTS_SERIF=Droid Serif"
|
||||
+ "\tHOME=" + m_service.getFilesDir().getAbsolutePath()
|
||||
+ "\tTMPDIR=" + m_service.getFilesDir().getAbsolutePath();
|
||||
if (Build.VERSION.SDK_INT < 14)
|
||||
additionalEnvironmentVariables += "\tQT_ANDROID_FONTS=Droid Sans;Droid Sans Fallback";
|
||||
else
|
||||
additionalEnvironmentVariables += "\tQT_ANDROID_FONTS=Roboto;Droid Sans;Droid Sans Fallback";
|
||||
|
||||
if (m_environmentVariables != null && m_environmentVariables.length() > 0)
|
||||
m_environmentVariables = additionalEnvironmentVariables + "\t" + m_environmentVariables;
|
||||
else
|
||||
m_environmentVariables = additionalEnvironmentVariables;
|
||||
|
||||
if (loaderParams.containsKey(APPLICATION_PARAMETERS_KEY))
|
||||
m_applicationParameters = loaderParams.getString(APPLICATION_PARAMETERS_KEY);
|
||||
else
|
||||
m_applicationParameters = "";
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean startApplication()
|
||||
{
|
||||
// start application
|
||||
try {
|
||||
String nativeLibraryDir = QtNativeLibrariesDir.nativeLibrariesDir(m_service);
|
||||
QtNative.startApplication(m_applicationParameters,
|
||||
m_environmentVariables,
|
||||
m_mainLib,
|
||||
nativeLibraryDir);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void onDestroy()
|
||||
{
|
||||
QtNative.setService(null, null);
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright (c) 2012-2013, BogDan Vatra <bogdan@kde.org>
|
||||
Copyright (c) 2016, BogDan Vatra <bogdan@kde.org>
|
||||
Contact: http://www.qt.io/licensing/
|
||||
|
||||
Commercial License Usage
|
||||
@ -36,47 +36,20 @@
|
||||
|
||||
package org.qtproject.qt5.android.bindings;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.kde.necessitas.ministro.IMinistro;
|
||||
import org.kde.necessitas.ministro.IMinistroCallback;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.content.ComponentName;
|
||||
import android.app.Fragment;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.ServiceConnection;
|
||||
import android.content.pm.ActivityInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.PackageManager.NameNotFoundException;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.res.Configuration;
|
||||
import android.content.res.Resources.Theme;
|
||||
import android.content.res.AssetManager;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
import android.os.RemoteException;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.view.ActionMode;
|
||||
import android.view.ActionMode.Callback;
|
||||
import android.view.ContextMenu;
|
||||
import android.view.ContextMenu.ContextMenuInfo;
|
||||
import android.view.KeyEvent;
|
||||
@ -84,601 +57,25 @@ import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.Window;
|
||||
import android.view.WindowManager.LayoutParams;
|
||||
import android.view.accessibility.AccessibilityEvent;
|
||||
import dalvik.system.DexClassLoader;
|
||||
|
||||
//@ANDROID-11
|
||||
import android.app.Fragment;
|
||||
import android.view.ActionMode;
|
||||
import android.view.ActionMode.Callback;
|
||||
//@ANDROID-11
|
||||
|
||||
|
||||
public class QtActivity extends Activity
|
||||
{
|
||||
private final static int MINISTRO_INSTALL_REQUEST_CODE = 0xf3ee; // request code used to know when Ministro instalation is finished
|
||||
private static final int MINISTRO_API_LEVEL = 5; // Ministro api level (check IMinistro.aidl file)
|
||||
private static final int NECESSITAS_API_LEVEL = 2; // Necessitas api level used by platform plugin
|
||||
private static final int QT_VERSION = 0x050100; // This app requires at least Qt version 5.1.0
|
||||
|
||||
private static final String ERROR_CODE_KEY = "error.code";
|
||||
private static final String ERROR_MESSAGE_KEY = "error.message";
|
||||
private static final String DEX_PATH_KEY = "dex.path";
|
||||
private static final String LIB_PATH_KEY = "lib.path";
|
||||
private static final String LOADER_CLASS_NAME_KEY = "loader.class.name";
|
||||
private static final String NATIVE_LIBRARIES_KEY = "native.libraries";
|
||||
private static final String ENVIRONMENT_VARIABLES_KEY = "environment.variables";
|
||||
private static final String APPLICATION_PARAMETERS_KEY = "application.parameters";
|
||||
private static final String BUNDLED_LIBRARIES_KEY = "bundled.libraries";
|
||||
private static final String BUNDLED_IN_LIB_RESOURCE_ID_KEY = "android.app.bundled_in_lib_resource_id";
|
||||
private static final String BUNDLED_IN_ASSETS_RESOURCE_ID_KEY = "android.app.bundled_in_assets_resource_id";
|
||||
private static final String MAIN_LIBRARY_KEY = "main.library";
|
||||
private static final String STATIC_INIT_CLASSES_KEY = "static.init.classes";
|
||||
private static final String NECESSITAS_API_LEVEL_KEY = "necessitas.api.level";
|
||||
private static final String EXTRACT_STYLE_KEY = "extract.android.style";
|
||||
|
||||
/// Ministro server parameter keys
|
||||
private static final String REQUIRED_MODULES_KEY = "required.modules";
|
||||
private static final String APPLICATION_TITLE_KEY = "application.title";
|
||||
private static final String MINIMUM_MINISTRO_API_KEY = "minimum.ministro.api";
|
||||
private static final String MINIMUM_QT_VERSION_KEY = "minimum.qt.version";
|
||||
private static final String SOURCES_KEY = "sources"; // needs MINISTRO_API_LEVEL >=3 !!!
|
||||
// Use this key to specify any 3rd party sources urls
|
||||
// Ministro will download these repositories into their
|
||||
// own folders, check http://community.kde.org/Necessitas/Ministro
|
||||
// for more details.
|
||||
|
||||
private static final String REPOSITORY_KEY = "repository"; // use this key to overwrite the default ministro repsitory
|
||||
private static final String ANDROID_THEMES_KEY = "android.themes"; // themes that your application uses
|
||||
|
||||
|
||||
public String APPLICATION_PARAMETERS = null; // use this variable to pass any parameters to your application,
|
||||
// the parameters must not contain any white spaces
|
||||
// and must be separated with "\t"
|
||||
// e.g "-param1\t-param2=value2\t-param3\tvalue3"
|
||||
|
||||
public String ENVIRONMENT_VARIABLES = "QT_USE_ANDROID_NATIVE_STYLE=1\tQT_USE_ANDROID_NATIVE_DIALOGS=1\t";
|
||||
// use this variable to add any environment variables to your application.
|
||||
// the env vars must be separated with "\t"
|
||||
// e.g. "ENV_VAR1=1\tENV_VAR2=2\t"
|
||||
// Currently the following vars are used by the android plugin:
|
||||
// * QT_USE_ANDROID_NATIVE_STYLE - 1 to use the android widget style if available.
|
||||
// * QT_USE_ANDROID_NATIVE_DIALOGS -1 to use the android native dialogs.
|
||||
|
||||
public String[] QT_ANDROID_THEMES = null; // A list with all themes that your application want to use.
|
||||
// The name of the theme must be the same with any theme from
|
||||
// http://developer.android.com/reference/android/R.style.html
|
||||
// The most used themes are:
|
||||
// * "Theme" - (fallback) check http://developer.android.com/reference/android/R.style.html#Theme
|
||||
// * "Theme_Black" - check http://developer.android.com/reference/android/R.style.html#Theme_Black
|
||||
// * "Theme_Light" - (default for API <=10) check http://developer.android.com/reference/android/R.style.html#Theme_Light
|
||||
// * "Theme_Holo" - check http://developer.android.com/reference/android/R.style.html#Theme_Holo
|
||||
// * "Theme_Holo_Light" - (default for API 11-13) check http://developer.android.com/reference/android/R.style.html#Theme_Holo_Light
|
||||
// * "Theme_DeviceDefault" - check http://developer.android.com/reference/android/R.style.html#Theme_DeviceDefault
|
||||
// * "Theme_DeviceDefault_Light" - (default for API 14+) check http://developer.android.com/reference/android/R.style.html#Theme_DeviceDefault_Light
|
||||
|
||||
public String QT_ANDROID_DEFAULT_THEME = null; // sets the default theme.
|
||||
|
||||
private static final int INCOMPATIBLE_MINISTRO_VERSION = 1; // Incompatible Ministro version. Ministro needs to be upgraded.
|
||||
private static final int BUFFER_SIZE = 1024;
|
||||
|
||||
private ActivityInfo m_activityInfo = null; // activity info object, used to access the libs and the strings
|
||||
private DexClassLoader m_classLoader = null; // loader object
|
||||
private String[] m_sources = {"https://download.qt-project.org/ministro/android/qt5/qt-5.2"}; // Make sure you are using ONLY secure locations
|
||||
private String m_repository = "default"; // Overwrites the default Ministro repository
|
||||
// Possible values:
|
||||
// * default - Ministro default repository set with "Ministro configuration tool".
|
||||
// By default the stable version is used. Only this or stable repositories should
|
||||
// be used in production.
|
||||
// * stable - stable repository, only this and default repositories should be used
|
||||
// in production.
|
||||
// * testing - testing repository, DO NOT use this repository in production,
|
||||
// this repository is used to push a new release, and should be used to test your application.
|
||||
// * unstable - unstable repository, DO NOT use this repository in production,
|
||||
// this repository is used to push Qt snapshots.
|
||||
private String[] m_qtLibs = null; // required qt libs
|
||||
private int m_displayDensity = -1;
|
||||
|
||||
QtActivityLoader m_loader;
|
||||
public QtActivity()
|
||||
{
|
||||
m_loader = new QtActivityLoader(this);
|
||||
if (Build.VERSION.SDK_INT >= 21) {
|
||||
QT_ANDROID_THEMES = new String[] {"Theme_Holo_Light"};
|
||||
QT_ANDROID_DEFAULT_THEME = "Theme_Holo_Light";
|
||||
m_loader.QT_ANDROID_THEMES = new String[] {"Theme_Holo_Light"};
|
||||
m_loader.QT_ANDROID_DEFAULT_THEME = "Theme_Holo_Light";
|
||||
} else {
|
||||
QT_ANDROID_THEMES = new String[] {"Theme_DeviceDefault_Light"};
|
||||
QT_ANDROID_DEFAULT_THEME = "Theme_DeviceDefault_Light";
|
||||
m_loader.QT_ANDROID_THEMES = new String[] {"Theme_DeviceDefault_Light"};
|
||||
m_loader.QT_ANDROID_DEFAULT_THEME = "Theme_DeviceDefault_Light";
|
||||
}
|
||||
}
|
||||
|
||||
// this function is used to load and start the loader
|
||||
private void loadApplication(Bundle loaderParams)
|
||||
{
|
||||
try {
|
||||
final int errorCode = loaderParams.getInt(ERROR_CODE_KEY);
|
||||
if (errorCode != 0) {
|
||||
if (errorCode == INCOMPATIBLE_MINISTRO_VERSION) {
|
||||
downloadUpgradeMinistro(loaderParams.getString(ERROR_MESSAGE_KEY));
|
||||
return;
|
||||
}
|
||||
|
||||
// fatal error, show the error and quit
|
||||
AlertDialog errorDialog = new AlertDialog.Builder(QtActivity.this).create();
|
||||
errorDialog.setMessage(loaderParams.getString(ERROR_MESSAGE_KEY));
|
||||
errorDialog.setButton(getResources().getString(android.R.string.ok), new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
finish();
|
||||
}
|
||||
});
|
||||
errorDialog.show();
|
||||
return;
|
||||
}
|
||||
|
||||
// add all bundled Qt libs to loader params
|
||||
ArrayList<String> libs = new ArrayList<String>();
|
||||
if ( m_activityInfo.metaData.containsKey("android.app.bundled_libs_resource_id") )
|
||||
libs.addAll(Arrays.asList(getResources().getStringArray(m_activityInfo.metaData.getInt("android.app.bundled_libs_resource_id"))));
|
||||
|
||||
String libName = null;
|
||||
if ( m_activityInfo.metaData.containsKey("android.app.lib_name") ) {
|
||||
libName = m_activityInfo.metaData.getString("android.app.lib_name");
|
||||
loaderParams.putString(MAIN_LIBRARY_KEY, libName); //main library contains main() function
|
||||
}
|
||||
|
||||
loaderParams.putStringArrayList(BUNDLED_LIBRARIES_KEY, libs);
|
||||
loaderParams.putInt(NECESSITAS_API_LEVEL_KEY, NECESSITAS_API_LEVEL);
|
||||
|
||||
// load and start QtLoader class
|
||||
m_classLoader = new DexClassLoader(loaderParams.getString(DEX_PATH_KEY), // .jar/.apk files
|
||||
getDir("outdex", Context.MODE_PRIVATE).getAbsolutePath(), // directory where optimized DEX files should be written.
|
||||
loaderParams.containsKey(LIB_PATH_KEY) ? loaderParams.getString(LIB_PATH_KEY) : null, // libs folder (if exists)
|
||||
getClassLoader()); // parent loader
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
Class loaderClass = m_classLoader.loadClass(loaderParams.getString(LOADER_CLASS_NAME_KEY)); // load QtLoader class
|
||||
Object qtLoader = loaderClass.newInstance(); // create an instance
|
||||
Method prepareAppMethod = qtLoader.getClass().getMethod("loadApplication",
|
||||
Activity.class,
|
||||
ClassLoader.class,
|
||||
Bundle.class);
|
||||
if (!(Boolean)prepareAppMethod.invoke(qtLoader, this, m_classLoader, loaderParams))
|
||||
throw new Exception("");
|
||||
|
||||
QtApplication.setQtActivityDelegate(qtLoader);
|
||||
|
||||
// now load the application library so it's accessible from this class loader
|
||||
if (libName != null)
|
||||
System.loadLibrary(libName);
|
||||
|
||||
Method startAppMethod=qtLoader.getClass().getMethod("startApplication");
|
||||
if (!(Boolean)startAppMethod.invoke(qtLoader))
|
||||
throw new Exception("");
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
AlertDialog errorDialog = new AlertDialog.Builder(QtActivity.this).create();
|
||||
if (m_activityInfo.metaData.containsKey("android.app.fatal_error_msg"))
|
||||
errorDialog.setMessage(m_activityInfo.metaData.getString("android.app.fatal_error_msg"));
|
||||
else
|
||||
errorDialog.setMessage("Fatal error, your application can't be started.");
|
||||
|
||||
errorDialog.setButton(getResources().getString(android.R.string.ok), new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
finish();
|
||||
}
|
||||
});
|
||||
errorDialog.show();
|
||||
}
|
||||
}
|
||||
|
||||
private ServiceConnection m_ministroConnection=new ServiceConnection() {
|
||||
private IMinistro m_service = null;
|
||||
@Override
|
||||
public void onServiceConnected(ComponentName name, IBinder service)
|
||||
{
|
||||
m_service = IMinistro.Stub.asInterface(service);
|
||||
try {
|
||||
if (m_service != null) {
|
||||
Bundle parameters = new Bundle();
|
||||
parameters.putStringArray(REQUIRED_MODULES_KEY, m_qtLibs);
|
||||
parameters.putString(APPLICATION_TITLE_KEY, (String)QtActivity.this.getTitle());
|
||||
parameters.putInt(MINIMUM_MINISTRO_API_KEY, MINISTRO_API_LEVEL);
|
||||
parameters.putInt(MINIMUM_QT_VERSION_KEY, QT_VERSION);
|
||||
parameters.putString(ENVIRONMENT_VARIABLES_KEY, ENVIRONMENT_VARIABLES);
|
||||
if (APPLICATION_PARAMETERS != null)
|
||||
parameters.putString(APPLICATION_PARAMETERS_KEY, APPLICATION_PARAMETERS);
|
||||
parameters.putStringArray(SOURCES_KEY, m_sources);
|
||||
parameters.putString(REPOSITORY_KEY, m_repository);
|
||||
if (QT_ANDROID_THEMES != null)
|
||||
parameters.putStringArray(ANDROID_THEMES_KEY, QT_ANDROID_THEMES);
|
||||
m_service.requestLoader(m_ministroCallback, parameters);
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private IMinistroCallback m_ministroCallback = new IMinistroCallback.Stub() {
|
||||
// this function is called back by Ministro.
|
||||
@Override
|
||||
public void loaderReady(final Bundle loaderParams) throws RemoteException {
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
unbindService(m_ministroConnection);
|
||||
loadApplication(loaderParams);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public void onServiceDisconnected(ComponentName name) {
|
||||
m_service = null;
|
||||
}
|
||||
};
|
||||
|
||||
private void downloadUpgradeMinistro(String msg)
|
||||
{
|
||||
AlertDialog.Builder downloadDialog = new AlertDialog.Builder(this);
|
||||
downloadDialog.setMessage(msg);
|
||||
downloadDialog.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialogInterface, int i) {
|
||||
try {
|
||||
Uri uri = Uri.parse("market://search?q=pname:org.kde.necessitas.ministro");
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
|
||||
startActivityForResult(intent, MINISTRO_INSTALL_REQUEST_CODE);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
ministroNotFound();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
downloadDialog.setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialogInterface, int i) {
|
||||
QtActivity.this.finish();
|
||||
}
|
||||
});
|
||||
downloadDialog.show();
|
||||
}
|
||||
|
||||
private void ministroNotFound()
|
||||
{
|
||||
AlertDialog errorDialog = new AlertDialog.Builder(QtActivity.this).create();
|
||||
|
||||
if (m_activityInfo.metaData.containsKey("android.app.ministro_not_found_msg"))
|
||||
errorDialog.setMessage(m_activityInfo.metaData.getString("android.app.ministro_not_found_msg"));
|
||||
else
|
||||
errorDialog.setMessage("Can't find Ministro service.\nThe application can't start.");
|
||||
|
||||
errorDialog.setButton(getResources().getString(android.R.string.ok), new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
finish();
|
||||
}
|
||||
});
|
||||
errorDialog.show();
|
||||
}
|
||||
|
||||
static private void copyFile(InputStream inputStream, OutputStream outputStream)
|
||||
throws IOException
|
||||
{
|
||||
byte[] buffer = new byte[BUFFER_SIZE];
|
||||
|
||||
int count;
|
||||
while ((count = inputStream.read(buffer)) > 0)
|
||||
outputStream.write(buffer, 0, count);
|
||||
}
|
||||
|
||||
|
||||
private void copyAsset(String source, String destination)
|
||||
throws IOException
|
||||
{
|
||||
// Already exists, we don't have to do anything
|
||||
File destinationFile = new File(destination);
|
||||
if (destinationFile.exists())
|
||||
return;
|
||||
|
||||
File parentDirectory = destinationFile.getParentFile();
|
||||
if (!parentDirectory.exists())
|
||||
parentDirectory.mkdirs();
|
||||
|
||||
destinationFile.createNewFile();
|
||||
|
||||
AssetManager assetsManager = getAssets();
|
||||
InputStream inputStream = assetsManager.open(source);
|
||||
OutputStream outputStream = new FileOutputStream(destinationFile);
|
||||
copyFile(inputStream, outputStream);
|
||||
|
||||
inputStream.close();
|
||||
outputStream.close();
|
||||
}
|
||||
|
||||
private static void createBundledBinary(String source, String destination)
|
||||
throws IOException
|
||||
{
|
||||
// Already exists, we don't have to do anything
|
||||
File destinationFile = new File(destination);
|
||||
if (destinationFile.exists())
|
||||
return;
|
||||
|
||||
File parentDirectory = destinationFile.getParentFile();
|
||||
if (!parentDirectory.exists())
|
||||
parentDirectory.mkdirs();
|
||||
|
||||
destinationFile.createNewFile();
|
||||
|
||||
InputStream inputStream = new FileInputStream(source);
|
||||
OutputStream outputStream = new FileOutputStream(destinationFile);
|
||||
copyFile(inputStream, outputStream);
|
||||
|
||||
inputStream.close();
|
||||
outputStream.close();
|
||||
}
|
||||
|
||||
private boolean cleanCacheIfNecessary(String pluginsPrefix, long packageVersion)
|
||||
{
|
||||
File versionFile = new File(pluginsPrefix + "cache.version");
|
||||
|
||||
long cacheVersion = 0;
|
||||
if (versionFile.exists() && versionFile.canRead()) {
|
||||
try {
|
||||
DataInputStream inputStream = new DataInputStream(new FileInputStream(versionFile));
|
||||
cacheVersion = inputStream.readLong();
|
||||
inputStream.close();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
if (cacheVersion != packageVersion) {
|
||||
deleteRecursively(new File(pluginsPrefix));
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void extractBundledPluginsAndImports(String pluginsPrefix)
|
||||
throws IOException
|
||||
{
|
||||
ArrayList<String> libs = new ArrayList<String>();
|
||||
|
||||
String libsDir = getApplicationInfo().nativeLibraryDir + "/";
|
||||
|
||||
long packageVersion = -1;
|
||||
try {
|
||||
PackageInfo packageInfo = getPackageManager().getPackageInfo(getPackageName(), 0);
|
||||
packageVersion = packageInfo.lastUpdateTime;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
if (!cleanCacheIfNecessary(pluginsPrefix, packageVersion))
|
||||
return;
|
||||
|
||||
{
|
||||
File versionFile = new File(pluginsPrefix + "cache.version");
|
||||
|
||||
File parentDirectory = versionFile.getParentFile();
|
||||
if (!parentDirectory.exists())
|
||||
parentDirectory.mkdirs();
|
||||
|
||||
versionFile.createNewFile();
|
||||
|
||||
DataOutputStream outputStream = new DataOutputStream(new FileOutputStream(versionFile));
|
||||
outputStream.writeLong(packageVersion);
|
||||
outputStream.close();
|
||||
}
|
||||
|
||||
{
|
||||
String key = BUNDLED_IN_LIB_RESOURCE_ID_KEY;
|
||||
java.util.Set<String> keys = m_activityInfo.metaData.keySet();
|
||||
if (m_activityInfo.metaData.containsKey(key)) {
|
||||
String[] list = getResources().getStringArray(m_activityInfo.metaData.getInt(key));
|
||||
|
||||
for (String bundledImportBinary : list) {
|
||||
String[] split = bundledImportBinary.split(":");
|
||||
String sourceFileName = libsDir + split[0];
|
||||
String destinationFileName = pluginsPrefix + split[1];
|
||||
createBundledBinary(sourceFileName, destinationFileName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
String key = BUNDLED_IN_ASSETS_RESOURCE_ID_KEY;
|
||||
if (m_activityInfo.metaData.containsKey(key)) {
|
||||
String[] list = getResources().getStringArray(m_activityInfo.metaData.getInt(key));
|
||||
|
||||
for (String fileName : list) {
|
||||
String[] split = fileName.split(":");
|
||||
String sourceFileName = split[0];
|
||||
String destinationFileName = pluginsPrefix + split[1];
|
||||
copyAsset(sourceFileName, destinationFileName);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private void deleteRecursively(File directory)
|
||||
{
|
||||
File[] files = directory.listFiles();
|
||||
if (files != null) {
|
||||
for (File file : files) {
|
||||
if (file.isDirectory())
|
||||
deleteRecursively(file);
|
||||
else
|
||||
file.delete();
|
||||
}
|
||||
|
||||
directory.delete();
|
||||
}
|
||||
}
|
||||
|
||||
private void cleanOldCacheIfNecessary(String oldLocalPrefix, String localPrefix)
|
||||
{
|
||||
File newCache = new File(localPrefix);
|
||||
if (!newCache.exists()) {
|
||||
{
|
||||
File oldPluginsCache = new File(oldLocalPrefix + "plugins/");
|
||||
if (oldPluginsCache.exists() && oldPluginsCache.isDirectory())
|
||||
deleteRecursively(oldPluginsCache);
|
||||
}
|
||||
|
||||
{
|
||||
File oldImportsCache = new File(oldLocalPrefix + "imports/");
|
||||
if (oldImportsCache.exists() && oldImportsCache.isDirectory())
|
||||
deleteRecursively(oldImportsCache);
|
||||
}
|
||||
|
||||
{
|
||||
File oldQmlCache = new File(oldLocalPrefix + "qml/");
|
||||
if (oldQmlCache.exists() && oldQmlCache.isDirectory())
|
||||
deleteRecursively(oldQmlCache);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void startApp(final boolean firstStart)
|
||||
{
|
||||
try {
|
||||
if (m_activityInfo.metaData.containsKey("android.app.qt_sources_resource_id")) {
|
||||
int resourceId = m_activityInfo.metaData.getInt("android.app.qt_sources_resource_id");
|
||||
m_sources = getResources().getStringArray(resourceId);
|
||||
}
|
||||
|
||||
if (m_activityInfo.metaData.containsKey("android.app.repository"))
|
||||
m_repository = m_activityInfo.metaData.getString("android.app.repository");
|
||||
|
||||
if (m_activityInfo.metaData.containsKey("android.app.qt_libs_resource_id")) {
|
||||
int resourceId = m_activityInfo.metaData.getInt("android.app.qt_libs_resource_id");
|
||||
m_qtLibs = getResources().getStringArray(resourceId);
|
||||
}
|
||||
|
||||
if (m_activityInfo.metaData.containsKey("android.app.use_local_qt_libs")
|
||||
&& m_activityInfo.metaData.getInt("android.app.use_local_qt_libs") == 1) {
|
||||
ArrayList<String> libraryList = new ArrayList<String>();
|
||||
|
||||
|
||||
String localPrefix = "/data/local/tmp/qt/";
|
||||
if (m_activityInfo.metaData.containsKey("android.app.libs_prefix"))
|
||||
localPrefix = m_activityInfo.metaData.getString("android.app.libs_prefix");
|
||||
|
||||
String pluginsPrefix = localPrefix;
|
||||
|
||||
boolean bundlingQtLibs = false;
|
||||
if (m_activityInfo.metaData.containsKey("android.app.bundle_local_qt_libs")
|
||||
&& m_activityInfo.metaData.getInt("android.app.bundle_local_qt_libs") == 1) {
|
||||
localPrefix = getApplicationInfo().dataDir + "/";
|
||||
pluginsPrefix = localPrefix + "qt-reserved-files/";
|
||||
cleanOldCacheIfNecessary(localPrefix, pluginsPrefix);
|
||||
extractBundledPluginsAndImports(pluginsPrefix);
|
||||
bundlingQtLibs = true;
|
||||
}
|
||||
|
||||
if (m_qtLibs != null) {
|
||||
for (int i=0;i<m_qtLibs.length;i++) {
|
||||
libraryList.add(localPrefix
|
||||
+ "lib/lib"
|
||||
+ m_qtLibs[i]
|
||||
+ ".so");
|
||||
}
|
||||
}
|
||||
|
||||
if (m_activityInfo.metaData.containsKey("android.app.load_local_libs")) {
|
||||
String[] extraLibs = m_activityInfo.metaData.getString("android.app.load_local_libs").split(":");
|
||||
for (String lib : extraLibs) {
|
||||
if (lib.length() > 0) {
|
||||
if (lib.startsWith("lib/"))
|
||||
libraryList.add(localPrefix + lib);
|
||||
else
|
||||
libraryList.add(pluginsPrefix + lib);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
String dexPaths = new String();
|
||||
String pathSeparator = System.getProperty("path.separator", ":");
|
||||
if (!bundlingQtLibs && m_activityInfo.metaData.containsKey("android.app.load_local_jars")) {
|
||||
String[] jarFiles = m_activityInfo.metaData.getString("android.app.load_local_jars").split(":");
|
||||
for (String jar:jarFiles) {
|
||||
if (jar.length() > 0) {
|
||||
if (dexPaths.length() > 0)
|
||||
dexPaths += pathSeparator;
|
||||
dexPaths += localPrefix + jar;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Bundle loaderParams = new Bundle();
|
||||
loaderParams.putInt(ERROR_CODE_KEY, 0);
|
||||
loaderParams.putString(DEX_PATH_KEY, dexPaths);
|
||||
loaderParams.putString(LOADER_CLASS_NAME_KEY, "org.qtproject.qt5.android.QtActivityDelegate");
|
||||
if (m_activityInfo.metaData.containsKey("android.app.static_init_classes")) {
|
||||
loaderParams.putStringArray(STATIC_INIT_CLASSES_KEY,
|
||||
m_activityInfo.metaData.getString("android.app.static_init_classes").split(":"));
|
||||
}
|
||||
loaderParams.putStringArrayList(NATIVE_LIBRARIES_KEY, libraryList);
|
||||
|
||||
|
||||
String themePath = getApplicationInfo().dataDir + "/qt-reserved-files/android-style/";
|
||||
String stylePath = themePath + m_displayDensity + "/";
|
||||
if (!(new File(stylePath)).exists())
|
||||
loaderParams.putString(EXTRACT_STYLE_KEY, stylePath);
|
||||
ENVIRONMENT_VARIABLES += "\tMINISTRO_ANDROID_STYLE_PATH=" + stylePath
|
||||
+ "\tQT_ANDROID_THEMES_ROOT_PATH=" + themePath;
|
||||
|
||||
loaderParams.putString(ENVIRONMENT_VARIABLES_KEY, ENVIRONMENT_VARIABLES
|
||||
+ "\tQML2_IMPORT_PATH=" + pluginsPrefix + "/qml"
|
||||
+ "\tQML_IMPORT_PATH=" + pluginsPrefix + "/imports"
|
||||
+ "\tQT_PLUGIN_PATH=" + pluginsPrefix + "/plugins");
|
||||
|
||||
if (APPLICATION_PARAMETERS != null) {
|
||||
loaderParams.putString(APPLICATION_PARAMETERS_KEY, APPLICATION_PARAMETERS);
|
||||
} else {
|
||||
Intent intent = getIntent();
|
||||
if (intent != null) {
|
||||
String parameters = intent.getStringExtra("applicationArguments");
|
||||
if (parameters != null)
|
||||
loaderParams.putString(APPLICATION_PARAMETERS_KEY, parameters.replace(' ', '\t'));
|
||||
}
|
||||
}
|
||||
|
||||
loadApplication(loaderParams);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (!bindService(new Intent(org.kde.necessitas.ministro.IMinistro.class.getCanonicalName()),
|
||||
m_ministroConnection,
|
||||
Context.BIND_AUTO_CREATE)) {
|
||||
throw new SecurityException("");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
if (firstStart) {
|
||||
String msg = "This application requires Ministro service. Would you like to install it?";
|
||||
if (m_activityInfo.metaData.containsKey("android.app.ministro_needed_msg"))
|
||||
msg = m_activityInfo.metaData.getString("android.app.ministro_needed_msg");
|
||||
downloadUpgradeMinistro(msg);
|
||||
} else {
|
||||
ministroNotFound();
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(QtApplication.QtTAG, "Can't create main activity", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/////////////////////////// forward all notifications ////////////////////////////
|
||||
/////////////////////////// Super class calls ////////////////////////////////////
|
||||
@ -749,8 +146,8 @@ public class QtActivity extends Activity
|
||||
QtApplication.invokeDelegateMethod(QtApplication.onActivityResult, requestCode, resultCode, data);
|
||||
return;
|
||||
}
|
||||
if (requestCode == MINISTRO_INSTALL_REQUEST_CODE)
|
||||
startApp(false);
|
||||
if (requestCode == QtLoader.MINISTRO_INSTALL_REQUEST_CODE)
|
||||
m_loader.startApp(false);
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
}
|
||||
public void super_onActivityResult(int requestCode, int resultCode, Intent data)
|
||||
@ -839,79 +236,7 @@ public class QtActivity extends Activity
|
||||
public void onCreate(Bundle savedInstanceState)
|
||||
{
|
||||
super.onCreate(savedInstanceState);
|
||||
try {
|
||||
m_activityInfo = getPackageManager().getActivityInfo(getComponentName(), PackageManager.GET_META_DATA);
|
||||
for (Field f : Class.forName("android.R$style").getDeclaredFields()) {
|
||||
if (f.getInt(null) == m_activityInfo.getThemeResource()) {
|
||||
QT_ANDROID_THEMES = new String[] {f.getName()};
|
||||
QT_ANDROID_DEFAULT_THEME = f.getName();
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT < 16) {
|
||||
// fatal error, show the error and quit
|
||||
AlertDialog errorDialog = new AlertDialog.Builder(QtActivity.this).create();
|
||||
if (m_activityInfo.metaData.containsKey("android.app.unsupported_android_version"))
|
||||
errorDialog.setMessage(m_activityInfo.metaData.getString("android.app.unsupported_android_version"));
|
||||
else
|
||||
errorDialog.setMessage("Unsupported Android version.");
|
||||
errorDialog.setButton(getResources().getString(android.R.string.ok), new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
finish();
|
||||
}
|
||||
});
|
||||
errorDialog.show();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setTheme(Class.forName("android.R$style").getDeclaredField(QT_ANDROID_DEFAULT_THEME).getInt(null));
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
requestWindowFeature(Window.FEATURE_ACTION_BAR);
|
||||
|
||||
if (QtApplication.m_delegateObject != null && QtApplication.onCreate != null) {
|
||||
QtApplication.invokeDelegateMethod(QtApplication.onCreate, savedInstanceState);
|
||||
return;
|
||||
}
|
||||
|
||||
m_displayDensity = getResources().getDisplayMetrics().densityDpi;
|
||||
|
||||
ENVIRONMENT_VARIABLES += "\tQT_ANDROID_THEME=" + QT_ANDROID_DEFAULT_THEME
|
||||
+ "/\tQT_ANDROID_THEME_DISPLAY_DPI=" + m_displayDensity + "\t";
|
||||
|
||||
if (null == getLastNonConfigurationInstance()) {
|
||||
// if splash screen is defined, then show it
|
||||
// Note: QtActivityDelegate handles updating the splash screen
|
||||
// in onConfigurationChanged, change that too if you are changing
|
||||
// how the splash screen should be displayed
|
||||
if (m_activityInfo.metaData.containsKey("android.app.splash_screen_drawable"))
|
||||
getWindow().setBackgroundDrawableResource(m_activityInfo.metaData.getInt("android.app.splash_screen_drawable"));
|
||||
else
|
||||
getWindow().setBackgroundDrawable(new ColorDrawable(0xff000000));
|
||||
|
||||
if (m_activityInfo.metaData.containsKey("android.app.background_running")
|
||||
&& m_activityInfo.metaData.getBoolean("android.app.background_running")) {
|
||||
ENVIRONMENT_VARIABLES += "QT_BLOCK_EVENT_LOOPS_WHEN_SUSPENDED=0\t";
|
||||
} else {
|
||||
ENVIRONMENT_VARIABLES += "QT_BLOCK_EVENT_LOOPS_WHEN_SUSPENDED=1\t";
|
||||
}
|
||||
|
||||
if (m_activityInfo.metaData.containsKey("android.app.auto_screen_scale_factor")
|
||||
&& m_activityInfo.metaData.getBoolean("android.app.auto_screen_scale_factor")) {
|
||||
ENVIRONMENT_VARIABLES += "QT_AUTO_SCREEN_SCALE_FACTOR=1\t";
|
||||
}
|
||||
|
||||
startApp(true);
|
||||
}
|
||||
m_loader.onCreate(savedInstanceState);
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
|
@ -0,0 +1,193 @@
|
||||
/*
|
||||
Copyright (c) 2016, BogDan Vatra <bogdan@kde.org>
|
||||
Contact: http://www.qt-project.org/legal
|
||||
|
||||
Commercial License Usage
|
||||
Licensees holding valid commercial Qt licenses may use this file in
|
||||
accordance with the commercial license agreement provided with the
|
||||
Software or, alternatively, in accordance with the terms contained in
|
||||
a written agreement between you and Digia. For licensing terms and
|
||||
conditions see http://qt.digia.com/licensing. For further information
|
||||
use the contact form at http://qt.digia.com/contact-us.
|
||||
|
||||
BSD License Usage
|
||||
Alternatively, this file may be used under the BSD license as follows:
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package org.qtproject.qt5.android.bindings;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ActivityInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.view.Window;
|
||||
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
public class QtActivityLoader extends QtLoader {
|
||||
QtActivity m_activity;
|
||||
|
||||
QtActivityLoader(QtActivity activity)
|
||||
{
|
||||
super(activity);
|
||||
m_activity = activity;
|
||||
}
|
||||
@Override
|
||||
protected void downloadUpgradeMinistro(String msg) {
|
||||
AlertDialog.Builder downloadDialog = new AlertDialog.Builder(m_activity);
|
||||
downloadDialog.setMessage(msg);
|
||||
downloadDialog.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialogInterface, int i) {
|
||||
try {
|
||||
Uri uri = Uri.parse("market://search?q=pname:org.kde.necessitas.ministro");
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
|
||||
m_activity.startActivityForResult(intent, MINISTRO_INSTALL_REQUEST_CODE);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
ministroNotFound();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
downloadDialog.setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialogInterface, int i) {
|
||||
m_activity.finish();
|
||||
}
|
||||
});
|
||||
downloadDialog.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String loaderClassName() {
|
||||
return "org.qtproject.qt5.android.QtActivityDelegate";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Class<?> contextClassName() {
|
||||
return android.app.Activity.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void finish() {
|
||||
m_activity.finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getTitle() {
|
||||
return (String) m_activity.getTitle();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void runOnUiThread(Runnable run) {
|
||||
m_activity.runOnUiThread(run);
|
||||
}
|
||||
|
||||
@Override
|
||||
Intent getIntent() {
|
||||
return m_activity.getIntent();
|
||||
}
|
||||
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
try {
|
||||
m_contextInfo = m_activity.getPackageManager().getActivityInfo(m_activity.getComponentName(), PackageManager.GET_META_DATA);
|
||||
for (Field f : Class.forName("android.R$style").getDeclaredFields()) {
|
||||
if (f.getInt(null) == ((ActivityInfo)m_contextInfo).getThemeResource()) {
|
||||
QT_ANDROID_THEMES = new String[] {f.getName()};
|
||||
QT_ANDROID_DEFAULT_THEME = f.getName();
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT < 16) {
|
||||
// fatal error, show the error and quit
|
||||
AlertDialog errorDialog = new AlertDialog.Builder(m_activity).create();
|
||||
if (m_contextInfo.metaData.containsKey("android.app.unsupported_android_version"))
|
||||
errorDialog.setMessage(m_contextInfo.metaData.getString("android.app.unsupported_android_version"));
|
||||
else
|
||||
errorDialog.setMessage("Unsupported Android version.");
|
||||
errorDialog.setButton(m_activity.getResources().getString(android.R.string.ok), new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
finish();
|
||||
}
|
||||
});
|
||||
errorDialog.show();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
m_activity.setTheme(Class.forName("android.R$style").getDeclaredField(QT_ANDROID_DEFAULT_THEME).getInt(null));
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
m_activity.requestWindowFeature(Window.FEATURE_ACTION_BAR);
|
||||
|
||||
if (QtApplication.m_delegateObject != null && QtApplication.onCreate != null) {
|
||||
QtApplication.invokeDelegateMethod(QtApplication.onCreate, savedInstanceState);
|
||||
return;
|
||||
}
|
||||
|
||||
m_displayDensity = m_activity.getResources().getDisplayMetrics().densityDpi;
|
||||
|
||||
ENVIRONMENT_VARIABLES += "\tQT_ANDROID_THEME=" + QT_ANDROID_DEFAULT_THEME
|
||||
+ "/\tQT_ANDROID_THEME_DISPLAY_DPI=" + m_displayDensity + "\t";
|
||||
|
||||
if (null == m_activity.getLastNonConfigurationInstance()) {
|
||||
// if splash screen is defined, then show it
|
||||
// Note: QtActivityDelegate handles updating the splash screen
|
||||
// in onConfigurationChanged, change that too if you are changing
|
||||
// how the splash screen should be displayed
|
||||
if (m_contextInfo.metaData.containsKey("android.app.splash_screen_drawable"))
|
||||
m_activity.getWindow().setBackgroundDrawableResource(m_contextInfo.metaData.getInt("android.app.splash_screen_drawable"));
|
||||
else
|
||||
m_activity.getWindow().setBackgroundDrawable(new ColorDrawable(0xff000000));
|
||||
|
||||
if (m_contextInfo.metaData.containsKey("android.app.background_running")
|
||||
&& m_contextInfo.metaData.getBoolean("android.app.background_running")) {
|
||||
ENVIRONMENT_VARIABLES += "QT_BLOCK_EVENT_LOOPS_WHEN_SUSPENDED=0\t";
|
||||
} else {
|
||||
ENVIRONMENT_VARIABLES += "QT_BLOCK_EVENT_LOOPS_WHEN_SUSPENDED=1\t";
|
||||
}
|
||||
|
||||
if (m_contextInfo.metaData.containsKey("android.app.auto_screen_scale_factor")
|
||||
&& m_contextInfo.metaData.getBoolean("android.app.auto_screen_scale_factor")) {
|
||||
ENVIRONMENT_VARIABLES += "QT_AUTO_SCREEN_SCALE_FACTOR=1\t";
|
||||
}
|
||||
|
||||
startApp(true);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright (c) 2012-2013, BogDan Vatra <bogdan@kde.org>
|
||||
Copyright (c) 2016, BogDan Vatra <bogdan@kde.org>
|
||||
Contact: http://www.qt.io/licensing/
|
||||
|
||||
Commercial License Usage
|
||||
@ -36,13 +36,13 @@
|
||||
|
||||
package org.qtproject.qt5.android.bindings;
|
||||
|
||||
import android.app.Application;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
|
||||
import android.app.Application;
|
||||
|
||||
public class QtApplication extends Application
|
||||
{
|
||||
public final static String QtTAG = "Qt";
|
||||
@ -64,10 +64,11 @@ public class QtApplication extends Application
|
||||
public static Method onKeyShortcut = null;
|
||||
public static Method dispatchGenericMotionEvent = null;
|
||||
public static Method onGenericMotionEvent = null;
|
||||
|
||||
public static void setQtActivityDelegate(Object listener)
|
||||
private static String activityClassName;
|
||||
public static void setQtContextDelegate(Class<?> clazz, Object listener)
|
||||
{
|
||||
QtApplication.m_delegateObject = listener;
|
||||
m_delegateObject = listener;
|
||||
activityClassName = clazz.getCanonicalName();
|
||||
|
||||
ArrayList<Method> delegateMethods = new ArrayList<Method>();
|
||||
for (Method m : listener.getClass().getMethods()) {
|
||||
@ -83,7 +84,7 @@ public class QtApplication extends Application
|
||||
|
||||
for (Method delegateMethod : delegateMethods) {
|
||||
try {
|
||||
QtActivity.class.getDeclaredMethod(delegateMethod.getName(), delegateMethod.getParameterTypes());
|
||||
clazz.getDeclaredMethod(delegateMethod.getName(), delegateMethod.getParameterTypes());
|
||||
if (QtApplication.m_delegateMethods.containsKey(delegateMethod.getName())) {
|
||||
QtApplication.m_delegateMethods.get(delegateMethod.getName()).add(delegateMethod);
|
||||
} else {
|
||||
@ -126,7 +127,6 @@ public class QtApplication extends Application
|
||||
return result;
|
||||
StackTraceElement[] elements = Thread.currentThread().getStackTrace();
|
||||
if (-1 == stackDeep) {
|
||||
String activityClassName = QtActivity.class.getCanonicalName();
|
||||
for (int it=0;it<elements.length;it++)
|
||||
if (elements[it].getClassName().equals(activityClassName)) {
|
||||
stackDeep = it;
|
||||
|
@ -0,0 +1,655 @@
|
||||
/*
|
||||
Copyright (c) 2016, BogDan Vatra <bogdan@kde.org>
|
||||
Contact: http://www.qt.io/licensing/
|
||||
|
||||
Commercial License Usage
|
||||
Licensees holding valid commercial Qt licenses may use this file in
|
||||
accordance with the commercial license agreement provided with the
|
||||
Software or, alternatively, in accordance with the terms contained in
|
||||
a written agreement between you and The Qt Company. For licensing terms
|
||||
and conditions see http://www.qt.io/terms-conditions. For further
|
||||
information use the contact form at http://www.qt.io/contact-us.
|
||||
|
||||
BSD License Usage
|
||||
Alternatively, this file may be used under the BSD license as follows:
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package org.qtproject.qt5.android.bindings;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.ContextWrapper;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.ServiceConnection;
|
||||
import android.content.pm.ComponentInfo;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.res.AssetManager;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
|
||||
import org.kde.necessitas.ministro.IMinistro;
|
||||
import org.kde.necessitas.ministro.IMinistroCallback;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
|
||||
import dalvik.system.DexClassLoader;
|
||||
|
||||
public abstract class QtLoader {
|
||||
|
||||
public final static int MINISTRO_INSTALL_REQUEST_CODE = 0xf3ee; // request code used to know when Ministro instalation is finished
|
||||
public static final int MINISTRO_API_LEVEL = 5; // Ministro api level (check IMinistro.aidl file)
|
||||
public static final int NECESSITAS_API_LEVEL = 2; // Necessitas api level used by platform plugin
|
||||
public static final int QT_VERSION = 0x050100; // This app requires at least Qt version 5.1.0
|
||||
|
||||
public static final String ERROR_CODE_KEY = "error.code";
|
||||
public static final String ERROR_MESSAGE_KEY = "error.message";
|
||||
public static final String DEX_PATH_KEY = "dex.path";
|
||||
public static final String LIB_PATH_KEY = "lib.path";
|
||||
public static final String LOADER_CLASS_NAME_KEY = "loader.class.name";
|
||||
public static final String NATIVE_LIBRARIES_KEY = "native.libraries";
|
||||
public static final String ENVIRONMENT_VARIABLES_KEY = "environment.variables";
|
||||
public static final String APPLICATION_PARAMETERS_KEY = "application.parameters";
|
||||
public static final String BUNDLED_LIBRARIES_KEY = "bundled.libraries";
|
||||
public static final String BUNDLED_IN_LIB_RESOURCE_ID_KEY = "android.app.bundled_in_lib_resource_id";
|
||||
public static final String BUNDLED_IN_ASSETS_RESOURCE_ID_KEY = "android.app.bundled_in_assets_resource_id";
|
||||
public static final String MAIN_LIBRARY_KEY = "main.library";
|
||||
public static final String STATIC_INIT_CLASSES_KEY = "static.init.classes";
|
||||
public static final String NECESSITAS_API_LEVEL_KEY = "necessitas.api.level";
|
||||
public static final String EXTRACT_STYLE_KEY = "extract.android.style";
|
||||
|
||||
/// Ministro server parameter keys
|
||||
public static final String REQUIRED_MODULES_KEY = "required.modules";
|
||||
public static final String APPLICATION_TITLE_KEY = "application.title";
|
||||
public static final String MINIMUM_MINISTRO_API_KEY = "minimum.ministro.api";
|
||||
public static final String MINIMUM_QT_VERSION_KEY = "minimum.qt.version";
|
||||
public static final String SOURCES_KEY = "sources"; // needs MINISTRO_API_LEVEL >=3 !!!
|
||||
// Use this key to specify any 3rd party sources urls
|
||||
// Ministro will download these repositories into their
|
||||
// own folders, check http://community.kde.org/Necessitas/Ministro
|
||||
// for more details.
|
||||
|
||||
public static final String REPOSITORY_KEY = "repository"; // use this key to overwrite the default ministro repsitory
|
||||
public static final String ANDROID_THEMES_KEY = "android.themes"; // themes that your application uses
|
||||
|
||||
|
||||
public String APPLICATION_PARAMETERS = null; // use this variable to pass any parameters to your application,
|
||||
// the parameters must not contain any white spaces
|
||||
// and must be separated with "\t"
|
||||
// e.g "-param1\t-param2=value2\t-param3\tvalue3"
|
||||
|
||||
public String ENVIRONMENT_VARIABLES = "QT_USE_ANDROID_NATIVE_STYLE=1\tQT_USE_ANDROID_NATIVE_DIALOGS=1\t";
|
||||
// use this variable to add any environment variables to your application.
|
||||
// the env vars must be separated with "\t"
|
||||
// e.g. "ENV_VAR1=1\tENV_VAR2=2\t"
|
||||
// Currently the following vars are used by the android plugin:
|
||||
// * QT_USE_ANDROID_NATIVE_STYLE - 1 to use the android widget style if available.
|
||||
// * QT_USE_ANDROID_NATIVE_DIALOGS -1 to use the android native dialogs.
|
||||
|
||||
public String[] QT_ANDROID_THEMES = null; // A list with all themes that your application want to use.
|
||||
// The name of the theme must be the same with any theme from
|
||||
// http://developer.android.com/reference/android/R.style.html
|
||||
// The most used themes are:
|
||||
// * "Theme" - (fallback) check http://developer.android.com/reference/android/R.style.html#Theme
|
||||
// * "Theme_Black" - check http://developer.android.com/reference/android/R.style.html#Theme_Black
|
||||
// * "Theme_Light" - (default for API <=10) check http://developer.android.com/reference/android/R.style.html#Theme_Light
|
||||
// * "Theme_Holo" - check http://developer.android.com/reference/android/R.style.html#Theme_Holo
|
||||
// * "Theme_Holo_Light" - (default for API 11-13) check http://developer.android.com/reference/android/R.style.html#Theme_Holo_Light
|
||||
// * "Theme_DeviceDefault" - check http://developer.android.com/reference/android/R.style.html#Theme_DeviceDefault
|
||||
// * "Theme_DeviceDefault_Light" - (default for API 14+) check http://developer.android.com/reference/android/R.style.html#Theme_DeviceDefault_Light
|
||||
|
||||
public String QT_ANDROID_DEFAULT_THEME = null; // sets the default theme.
|
||||
|
||||
public static final int INCOMPATIBLE_MINISTRO_VERSION = 1; // Incompatible Ministro version. Ministro needs to be upgraded.
|
||||
public static final int BUFFER_SIZE = 1024;
|
||||
|
||||
public String[] m_sources = {"https://download.qt-project.org/ministro/android/qt5/qt-5.7"}; // Make sure you are using ONLY secure locations
|
||||
public String m_repository = "default"; // Overwrites the default Ministro repository
|
||||
// Possible values:
|
||||
// * default - Ministro default repository set with "Ministro configuration tool".
|
||||
// By default the stable version is used. Only this or stable repositories should
|
||||
// be used in production.
|
||||
// * stable - stable repository, only this and default repositories should be used
|
||||
// in production.
|
||||
// * testing - testing repository, DO NOT use this repository in production,
|
||||
// this repository is used to push a new release, and should be used to test your application.
|
||||
// * unstable - unstable repository, DO NOT use this repository in production,
|
||||
// this repository is used to push Qt snapshots.
|
||||
public String[] m_qtLibs = null; // required qt libs
|
||||
public int m_displayDensity = -1;
|
||||
private ContextWrapper m_context;
|
||||
protected ComponentInfo m_contextInfo;
|
||||
|
||||
QtLoader(ContextWrapper context) {
|
||||
m_context = context;
|
||||
}
|
||||
|
||||
// Implement in subclass
|
||||
protected void finish() {}
|
||||
|
||||
protected String getTitle() {
|
||||
return "Qt";
|
||||
}
|
||||
|
||||
protected void runOnUiThread(Runnable run) {
|
||||
run.run();
|
||||
}
|
||||
protected void downloadUpgradeMinistro(String msg)
|
||||
{
|
||||
Log.e(QtApplication.QtTAG, msg);
|
||||
}
|
||||
|
||||
protected abstract String loaderClassName();
|
||||
protected abstract Class<?> contextClassName();
|
||||
|
||||
Intent getIntent()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
// Implement in subclass
|
||||
|
||||
|
||||
// this function is used to load and start the loader
|
||||
private void loadApplication(Bundle loaderParams)
|
||||
{
|
||||
try {
|
||||
final int errorCode = loaderParams.getInt(ERROR_CODE_KEY);
|
||||
if (errorCode != 0) {
|
||||
if (errorCode == INCOMPATIBLE_MINISTRO_VERSION) {
|
||||
downloadUpgradeMinistro(loaderParams.getString(ERROR_MESSAGE_KEY));
|
||||
return;
|
||||
}
|
||||
|
||||
// fatal error, show the error and quit
|
||||
AlertDialog errorDialog = new AlertDialog.Builder(m_context).create();
|
||||
errorDialog.setMessage(loaderParams.getString(ERROR_MESSAGE_KEY));
|
||||
errorDialog.setButton(m_context.getResources().getString(android.R.string.ok), new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
finish();
|
||||
}
|
||||
});
|
||||
errorDialog.show();
|
||||
return;
|
||||
}
|
||||
|
||||
// add all bundled Qt libs to loader params
|
||||
ArrayList<String> libs = new ArrayList<String>();
|
||||
if ( m_contextInfo.metaData.containsKey("android.app.bundled_libs_resource_id") )
|
||||
libs.addAll(Arrays.asList(m_context.getResources().getStringArray(m_contextInfo.metaData.getInt("android.app.bundled_libs_resource_id"))));
|
||||
|
||||
String libName = null;
|
||||
if ( m_contextInfo.metaData.containsKey("android.app.lib_name") ) {
|
||||
libName = m_contextInfo.metaData.getString("android.app.lib_name");
|
||||
loaderParams.putString(MAIN_LIBRARY_KEY, libName); //main library contains main() function
|
||||
}
|
||||
|
||||
loaderParams.putStringArrayList(BUNDLED_LIBRARIES_KEY, libs);
|
||||
loaderParams.putInt(NECESSITAS_API_LEVEL_KEY, NECESSITAS_API_LEVEL);
|
||||
|
||||
// load and start QtLoader class
|
||||
DexClassLoader classLoader = new DexClassLoader(loaderParams.getString(DEX_PATH_KEY), // .jar/.apk files
|
||||
m_context.getDir("outdex", Context.MODE_PRIVATE).getAbsolutePath(), // directory where optimized DEX files should be written.
|
||||
loaderParams.containsKey(LIB_PATH_KEY) ? loaderParams.getString(LIB_PATH_KEY) : null, // libs folder (if exists)
|
||||
m_context.getClassLoader()); // parent loader
|
||||
|
||||
Class<?> loaderClass = classLoader.loadClass(loaderParams.getString(LOADER_CLASS_NAME_KEY)); // load QtLoader class
|
||||
Object qtLoader = loaderClass.newInstance(); // create an instance
|
||||
Method prepareAppMethod = qtLoader.getClass().getMethod("loadApplication",
|
||||
contextClassName(),
|
||||
ClassLoader.class,
|
||||
Bundle.class);
|
||||
if (!(Boolean)prepareAppMethod.invoke(qtLoader, m_context, classLoader, loaderParams))
|
||||
throw new Exception("");
|
||||
|
||||
QtApplication.setQtContextDelegate(m_context.getClass(), qtLoader);
|
||||
|
||||
// now load the application library so it's accessible from this class loader
|
||||
if (libName != null)
|
||||
System.loadLibrary(libName);
|
||||
|
||||
Method startAppMethod=qtLoader.getClass().getMethod("startApplication");
|
||||
if (!(Boolean)startAppMethod.invoke(qtLoader))
|
||||
throw new Exception("");
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
AlertDialog errorDialog = new AlertDialog.Builder(m_context).create();
|
||||
if (m_contextInfo.metaData.containsKey("android.app.fatal_error_msg"))
|
||||
errorDialog.setMessage(m_contextInfo.metaData.getString("android.app.fatal_error_msg"));
|
||||
else
|
||||
errorDialog.setMessage("Fatal error, your application can't be started.");
|
||||
|
||||
errorDialog.setButton(m_context.getResources().getString(android.R.string.ok), new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
finish();
|
||||
}
|
||||
});
|
||||
errorDialog.show();
|
||||
}
|
||||
}
|
||||
|
||||
private ServiceConnection m_ministroConnection=new ServiceConnection() {
|
||||
private IMinistro m_service = null;
|
||||
@Override
|
||||
public void onServiceConnected(ComponentName name, IBinder service)
|
||||
{
|
||||
m_service = IMinistro.Stub.asInterface(service);
|
||||
try {
|
||||
if (m_service != null) {
|
||||
Bundle parameters = new Bundle();
|
||||
parameters.putStringArray(REQUIRED_MODULES_KEY, m_qtLibs);
|
||||
parameters.putString(APPLICATION_TITLE_KEY, getTitle());
|
||||
parameters.putInt(MINIMUM_MINISTRO_API_KEY, MINISTRO_API_LEVEL);
|
||||
parameters.putInt(MINIMUM_QT_VERSION_KEY, QT_VERSION);
|
||||
parameters.putString(ENVIRONMENT_VARIABLES_KEY, ENVIRONMENT_VARIABLES);
|
||||
if (APPLICATION_PARAMETERS != null)
|
||||
parameters.putString(APPLICATION_PARAMETERS_KEY, APPLICATION_PARAMETERS);
|
||||
parameters.putStringArray(SOURCES_KEY, m_sources);
|
||||
parameters.putString(REPOSITORY_KEY, m_repository);
|
||||
if (QT_ANDROID_THEMES != null)
|
||||
parameters.putStringArray(ANDROID_THEMES_KEY, QT_ANDROID_THEMES);
|
||||
m_service.requestLoader(m_ministroCallback, parameters);
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private IMinistroCallback m_ministroCallback = new IMinistroCallback.Stub() {
|
||||
// this function is called back by Ministro.
|
||||
@Override
|
||||
public void loaderReady(final Bundle loaderParams) throws RemoteException {
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
m_context.unbindService(m_ministroConnection);
|
||||
loadApplication(loaderParams);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public void onServiceDisconnected(ComponentName name) {
|
||||
m_service = null;
|
||||
}
|
||||
};
|
||||
|
||||
protected void ministroNotFound()
|
||||
{
|
||||
AlertDialog errorDialog = new AlertDialog.Builder(m_context).create();
|
||||
|
||||
if (m_contextInfo.metaData.containsKey("android.app.ministro_not_found_msg"))
|
||||
errorDialog.setMessage(m_contextInfo.metaData.getString("android.app.ministro_not_found_msg"));
|
||||
else
|
||||
errorDialog.setMessage("Can't find Ministro service.\nThe application can't start.");
|
||||
|
||||
errorDialog.setButton(m_context.getResources().getString(android.R.string.ok), new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
finish();
|
||||
}
|
||||
});
|
||||
errorDialog.show();
|
||||
}
|
||||
|
||||
static private void copyFile(InputStream inputStream, OutputStream outputStream)
|
||||
throws IOException
|
||||
{
|
||||
byte[] buffer = new byte[BUFFER_SIZE];
|
||||
|
||||
int count;
|
||||
while ((count = inputStream.read(buffer)) > 0)
|
||||
outputStream.write(buffer, 0, count);
|
||||
}
|
||||
|
||||
private void copyAsset(String source, String destination)
|
||||
throws IOException
|
||||
{
|
||||
// Already exists, we don't have to do anything
|
||||
File destinationFile = new File(destination);
|
||||
if (destinationFile.exists())
|
||||
return;
|
||||
|
||||
File parentDirectory = destinationFile.getParentFile();
|
||||
if (!parentDirectory.exists())
|
||||
parentDirectory.mkdirs();
|
||||
|
||||
destinationFile.createNewFile();
|
||||
|
||||
AssetManager assetsManager = m_context.getAssets();
|
||||
InputStream inputStream = assetsManager.open(source);
|
||||
OutputStream outputStream = new FileOutputStream(destinationFile);
|
||||
copyFile(inputStream, outputStream);
|
||||
|
||||
inputStream.close();
|
||||
outputStream.close();
|
||||
}
|
||||
|
||||
private static void createBundledBinary(String source, String destination)
|
||||
throws IOException
|
||||
{
|
||||
// Already exists, we don't have to do anything
|
||||
File destinationFile = new File(destination);
|
||||
if (destinationFile.exists())
|
||||
return;
|
||||
|
||||
File parentDirectory = destinationFile.getParentFile();
|
||||
if (!parentDirectory.exists())
|
||||
parentDirectory.mkdirs();
|
||||
|
||||
destinationFile.createNewFile();
|
||||
|
||||
InputStream inputStream = new FileInputStream(source);
|
||||
OutputStream outputStream = new FileOutputStream(destinationFile);
|
||||
copyFile(inputStream, outputStream);
|
||||
|
||||
inputStream.close();
|
||||
outputStream.close();
|
||||
}
|
||||
|
||||
private boolean cleanCacheIfNecessary(String pluginsPrefix, long packageVersion)
|
||||
{
|
||||
File versionFile = new File(pluginsPrefix + "cache.version");
|
||||
|
||||
long cacheVersion = 0;
|
||||
if (versionFile.exists() && versionFile.canRead()) {
|
||||
try {
|
||||
DataInputStream inputStream = new DataInputStream(new FileInputStream(versionFile));
|
||||
cacheVersion = inputStream.readLong();
|
||||
inputStream.close();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
if (cacheVersion != packageVersion) {
|
||||
deleteRecursively(new File(pluginsPrefix));
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void extractBundledPluginsAndImports(String pluginsPrefix)
|
||||
throws IOException
|
||||
{
|
||||
ArrayList<String> libs = new ArrayList<String>();
|
||||
|
||||
String libsDir = m_context.getApplicationInfo().nativeLibraryDir + "/";
|
||||
|
||||
long packageVersion = -1;
|
||||
try {
|
||||
PackageInfo packageInfo = m_context.getPackageManager().getPackageInfo(m_context.getPackageName(), 0);
|
||||
packageVersion = packageInfo.lastUpdateTime;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
if (!cleanCacheIfNecessary(pluginsPrefix, packageVersion))
|
||||
return;
|
||||
|
||||
{
|
||||
File versionFile = new File(pluginsPrefix + "cache.version");
|
||||
|
||||
File parentDirectory = versionFile.getParentFile();
|
||||
if (!parentDirectory.exists())
|
||||
parentDirectory.mkdirs();
|
||||
|
||||
versionFile.createNewFile();
|
||||
|
||||
DataOutputStream outputStream = new DataOutputStream(new FileOutputStream(versionFile));
|
||||
outputStream.writeLong(packageVersion);
|
||||
outputStream.close();
|
||||
}
|
||||
|
||||
{
|
||||
String key = BUNDLED_IN_LIB_RESOURCE_ID_KEY;
|
||||
java.util.Set<String> keys = m_contextInfo.metaData.keySet();
|
||||
if (m_contextInfo.metaData.containsKey(key)) {
|
||||
String[] list = m_context.getResources().getStringArray(m_contextInfo.metaData.getInt(key));
|
||||
|
||||
for (String bundledImportBinary : list) {
|
||||
String[] split = bundledImportBinary.split(":");
|
||||
String sourceFileName = libsDir + split[0];
|
||||
String destinationFileName = pluginsPrefix + split[1];
|
||||
createBundledBinary(sourceFileName, destinationFileName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
String key = BUNDLED_IN_ASSETS_RESOURCE_ID_KEY;
|
||||
if (m_contextInfo.metaData.containsKey(key)) {
|
||||
String[] list = m_context.getResources().getStringArray(m_contextInfo.metaData.getInt(key));
|
||||
|
||||
for (String fileName : list) {
|
||||
String[] split = fileName.split(":");
|
||||
String sourceFileName = split[0];
|
||||
String destinationFileName = pluginsPrefix + split[1];
|
||||
copyAsset(sourceFileName, destinationFileName);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private void deleteRecursively(File directory)
|
||||
{
|
||||
File[] files = directory.listFiles();
|
||||
if (files != null) {
|
||||
for (File file : files) {
|
||||
if (file.isDirectory())
|
||||
deleteRecursively(file);
|
||||
else
|
||||
file.delete();
|
||||
}
|
||||
|
||||
directory.delete();
|
||||
}
|
||||
}
|
||||
|
||||
private void cleanOldCacheIfNecessary(String oldLocalPrefix, String localPrefix)
|
||||
{
|
||||
File newCache = new File(localPrefix);
|
||||
if (!newCache.exists()) {
|
||||
{
|
||||
File oldPluginsCache = new File(oldLocalPrefix + "plugins/");
|
||||
if (oldPluginsCache.exists() && oldPluginsCache.isDirectory())
|
||||
deleteRecursively(oldPluginsCache);
|
||||
}
|
||||
|
||||
{
|
||||
File oldImportsCache = new File(oldLocalPrefix + "imports/");
|
||||
if (oldImportsCache.exists() && oldImportsCache.isDirectory())
|
||||
deleteRecursively(oldImportsCache);
|
||||
}
|
||||
|
||||
{
|
||||
File oldQmlCache = new File(oldLocalPrefix + "qml/");
|
||||
if (oldQmlCache.exists() && oldQmlCache.isDirectory())
|
||||
deleteRecursively(oldQmlCache);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void startApp(final boolean firstStart)
|
||||
{
|
||||
try {
|
||||
if (m_contextInfo.metaData.containsKey("android.app.qt_sources_resource_id")) {
|
||||
int resourceId = m_contextInfo.metaData.getInt("android.app.qt_sources_resource_id");
|
||||
m_sources = m_context.getResources().getStringArray(resourceId);
|
||||
}
|
||||
|
||||
if (m_contextInfo.metaData.containsKey("android.app.repository"))
|
||||
m_repository = m_contextInfo.metaData.getString("android.app.repository");
|
||||
|
||||
if (m_contextInfo.metaData.containsKey("android.app.qt_libs_resource_id")) {
|
||||
int resourceId = m_contextInfo.metaData.getInt("android.app.qt_libs_resource_id");
|
||||
m_qtLibs = m_context.getResources().getStringArray(resourceId);
|
||||
}
|
||||
|
||||
if (m_contextInfo.metaData.containsKey("android.app.use_local_qt_libs")
|
||||
&& m_contextInfo.metaData.getInt("android.app.use_local_qt_libs") == 1) {
|
||||
ArrayList<String> libraryList = new ArrayList<String>();
|
||||
|
||||
|
||||
String localPrefix = "/data/local/tmp/qt/";
|
||||
if (m_contextInfo.metaData.containsKey("android.app.libs_prefix"))
|
||||
localPrefix = m_contextInfo.metaData.getString("android.app.libs_prefix");
|
||||
|
||||
String pluginsPrefix = localPrefix;
|
||||
|
||||
boolean bundlingQtLibs = false;
|
||||
if (m_contextInfo.metaData.containsKey("android.app.bundle_local_qt_libs")
|
||||
&& m_contextInfo.metaData.getInt("android.app.bundle_local_qt_libs") == 1) {
|
||||
localPrefix = m_context.getApplicationInfo().dataDir + "/";
|
||||
pluginsPrefix = localPrefix + "qt-reserved-files/";
|
||||
cleanOldCacheIfNecessary(localPrefix, pluginsPrefix);
|
||||
extractBundledPluginsAndImports(pluginsPrefix);
|
||||
bundlingQtLibs = true;
|
||||
}
|
||||
|
||||
if (m_qtLibs != null) {
|
||||
for (int i=0;i<m_qtLibs.length;i++) {
|
||||
libraryList.add(localPrefix
|
||||
+ "lib/lib"
|
||||
+ m_qtLibs[i]
|
||||
+ ".so");
|
||||
}
|
||||
}
|
||||
|
||||
if (m_contextInfo.metaData.containsKey("android.app.load_local_libs")) {
|
||||
String[] extraLibs = m_contextInfo.metaData.getString("android.app.load_local_libs").split(":");
|
||||
for (String lib : extraLibs) {
|
||||
if (lib.length() > 0) {
|
||||
if (lib.startsWith("lib/"))
|
||||
libraryList.add(localPrefix + lib);
|
||||
else
|
||||
libraryList.add(pluginsPrefix + lib);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
String dexPaths = new String();
|
||||
String pathSeparator = System.getProperty("path.separator", ":");
|
||||
if (!bundlingQtLibs && m_contextInfo.metaData.containsKey("android.app.load_local_jars")) {
|
||||
String[] jarFiles = m_contextInfo.metaData.getString("android.app.load_local_jars").split(":");
|
||||
for (String jar:jarFiles) {
|
||||
if (jar.length() > 0) {
|
||||
if (dexPaths.length() > 0)
|
||||
dexPaths += pathSeparator;
|
||||
dexPaths += localPrefix + jar;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Bundle loaderParams = new Bundle();
|
||||
loaderParams.putInt(ERROR_CODE_KEY, 0);
|
||||
loaderParams.putString(DEX_PATH_KEY, dexPaths);
|
||||
loaderParams.putString(LOADER_CLASS_NAME_KEY, loaderClassName());
|
||||
if (m_contextInfo.metaData.containsKey("android.app.static_init_classes")) {
|
||||
loaderParams.putStringArray(STATIC_INIT_CLASSES_KEY,
|
||||
m_contextInfo.metaData.getString("android.app.static_init_classes").split(":"));
|
||||
}
|
||||
loaderParams.putStringArrayList(NATIVE_LIBRARIES_KEY, libraryList);
|
||||
|
||||
|
||||
String themePath = m_context.getApplicationInfo().dataDir + "/qt-reserved-files/android-style/";
|
||||
String stylePath = themePath + m_displayDensity + "/";
|
||||
if (!(new File(stylePath)).exists())
|
||||
loaderParams.putString(EXTRACT_STYLE_KEY, stylePath);
|
||||
ENVIRONMENT_VARIABLES += "\tMINISTRO_ANDROID_STYLE_PATH=" + stylePath
|
||||
+ "\tQT_ANDROID_THEMES_ROOT_PATH=" + themePath;
|
||||
|
||||
loaderParams.putString(ENVIRONMENT_VARIABLES_KEY, ENVIRONMENT_VARIABLES
|
||||
+ "\tQML2_IMPORT_PATH=" + pluginsPrefix + "/qml"
|
||||
+ "\tQML_IMPORT_PATH=" + pluginsPrefix + "/imports"
|
||||
+ "\tQT_PLUGIN_PATH=" + pluginsPrefix + "/plugins");
|
||||
|
||||
String appParams = null;
|
||||
if (APPLICATION_PARAMETERS != null)
|
||||
appParams = APPLICATION_PARAMETERS;
|
||||
|
||||
Intent intent = getIntent();
|
||||
if (intent != null) {
|
||||
String parameters = intent.getStringExtra("applicationArguments");
|
||||
if (parameters != null)
|
||||
if (appParams == null)
|
||||
appParams = parameters;
|
||||
else
|
||||
appParams += '\t' + parameters;
|
||||
}
|
||||
|
||||
if (m_contextInfo.metaData.containsKey("android.app.arguments")) {
|
||||
String parameters = m_contextInfo.metaData.getString("android.app.arguments");
|
||||
if (appParams == null)
|
||||
appParams = parameters;
|
||||
else
|
||||
appParams += '\t' + parameters;
|
||||
}
|
||||
|
||||
if (appParams != null)
|
||||
loaderParams.putString(APPLICATION_PARAMETERS_KEY, appParams.replace(' ', '\t').trim());
|
||||
|
||||
loadApplication(loaderParams);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (!m_context.bindService(new Intent(org.kde.necessitas.ministro.IMinistro.class.getCanonicalName()),
|
||||
m_ministroConnection,
|
||||
Context.BIND_AUTO_CREATE)) {
|
||||
throw new SecurityException("");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
if (firstStart) {
|
||||
String msg = "This application requires Ministro service. Would you like to install it?";
|
||||
if (m_contextInfo.metaData.containsKey("android.app.ministro_needed_msg"))
|
||||
msg = m_contextInfo.metaData.getString("android.app.ministro_needed_msg");
|
||||
downloadUpgradeMinistro(msg);
|
||||
} else {
|
||||
ministroNotFound();
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(QtApplication.QtTAG, "Can't create main activity", e);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,153 @@
|
||||
/*
|
||||
Copyright (c) 2016, BogDan Vatra <bogdan@kde.org>
|
||||
Contact: http://www.qt.io/licensing/
|
||||
|
||||
Commercial License Usage
|
||||
Licensees holding valid commercial Qt licenses may use this file in
|
||||
accordance with the commercial license agreement provided with the
|
||||
Software or, alternatively, in accordance with the terms contained in
|
||||
a written agreement between you and The Qt Company. For licensing terms
|
||||
and conditions see http://www.qt.io/terms-conditions. For further
|
||||
information use the contact form at http://www.qt.io/contact-us.
|
||||
|
||||
BSD License Usage
|
||||
Alternatively, this file may be used under the BSD license as follows:
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package org.qtproject.qt5.android.bindings;
|
||||
|
||||
import android.app.Service;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Configuration;
|
||||
import android.os.IBinder;
|
||||
|
||||
public class QtService extends Service
|
||||
{
|
||||
QtServiceLoader m_loader = new QtServiceLoader(this);
|
||||
|
||||
|
||||
/////////////////////////// forward all notifications ////////////////////////////
|
||||
/////////////////////////// Super class calls ////////////////////////////////////
|
||||
/////////////// PLEASE DO NOT CHANGE THE FOLLOWING CODE //////////////////////////
|
||||
//////////////////////////////////////////////////////////////////////////////////
|
||||
@Override
|
||||
public void onCreate()
|
||||
{
|
||||
super.onCreate();
|
||||
m_loader.onCreate();
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
public void onDestroy()
|
||||
{
|
||||
super.onDestroy();
|
||||
QtApplication.invokeDelegate();
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent)
|
||||
{
|
||||
QtApplication.InvokeResult res = QtApplication.invokeDelegate(intent);
|
||||
if (res.invoked)
|
||||
return (IBinder)res.methodReturns;
|
||||
else
|
||||
return null;
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
public void onConfigurationChanged(Configuration newConfig)
|
||||
{
|
||||
if (!QtApplication.invokeDelegate(newConfig).invoked)
|
||||
super.onConfigurationChanged(newConfig);
|
||||
}
|
||||
public void super_onConfigurationChanged(Configuration newConfig)
|
||||
{
|
||||
super.onConfigurationChanged(newConfig);
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
public void onLowMemory()
|
||||
{
|
||||
if (!QtApplication.invokeDelegate().invoked)
|
||||
super.onLowMemory();
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId)
|
||||
{
|
||||
QtApplication.InvokeResult res = QtApplication.invokeDelegate(intent, flags, startId);
|
||||
if (res.invoked)
|
||||
return (int) res.methodReturns;
|
||||
else
|
||||
return super.onStartCommand(intent, flags, startId);
|
||||
}
|
||||
public int super_onStartCommand(Intent intent, int flags, int startId)
|
||||
{
|
||||
return super.onStartCommand(intent, flags, startId);
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
public void onTaskRemoved(Intent rootIntent)
|
||||
{
|
||||
if (!QtApplication.invokeDelegate(rootIntent).invoked)
|
||||
super.onTaskRemoved(rootIntent);
|
||||
}
|
||||
public void super_onTaskRemoved(Intent rootIntent)
|
||||
{
|
||||
super.onTaskRemoved(rootIntent);
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
public void onTrimMemory(int level)
|
||||
{
|
||||
if (!QtApplication.invokeDelegate(level).invoked)
|
||||
super.onTrimMemory(level);
|
||||
}
|
||||
public void super_onTrimMemory(int level)
|
||||
{
|
||||
super.onTrimMemory(level);
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
public boolean onUnbind(Intent intent)
|
||||
{
|
||||
QtApplication.InvokeResult res = QtApplication.invokeDelegate(intent);
|
||||
if (res.invoked)
|
||||
return (boolean) res.methodReturns;
|
||||
else
|
||||
return super.onUnbind(intent);
|
||||
}
|
||||
public boolean super_onUnbind(Intent intent)
|
||||
{
|
||||
return super.onUnbind(intent);
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
}
|
@ -0,0 +1,77 @@
|
||||
/*
|
||||
Copyright (c) 2016, BogDan Vatra <bogdan@kde.org>
|
||||
Contact: http://www.qt.io/licensing/
|
||||
|
||||
Commercial License Usage
|
||||
Licensees holding valid commercial Qt licenses may use this file in
|
||||
accordance with the commercial license agreement provided with the
|
||||
Software or, alternatively, in accordance with the terms contained in
|
||||
a written agreement between you and The Qt Company. For licensing terms
|
||||
and conditions see http://www.qt.io/terms-conditions. For further
|
||||
information use the contact form at http://www.qt.io/contact-us.
|
||||
|
||||
BSD License Usage
|
||||
Alternatively, this file may be used under the BSD license as follows:
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package org.qtproject.qt5.android.bindings;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.content.pm.PackageManager;
|
||||
|
||||
public class QtServiceLoader extends QtLoader {
|
||||
QtService m_service;
|
||||
QtServiceLoader(QtService service) {
|
||||
super(service);
|
||||
m_service = service;
|
||||
}
|
||||
|
||||
public void onCreate() {
|
||||
try {
|
||||
m_contextInfo = m_service.getPackageManager().getServiceInfo(new ComponentName(m_service, m_service.getClass()), PackageManager.GET_META_DATA);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
m_service.stopSelf();
|
||||
return;
|
||||
}
|
||||
|
||||
if (QtApplication.m_delegateObject != null && QtApplication.onCreate != null)
|
||||
QtApplication.invokeDelegateMethod(QtApplication.onCreate);
|
||||
startApp(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void finish() {
|
||||
m_service.stopSelf();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String loaderClassName() {
|
||||
return "org.qtproject.qt5.android.QtServiceDelegate";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Class<?> contextClassName() {
|
||||
return android.app.Service.class;
|
||||
}
|
||||
}
|
@ -10,6 +10,11 @@
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
</intent-filter>
|
||||
|
||||
<!-- Application arguments -->
|
||||
<!-- meta-data android:name="android.app.arguments" android:value="arg1 arg2 arg3"/ -->
|
||||
<!-- Application arguments -->
|
||||
|
||||
<meta-data android:name="android.app.lib_name" android:value="-- %%INSERT_APP_LIB_NAME%% --"/>
|
||||
<meta-data android:name="android.app.qt_sources_resource_id" android:resource="@array/qt_sources"/>
|
||||
<meta-data android:name="android.app.repository" android:value="default"/>
|
||||
@ -32,9 +37,7 @@
|
||||
<!-- Messages maps -->
|
||||
|
||||
<!-- Splash screen -->
|
||||
<!--
|
||||
<meta-data android:name="android.app.splash_screen_drawable" android:resource="@drawable/logo"/>
|
||||
-->
|
||||
<!-- meta-data android:name="android.app.splash_screen_drawable" android:resource="@drawable/logo"/ -->
|
||||
<!-- Splash screen -->
|
||||
|
||||
<!-- Background running -->
|
||||
@ -49,7 +52,53 @@
|
||||
<meta-data android:name="android.app.auto_screen_scale_factor" android:value="false"/>
|
||||
<!-- auto screen scale factor -->
|
||||
</activity>
|
||||
|
||||
<!--service android:process=":qt" android:name="org.qtproject.qt5.android.bindings.QtService"-->
|
||||
<!-- android:process=":qt" is needed to force the service to run on a separate process than the Activity -->
|
||||
|
||||
<!-- Application arguments -->
|
||||
<!-- meta-data android:name="android.app.arguments" android:value="-service"/ -->
|
||||
<!-- Application arguments -->
|
||||
<!-- If you're using the same application (.so file) for activity and also for service, then you
|
||||
need to use *android.app.arguments* to pass some arguments to your service in order to know which
|
||||
one is which
|
||||
-->
|
||||
|
||||
<!-- Ministro -->
|
||||
<!-- meta-data android:name="android.app.lib_name" android:value="-- %%INSERT_APP_LIB_NAME%% --"/>
|
||||
<meta-data android:name="android.app.qt_sources_resource_id" android:resource="@array/qt_sources"/>
|
||||
<meta-data android:name="android.app.repository" android:value="default"/>
|
||||
<meta-data android:name="android.app.qt_libs_resource_id" android:resource="@array/qt_libs"/>
|
||||
<meta-data android:name="android.app.bundled_libs_resource_id" android:resource="@array/bundled_libs"/ -->
|
||||
<!-- Ministro -->
|
||||
|
||||
<!-- Deploy Qt libs as part of package -->
|
||||
<!-- meta-data android:name="android.app.bundle_local_qt_libs" android:value="-- %%BUNDLE_LOCAL_QT_LIBS%% --"/>
|
||||
<meta-data android:name="android.app.bundled_in_lib_resource_id" android:resource="@array/bundled_in_lib"/>
|
||||
<meta-data android:name="android.app.bundled_in_assets_resource_id" android:resource="@array/bundled_in_assets"/ -->
|
||||
<!-- Deploy Qt libs as part of package -->
|
||||
|
||||
<!-- Run with local libs -->
|
||||
<!-- meta-data android:name="android.app.use_local_qt_libs" android:value="-- %%USE_LOCAL_QT_LIBS%% --"/>
|
||||
<meta-data android:name="android.app.libs_prefix" android:value="/data/local/tmp/qt/"/>
|
||||
<meta-data android:name="android.app.load_local_libs" android:value="-- %%INSERT_LOCAL_LIBS%% --"/>
|
||||
<meta-data android:name="android.app.load_local_jars" android:value="-- %%INSERT_LOCAL_JARS%% --"/>
|
||||
<meta-data android:name="android.app.static_init_classes" android:value="-- %%INSERT_INIT_CLASSES%% --"/ -->
|
||||
<!-- Run with local libs -->
|
||||
|
||||
<!-- Messages maps -->
|
||||
<!-- meta-data android:value="@string/ministro_not_found_msg" android:name="android.app.ministro_not_found_msg"/>
|
||||
<meta-data android:value="@string/ministro_needed_msg" android:name="android.app.ministro_needed_msg"/>
|
||||
<meta-data android:value="@string/fatal_error_msg" android:name="android.app.fatal_error_msg"/ -->
|
||||
<!-- Messages maps -->
|
||||
|
||||
|
||||
<!-- Background running -->
|
||||
<!-- meta-data android:name="android.app.background_running" android:value="true"/ -->
|
||||
<!-- Background running -->
|
||||
<!--/service -->
|
||||
</application>
|
||||
|
||||
<uses-sdk android:minSdkVersion="16" android:targetSdkVersion="16"/>
|
||||
<supports-screens android:largeScreens="true" android:normalScreens="true" android:anyDensity="true" android:smallScreens="true"/>
|
||||
|
||||
|
@ -63,12 +63,15 @@ static QJNIObjectPrivate applicationContext()
|
||||
if (appCtx.isValid())
|
||||
return appCtx;
|
||||
|
||||
QJNIObjectPrivate activity(QtAndroidPrivate::activity());
|
||||
if (!activity.isValid())
|
||||
return appCtx;
|
||||
QJNIObjectPrivate context(QtAndroidPrivate::activity());
|
||||
if (!context.isValid()) {
|
||||
context = QtAndroidPrivate::service();
|
||||
if (!context.isValid())
|
||||
return appCtx;
|
||||
}
|
||||
|
||||
appCtx = activity.callObjectMethod("getApplicationContext",
|
||||
"()Landroid/content/Context;");
|
||||
appCtx = context.callObjectMethod("getApplicationContext",
|
||||
"()Landroid/content/Context;");
|
||||
return appCtx;
|
||||
}
|
||||
|
||||
@ -137,10 +140,6 @@ static QString getExternalFilesDir(const char *directoryField = 0)
|
||||
if (!path.isEmpty())
|
||||
return path;
|
||||
|
||||
QJNIObjectPrivate activity(QtAndroidPrivate::activity());
|
||||
if (!activity.isValid())
|
||||
return QString();
|
||||
|
||||
QJNIObjectPrivate appCtx = applicationContext();
|
||||
if (!appCtx.isValid())
|
||||
return QString();
|
||||
|
@ -51,6 +51,7 @@ QT_BEGIN_NAMESPACE
|
||||
|
||||
static JavaVM *g_javaVM = Q_NULLPTR;
|
||||
static jobject g_jActivity = Q_NULLPTR;
|
||||
static jobject g_jService = Q_NULLPTR;
|
||||
static jobject g_jClassLoader = Q_NULLPTR;
|
||||
static jint g_androidSdkVersion = 0;
|
||||
static jclass g_jNativeClass = Q_NULLPTR;
|
||||
@ -239,6 +240,32 @@ static void setAndroidSdkVersion(JNIEnv *env)
|
||||
g_androidSdkVersion = env->GetStaticIntField(androidVersionClass, androidSDKFieldID);
|
||||
}
|
||||
|
||||
static void setNativeActivity(JNIEnv *env, jclass, jobject activity)
|
||||
{
|
||||
if (g_jActivity != 0)
|
||||
env->DeleteGlobalRef(g_jActivity);
|
||||
|
||||
if (activity != 0) {
|
||||
g_jActivity = env->NewGlobalRef(activity);
|
||||
env->DeleteLocalRef(activity);
|
||||
} else {
|
||||
g_jActivity = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void setNativeService(JNIEnv *env, jclass, jobject service)
|
||||
{
|
||||
if (g_jService != 0)
|
||||
env->DeleteGlobalRef(g_jService);
|
||||
|
||||
if (service != 0) {
|
||||
g_jService = env->NewGlobalRef(service);
|
||||
env->DeleteLocalRef(service);
|
||||
} else {
|
||||
g_jService = 0;
|
||||
}
|
||||
}
|
||||
|
||||
jint QtAndroidPrivate::initJNI(JavaVM *vm, JNIEnv *env)
|
||||
{
|
||||
jclass jQtNative = env->FindClass("org/qtproject/qt5/android/QtNative");
|
||||
@ -254,10 +281,21 @@ jint QtAndroidPrivate::initJNI(JavaVM *vm, JNIEnv *env)
|
||||
return JNI_ERR;
|
||||
|
||||
jobject activity = env->CallStaticObjectMethod(jQtNative, activityMethodID);
|
||||
|
||||
if (exceptionCheck(env))
|
||||
return JNI_ERR;
|
||||
|
||||
jmethodID serviceMethodID = env->GetStaticMethodID(jQtNative,
|
||||
"service",
|
||||
"()Landroid/app/Service;");
|
||||
|
||||
if (exceptionCheck(env))
|
||||
return JNI_ERR;
|
||||
|
||||
jobject service = env->CallStaticObjectMethod(jQtNative, serviceMethodID);
|
||||
|
||||
if (exceptionCheck(env))
|
||||
return JNI_ERR;
|
||||
|
||||
jmethodID classLoaderMethodID = env->GetStaticMethodID(jQtNative,
|
||||
"classLoader",
|
||||
@ -274,14 +312,22 @@ jint QtAndroidPrivate::initJNI(JavaVM *vm, JNIEnv *env)
|
||||
|
||||
g_jClassLoader = env->NewGlobalRef(classLoader);
|
||||
env->DeleteLocalRef(classLoader);
|
||||
g_jActivity = env->NewGlobalRef(activity);
|
||||
env->DeleteLocalRef(activity);
|
||||
if (activity) {
|
||||
g_jActivity = env->NewGlobalRef(activity);
|
||||
env->DeleteLocalRef(activity);
|
||||
}
|
||||
if (service) {
|
||||
g_jService = env->NewGlobalRef(service);
|
||||
env->DeleteLocalRef(service);
|
||||
}
|
||||
g_javaVM = vm;
|
||||
|
||||
static const JNINativeMethod methods[] = {
|
||||
{"runPendingCppRunnables", "()V", reinterpret_cast<void *>(runPendingCppRunnables)},
|
||||
{"dispatchGenericMotionEvent", "(Landroid/view/MotionEvent;)Z", reinterpret_cast<void *>(dispatchGenericMotionEvent)},
|
||||
{"dispatchKeyEvent", "(Landroid/view/KeyEvent;)Z", reinterpret_cast<void *>(dispatchKeyEvent)},
|
||||
{"setNativeActivity", "(Landroid/app/Activity;)V", reinterpret_cast<void *>(setNativeActivity)},
|
||||
{"setNativeService", "(Landroid/app/Service;)V", reinterpret_cast<void *>(setNativeService)}
|
||||
};
|
||||
|
||||
const bool regOk = (env->RegisterNatives(jQtNative, methods, sizeof(methods) / sizeof(methods[0])) == JNI_OK);
|
||||
@ -305,6 +351,11 @@ jobject QtAndroidPrivate::activity()
|
||||
return g_jActivity;
|
||||
}
|
||||
|
||||
jobject QtAndroidPrivate::service()
|
||||
{
|
||||
return g_jService;
|
||||
}
|
||||
|
||||
JavaVM *QtAndroidPrivate::javaVM()
|
||||
{
|
||||
return g_javaVM;
|
||||
|
@ -100,6 +100,7 @@ namespace QtAndroidPrivate
|
||||
typedef std::function<void()> Runnable;
|
||||
|
||||
Q_CORE_EXPORT jobject activity();
|
||||
Q_CORE_EXPORT jobject service();
|
||||
Q_CORE_EXPORT JavaVM *javaVM();
|
||||
Q_CORE_EXPORT jint initJNI(JavaVM *vm, JNIEnv *env);
|
||||
jobject classLoader();
|
||||
|
@ -78,6 +78,7 @@ static AAssetManager *m_assetManager = nullptr;
|
||||
static jobject m_resourcesObj = nullptr;
|
||||
static jobject m_activityObject = nullptr;
|
||||
static jmethodID m_createSurfaceMethodID = nullptr;
|
||||
static jobject m_serviceObject = nullptr;
|
||||
static jmethodID m_setSurfaceGeometryMethodID = nullptr;
|
||||
static jmethodID m_destroySurfaceMethodID = nullptr;
|
||||
|
||||
@ -193,6 +194,11 @@ namespace QtAndroid
|
||||
return m_activityObject;
|
||||
}
|
||||
|
||||
jobject service()
|
||||
{
|
||||
return m_serviceObject;
|
||||
}
|
||||
|
||||
void showStatusBar()
|
||||
{
|
||||
if (m_statusBarShowing)
|
||||
@ -534,7 +540,6 @@ static jboolean startQtApplication(JNIEnv *env, jobject /*object*/, jstring para
|
||||
return pthread_create(&m_qtAppThread, nullptr, startMainMethod, nullptr) == 0;
|
||||
}
|
||||
|
||||
|
||||
static void quitQtAndroidPlugin(JNIEnv *env, jclass /*clazz*/)
|
||||
{
|
||||
Q_UNUSED(env);
|
||||
@ -553,6 +558,8 @@ static void terminateQt(JNIEnv *env, jclass /*clazz*/)
|
||||
env->DeleteGlobalRef(m_resourcesObj);
|
||||
if (m_activityObject)
|
||||
env->DeleteGlobalRef(m_activityObject);
|
||||
if (m_serviceObject)
|
||||
env->DeleteGlobalRef(m_serviceObject);
|
||||
if (m_bitmapClass)
|
||||
env->DeleteGlobalRef(m_bitmapClass);
|
||||
if (m_ARGB_8888_BitmapConfigValue)
|
||||
@ -785,20 +792,26 @@ static int registerNatives(JNIEnv *env)
|
||||
jmethodID methodID;
|
||||
GET_AND_CHECK_STATIC_METHOD(methodID, m_applicationClass, "activity", "()Landroid/app/Activity;");
|
||||
jobject activityObject = env->CallStaticObjectMethod(m_applicationClass, methodID);
|
||||
GET_AND_CHECK_STATIC_METHOD(methodID, m_applicationClass, "service", "()Landroid/app/Service;");
|
||||
jobject serviceObject = env->CallStaticObjectMethod(m_applicationClass, methodID);
|
||||
GET_AND_CHECK_STATIC_METHOD(methodID, m_applicationClass, "classLoader", "()Ljava/lang/ClassLoader;");
|
||||
m_classLoaderObject = env->NewGlobalRef(env->CallStaticObjectMethod(m_applicationClass, methodID));
|
||||
clazz = env->GetObjectClass(m_classLoaderObject);
|
||||
GET_AND_CHECK_METHOD(m_loadClassMethodID, clazz, "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;");
|
||||
if (serviceObject)
|
||||
m_serviceObject = env->NewGlobalRef(serviceObject);
|
||||
|
||||
if (activityObject) {
|
||||
if (activityObject)
|
||||
m_activityObject = env->NewGlobalRef(activityObject);
|
||||
|
||||
jobject object = activityObject ? activityObject : serviceObject;
|
||||
if (object) {
|
||||
FIND_AND_CHECK_CLASS("android/content/ContextWrapper");
|
||||
GET_AND_CHECK_METHOD(methodID, clazz, "getAssets", "()Landroid/content/res/AssetManager;");
|
||||
m_assetManager = AAssetManager_fromJava(env, env->CallObjectMethod(activityObject, methodID));
|
||||
m_assetManager = AAssetManager_fromJava(env, env->CallObjectMethod(object, methodID));
|
||||
|
||||
GET_AND_CHECK_METHOD(methodID, clazz, "getResources", "()Landroid/content/res/Resources;");
|
||||
m_resourcesObj = env->NewGlobalRef(env->CallObjectMethod(activityObject, methodID));
|
||||
m_resourcesObj = env->NewGlobalRef(env->CallObjectMethod(object, methodID));
|
||||
|
||||
FIND_AND_CHECK_CLASS("android/graphics/Bitmap");
|
||||
m_bitmapClass = static_cast<jclass>(env->NewGlobalRef(clazz));
|
||||
@ -819,8 +832,6 @@ static int registerNatives(JNIEnv *env)
|
||||
"(Landroid/content/res/Resources;Landroid/graphics/Bitmap;)V");
|
||||
}
|
||||
|
||||
|
||||
|
||||
return JNI_TRUE;
|
||||
}
|
||||
|
||||
|
@ -83,6 +83,7 @@ namespace QtAndroid
|
||||
AAssetManager *assetManager();
|
||||
jclass applicationClass();
|
||||
jobject activity();
|
||||
jobject service();
|
||||
|
||||
void setApplicationActive();
|
||||
|
||||
|
@ -66,7 +66,6 @@
|
||||
#include "qandroidplatformtheme.h"
|
||||
#include "qandroidsystemlocale.h"
|
||||
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
int QAndroidPlatformIntegration::m_defaultGeometryWidth = 320;
|
||||
@ -87,6 +86,8 @@ void *QAndroidPlatformNativeInterface::nativeResourceForIntegration(const QByteA
|
||||
return QtAndroid::javaVM();
|
||||
if (resource == "QtActivity")
|
||||
return QtAndroid::activity();
|
||||
if (resource == "QtService")
|
||||
return QtAndroid::service();
|
||||
if (resource == "AndroidStyleData") {
|
||||
if (m_androidStyle) {
|
||||
if (m_androidStyle->m_styleData.isEmpty())
|
||||
@ -122,7 +123,6 @@ QAndroidPlatformIntegration::QAndroidPlatformIntegration(const QStringList ¶
|
||||
#endif
|
||||
{
|
||||
Q_UNUSED(paramList);
|
||||
|
||||
m_androidPlatformNativeInterface = new QAndroidPlatformNativeInterface();
|
||||
|
||||
m_eglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
|
||||
@ -159,6 +159,9 @@ QAndroidPlatformIntegration::QAndroidPlatformIntegration(const QStringList ¶
|
||||
#endif // QT_NO_ACCESSIBILITY
|
||||
|
||||
QJNIObjectPrivate javaActivity(QtAndroid::activity());
|
||||
if (!javaActivity.isValid())
|
||||
javaActivity = QtAndroid::service();
|
||||
|
||||
if (javaActivity.isValid()) {
|
||||
QJNIObjectPrivate resources = javaActivity.callObjectMethod("getResources", "()Landroid/content/res/Resources;");
|
||||
QJNIObjectPrivate configuration = resources.callObjectMethod("getConfiguration", "()Landroid/content/res/Configuration;");
|
||||
@ -205,13 +208,13 @@ static bool needsBasicRenderloopWorkaround()
|
||||
bool QAndroidPlatformIntegration::hasCapability(Capability cap) const
|
||||
{
|
||||
switch (cap) {
|
||||
case ThreadedPixmaps: return true;
|
||||
case ApplicationState: return true;
|
||||
case NativeWidgets: return true;
|
||||
case OpenGL: return true;
|
||||
case ForeignWindows: return true;
|
||||
case ThreadedOpenGL: return !needsBasicRenderloopWorkaround();
|
||||
case RasterGLSurface: return true;
|
||||
case ThreadedPixmaps: return true;
|
||||
case NativeWidgets: return QtAndroid::activity();
|
||||
case OpenGL: return QtAndroid::activity();
|
||||
case ForeignWindows: return QtAndroid::activity();
|
||||
case ThreadedOpenGL: return !needsBasicRenderloopWorkaround() && QtAndroid::activity();
|
||||
case RasterGLSurface: return QtAndroid::activity();
|
||||
default:
|
||||
return QPlatformIntegration::hasCapability(cap);
|
||||
}
|
||||
@ -219,11 +222,15 @@ bool QAndroidPlatformIntegration::hasCapability(Capability cap) const
|
||||
|
||||
QPlatformBackingStore *QAndroidPlatformIntegration::createPlatformBackingStore(QWindow *window) const
|
||||
{
|
||||
if (!QtAndroid::activity())
|
||||
return nullptr;
|
||||
return new QAndroidPlatformBackingStore(window);
|
||||
}
|
||||
|
||||
QPlatformOpenGLContext *QAndroidPlatformIntegration::createPlatformOpenGLContext(QOpenGLContext *context) const
|
||||
{
|
||||
if (!QtAndroid::activity())
|
||||
return nullptr;
|
||||
QSurfaceFormat format(context->format());
|
||||
format.setAlphaBufferSize(8);
|
||||
format.setRedBufferSize(8);
|
||||
@ -234,6 +241,8 @@ QPlatformOpenGLContext *QAndroidPlatformIntegration::createPlatformOpenGLContext
|
||||
|
||||
QPlatformOffscreenSurface *QAndroidPlatformIntegration::createPlatformOffscreenSurface(QOffscreenSurface *surface) const
|
||||
{
|
||||
if (!QtAndroid::activity())
|
||||
return nullptr;
|
||||
QSurfaceFormat format(surface->requestedFormat());
|
||||
format.setAlphaBufferSize(8);
|
||||
format.setRedBufferSize(8);
|
||||
@ -245,6 +254,8 @@ QPlatformOffscreenSurface *QAndroidPlatformIntegration::createPlatformOffscreenS
|
||||
|
||||
QPlatformWindow *QAndroidPlatformIntegration::createPlatformWindow(QWindow *window) const
|
||||
{
|
||||
if (!QtAndroid::activity())
|
||||
return nullptr;
|
||||
if (window->type() == Qt::ForeignWindow)
|
||||
return new QAndroidPlatformForeignWindow(window);
|
||||
else
|
||||
|
@ -294,6 +294,8 @@ int QAndroidPlatformScreen::rasterSurfaces()
|
||||
void QAndroidPlatformScreen::doRedraw()
|
||||
{
|
||||
PROFILE_SCOPE;
|
||||
if (!QtAndroid::activity())
|
||||
return;
|
||||
|
||||
if (m_dirtyRect.isEmpty())
|
||||
return;
|
||||
|
@ -56,6 +56,8 @@ void QAndroidSystemLocale::getLocaleFromJava() const
|
||||
|
||||
QJNIObjectPrivate javaLocaleObject;
|
||||
QJNIObjectPrivate javaActivity(QtAndroid::activity());
|
||||
if (!javaActivity.isValid())
|
||||
javaActivity = QtAndroid::service();
|
||||
if (javaActivity.isValid()) {
|
||||
QJNIObjectPrivate resources = javaActivity.callObjectMethod("getResources", "()Landroid/content/res/Resources;");
|
||||
QJNIObjectPrivate configuration = resources.callObjectMethod("getConfiguration", "()Landroid/content/res/Configuration;");
|
||||
|
Loading…
Reference in New Issue
Block a user