diff options
author | BogDan Vatra <bogdan@kdab.com> | 2016-02-17 14:37:50 +0200 |
---|---|---|
committer | BogDan Vatra <bogdan@kdab.com> | 2016-02-17 14:29:58 +0000 |
commit | efcf1dec4992bf7aab5bf1f0f4c0ee8c54030465 (patch) | |
tree | 5c46f66065750007a5dafdf7ca394af6f33c2b82 /src/android/jar | |
parent | 4a7ccf74ff85a49e8c701a4d1c40f316b6fec4c5 (diff) |
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>
Diffstat (limited to 'src/android/jar')
5 files changed, 279 insertions, 36 deletions
diff --git a/src/android/jar/jar.pri b/src/android/jar/jar.pri index b45b353f95..58caacb837 100644 --- a/src/android/jar/jar.pri +++ b/src/android/jar/jar.pri @@ -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 diff --git a/src/android/jar/src/org/qtproject/qt5/android/QtActivityDelegate.java b/src/android/jar/src/org/qtproject/qt5/android/QtActivityDelegate.java index 985661bf3c..c434cc24b3 100644 --- a/src/android/jar/src/org/qtproject/qt5/android/QtActivityDelegate.java +++ b/src/android/jar/src/org/qtproject/qt5/android/QtActivityDelegate.java @@ -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 diff --git a/src/android/jar/src/org/qtproject/qt5/android/QtNative.java b/src/android/jar/src/org/qtproject/qt5/android/QtNative.java index d9be4c7d9f..5d4f6e791a 100644 --- a/src/android/jar/src/org/qtproject/qt5/android/QtNative.java +++ b/src/android/jar/src/org/qtproject/qt5/android/QtNative.java @@ -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); } diff --git a/src/android/jar/src/org/qtproject/qt5/android/QtNativeLibrariesDir.java b/src/android/jar/src/org/qtproject/qt5/android/QtNativeLibrariesDir.java index 8481baec54..ff3bf19383 100644 --- a/src/android/jar/src/org/qtproject/qt5/android/QtNativeLibrariesDir.java +++ b/src/android/jar/src/org/qtproject/qt5/android/QtNativeLibrariesDir.java @@ -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(); } diff --git a/src/android/jar/src/org/qtproject/qt5/android/QtServiceDelegate.java b/src/android/jar/src/org/qtproject/qt5/android/QtServiceDelegate.java new file mode 100644 index 0000000000..634658eefb --- /dev/null +++ b/src/android/jar/src/org/qtproject/qt5/android/QtServiceDelegate.java @@ -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); + } +} |