From 97fcf3bc987a18f32233fea550eb4a5a22e2b822 Mon Sep 17 00:00:00 2001 From: Paul Olav Tvete Date: Mon, 4 Mar 2013 10:16:42 +0100 Subject: Introducing the Qt Android port MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Based on the Necessitas project by Bogdan Vatra. Contributors to the Qt5 project: BogDan Vatra Eskil Abrahamsen Blomfeldt hjk Oswald Buddenhagen Paul Olav Tvete Robin Burchell Samuel Rødal Yoann Lopes The full history of the Qt5 port can be found in refs/old-heads/android, SHA-1 249ca9ca2c7d876b91b31df9434dde47f9065d0d Change-Id: Iff1a7b2dbb707c986f2639e65e39ed8f22430120 Reviewed-by: Oswald Buddenhagen Reviewed-by: Lars Knoll --- src/android/android.pro | 15 + src/android/jar/AndroidManifest.xml | 4 + src/android/jar/res/values/strings.xml | 4 + .../qtproject/qt5/android/QtActivityDelegate.java | 714 +++++++++++ .../src/org/qtproject/qt5/android/QtEditText.java | 97 ++ .../qtproject/qt5/android/QtInputConnection.java | 244 ++++ .../src/org/qtproject/qt5/android/QtLayout.java | 201 +++ .../src/org/qtproject/qt5/android/QtNative.java | 611 ++++++++++ .../qt5/android/QtNativeLibrariesDir.java | 61 + .../src/org/qtproject/qt5/android/QtSurface.java | 206 ++++ src/android/java/AndroidManifest.xml | 25 + src/android/java/res/layout/splash.xml | 13 + src/android/java/res/values-de/strings.xml | 6 + src/android/java/res/values-el/strings.xml | 6 + src/android/java/res/values-es/strings.xml | 6 + src/android/java/res/values-et/strings.xml | 6 + src/android/java/res/values-fa/strings.xml | 6 + src/android/java/res/values-fr/strings.xml | 6 + src/android/java/res/values-id/strings.xml | 6 + src/android/java/res/values-it/strings.xml | 6 + src/android/java/res/values-ja/strings.xml | 6 + src/android/java/res/values-ms/strings.xml | 6 + src/android/java/res/values-nb/strings.xml | 6 + src/android/java/res/values-nl/strings.xml | 6 + src/android/java/res/values-pl/strings.xml | 6 + src/android/java/res/values-pt-rBR/strings.xml | 6 + src/android/java/res/values-ro/strings.xml | 6 + src/android/java/res/values-rs/strings.xml | 6 + src/android/java/res/values-ru/strings.xml | 6 + src/android/java/res/values-zh-rCN/strings.xml | 6 + src/android/java/res/values-zh-rTW/strings.xml | 6 + src/android/java/res/values/libs.xml | 8 + src/android/java/res/values/strings.xml | 7 + .../src/org/kde/necessitas/ministro/IMinistro.aidl | 49 + .../kde/necessitas/ministro/IMinistroCallback.aidl | 53 + .../qtproject/qt5/android/bindings/QtActivity.java | 1278 ++++++++++++++++++++ .../qt5/android/bindings/QtApplication.java | 149 +++ src/android/java/version.xml | 8 + 38 files changed, 3861 insertions(+) create mode 100644 src/android/android.pro create mode 100644 src/android/jar/AndroidManifest.xml create mode 100644 src/android/jar/res/values/strings.xml create mode 100644 src/android/jar/src/org/qtproject/qt5/android/QtActivityDelegate.java create mode 100644 src/android/jar/src/org/qtproject/qt5/android/QtEditText.java create mode 100644 src/android/jar/src/org/qtproject/qt5/android/QtInputConnection.java create mode 100644 src/android/jar/src/org/qtproject/qt5/android/QtLayout.java create mode 100644 src/android/jar/src/org/qtproject/qt5/android/QtNative.java create mode 100644 src/android/jar/src/org/qtproject/qt5/android/QtNativeLibrariesDir.java create mode 100644 src/android/jar/src/org/qtproject/qt5/android/QtSurface.java create mode 100644 src/android/java/AndroidManifest.xml create mode 100644 src/android/java/res/layout/splash.xml create mode 100644 src/android/java/res/values-de/strings.xml create mode 100644 src/android/java/res/values-el/strings.xml create mode 100644 src/android/java/res/values-es/strings.xml create mode 100644 src/android/java/res/values-et/strings.xml create mode 100644 src/android/java/res/values-fa/strings.xml create mode 100644 src/android/java/res/values-fr/strings.xml create mode 100644 src/android/java/res/values-id/strings.xml create mode 100644 src/android/java/res/values-it/strings.xml create mode 100644 src/android/java/res/values-ja/strings.xml create mode 100644 src/android/java/res/values-ms/strings.xml create mode 100644 src/android/java/res/values-nb/strings.xml create mode 100644 src/android/java/res/values-nl/strings.xml create mode 100644 src/android/java/res/values-pl/strings.xml create mode 100644 src/android/java/res/values-pt-rBR/strings.xml create mode 100644 src/android/java/res/values-ro/strings.xml create mode 100644 src/android/java/res/values-rs/strings.xml create mode 100644 src/android/java/res/values-ru/strings.xml create mode 100644 src/android/java/res/values-zh-rCN/strings.xml create mode 100644 src/android/java/res/values-zh-rTW/strings.xml create mode 100644 src/android/java/res/values/libs.xml create mode 100644 src/android/java/res/values/strings.xml create mode 100644 src/android/java/src/org/kde/necessitas/ministro/IMinistro.aidl create mode 100644 src/android/java/src/org/kde/necessitas/ministro/IMinistroCallback.aidl create mode 100644 src/android/java/src/org/qtproject/qt5/android/bindings/QtActivity.java create mode 100644 src/android/java/src/org/qtproject/qt5/android/bindings/QtApplication.java create mode 100644 src/android/java/version.xml (limited to 'src/android') diff --git a/src/android/android.pro b/src/android/android.pro new file mode 100644 index 0000000000..049a51904c --- /dev/null +++ b/src/android/android.pro @@ -0,0 +1,15 @@ +CONFIG += java +TARGET = QtAndroid +DESTDIR = $$[QT_INSTALL_PREFIX/get]/jar + +PATHPREFIX = $$PWD/jar/src/org/qtproject/qt5/android/ + +JAVACLASSPATH += $$PWD/jar/src/ +JAVASOURCES += \ + $$PATHPREFIX/QtActivityDelegate.java \ + $$PATHPREFIX/QtEditText.java \ + $$PATHPREFIX/QtInputConnection.java \ + $$PATHPREFIX/QtLayout.java \ + $$PATHPREFIX/QtNative.java \ + $$PATHPREFIX/QtNativeLibrariesDir.java \ + $$PATHPREFIX/QtSurface.java diff --git a/src/android/jar/AndroidManifest.xml b/src/android/jar/AndroidManifest.xml new file mode 100644 index 0000000000..ebc6fcfea7 --- /dev/null +++ b/src/android/jar/AndroidManifest.xml @@ -0,0 +1,4 @@ + + + + diff --git a/src/android/jar/res/values/strings.xml b/src/android/jar/res/values/strings.xml new file mode 100644 index 0000000000..1021b5478a --- /dev/null +++ b/src/android/jar/res/values/strings.xml @@ -0,0 +1,4 @@ + + + QtJar + diff --git a/src/android/jar/src/org/qtproject/qt5/android/QtActivityDelegate.java b/src/android/jar/src/org/qtproject/qt5/android/QtActivityDelegate.java new file mode 100644 index 0000000000..b6e6e3397e --- /dev/null +++ b/src/android/jar/src/org/qtproject/qt5/android/QtActivityDelegate.java @@ -0,0 +1,714 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Copyright (C) 2012 BogDan Vatra +** Contact: http://www.qt-project.org/legal +** +** 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 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. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +package org.qtproject.qt5.android; + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Iterator; + +import android.app.Activity; +import android.content.Context; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.res.Configuration; +import android.graphics.Rect; +import android.os.Build; +import android.os.Bundle; +import android.text.method.MetaKeyKeyListener; +import android.util.DisplayMetrics; +import android.util.Log; +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.View; +import android.view.ViewGroup; +import android.view.WindowManager; +import android.view.inputmethod.InputMethodManager; + +public class QtActivityDelegate +{ + private Activity m_activity = null; + private Method m_super_dispatchKeyEvent = null; + private Method m_super_onRestoreInstanceState = null; + private Method m_super_onRetainNonConfigurationInstance = null; + private Method m_super_onSaveInstanceState = null; + private Method m_super_onKeyDown = null; + private Method m_super_onKeyUp = null; + private Method m_super_onConfigurationChanged = null; + + 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 NECESSITAS_API_LEVEL_KEY = "necessitas.api.level"; + + private static String m_environmentVariables = null; + private static String m_applicationParameters = null; + + private int m_currentOrientation = Configuration.ORIENTATION_UNDEFINED; + + private String m_mainLib; + private long m_metaState; + private int m_lastChar = 0; + private boolean m_fullScreen = false; + private boolean m_started = false; + private QtSurface m_surface = null; + private QtLayout m_layout = null; + private QtEditText m_editText = null; + private InputMethodManager m_imm = null; + private boolean m_quitApp = true; + private Process m_debuggerProcess = null; // debugger process + + public boolean m_keyboardIsVisible = false; + public boolean m_keyboardIsHiding = false; + + public QtLayout getQtLayout() + { + return m_layout; + } + + public QtSurface getQtSurface() + { + return m_surface; + } + + public void redrawWindow(int left, int top, int right, int bottom) + { + m_surface.drawBitmap(new Rect(left, top, right, bottom)); + } + + public void setFullScreen(boolean enterFullScreen) + { + if (m_fullScreen == enterFullScreen) + return; + + if (m_fullScreen = enterFullScreen) { + m_activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); + m_activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN); + } else { + m_activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN); + m_activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); + } + } + + // case status + private final int ImhNoAutoUppercase = 0x2; + private final int ImhPreferUppercase = 0x8; + @SuppressWarnings("unused") + private final int ImhPreferLowercase = 0x10; + private final int ImhUppercaseOnly = 0x40000; + private final int ImhLowercaseOnly = 0x80000; + + // options + private final int ImhNoPredictiveText = 0x20; + + // layout + private final int ImhHiddenText = 0x1; + private final int ImhPreferNumbers = 0x4; + private final int ImhMultiLine = 0x400; + private final int ImhDigitsOnly = 0x10000; + private final int ImhFormattedNumbersOnly = 0x20000; + private final int ImhDialableCharactersOnly = 0x100000; + private final int ImhEmailCharactersOnly = 0x200000; + private final int ImhUrlCharactersOnly = 0x400000; + + public void resetSoftwareKeyboard() + { + if (m_imm == null) + return; + m_editText.postDelayed(new Runnable() { + @Override + public void run() { + m_imm.restartInput(m_editText); + } + }, 5); + } + + public void showSoftwareKeyboard(int x, int y, int width, int height, int inputHints) + { + if (m_imm == null) + return; + if (height > m_surface.getHeight()*2/3) + m_activity.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE); + else + m_activity.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN); + + int initialCapsMode = 0; + int imeOptions = android.view.inputmethod.EditorInfo.IME_ACTION_DONE; + int inputType = android.text.InputType.TYPE_CLASS_TEXT; + + if ((inputHints & ImhMultiLine) != 0) { + inputType = android.text.InputType.TYPE_CLASS_TEXT | android.text.InputType.TYPE_TEXT_FLAG_MULTI_LINE; + imeOptions = android.view.inputmethod.EditorInfo.IME_FLAG_NO_ENTER_ACTION; + } + + if (((inputHints & ImhNoAutoUppercase) != 0 || (inputHints & ImhPreferUppercase) != 0) + && (inputHints & ImhLowercaseOnly) == 0) { + initialCapsMode = android.text.TextUtils.CAP_MODE_SENTENCES; + } + + if ((inputHints & ImhUppercaseOnly) != 0) + initialCapsMode = android.text.TextUtils.CAP_MODE_CHARACTERS; + + if ((inputHints & ImhHiddenText) != 0) + inputType = android.text.InputType.TYPE_TEXT_VARIATION_PASSWORD; + + if ((inputHints & ImhPreferNumbers) != 0) + inputType = android.text.InputType.TYPE_CLASS_NUMBER; + + if ((inputHints & ImhDigitsOnly) != 0) + inputType = android.text.InputType.TYPE_CLASS_NUMBER; + + if ((inputHints & ImhFormattedNumbersOnly) != 0) { + inputType = android.text.InputType.TYPE_CLASS_NUMBER + | android.text.InputType.TYPE_NUMBER_FLAG_DECIMAL + | android.text.InputType.TYPE_NUMBER_FLAG_SIGNED; + } + + if ((inputHints & ImhDialableCharactersOnly) != 0) + inputType = android.text.InputType.TYPE_CLASS_PHONE; + + if ((inputHints & ImhEmailCharactersOnly) != 0) + inputType = android.text.InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS; + + if ((inputHints & ImhUrlCharactersOnly) != 0) { + inputType = android.text.InputType.TYPE_TEXT_VARIATION_URI; + imeOptions = android.view.inputmethod.EditorInfo.IME_ACTION_GO; + } + + if ((inputHints & ImhNoPredictiveText) != 0) { + //android.text.InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS | android.text.InputType.TYPE_CLASS_TEXT; + inputType = android.text.InputType.TYPE_CLASS_TEXT | android.text.InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD; + } + + m_editText.setInitialCapsMode(initialCapsMode); + m_editText.setImeOptions(imeOptions); + m_editText.setInputType(inputType); + + m_layout.removeView(m_editText); + m_layout.addView(m_editText, new QtLayout.LayoutParams(width, height, x, y)); + m_editText.bringToFront(); + m_editText.requestFocus(); + m_editText.postDelayed(new Runnable() { + @Override + public void run() { + m_imm.showSoftInput(m_editText, 0); + m_keyboardIsVisible = true; + m_keyboardIsHiding = false; + m_editText.postDelayed(new Runnable() { + @Override + public void run() { + m_imm.restartInput(m_editText); + } + }, 25); + } + }, 15); + } + + public void hideSoftwareKeyboard() + { + if (m_imm == null) + return; + m_imm.hideSoftInputFromWindow(m_editText.getWindowToken(), 0); + m_keyboardIsVisible = false; + m_keyboardIsHiding = false; + } + + public boolean isSoftwareKeyboardVisible() + { + return m_keyboardIsVisible; + } + + String getAppIconSize(Activity a) + { + int size = a.getResources().getDimensionPixelSize(android.R.dimen.app_icon_size); + if (size < 36 || size > 512) { // check size sanity + DisplayMetrics metrics = new DisplayMetrics(); + a.getWindowManager().getDefaultDisplay().getMetrics(metrics); + size = metrics.densityDpi/10*3; + if (size < 36) + size = 36; + + if (size > 512) + size = 512; + } + return "\tQT_ANDROID_APP_ICON_SIZE=" + size; + } + + public void updateSelection(int selStart, int selEnd, int candidatesStart, int candidatesEnd) + { + if (m_imm == null) + return; + + m_imm.updateSelection(m_editText, selStart, selEnd, candidatesStart, candidatesEnd); + } + + public boolean loadApplication(Activity activity, ClassLoader classLoader, Bundle loaderParams) + { + /// check parameters integrity + if (!loaderParams.containsKey(NATIVE_LIBRARIES_KEY) + || !loaderParams.containsKey(BUNDLED_LIBRARIES_KEY) + || !loaderParams.containsKey(ENVIRONMENT_VARIABLES_KEY)) { + return false; + } + + m_activity = activity; + QtNative.setActivity(m_activity, this); + QtNative.setClassLoader(classLoader); + if (loaderParams.containsKey(STATIC_INIT_CLASSES_KEY)) { + for (String className: loaderParams.getStringArray(STATIC_INIT_CLASSES_KEY)) { + if (className.length() == 0) + continue; + + try { + @SuppressWarnings("rawtypes") + Class initClass = classLoader.loadClass(className); + Object staticInitDataObject = initClass.newInstance(); // create an instance + Method m = initClass.getMethod("setActivity", Activity.class, Object.class); + m.invoke(staticInitDataObject, m_activity, this); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + QtNative.loadQtLibraries(loaderParams.getStringArrayList(NATIVE_LIBRARIES_KEY)); + ArrayList libraries = loaderParams.getStringArrayList(BUNDLED_LIBRARIES_KEY); + QtNative.loadBundledLibraries(libraries, QtNativeLibrariesDir.nativeLibrariesDir(m_activity)); + m_mainLib = loaderParams.getString(MAIN_LIBRARY_KEY); + // older apps provide the main library as the last bundled library; look for this if the main library isn't provided + if (null == m_mainLib && libraries.size() > 0) + m_mainLib = libraries.get(libraries.size() - 1); + + try { + m_super_dispatchKeyEvent = m_activity.getClass().getMethod("super_dispatchKeyEvent", KeyEvent.class); + m_super_onRestoreInstanceState = m_activity.getClass().getMethod("super_onRestoreInstanceState", Bundle.class); + m_super_onRetainNonConfigurationInstance = m_activity.getClass().getMethod("super_onRetainNonConfigurationInstance"); + m_super_onSaveInstanceState = m_activity.getClass().getMethod("super_onSaveInstanceState", Bundle.class); + m_super_onKeyDown = m_activity.getClass().getMethod("super_onKeyDown", Integer.TYPE, KeyEvent.class); + m_super_onKeyUp = m_activity.getClass().getMethod("super_onKeyUp", Integer.TYPE, KeyEvent.class); + m_super_onConfigurationChanged = m_activity.getClass().getMethod("super_onConfigurationChanged", Configuration.class); + } catch (Exception e) { + e.printStackTrace(); + return false; + } + + int necessitasApiLevel = 1; + if (loaderParams.containsKey(NECESSITAS_API_LEVEL_KEY)) + necessitasApiLevel = loaderParams.getInt(NECESSITAS_API_LEVEL_KEY); + + m_environmentVariables = loaderParams.getString(ENVIRONMENT_VARIABLES_KEY); + String additionalEnvironmentVariables = "QT_ANDROID_FONTS_MONOSPACE=Droid Sans Mono;Droid Sans;Droid Sans Fallback" + + "\tNECESSITAS_API_LEVEL=" + necessitasApiLevel + + "\tHOME=" + m_activity.getFilesDir().getAbsolutePath() + + "\tTMPDIR=" + m_activity.getFilesDir().getAbsolutePath(); + if (android.os.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"; + + additionalEnvironmentVariables += getAppIconSize(activity); + + 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 { + // FIXME turn on debuggable check + // if the applications is debuggable and it has a native debug request + Bundle extras = m_activity.getIntent().getExtras(); + if ( /*(ai.flags&ApplicationInfo.FLAG_DEBUGGABLE) != 0 + &&*/ extras != null + && extras.containsKey("native_debug") + && extras.getString("native_debug").equals("true")) { + try { + String packagePath = + m_activity.getPackageManager().getApplicationInfo(m_activity.getPackageName(), + PackageManager.GET_CONFIGURATIONS).dataDir + "/"; + String gdbserverPath = + extras.containsKey("gdbserver_path") + ? extras.getString("gdbserver_path") + : packagePath+"lib/gdbserver "; + + String socket = + extras.containsKey("gdbserver_socket") + ? extras.getString("gdbserver_socket") + : "+debug-socket"; + + // start debugger + m_debuggerProcess = Runtime.getRuntime().exec(gdbserverPath + + socket + + " --attach " + + android.os.Process.myPid(), + null, + new File(packagePath)); + } catch (IOException ioe) { + Log.e(QtNative.QtTAG,"Can't start debugger" + ioe.getMessage()); + } catch (SecurityException se) { + Log.e(QtNative.QtTAG,"Can't start debugger" + se.getMessage()); + } catch (NameNotFoundException e) { + Log.e(QtNative.QtTAG,"Can't start debugger" + e.getMessage()); + } + } + + + if ( /*(ai.flags&ApplicationInfo.FLAG_DEBUGGABLE) != 0 + &&*/ extras != null + && extras.containsKey("debug_ping") + && extras.getString("debug_ping").equals("true")) { + String packagePath = + m_activity.getPackageManager().getApplicationInfo(m_activity.getPackageName(), + PackageManager.GET_CONFIGURATIONS).dataDir + "/"; + String debugPing = packagePath + "debug_ping"; + int i = 0; + while (true) { + ++i; + Log.i(QtNative.QtTAG, "DEBUGGER: WAITING FOR PING AT " + debugPing + ", ATTEMPT " + i); + File file = new File(debugPing); + if (file.exists()) { + file.delete(); + break; + } + Thread.sleep(1000); + } + + Log.i(QtNative.QtTAG, "DEBUGGER: GOT PING " + debugPing); + } + + + if (/*(ai.flags&ApplicationInfo.FLAG_DEBUGGABLE) != 0 + &&*/ extras != null + && extras.containsKey("qml_debug") + && extras.getString("qml_debug").equals("true")) { + String qmljsdebugger; + if (extras.containsKey("qmljsdebugger")) { + qmljsdebugger = extras.getString("qmljsdebugger"); + qmljsdebugger.replaceAll("\\s", ""); // remove whitespace for security + } else { + qmljsdebugger = "port:3768"; + } + m_applicationParameters += "\t-qmljsdebugger=" + qmljsdebugger; + } + + if (null == m_surface) + onCreate(null); + String nativeLibraryDir = QtNativeLibrariesDir.nativeLibrariesDir(m_activity); + m_surface.applicationStarted( QtNative.startApplication(m_applicationParameters, + m_environmentVariables, + m_mainLib, + nativeLibraryDir)); + m_started = true; + return true; + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + public void onTerminate() + { + QtNative.terminateQt(); + } + + public void onCreate(Bundle savedInstanceState) + { + m_quitApp = true; + if (null == savedInstanceState) { + DisplayMetrics metrics = new DisplayMetrics(); + m_activity.getWindowManager().getDefaultDisplay().getMetrics(metrics); + QtNative.setApplicationDisplayMetrics(metrics.widthPixels, metrics.heightPixels, + metrics.widthPixels, metrics.heightPixels, + metrics.xdpi, metrics.ydpi); + } + m_layout = new QtLayout(m_activity); + m_surface = new QtSurface(m_activity, 0); + m_editText = new QtEditText(m_activity); + m_imm = (InputMethodManager)m_activity.getSystemService(Context.INPUT_METHOD_SERVICE); + m_layout.addView(m_surface,0); + m_activity.setContentView(m_layout, + new ViewGroup.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT, + ViewGroup.LayoutParams.FILL_PARENT)); + m_layout.bringChildToFront(m_surface); + m_activity.registerForContextMenu(m_layout); + + m_currentOrientation = m_activity.getResources().getConfiguration().orientation; + } + + public void onConfigurationChanged(Configuration configuration) + { + try { + m_super_onConfigurationChanged.invoke(m_activity, configuration); + } catch (Exception e) { + e.printStackTrace(); + } + + if (configuration.orientation != m_currentOrientation + && m_currentOrientation != Configuration.ORIENTATION_UNDEFINED) { + QtNative.handleOrientationChanged(configuration.orientation); + } + + m_currentOrientation = configuration.orientation; + } + + public void onDestroy() + { + if (m_quitApp) { + if (m_debuggerProcess != null) + m_debuggerProcess.destroy(); + System.exit(0);// FIXME remove it or find a better way + } + } + + public void onRestoreInstanceState(Bundle savedInstanceState) + { + try { + m_super_onRestoreInstanceState.invoke(m_activity, savedInstanceState); + } catch (Exception e) { + e.printStackTrace(); + } +// setFullScreen(savedInstanceState.getBoolean("FullScreen")); + m_started = savedInstanceState.getBoolean("Started"); + if (m_started) + m_surface.applicationStarted(true); + } + + public void onResume() + { + // fire all lostActions + synchronized (QtNative.m_mainActivityMutex) + { + Iterator itr = QtNative.getLostActions().iterator(); + while (itr.hasNext()) + m_activity.runOnUiThread(itr.next()); + + if (m_started) { + QtNative.clearLostActions(); + QtNative.updateWindow(); + } + } + } + + public Object onRetainNonConfigurationInstance() + { + try { + m_super_onRetainNonConfigurationInstance.invoke(m_activity); + } catch (Exception e) { + e.printStackTrace(); + } + m_quitApp = false; + return true; + } + + public void onSaveInstanceState(Bundle outState) { + try { + m_super_onSaveInstanceState.invoke(m_activity, outState); + } catch (Exception e) { + e.printStackTrace(); + } + outState.putBoolean("FullScreen", m_fullScreen); + outState.putBoolean("Started", m_started); + } + + public boolean onKeyDown(int keyCode, KeyEvent event) + { + if (!m_started) + return false; + + if (keyCode == KeyEvent.KEYCODE_MENU) { + try { + return (Boolean)m_super_onKeyDown.invoke(m_activity, keyCode, event); + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + m_metaState = MetaKeyKeyListener.handleKeyDown(m_metaState, keyCode, event); + int c = event.getUnicodeChar(MetaKeyKeyListener.getMetaState(m_metaState)); + int lc = c; + m_metaState = MetaKeyKeyListener.adjustMetaAfterKeypress(m_metaState); + + if ((c & KeyCharacterMap.COMBINING_ACCENT) != 0) { + c = c & KeyCharacterMap.COMBINING_ACCENT_MASK; + int composed = KeyEvent.getDeadChar(m_lastChar, c); + c = composed; + } + + m_lastChar = lc; + if (keyCode != KeyEvent.KEYCODE_BACK) + QtNative.keyDown(keyCode, c, event.getMetaState()); + + return true; + } + + public boolean onKeyUp(int keyCode, KeyEvent event) + { + if (!m_started) + return false; + + if (keyCode == KeyEvent.KEYCODE_MENU) { + try { + return (Boolean)m_super_onKeyUp.invoke(m_activity, keyCode, event); + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + if (keyCode == KeyEvent.KEYCODE_BACK && m_keyboardIsVisible) + { + if (!m_keyboardIsHiding) + hideSoftwareKeyboard(); + return true; + } + + m_metaState = MetaKeyKeyListener.handleKeyUp(m_metaState, keyCode, event); + QtNative.keyUp(keyCode, event.getUnicodeChar(), event.getMetaState()); + return true; + } + + public boolean dispatchKeyEvent(KeyEvent event) + { + if (m_started + && event.getAction() == KeyEvent.ACTION_MULTIPLE + && event.getCharacters() != null + && event.getCharacters().length() == 1 + && event.getKeyCode() == 0) { + QtNative.keyDown(0, event.getCharacters().charAt(0), event.getMetaState()); + QtNative.keyUp(0, event.getCharacters().charAt(0), event.getMetaState()); + } + + try { + return (Boolean) m_super_dispatchKeyEvent.invoke(m_activity, event); + } catch (Exception e) { + e.printStackTrace(); + } + return false; + } + + private boolean m_opionsMenuIsVisible = false; + public boolean onCreateOptionsMenu(Menu menu) + { + menu.clear(); + return true; + } + public boolean onPrepareOptionsMenu(Menu menu) + { + m_opionsMenuIsVisible = true; + return QtNative.onPrepareOptionsMenu(menu); + } + + public boolean onOptionsItemSelected(MenuItem item) + { + return QtNative.onOptionsItemSelected(item.getItemId(), item.isChecked()); + } + + public void onOptionsMenuClosed(Menu menu) + { + m_opionsMenuIsVisible = false; + QtNative.onOptionsMenuClosed(menu); + } + + public void resetOptionsMenu() + { + if (m_opionsMenuIsVisible) + m_activity.closeOptionsMenu(); + } + private boolean m_contextMenuVisible = false; + public void onCreateContextMenu(ContextMenu menu, + View v, + ContextMenuInfo menuInfo) + { + menu.clearHeader(); + QtNative.onCreateContextMenu(menu); + m_contextMenuVisible = true; + } + + public void onContextMenuClosed(Menu menu) + { + if (!m_contextMenuVisible) { + Log.e(QtNative.QtTAG, "invalid onContextMenuClosed call"); + return; + } + m_contextMenuVisible = false; + QtNative.onContextMenuClosed(menu); + } + + public boolean onContextItemSelected(MenuItem item) + { + return QtNative.onContextItemSelected(item.getItemId(), item.isChecked()); + } + + public void openContextMenu() + { + m_layout.postDelayed(new Runnable() { + @Override + public void run() { + m_activity.openContextMenu(m_layout); + } + }, 10); + } + + public void closeContextMenu() + { + m_activity.closeContextMenu(); + } +} diff --git a/src/android/jar/src/org/qtproject/qt5/android/QtEditText.java b/src/android/jar/src/org/qtproject/qt5/android/QtEditText.java new file mode 100644 index 0000000000..b95e0c070c --- /dev/null +++ b/src/android/jar/src/org/qtproject/qt5/android/QtEditText.java @@ -0,0 +1,97 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Copyright (C) 2012 BogDan Vatra +** Contact: http://www.qt-project.org/legal +** +** 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 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. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +package org.qtproject.qt5.android; + +import android.content.Context; +import android.text.InputType; +import android.view.View; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputConnection; + +public class QtEditText extends View +{ + QtInputConnection m_inputConnection; + int m_initialCapsMode = 0; + int m_imeOptions = 0; + int m_inputType = InputType.TYPE_CLASS_TEXT; + + public void setImeOptions(int m_imeOptions) + { + this.m_imeOptions = m_imeOptions; + } + + public void setInitialCapsMode(int m_initialCapsMode) + { + this.m_initialCapsMode = m_initialCapsMode; + } + + + public void setInputType(int m_inputType) + { + this.m_inputType = m_inputType; + } + + public QtEditText(Context context) + { + super(context); + setFocusable(true); + setFocusableInTouchMode(true); + m_inputConnection = new QtInputConnection(this); + } + + @Override + public InputConnection onCreateInputConnection(EditorInfo outAttrs) + { + outAttrs.inputType = m_inputType; + outAttrs.imeOptions = m_imeOptions; + outAttrs.initialCapsMode = m_initialCapsMode; + outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_EXTRACT_UI; + return m_inputConnection; + } +// // DEBUG CODE +// @Override +// protected void onDraw(Canvas canvas) { +// canvas.drawARGB(127, 255, 0, 255); +// super.onDraw(canvas); +// } +} diff --git a/src/android/jar/src/org/qtproject/qt5/android/QtInputConnection.java b/src/android/jar/src/org/qtproject/qt5/android/QtInputConnection.java new file mode 100644 index 0000000000..3bcec030b5 --- /dev/null +++ b/src/android/jar/src/org/qtproject/qt5/android/QtInputConnection.java @@ -0,0 +1,244 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Copyright (C) 2012 BogDan Vatra +** Contact: http://www.qt-project.org/legal +** +** 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 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. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +package org.qtproject.qt5.android; + +import android.content.Context; +import android.os.Build; +import android.view.View; +import android.view.inputmethod.BaseInputConnection; +import android.view.inputmethod.CompletionInfo; +import android.view.inputmethod.ExtractedText; +import android.view.inputmethod.ExtractedTextRequest; +import android.view.inputmethod.InputMethodManager; + +class QtExtractedText +{ + public int partialEndOffset; + public int partialStartOffset; + public int selectionEnd; + public int selectionStart; + public int startOffset; + public String text; +} + +class QtNativeInputConnection +{ + static native boolean commitText(String text, int newCursorPosition); + static native boolean commitCompletion(String text, int position); + static native boolean deleteSurroundingText(int leftLength, int rightLength); + static native boolean finishComposingText(); + static native int getCursorCapsMode(int reqModes); + static native QtExtractedText getExtractedText(int hintMaxChars, int hintMaxLines, int flags); + static native String getSelectedText(int flags); + static native String getTextAfterCursor(int length, int flags); + static native String getTextBeforeCursor(int length, int flags); + static native boolean setComposingText(String text, int newCursorPosition); + static native boolean setSelection(int start, int end); + static native boolean selectAll(); + static native boolean cut(); + static native boolean copy(); + static native boolean copyURL(); + static native boolean paste(); +} + +public class QtInputConnection extends BaseInputConnection +{ + private static final int ID_SELECT_ALL = android.R.id.selectAll; + private static final int ID_CUT = android.R.id.cut; + private static final int ID_COPY = android.R.id.copy; + private static final int ID_PASTE = android.R.id.paste; + private static final int ID_COPY_URL = android.R.id.copyUrl; + private static final int ID_SWITCH_INPUT_METHOD = android.R.id.switchInputMethod; + private static final int ID_ADD_TO_DICTIONARY = android.R.id.addToDictionary; + + View m_view; + boolean m_closing; + public QtInputConnection(View targetView) + { + super(targetView, true); + m_view = targetView; + m_closing = false; + } + + @Override + public boolean beginBatchEdit() + { + m_closing = false; + return true; + } + + @Override + public boolean endBatchEdit() + { + m_closing = false; + return true; + } + + @Override + public boolean commitCompletion(CompletionInfo text) + { + m_closing = false; + return QtNativeInputConnection.commitCompletion(text.getText().toString(), text.getPosition()); + } + + @Override + public boolean commitText(CharSequence text, int newCursorPosition) + { + m_closing = false; + return QtNativeInputConnection.commitText(text.toString(), newCursorPosition); + } + + @Override + public boolean deleteSurroundingText(int leftLength, int rightLength) + { + m_closing = false; + return QtNativeInputConnection.deleteSurroundingText(leftLength, rightLength); + } + + @Override + public boolean finishComposingText() + { + if (m_closing) { + QtNative.activityDelegate().m_keyboardIsHiding = true; + m_view.postDelayed(new Runnable() { + @Override + public void run() { + if (QtNative.activityDelegate().m_keyboardIsHiding) + QtNative.activityDelegate().m_keyboardIsVisible=false; + } + }, 5000); // it seems finishComposingText comes musch faster than onKeyUp event, + // so we must delay hide notification + m_closing = false; + } else { + m_closing = true; + } + return QtNativeInputConnection.finishComposingText(); + } + + @Override + public int getCursorCapsMode(int reqModes) + { + return QtNativeInputConnection.getCursorCapsMode(reqModes); + } + + @Override + public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) + { + QtExtractedText qExtractedText = QtNativeInputConnection.getExtractedText(request.hintMaxChars, + request.hintMaxLines, + flags); + ExtractedText extractedText = new ExtractedText(); + extractedText.partialEndOffset = qExtractedText.partialEndOffset; + extractedText.partialStartOffset = qExtractedText.partialStartOffset; + extractedText.selectionEnd = qExtractedText.selectionEnd; + extractedText.selectionStart = qExtractedText.selectionStart; + extractedText.startOffset = qExtractedText.startOffset; + extractedText.text = qExtractedText.text; + return extractedText; + } + + public CharSequence getSelectedText(int flags) + { + return QtNativeInputConnection.getSelectedText(flags); + } + + @Override + public CharSequence getTextAfterCursor(int length, int flags) + { + return QtNativeInputConnection.getTextAfterCursor(length, flags); + } + + @Override + public CharSequence getTextBeforeCursor(int length, int flags) + { + return QtNativeInputConnection.getTextBeforeCursor(length, flags); + } + + @Override + public boolean performContextMenuAction(int id) + { + switch (id) { + case ID_SELECT_ALL: + return QtNativeInputConnection.selectAll(); + case ID_COPY: + return QtNativeInputConnection.copy(); + case ID_COPY_URL: + return QtNativeInputConnection.copyURL(); + case ID_CUT: + return QtNativeInputConnection.cut(); + case ID_PASTE: + return QtNativeInputConnection.paste(); + + case ID_SWITCH_INPUT_METHOD: + InputMethodManager imm = (InputMethodManager)m_view.getContext().getSystemService(Context.INPUT_METHOD_SERVICE); + if (imm != null) + imm.showInputMethodPicker(); + + return true; + + case ID_ADD_TO_DICTIONARY: +// TODO +// String word = m_editable.subSequence(0, m_editable.length()).toString(); +// if (word != null) { +// Intent i = new Intent("com.android.settings.USER_DICTIONARY_INSERT"); +// i.putExtra("word", word); +// i.setFlags(i.getFlags() | Intent.FLAG_ACTIVITY_NEW_TASK); +// m_view.getContext().startActivity(i); +// } + return true; + } + return super.performContextMenuAction(id); + } + + @Override + public boolean setComposingText(CharSequence text, int newCursorPosition) + { + return QtNativeInputConnection.setComposingText(text.toString(), newCursorPosition); + } + + @Override + public boolean setSelection(int start, int end) + { + return QtNativeInputConnection.setSelection(start, end); + } +} diff --git a/src/android/jar/src/org/qtproject/qt5/android/QtLayout.java b/src/android/jar/src/org/qtproject/qt5/android/QtLayout.java new file mode 100644 index 0000000000..043dab5ce8 --- /dev/null +++ b/src/android/jar/src/org/qtproject/qt5/android/QtLayout.java @@ -0,0 +1,201 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Copyright (C) 2012 BogDan Vatra +** Contact: http://www.qt-project.org/legal +** +** 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 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. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +package org.qtproject.qt5.android; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.View; +import android.view.ViewGroup; + +public class QtLayout extends ViewGroup +{ + public QtLayout(Context context) + { + super(context); + } + + public QtLayout(Context context, AttributeSet attrs) + { + super(context, attrs); + } + + public QtLayout(Context context, AttributeSet attrs, int defStyle) + { + super(context, attrs, defStyle); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) + { + int count = getChildCount(); + + int maxHeight = 0; + int maxWidth = 0; + + // Find out how big everyone wants to be + measureChildren(widthMeasureSpec, heightMeasureSpec); + + // Find rightmost and bottom-most child + for (int i = 0; i < count; i++) { + View child = getChildAt(i); + if (child.getVisibility() != GONE) { + int childRight; + int childBottom; + + QtLayout.LayoutParams lp + = (QtLayout.LayoutParams) child.getLayoutParams(); + + childRight = lp.x + child.getMeasuredWidth(); + childBottom = lp.y + child.getMeasuredHeight(); + + maxWidth = Math.max(maxWidth, childRight); + maxHeight = Math.max(maxHeight, childBottom); + } + } + + // Check against minimum height and width + maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight()); + maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth()); + + setMeasuredDimension(resolveSize(maxWidth, widthMeasureSpec), + resolveSize(maxHeight, heightMeasureSpec)); + } + + /** + * Returns a set of layout parameters with a width of + * {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT}, + * a height of {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} + * and with the coordinates (0, 0). + */ + @Override + protected ViewGroup.LayoutParams generateDefaultLayoutParams() + { + return new LayoutParams(android.view.ViewGroup.LayoutParams.WRAP_CONTENT, + android.view.ViewGroup.LayoutParams.WRAP_CONTENT, + 0, + 0); + } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) + { + int count = getChildCount(); + + for (int i = 0; i < count; i++) { + View child = getChildAt(i); + if (child.getVisibility() != GONE) { + QtLayout.LayoutParams lp = + (QtLayout.LayoutParams) child.getLayoutParams(); + + int childLeft = lp.x; + int childTop = lp.y; + child.layout(childLeft, childTop, + childLeft + child.getMeasuredWidth(), + childTop + child.getMeasuredHeight()); + + } + } + } + + // Override to allow type-checking of LayoutParams. + @Override + protected boolean checkLayoutParams(ViewGroup.LayoutParams p) + { + return p instanceof QtLayout.LayoutParams; + } + + @Override + protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) + { + return new LayoutParams(p); + } + + /** + * Per-child layout information associated with AbsoluteLayout. + * See + * {@link android.R.styleable#AbsoluteLayout_Layout Absolute Layout Attributes} + * for a list of all child view attributes that this class supports. + */ + public static class LayoutParams extends ViewGroup.LayoutParams + { + /** + * The horizontal, or X, location of the child within the view group. + */ + public int x; + /** + * The vertical, or Y, location of the child within the view group. + */ + public int y; + + /** + * Creates a new set of layout parameters with the specified width, + * height and location. + * + * @param width the width, either {@link #FILL_PARENT}, + {@link #WRAP_CONTENT} or a fixed size in pixels + * @param height the height, either {@link #FILL_PARENT}, + {@link #WRAP_CONTENT} or a fixed size in pixels + * @param x the X location of the child + * @param y the Y location of the child + */ + public LayoutParams(int width, int height, int x, int y) + { + super(width, height); + this.x = x; + this.y = y; + } + + /** + * {@inheritDoc} + */ + public LayoutParams(ViewGroup.LayoutParams source) + { + super(source); + } + } + + public void bringChildFront(int child) + { + bringChildToFront(getChildAt(child)); + } +} diff --git a/src/android/jar/src/org/qtproject/qt5/android/QtNative.java b/src/android/jar/src/org/qtproject/qt5/android/QtNative.java new file mode 100644 index 0000000000..346bc1221a --- /dev/null +++ b/src/android/jar/src/org/qtproject/qt5/android/QtNative.java @@ -0,0 +1,611 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Copyright (C) 2012 BogDan Vatra +** Contact: http://www.qt-project.org/legal +** +** 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 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. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +package org.qtproject.qt5.android; + +import java.io.File; +import java.util.ArrayList; +import java.util.concurrent.Semaphore; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.os.Build; +import android.text.ClipboardManager; +import android.util.Log; +import android.view.ContextMenu; +import android.view.Menu; +import android.view.MotionEvent; + +public class QtNative +{ + private static Activity m_activity = null; + private static QtActivityDelegate m_activityDelegate = 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 + private static ArrayList m_lostActions = new ArrayList(); // a list containing all actions which could not be performed (e.g. the main activity is destroyed, etc.) + private static boolean m_started = false; + private static int m_displayMetricsScreenWidthPixels = 0; + private static int m_displayMetricsScreenHeightPixels = 0; + private static int m_displayMetricsDesktopWidthPixels = 0; + private static int m_displayMetricsDesktopHeightPixels = 0; + private static double m_displayMetricsXDpi = .0; + private static double m_displayMetricsYDpi = .0; + private static int m_oldx, m_oldy; + private static final int m_moveThreshold = 0; + private static ClipboardManager m_clipboardManager = null; + + private static ClassLoader m_classLoader = null; + public static ClassLoader classLoader() + { + return m_classLoader; + } + + public static void setClassLoader(ClassLoader classLoader) + { + m_classLoader = classLoader; + } + + public static Activity activity() + { + synchronized (m_mainActivityMutex) { + return m_activity; + } + } + + public static QtActivityDelegate activityDelegate() + { + synchronized (m_mainActivityMutex) { + return m_activityDelegate; + } + } + + public static void openURL(String url) + { + Uri uri = Uri.parse(url); + Intent intent = new Intent(Intent.ACTION_VIEW, uri); + activity().startActivity(intent); + } + + // this method loads full path libs + public static void loadQtLibraries(ArrayList libraries) + { + if (libraries == null) + return; + + for (String libName : libraries) { + try { + File f = new File(libName); + if (f.exists()) + System.load(libName); + } catch (SecurityException e) { + Log.i(QtTAG, "Can't load '" + libName + "'", e); + } catch (Exception e) { + Log.i(QtTAG, "Can't load '" + libName + "'", e); + } + } + } + + // this method loads bundled libs by name. + public static void loadBundledLibraries(ArrayList libraries, String nativeLibraryDir) + { + if (libraries == null) + return; + + for (String libName : libraries) { + try { + File f = new File(nativeLibraryDir+"lib"+libName+".so"); + if (f.exists()) + System.load(f.getAbsolutePath()); + else + Log.i(QtTAG, "Can't find '" + f.getAbsolutePath()); + } catch (Exception e) { + Log.i(QtTAG, "Can't load '" + libName + "'", e); + } + } + } + + public static void setActivity(Activity qtMainActivity, QtActivityDelegate qtActivityDelegate) + { + synchronized (m_mainActivityMutex) { + m_activity = qtMainActivity; + m_activityDelegate = qtActivityDelegate; + } + } + + static public ArrayList getLostActions() + { + return m_lostActions; + } + + static public void clearLostActions() + { + m_lostActions.clear(); + } + + private static boolean runAction(Runnable action) + { + synchronized (m_mainActivityMutex) { + if (m_activity == null) + m_lostActions.add(action); + else + m_activity.runOnUiThread(action); + return m_activity != null; + } + } + + public static boolean startApplication(String params, + String environment, + String mainLibrary, + String nativeLibraryDir) throws Exception + { + File f = new File(nativeLibraryDir + "lib" + mainLibrary + ".so"); + if (!f.exists()) + throw new Exception("Can't find main library '" + mainLibrary + "'"); + + if (params == null) + params = "-platform\tandroid"; + + boolean res = false; + synchronized (m_mainActivityMutex) { + res = startQtAndroidPlugin(); + setDisplayMetrics(m_displayMetricsScreenWidthPixels, + m_displayMetricsScreenHeightPixels, + m_displayMetricsDesktopWidthPixels, + m_displayMetricsDesktopHeightPixels, + m_displayMetricsXDpi, + m_displayMetricsYDpi); + if (params.length() > 0) + params = "\t" + params; + startQtApplication(f.getAbsolutePath() + "\t" + params, environment); + m_started = true; + } + return res; + } + + public static void setApplicationDisplayMetrics(int screenWidthPixels, + int screenHeightPixels, + int desktopWidthPixels, + int desktopHeightPixels, + double XDpi, + double YDpi) + { + /* Fix buggy dpi report */ + if (XDpi < android.util.DisplayMetrics.DENSITY_LOW) + XDpi = android.util.DisplayMetrics.DENSITY_LOW; + if (YDpi < android.util.DisplayMetrics.DENSITY_LOW) + YDpi = android.util.DisplayMetrics.DENSITY_LOW; + + synchronized (m_mainActivityMutex) { + if (m_started) { + setDisplayMetrics(screenWidthPixels, + screenHeightPixels, + desktopWidthPixels, + desktopHeightPixels, + XDpi, + YDpi); + } else { + m_displayMetricsScreenWidthPixels = screenWidthPixels; + m_displayMetricsScreenHeightPixels = screenHeightPixels; + m_displayMetricsDesktopWidthPixels = desktopWidthPixels; + m_displayMetricsDesktopHeightPixels = desktopHeightPixels; + m_displayMetricsXDpi = XDpi; + m_displayMetricsYDpi = YDpi; + } + } + } + + public static void pauseApplication() + { + synchronized (m_mainActivityMutex) { + if (m_started) + pauseQtApp(); + } + } + + public static void resumeApplication() + { + synchronized (m_mainActivityMutex) { + if (m_started) { + resumeQtApp(); + updateWindow(); + } + } + } + + // application methods + public static native void startQtApplication(String params, String env); + public static native void startQtApp(String params, String env); + public static native void pauseQtApp(); + public static native void resumeQtApp(); + public static native boolean startQtAndroidPlugin(); + public static native void quitQtAndroidPlugin(); + public static native void terminateQt(); + // application methods + + private static void quitApp() + { + m_activity.finish(); + } + + private static void redrawSurface(final int left, final int top, final int right, final int bottom ) + { + runAction(new Runnable() { + @Override + public void run() { + m_activityDelegate.redrawWindow(left, top, right, bottom); + } + }); + } + + //@ANDROID-5 + static private int getAction(int index, MotionEvent event) + { + int action = event.getAction(); + if (action == MotionEvent.ACTION_MOVE) { + int hsz = event.getHistorySize(); + if (hsz > 0) { + if (Math.abs(event.getX(index) - event.getHistoricalX(index, hsz-1)) > 1 + || Math.abs(event.getY(index) - event.getHistoricalY(index, hsz-1)) > 1) { + return 1; + } else { + return 2; + } + } + return 1; + } + + switch (index) { + case 0: + if (action == MotionEvent.ACTION_DOWN + || action == MotionEvent.ACTION_POINTER_1_DOWN) { + return 0; + } + + if (action == MotionEvent.ACTION_UP + || action == MotionEvent.ACTION_POINTER_1_UP) { + return 3; + } + break; + + case 1: + if (action == MotionEvent.ACTION_POINTER_2_DOWN + || action == MotionEvent.ACTION_POINTER_DOWN) { + return 0; + } + + if (action == MotionEvent.ACTION_POINTER_2_UP + || action == MotionEvent.ACTION_POINTER_UP) { + return 3; + } + break; + + case 2: + if (action == MotionEvent.ACTION_POINTER_3_DOWN + || action == MotionEvent.ACTION_POINTER_DOWN) { + return 0; + } + + if (action == MotionEvent.ACTION_POINTER_3_UP + || action == MotionEvent.ACTION_POINTER_UP) { + return 3; + } + + break; + } + return 2; + } + //@ANDROID-5 + + static public void sendTouchEvent(MotionEvent event, int id) + { + //@ANDROID-5 + touchBegin(id); + for (int i=0;i m_moveThreshold || Math.abs(dy) > m_moveThreshold) { + mouseMove(id, (int) event.getX(), (int) event.getY()); + m_oldx = (int) event.getX(); + m_oldy = (int) event.getY(); + } + break; + } + } + + static public void sendTrackballEvent(MotionEvent event, int id) + { + switch (event.getAction()) { + case MotionEvent.ACTION_UP: + mouseUp(id, (int) event.getX(), (int) event.getY()); + break; + + case MotionEvent.ACTION_DOWN: + mouseDown(id, (int) event.getX(), (int) event.getY()); + m_oldx = (int) event.getX(); + m_oldy = (int) event.getY(); + break; + + case MotionEvent.ACTION_MOVE: + int dx = (int) (event.getX() - m_oldx); + int dy = (int) (event.getY() - m_oldy); + if (Math.abs(dx) > 5 || Math.abs(dy) > 5) { + mouseMove(id, (int) event.getX(), (int) event.getY()); + m_oldx = (int) event.getX(); + m_oldy = (int) event.getY(); + } + break; + } + } + + private static void updateSelection(final int selStart, + final int selEnd, + final int candidatesStart, + final int candidatesEnd) + { + runAction(new Runnable() { + @Override + public void run() { + m_activityDelegate.updateSelection(selStart, selEnd, candidatesStart, candidatesEnd); + } + }); + } + + private static void showSoftwareKeyboard(final int x, + final int y, + final int width, + final int height, + final int inputHints ) + { + runAction(new Runnable() { + @Override + public void run() { + m_activityDelegate.showSoftwareKeyboard(x, y, width, height, inputHints); + } + }); + } + + private static void resetSoftwareKeyboard() + { + runAction(new Runnable() { + @Override + public void run() { + m_activityDelegate.resetSoftwareKeyboard(); + } + }); + } + + private static void hideSoftwareKeyboard() + { + runAction(new Runnable() { + @Override + public void run() { + m_activityDelegate.hideSoftwareKeyboard(); + } + }); + } + + private static boolean isSoftwareKeyboardVisible() + { + Semaphore semaphore = new Semaphore(1); + Boolean ret = false; + class RunnableRes implements Runnable { + @SuppressWarnings("unused") + Boolean returnValue = null; + Semaphore semaphore = null; + RunnableRes(Boolean ret, Semaphore sem) { + semaphore = sem; + returnValue = ret; + } + @Override + public void run() { + returnValue = m_activityDelegate.isSoftwareKeyboardVisible(); + semaphore.release(); + } + } + + runAction(new RunnableRes(ret, semaphore)); + try { + semaphore.acquire(); + } catch (Exception e) { + e.printStackTrace(); + } + return ret; + } + + private static void setFullScreen(final boolean fullScreen) + { + runAction(new Runnable() { + @Override + public void run() { + m_activityDelegate.setFullScreen(fullScreen); + updateWindow(); + } + }); + } + + private static void registerClipboardManager() + { + final Semaphore semaphore = new Semaphore(1); + runAction(new Runnable() { + @Override + public void run() { + m_clipboardManager = (android.text.ClipboardManager) m_activity.getSystemService(Context.CLIPBOARD_SERVICE); + semaphore.release(); + } + }); + try { + semaphore.acquire(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + private static void setClipboardText(String text) + { + m_clipboardManager.setText(text); + } + + private static boolean hasClipboardText() + { + return m_clipboardManager.hasText(); + } + + private static String getClipboardText() + { + return m_clipboardManager.getText().toString(); + } + + private static void openContextMenu() + { + runAction(new Runnable() { + @Override + public void run() { + m_activityDelegate.openContextMenu(); + } + }); + } + + private static void closeContextMenu() + { + runAction(new Runnable() { + @Override + public void run() { + m_activityDelegate.closeContextMenu(); + } + }); + } + + private static void resetOptionsMenu() + { + runAction(new Runnable() { + @Override + public void run() { + m_activityDelegate.resetOptionsMenu(); + } + }); + } + + // screen methods + public static native void setDisplayMetrics(int screenWidthPixels, + int screenHeightPixels, + int desktopWidthPixels, + int desktopHeightPixels, + double XDpi, + double YDpi); + public static native void handleOrientationChanged(int newOrientation); + // screen methods + + // pointer methods + public static native void mouseDown(int winId, int x, int y); + public static native void mouseUp(int winId, int x, int y); + public static native void mouseMove(int winId, int x, int y); + public static native void touchBegin(int winId); + public static native void touchAdd(int winId, int pointerId, int action, boolean primary, int x, int y, float size, float pressure); + public static native void touchEnd(int winId, int action); + public static native void longPress(int winId, int x, int y); + // pointer methods + + // keyboard methods + public static native void keyDown(int key, int unicode, int modifier); + public static native void keyUp(int key, int unicode, int modifier); + // keyboard methods + + // surface methods + public static native void destroySurface(); + public static native void setSurface(Object surface); + public static native void lockSurface(); + public static native void unlockSurface(); + // surface methods + + // window methods + public static native void updateWindow(); + // window methods + + // menu methods + public static native boolean onPrepareOptionsMenu(Menu menu); + public static native boolean onOptionsItemSelected(int itemId, boolean checked); + public static native void onOptionsMenuClosed(Menu menu); + + public static native void onCreateContextMenu(ContextMenu menu); + public static native boolean onContextItemSelected(int itemId, boolean checked); + public static native void onContextMenuClosed(Menu menu); + // menu methods +} diff --git a/src/android/jar/src/org/qtproject/qt5/android/QtNativeLibrariesDir.java b/src/android/jar/src/org/qtproject/qt5/android/QtNativeLibrariesDir.java new file mode 100644 index 0000000000..219ea13f1a --- /dev/null +++ b/src/android/jar/src/org/qtproject/qt5/android/QtNativeLibrariesDir.java @@ -0,0 +1,61 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Copyright (C) 2012 BogDan Vatra +** Contact: http://www.qt-project.org/legal +** +** 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 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. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +package org.qtproject.qt5.android; + +import android.app.Activity; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager.NameNotFoundException; + +public class QtNativeLibrariesDir { + public static String nativeLibrariesDir(Activity activity) + { + String m_nativeLibraryDir = null; + try { + ApplicationInfo ai = activity.getPackageManager().getApplicationInfo(activity.getPackageName(), 0); + m_nativeLibraryDir = ai.nativeLibraryDir+"/"; + } catch (NameNotFoundException e) { + e.printStackTrace(); + } + return m_nativeLibraryDir; + } +} diff --git a/src/android/jar/src/org/qtproject/qt5/android/QtSurface.java b/src/android/jar/src/org/qtproject/qt5/android/QtSurface.java new file mode 100644 index 0000000000..77126ec1c8 --- /dev/null +++ b/src/android/jar/src/org/qtproject/qt5/android/QtSurface.java @@ -0,0 +1,206 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Copyright (C) 2012 BogDan Vatra +** Contact: http://www.qt-project.org/legal +** +** 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 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. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +package org.qtproject.qt5.android; + +import android.app.Activity; +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Rect; +import android.graphics.PixelFormat; +import android.util.DisplayMetrics; +import android.util.Log; +import android.view.GestureDetector; +import android.view.MotionEvent; +import android.view.SurfaceHolder; +import android.view.SurfaceView; + +public class QtSurface extends SurfaceView implements SurfaceHolder.Callback +{ + private Bitmap m_bitmap = null; + private boolean m_started = false; + private boolean m_usesGL = false; + private GestureDetector m_gestureDetector; + + public QtSurface(Context context, int id) + { + super(context); + setFocusable(true); + getHolder().addCallback(this); + getHolder().setType(SurfaceHolder.SURFACE_TYPE_GPU); + setId(id); + m_gestureDetector = + new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() { + public void onLongPress(MotionEvent event) { + if (!m_started) + return; + QtNative.longPress(getId(), (int) event.getX(), (int) event.getY()); + } + }); + m_gestureDetector.setIsLongpressEnabled(true); + } + + public void applicationStarted(boolean usesGL) + { + m_started = true; + m_usesGL = usesGL; + if (getWidth() < 1 || getHeight() < 1) + return; + if (m_usesGL) { + QtNative.setSurface(getHolder().getSurface()); + } else { + QtNative.lockSurface(); + QtNative.setSurface(null); + m_bitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.RGB_565); + QtNative.setSurface(m_bitmap); + QtNative.unlockSurface(); + } + } + + @Override + public void surfaceCreated(SurfaceHolder holder) + { + DisplayMetrics metrics = new DisplayMetrics(); + ((Activity) getContext()).getWindowManager().getDefaultDisplay().getMetrics(metrics); + QtNative.setApplicationDisplayMetrics(metrics.widthPixels, + metrics.heightPixels, getWidth(), getHeight(), metrics.xdpi, metrics.ydpi); + + if (m_usesGL) + holder.setFormat(PixelFormat.RGBA_8888); + +// if (!m_started) +// return; +// +// if (m_usesGL) +// QtApplication.setSurface(holder.getSurface()); +// else +// { +// QtApplication.lockSurface(); +// QtApplication.setSurface(null); +// m_bitmap=Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.RGB_565); +// QtApplication.setSurface(m_bitmap); +// QtApplication.unlockSurface(); +// } + } + + @Override + public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) + { + if (width<1 || height<1) + return; + + DisplayMetrics metrics = new DisplayMetrics(); + ((Activity) getContext()).getWindowManager().getDefaultDisplay().getMetrics(metrics); + QtNative.setApplicationDisplayMetrics(metrics.widthPixels, + metrics.heightPixels, + width, + height, + metrics.xdpi, + metrics.ydpi); + + if (!m_started) + return; + + if (m_usesGL) { + QtNative.setSurface(holder.getSurface()); + } else { + QtNative.lockSurface(); + QtNative.setSurface(null); + m_bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565); + QtNative.setSurface(m_bitmap); + QtNative.unlockSurface(); + QtNative.updateWindow(); + } + } + + @Override + public void surfaceDestroyed(SurfaceHolder holder) + { + if (m_usesGL) { + QtNative.destroySurface(); + } else { + if (!m_started) + return; + + QtNative.lockSurface(); + QtNative.setSurface(null); + QtNative.unlockSurface(); + } + } + + public void drawBitmap(Rect rect) + { + if (!m_started) + return; + QtNative.lockSurface(); + if (null != m_bitmap) { + try { + Canvas cv = getHolder().lockCanvas(rect); + cv.drawBitmap(m_bitmap, rect, rect, null); + getHolder().unlockCanvasAndPost(cv); + } catch (Exception e) { + Log.e(QtNative.QtTAG, "Can't create main activity", e); + } + } + QtNative.unlockSurface(); + } + + @Override + public boolean onTouchEvent(MotionEvent event) + { + if (!m_started) + return false; + QtNative.sendTouchEvent(event, getId()); + m_gestureDetector.onTouchEvent(event); + return true; + } + + @Override + public boolean onTrackballEvent(MotionEvent event) + { + if (!m_started) + return false; + QtNative.sendTrackballEvent(event, getId()); + return true; + } +} diff --git a/src/android/java/AndroidManifest.xml b/src/android/java/AndroidManifest.xml new file mode 100644 index 0000000000..7a77d6fd83 --- /dev/null +++ b/src/android/java/AndroidManifest.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/android/java/res/layout/splash.xml b/src/android/java/res/layout/splash.xml new file mode 100644 index 0000000000..6b0d492dd5 --- /dev/null +++ b/src/android/java/res/layout/splash.xml @@ -0,0 +1,13 @@ + + + + diff --git a/src/android/java/res/values-de/strings.xml b/src/android/java/res/values-de/strings.xml new file mode 100644 index 0000000000..320d9ec33f --- /dev/null +++ b/src/android/java/res/values-de/strings.xml @@ -0,0 +1,6 @@ + + + Ministro-Dienst wurde nicht gefunden.\nAnwendung kann nicht gestartet werden + Diese Anwendung benötigt den Ministro-Dienst. Möchten Sie ihn installieren? + In Ihrer Anwendung ist ein schwerwiegender Fehler aufgetreten, sie kann nicht fortgesetzt werden + diff --git a/src/android/java/res/values-el/strings.xml b/src/android/java/res/values-el/strings.xml new file mode 100644 index 0000000000..3cab212f2b --- /dev/null +++ b/src/android/java/res/values-el/strings.xml @@ -0,0 +1,6 @@ + + + Δεν ήταν δυνατή η εύρεση της υπηρεσίας Ministro. Δεν είναι δυνατή η εκκίνηση της εφαρμογής. + Η εφαρμογή απαιτεί την υπηρεσία Ministro. Να εγκατασταθεί η υπηρεσία? + Παρουσιάστηκε ένα κρίσιμο σφάλμα και η εφαρμογή δεν μπορεί να συνεχίσει. + diff --git a/src/android/java/res/values-es/strings.xml b/src/android/java/res/values-es/strings.xml new file mode 100644 index 0000000000..cf0b54d0b0 --- /dev/null +++ b/src/android/java/res/values-es/strings.xml @@ -0,0 +1,6 @@ + + + Servicio Ministro inesistente. Imposible ejecutar la aplicación. + Esta aplicación requiere el servicio Ministro. Instalarlo? + La aplicación ha causado un error grave y no es posible continuar. + diff --git a/src/android/java/res/values-et/strings.xml b/src/android/java/res/values-et/strings.xml new file mode 100644 index 0000000000..d55a3c1471 --- /dev/null +++ b/src/android/java/res/values-et/strings.xml @@ -0,0 +1,6 @@ + + + Ei suuda leida Ministro teenust.\nProgrammi ei saa käivitada. + See programm vajab Ministro teenust.\nKas soovite paigaldada? + Programmiga juhtus fataalne viga.\nKahjuks ei saa jätkata. + diff --git a/src/android/java/res/values-fa/strings.xml b/src/android/java/res/values-fa/strings.xml new file mode 100644 index 0000000000..a8d1b87444 --- /dev/null +++ b/src/android/java/res/values-fa/strings.xml @@ -0,0 +1,6 @@ + + + سرویس Ministro را پیدا نمی‌کند. برنامه نمی‌تواند آغاز شود. + این نرم‌افزار به سرویس Ministro احتیاج دارد. آیا دوست دارید آن را نصب کنید؟ + خطایی اساسی در برنامه‌تان رخ داد و اجرای برنامه نمی‌تواند ادامه یابد. + diff --git a/src/android/java/res/values-fr/strings.xml b/src/android/java/res/values-fr/strings.xml new file mode 100644 index 0000000000..efc0fb6e1e --- /dev/null +++ b/src/android/java/res/values-fr/strings.xml @@ -0,0 +1,6 @@ + + + Le service Ministro est introuvable.\nL\'application ne peut pas démarrer. + Cette application requiert le service Ministro. Voulez-vous l\'installer? + Votre application a rencontré une erreur fatale et ne peut pas continuer. + diff --git a/src/android/java/res/values-id/strings.xml b/src/android/java/res/values-id/strings.xml new file mode 100644 index 0000000000..aaa5bda0de --- /dev/null +++ b/src/android/java/res/values-id/strings.xml @@ -0,0 +1,6 @@ + + + Layanan Ministro tidak bisa ditemukan.\nAplikasi tidak bisa dimulai. + Aplikasi ini membutuhkan layanan Ministro. Apakah Anda ingin menginstalnya? + Aplikasi Anda mengalami kesalahan fatal dan tidak dapat melanjutkan. + diff --git a/src/android/java/res/values-it/strings.xml b/src/android/java/res/values-it/strings.xml new file mode 100644 index 0000000000..4773419c44 --- /dev/null +++ b/src/android/java/res/values-it/strings.xml @@ -0,0 +1,6 @@ + + + Servizio Ministro inesistente. Impossibile eseguire \nl\'applicazione. + Questa applicazione richiede il servizio Ministro.Installarlo? + L\'applicazione ha provocato un errore grave e non puo\' continuare. + diff --git a/src/android/java/res/values-ja/strings.xml b/src/android/java/res/values-ja/strings.xml new file mode 100644 index 0000000000..ba1cfda9ec --- /dev/null +++ b/src/android/java/res/values-ja/strings.xml @@ -0,0 +1,6 @@ + + + Ministroサービスが見つかりません。\nアプリケーションが起動できません。 + このアプリケーションにはMinistroサービスが必要です。 インストールしてもよろしいですか? + アプリケーションで致命的なエラーが発生したため続行できません。 + diff --git a/src/android/java/res/values-ms/strings.xml b/src/android/java/res/values-ms/strings.xml new file mode 100644 index 0000000000..6e3952eaec --- /dev/null +++ b/src/android/java/res/values-ms/strings.xml @@ -0,0 +1,6 @@ + + + Tidak jumpa servis Ministro.\nAplikasi tidak boleh dimulakan. + Aplikasi ini memerlukan servis Ministro. Adakah anda ingin pasang servis itu? + Aplikasi anda menemui ralat muat dan tidak boleh diteruskan. + diff --git a/src/android/java/res/values-nb/strings.xml b/src/android/java/res/values-nb/strings.xml new file mode 100644 index 0000000000..8a550e99a2 --- /dev/null +++ b/src/android/java/res/values-nb/strings.xml @@ -0,0 +1,6 @@ + + + Kan ikke finne tjenesten Ministro. Applikasjonen kan ikke starte. + Denne applikasjonen krever tjenesten Ministro. Vil du installere denne? + Applikasjonen fikk en kritisk feil og kan ikke fortsette + diff --git a/src/android/java/res/values-nl/strings.xml b/src/android/java/res/values-nl/strings.xml new file mode 100644 index 0000000000..8a45a724ff --- /dev/null +++ b/src/android/java/res/values-nl/strings.xml @@ -0,0 +1,6 @@ + + + De Ministro service is niet gevonden.\nDe applicatie kan niet starten. + Deze applicatie maakt gebruik van de Ministro service. Wilt u deze installeren? + Er is een fatale fout in de applicatie opgetreden. De applicatie kan niet verder gaan. + diff --git a/src/android/java/res/values-pl/strings.xml b/src/android/java/res/values-pl/strings.xml new file mode 100644 index 0000000000..9fefc92dcd --- /dev/null +++ b/src/android/java/res/values-pl/strings.xml @@ -0,0 +1,6 @@ + + + Usługa Ministro nie została znaleziona.\nAplikacja nie może zostać uruchomiona. + Aplikacja wymaga usługi Ministro. Czy chcesz ją zainstalować? + Wystąpił błąd krytyczny. Aplikacja zostanie zamknięta. + diff --git a/src/android/java/res/values-pt-rBR/strings.xml b/src/android/java/res/values-pt-rBR/strings.xml new file mode 100644 index 0000000000..67ac3f9f98 --- /dev/null +++ b/src/android/java/res/values-pt-rBR/strings.xml @@ -0,0 +1,6 @@ + + + Não foi possível encontrar o serviço Ministro.\nA aplicação não pode iniciar. + Essa aplicação requer o serviço Ministro. Gostaria de instalá-lo? + Sua aplicação encontrou um erro fatal e não pode continuar. + diff --git a/src/android/java/res/values-ro/strings.xml b/src/android/java/res/values-ro/strings.xml new file mode 100644 index 0000000000..f88a442b35 --- /dev/null +++ b/src/android/java/res/values-ro/strings.xml @@ -0,0 +1,6 @@ + + + Serviciul Ministro nu poate fi găsit.\nAplicaţia nu poate porni. + Această aplicaţie necesită serviciul Ministro.\nDoriţi să-l instalaţi? + Aplicaţia dumneavoastră a întâmpinat o eroare fatală şi nu poate continua. + diff --git a/src/android/java/res/values-rs/strings.xml b/src/android/java/res/values-rs/strings.xml new file mode 100644 index 0000000000..3194ce9022 --- /dev/null +++ b/src/android/java/res/values-rs/strings.xml @@ -0,0 +1,6 @@ + + + Ministro servise nije pronađen. Aplikacija ne može biti pokrenuta. + Ova aplikacija zahteva Ministro servis. Želite li da ga instalirate? + Vaša aplikacija je naišla na fatalnu grešku i ne može nastaviti sa radom. + diff --git a/src/android/java/res/values-ru/strings.xml b/src/android/java/res/values-ru/strings.xml new file mode 100644 index 0000000000..d3cee80f9d --- /dev/null +++ b/src/android/java/res/values-ru/strings.xml @@ -0,0 +1,6 @@ + + + Сервис Ministro не найден.\nПриложение нельзя запустить. + Этому приложению необходим сервис Ministro. Вы хотите его установить? + Ваше приложение столкнулось с фатальной ошибкой и не может более работать. + diff --git a/src/android/java/res/values-zh-rCN/strings.xml b/src/android/java/res/values-zh-rCN/strings.xml new file mode 100644 index 0000000000..2eb1269880 --- /dev/null +++ b/src/android/java/res/values-zh-rCN/strings.xml @@ -0,0 +1,6 @@ + + + 无法找到Ministro服务。\n应用程序无法启动。 + 此应用程序需要Ministro服务。您想安装它吗? + 您的应用程序遇到一个致命错误导致它无法继续。 + diff --git a/src/android/java/res/values-zh-rTW/strings.xml b/src/android/java/res/values-zh-rTW/strings.xml new file mode 100644 index 0000000000..f6e68efa52 --- /dev/null +++ b/src/android/java/res/values-zh-rTW/strings.xml @@ -0,0 +1,6 @@ + + + 無法找到Ministro服務。\n應用程序無法啟動。 + 此應用程序需要Ministro服務。您想安裝它嗎? + 您的應用程序遇到一個致命錯誤導致它無法繼續。 + diff --git a/src/android/java/res/values/libs.xml b/src/android/java/res/values/libs.xml new file mode 100644 index 0000000000..92ce09b7c0 --- /dev/null +++ b/src/android/java/res/values/libs.xml @@ -0,0 +1,8 @@ + + + + QtCore + QtGui + + + diff --git a/src/android/java/res/values/strings.xml b/src/android/java/res/values/strings.xml new file mode 100644 index 0000000000..bd6928fe59 --- /dev/null +++ b/src/android/java/res/values/strings.xml @@ -0,0 +1,7 @@ + + + + Can\'t find Ministro service.\nThe application can\'t start. + This application requires Ministro service. Would you like to install it? + Your application encountered a fatal error and cannot continue. + diff --git a/src/android/java/src/org/kde/necessitas/ministro/IMinistro.aidl b/src/android/java/src/org/kde/necessitas/ministro/IMinistro.aidl new file mode 100644 index 0000000000..1c33a77fdb --- /dev/null +++ b/src/android/java/src/org/kde/necessitas/ministro/IMinistro.aidl @@ -0,0 +1,49 @@ +/* + Copyright (c) 2012, BogDan Vatra + Contact: http://www.qt-project.org/legal + + 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.kde.necessitas.ministro; + +import org.kde.necessitas.ministro.IMinistroCallback; + +interface IMinistro +{ +/** +* Check/download required libs to run the application +* +* param callback - interface used by Minsitro service to notify the client when the loader is ready +* param parameters +* parameters fields: +* * Key Name Key type Explanations +* "required.modules" StringArray Required modules by your application +* "application.title" String Application name, used to show more informations to user +* "qt.provider" String Qt libs provider, currently only "necessitas" is supported. +* "minimum.ministro.api" Integer Minimum Ministro API level, used to check if Ministro service compatible with your application. Current API Level is 1 ! +* "minimum.qt.version" Integer Minimim Qt version (e.g. 0x040800, which means Qt 4.8.0, check http://doc.trolltech.com/4.8/qtglobal.html#QT_VERSION)! +* "3rd.party.repositories" StringArray 3rd party repositories, which should be downloaded by ministro, needs minimum.ministro.api >= 2 +* Check http://community.kde.org/Necessitas/Ministro for more details. +*/ + void requestLoader(in IMinistroCallback callback, in Bundle parameters); +} diff --git a/src/android/java/src/org/kde/necessitas/ministro/IMinistroCallback.aidl b/src/android/java/src/org/kde/necessitas/ministro/IMinistroCallback.aidl new file mode 100644 index 0000000000..eec270aec1 --- /dev/null +++ b/src/android/java/src/org/kde/necessitas/ministro/IMinistroCallback.aidl @@ -0,0 +1,53 @@ +/* + Copyright (c) 2012, BogDan Vatra + Contact: http://www.qt-project.org/legal + + 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.kde.necessitas.ministro; + +oneway interface IMinistroCallback { +/** +* This method is called by the Ministro service back into the application which +* implements this interface. +* +* param in - loaderParams +* loaderParams fields: +* * Key Name Key type Explanations +* * "error.code" Integer See below +* * "error.message" String Missing if no error, otherwise will contain the error message translated into phone language where available. +* * "dex.path" String The list of jar/apk files containing classes and resources, needed to be passed to application DexClassLoader +* * "lib.path" String The list of directories containing native libraries; may be missing, needed to be passed to application DexClassLoader +* * "loader.class.name" String Loader class name. +* +* "error.code" field possible errors: +* - 0 no error. +* - 1 incompatible Ministro version. Ministro needs to be upgraded. +* - 2 not all modules could be satisfy. +* - 3 invalid parameters +* +* This parameter will contain additional fields which are used by the loader to start your application, so it must be passed to loader. +*/ + + void loaderReady(in Bundle loaderParams); +} diff --git a/src/android/java/src/org/qtproject/qt5/android/bindings/QtActivity.java b/src/android/java/src/org/qtproject/qt5/android/bindings/QtActivity.java new file mode 100644 index 0000000000..c3d82bca8d --- /dev/null +++ b/src/android/java/src/org/qtproject/qt5/android/bindings/QtActivity.java @@ -0,0 +1,1278 @@ +/* + Copyright (c) 2012, BogDan Vatra + Contact: http://www.qt-project.org/legal + + 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 java.io.File; +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.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.res.Configuration; +import android.content.res.Resources.Theme; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.net.Uri; +import android.os.Bundle; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.AttributeSet; +import android.util.Log; +import android.view.ContextMenu; +import android.view.ContextMenu.ContextMenuInfo; +import android.view.KeyEvent; +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 = 2; // 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 String QT_PROVIDER = "necessitas"; + 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 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"; + + /// 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 QT_PROVIDER_KEY = "qt.provider"; + 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 REPOSITORIES="3rd.party.repositories"; // needs MINISTRO_API_LEVEL >=2 !!! + // Use this key to specify any 3rd party repositories urls + // Ministro will download these repositories into thier + // own folders, check http://community.kde.org/Necessitas/Ministro + // for more details. + + private static final 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" + + private static final String ENVIRONMENT_VARIABLES = "QT_USE_ANDROID_NATIVE_STYLE=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 - 0 if you don't want to use android style plugin, it will save a few ms at startup. + + private static final int INCOMPATIBLE_MINISTRO_VERSION = 1; // Incompatible Ministro version. Ministro needs to be upgraded. + 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_qtLibs = null; // required qt libs + + // 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 libs = new ArrayList(); + 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 perpareAppMethod = qtLoader.getClass().getMethod("loadApplication", + Activity.class, + ClassLoader.class, + Bundle.class); + if (!(Boolean)perpareAppMethod.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 != null && 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.putString(QT_PROVIDER_KEY, QT_PROVIDER); + parameters.putInt(MINIMUM_QT_VERSION_KEY, QT_VERSION); + parameters.putString(ENVIRONMENT_VARIABLES_KEY, ENVIRONMENT_VARIABLES); + if (null!=APPLICATION_PARAMETERS) + parameters.putString(APPLICATION_PARAMETERS_KEY, APPLICATION_PARAMETERS); + // parameters.putStringArray(REPOSITORIES, null); + 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 != null && 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(); + } + + private void startApp(final boolean firstStart) + { + try { + ActivityInfo ai=getPackageManager().getActivityInfo(getComponentName(), PackageManager.GET_META_DATA); + if (ai.metaData.containsKey("android.app.qt_libs_resource_id")) { + int resourceId = ai.metaData.getInt("android.app.qt_libs_resource_id"); + m_qtLibs = getResources().getStringArray(resourceId); + } + + if (getIntent().getExtras()!= null + && getIntent().getExtras().containsKey("use_local_qt_libs") + && getIntent().getExtras().getString("use_local_qt_libs").equals("true")) { + ArrayList libraryList= new ArrayList(); + + String localPrefix="/data/local/tmp/qt/"; + if (getIntent().getExtras().containsKey("libs_prefix")) + localPrefix=getIntent().getExtras().getString("libs_prefix"); + + if (m_qtLibs != null) { + for (int i=0;i 0) + libraryList.add(localPrefix + lib); + } + } + + String dexPaths = new String(); + String pathSeparator = System.getProperty("path.separator", ":"); + if (getIntent().getExtras().containsKey("load_local_jars")) { + String[] jarFiles = getIntent().getExtras().getString("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, getIntent().getExtras().containsKey("loader_class_name") + ? getIntent().getExtras().getString("loader_class_name") + : "org.qtproject.qt5.android.QtActivityDelegate"); + if (getIntent().getExtras().containsKey("static_init_classes")) { + loaderParams.putStringArray(STATIC_INIT_CLASSES_KEY, + getIntent().getExtras().getString("static_init_classes").split(":")); + } + loaderParams.putStringArrayList(NATIVE_LIBRARIES_KEY, libraryList); + loaderParams.putString(ENVIRONMENT_VARIABLES_KEY, ENVIRONMENT_VARIABLES + + "QT_QPA_EGLFS_HIDECURSOR=1" + + "\tQML2_IMPORT_PATH=" + localPrefix + "/qml" + + "\tQML_IMPORT_PATH=" + localPrefix + "/imports" + + "\tQT_PLUGIN_PATH=" + localPrefix + "/plugins"); + 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 != null && 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 //////////////////////////////////// + /////////////// PLEASE DO NOT CHANGE THE FOLLOWING CODE ////////////////////////// + ////////////////////////////////////////////////////////////////////////////////// + + @Override + public boolean dispatchKeyEvent(KeyEvent event) + { + if (QtApplication.m_delegateObject != null && QtApplication.dispatchKeyEvent != null) + return (Boolean) QtApplication.invokeDelegateMethod(QtApplication.dispatchKeyEvent, event); + else + return super.dispatchKeyEvent(event); + } + public boolean super_dispatchKeyEvent(KeyEvent event) + { + return super.dispatchKeyEvent(event); + } + //--------------------------------------------------------------------------- + + @Override + public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) + { + if (QtApplication.m_delegateObject != null && QtApplication.dispatchPopulateAccessibilityEvent != null) + return (Boolean) QtApplication.invokeDelegateMethod(QtApplication.dispatchPopulateAccessibilityEvent, event); + else + return super.dispatchPopulateAccessibilityEvent(event); + } + public boolean super_dispatchPopulateAccessibilityEvent(AccessibilityEvent event) + { + return super_dispatchPopulateAccessibilityEvent(event); + } + //--------------------------------------------------------------------------- + + @Override + public boolean dispatchTouchEvent(MotionEvent ev) + { + if (QtApplication.m_delegateObject != null && QtApplication.dispatchTouchEvent != null) + return (Boolean) QtApplication.invokeDelegateMethod(QtApplication.dispatchTouchEvent, ev); + else + return super.dispatchTouchEvent(ev); + } + public boolean super_dispatchTouchEvent(MotionEvent event) + { + return super.dispatchTouchEvent(event); + } + //--------------------------------------------------------------------------- + + @Override + public boolean dispatchTrackballEvent(MotionEvent ev) + { + if (QtApplication.m_delegateObject != null && QtApplication.dispatchTrackballEvent != null) + return (Boolean) QtApplication.invokeDelegateMethod(QtApplication.dispatchTrackballEvent, ev); + else + return super.dispatchTrackballEvent(ev); + } + public boolean super_dispatchTrackballEvent(MotionEvent event) + { + return super.dispatchTrackballEvent(event); + } + //--------------------------------------------------------------------------- + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) + { + + if (QtApplication.m_delegateObject != null && QtApplication.onActivityResult != null) { + QtApplication.invokeDelegateMethod(QtApplication.onActivityResult, requestCode, resultCode, data); + return; + } + if (requestCode == MINISTRO_INSTALL_REQUEST_CODE) + startApp(false); + super.onActivityResult(requestCode, resultCode, data); + } + public void super_onActivityResult(int requestCode, int resultCode, Intent data) + { + super.onActivityResult(requestCode, resultCode, data); + } + //--------------------------------------------------------------------------- + + @Override + protected void onApplyThemeResource(Theme theme, int resid, boolean first) + { + if (!QtApplication.invokeDelegate(theme, resid, first).invoked) + super.onApplyThemeResource(theme, resid, first); + } + public void super_onApplyThemeResource(Theme theme, int resid, boolean first) + { + super.onApplyThemeResource(theme, resid, first); + } + //--------------------------------------------------------------------------- + + + @Override + protected void onChildTitleChanged(Activity childActivity, CharSequence title) + { + if (!QtApplication.invokeDelegate(childActivity, title).invoked) + super.onChildTitleChanged(childActivity, title); + } + public void super_onChildTitleChanged(Activity childActivity, CharSequence title) + { + super.onChildTitleChanged(childActivity, title); + } + //--------------------------------------------------------------------------- + + @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 onContentChanged() + { + if (!QtApplication.invokeDelegate().invoked) + super.onContentChanged(); + } + public void super_onContentChanged() + { + super.onContentChanged(); + } + //--------------------------------------------------------------------------- + + @Override + public boolean onContextItemSelected(MenuItem item) + { + QtApplication.InvokeResult res = QtApplication.invokeDelegate(item); + if (res.invoked) + return (Boolean)res.methodReturns; + else + return super.onContextItemSelected(item); + } + public boolean super_onContextItemSelected(MenuItem item) + { + return super.onContextItemSelected(item); + } + //--------------------------------------------------------------------------- + + @Override + public void onContextMenuClosed(Menu menu) + { + if (!QtApplication.invokeDelegate(menu).invoked) + super.onContextMenuClosed(menu); + } + public void super_onContextMenuClosed(Menu menu) + { + super.onContextMenuClosed(menu); + } + //--------------------------------------------------------------------------- + + @Override + public void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + if (QtApplication.m_delegateObject != null && QtApplication.onCreate != null) { + QtApplication.invokeDelegateMethod(QtApplication.onCreate, savedInstanceState); + return; + } + + requestWindowFeature(Window.FEATURE_NO_TITLE); + try { + m_activityInfo = getPackageManager().getActivityInfo(getComponentName(), PackageManager.GET_META_DATA); + } catch (NameNotFoundException e) { + e.printStackTrace(); + finish(); + return; + } + + if (null == getLastNonConfigurationInstance()) { + // if splash screen is defined, then show it + if (m_activityInfo.metaData.containsKey("android.app.splash_screen") ) + setContentView(m_activityInfo.metaData.getInt("android.app.splash_screen")); + startApp(true); + } + } + //--------------------------------------------------------------------------- + + @Override + public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) + { + if (!QtApplication.invokeDelegate(menu, v, menuInfo).invoked) + super.onCreateContextMenu(menu, v, menuInfo); + } + public void super_onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) + { + super.onCreateContextMenu(menu, v, menuInfo); + } + //--------------------------------------------------------------------------- + + @Override + public CharSequence onCreateDescription() + { + QtApplication.InvokeResult res = QtApplication.invokeDelegate(); + if (res.invoked) + return (CharSequence)res.methodReturns; + else + return super.onCreateDescription(); + } + public CharSequence super_onCreateDescription() + { + return super.onCreateDescription(); + } + //--------------------------------------------------------------------------- + + @Override + protected Dialog onCreateDialog(int id) + { + QtApplication.InvokeResult res = QtApplication.invokeDelegate(id); + if (res.invoked) + return (Dialog)res.methodReturns; + else + return super.onCreateDialog(id); + } + public Dialog super_onCreateDialog(int id) + { + return super.onCreateDialog(id); + } + //--------------------------------------------------------------------------- + + @Override + public boolean onCreateOptionsMenu(Menu menu) + { + QtApplication.InvokeResult res = QtApplication.invokeDelegate(menu); + if (res.invoked) + return (Boolean)res.methodReturns; + else + return super.onCreateOptionsMenu(menu); + } + public boolean super_onCreateOptionsMenu(Menu menu) + { + return super.onCreateOptionsMenu(menu); + } + //--------------------------------------------------------------------------- + + @Override + public boolean onCreatePanelMenu(int featureId, Menu menu) + { + QtApplication.InvokeResult res = QtApplication.invokeDelegate(featureId, menu); + if (res.invoked) + return (Boolean)res.methodReturns; + else + return super.onCreatePanelMenu(featureId, menu); + } + public boolean super_onCreatePanelMenu(int featureId, Menu menu) + { + return super.onCreatePanelMenu(featureId, menu); + } + //--------------------------------------------------------------------------- + + + @Override + public View onCreatePanelView(int featureId) + { + QtApplication.InvokeResult res = QtApplication.invokeDelegate(featureId); + if (res.invoked) + return (View)res.methodReturns; + else + return super.onCreatePanelView(featureId); + } + public View super_onCreatePanelView(int featureId) + { + return super.onCreatePanelView(featureId); + } + //--------------------------------------------------------------------------- + + @Override + public boolean onCreateThumbnail(Bitmap outBitmap, Canvas canvas) + { + QtApplication.InvokeResult res = QtApplication.invokeDelegate(outBitmap, canvas); + if (res.invoked) + return (Boolean)res.methodReturns; + else + return super.onCreateThumbnail(outBitmap, canvas); + } + public boolean super_onCreateThumbnail(Bitmap outBitmap, Canvas canvas) + { + return super.onCreateThumbnail(outBitmap, canvas); + } + //--------------------------------------------------------------------------- + + @Override + public View onCreateView(String name, Context context, AttributeSet attrs) + { + QtApplication.InvokeResult res = QtApplication.invokeDelegate(name, context, attrs); + if (res.invoked) + return (View)res.methodReturns; + else + return super.onCreateView(name, context, attrs); + } + public View super_onCreateView(String name, Context context, AttributeSet attrs) + { + return super.onCreateView(name, context, attrs); + } + //--------------------------------------------------------------------------- + + @Override + protected void onDestroy() + { + super.onDestroy(); + QtApplication.invokeDelegate(); + } + //--------------------------------------------------------------------------- + + + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) + { + if (QtApplication.m_delegateObject != null && QtApplication.onKeyDown != null) + return (Boolean) QtApplication.invokeDelegateMethod(QtApplication.onKeyDown, keyCode, event); + else + return super.onKeyDown(keyCode, event); + } + public boolean super_onKeyDown(int keyCode, KeyEvent event) + { + return super.onKeyDown(keyCode, event); + } + //--------------------------------------------------------------------------- + + + @Override + public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) + { + if (QtApplication.m_delegateObject != null && QtApplication.onKeyMultiple != null) + return (Boolean) QtApplication.invokeDelegateMethod(QtApplication.onKeyMultiple, keyCode, repeatCount, event); + else + return super.onKeyMultiple(keyCode, repeatCount, event); + } + public boolean super_onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) + { + return super.onKeyMultiple(keyCode, repeatCount, event); + } + //--------------------------------------------------------------------------- + + @Override + public boolean onKeyUp(int keyCode, KeyEvent event) + { + if (QtApplication.m_delegateObject != null && QtApplication.onKeyDown != null) + return (Boolean) QtApplication.invokeDelegateMethod(QtApplication.onKeyUp, keyCode, event); + else + return super.onKeyUp(keyCode, event); + } + public boolean super_onKeyUp(int keyCode, KeyEvent event) + { + return super.onKeyUp(keyCode, event); + } + //--------------------------------------------------------------------------- + + @Override + public void onLowMemory() + { + if (!QtApplication.invokeDelegate().invoked) + super.onLowMemory(); + } + //--------------------------------------------------------------------------- + + @Override + public boolean onMenuItemSelected(int featureId, MenuItem item) + { + QtApplication.InvokeResult res = QtApplication.invokeDelegate(featureId, item); + if (res.invoked) + return (Boolean)res.methodReturns; + else + return super.onMenuItemSelected(featureId, item); + } + public boolean super_onMenuItemSelected(int featureId, MenuItem item) + { + return super.onMenuItemSelected(featureId, item); + } + //--------------------------------------------------------------------------- + + @Override + public boolean onMenuOpened(int featureId, Menu menu) + { + QtApplication.InvokeResult res = QtApplication.invokeDelegate(featureId, menu); + if (res.invoked) + return (Boolean)res.methodReturns; + else + return super.onMenuOpened(featureId, menu); + } + public boolean super_onMenuOpened(int featureId, Menu menu) + { + return super.onMenuOpened(featureId, menu); + } + //--------------------------------------------------------------------------- + + @Override + protected void onNewIntent(Intent intent) + { + if (!QtApplication.invokeDelegate(intent).invoked) + super.onNewIntent(intent); + } + public void super_onNewIntent(Intent intent) + { + super.onNewIntent(intent); + } + //--------------------------------------------------------------------------- + + @Override + public boolean onOptionsItemSelected(MenuItem item) + { + QtApplication.InvokeResult res = QtApplication.invokeDelegate(item); + if (res.invoked) + return (Boolean)res.methodReturns; + else + return super.onOptionsItemSelected(item); + } + public boolean super_onOptionsItemSelected(MenuItem item) + { + return super.onOptionsItemSelected(item); + } + //--------------------------------------------------------------------------- + + @Override + public void onOptionsMenuClosed(Menu menu) + { + if (!QtApplication.invokeDelegate(menu).invoked) + super.onOptionsMenuClosed(menu); + } + public void super_onOptionsMenuClosed(Menu menu) + { + super.onOptionsMenuClosed(menu); + } + //--------------------------------------------------------------------------- + + @Override + public void onPanelClosed(int featureId, Menu menu) + { + if (!QtApplication.invokeDelegate(featureId, menu).invoked) + super.onPanelClosed(featureId, menu); + } + public void super_onPanelClosed(int featureId, Menu menu) + { + super.onPanelClosed(featureId, menu); + } + //--------------------------------------------------------------------------- + + @Override + protected void onPause() + { + super.onPause(); + QtApplication.invokeDelegate(); + } + //--------------------------------------------------------------------------- + + @Override + protected void onPostCreate(Bundle savedInstanceState) + { + super.onPostCreate(savedInstanceState); + QtApplication.invokeDelegate(savedInstanceState); + } + //--------------------------------------------------------------------------- + + @Override + protected void onPostResume() + { + super.onPostResume(); + QtApplication.invokeDelegate(); + } + //--------------------------------------------------------------------------- + + @Override + protected void onPrepareDialog(int id, Dialog dialog) + { + if (!QtApplication.invokeDelegate(id, dialog).invoked) + super.onPrepareDialog(id, dialog); + } + public void super_onPrepareDialog(int id, Dialog dialog) + { + super.onPrepareDialog(id, dialog); + } + //--------------------------------------------------------------------------- + + @Override + public boolean onPrepareOptionsMenu(Menu menu) + { + QtApplication.InvokeResult res = QtApplication.invokeDelegate(menu); + if (res.invoked) + return (Boolean)res.methodReturns; + else + return super.onPrepareOptionsMenu(menu); + } + public boolean super_onPrepareOptionsMenu(Menu menu) + { + return super.onPrepareOptionsMenu(menu); + } + //--------------------------------------------------------------------------- + + @Override + public boolean onPreparePanel(int featureId, View view, Menu menu) + { + QtApplication.InvokeResult res = QtApplication.invokeDelegate(featureId, view, menu); + if (res.invoked) + return (Boolean)res.methodReturns; + else + return super.onPreparePanel(featureId, view, menu); + } + public boolean super_onPreparePanel(int featureId, View view, Menu menu) + { + return super.onPreparePanel(featureId, view, menu); + } + //--------------------------------------------------------------------------- + + @Override + protected void onRestart() + { + super.onRestart(); + QtApplication.invokeDelegate(); + } + //--------------------------------------------------------------------------- + + @Override + protected void onRestoreInstanceState(Bundle savedInstanceState) + { + if (!QtApplication.invokeDelegate(savedInstanceState).invoked) + super.onRestoreInstanceState(savedInstanceState); + } + public void super_onRestoreInstanceState(Bundle savedInstanceState) + { + super.onRestoreInstanceState(savedInstanceState); + } + //--------------------------------------------------------------------------- + + @Override + protected void onResume() + { + super.onResume(); + QtApplication.invokeDelegate(); + } + //--------------------------------------------------------------------------- + + @Override + public Object onRetainNonConfigurationInstance() + { + QtApplication.InvokeResult res = QtApplication.invokeDelegate(); + if (res.invoked) + return res.methodReturns; + else + return super.onRetainNonConfigurationInstance(); + } + public Object super_onRetainNonConfigurationInstance() + { + return super.onRetainNonConfigurationInstance(); + } + //--------------------------------------------------------------------------- + + @Override + protected void onSaveInstanceState(Bundle outState) + { + if (!QtApplication.invokeDelegate(outState).invoked) + super.onSaveInstanceState(outState); + } + public void super_onSaveInstanceState(Bundle outState) + { + super.onSaveInstanceState(outState); + + } + //--------------------------------------------------------------------------- + + @Override + public boolean onSearchRequested() + { + QtApplication.InvokeResult res = QtApplication.invokeDelegate(); + if (res.invoked) + return (Boolean)res.methodReturns; + else + return super.onSearchRequested(); + } + public boolean super_onSearchRequested() + { + return super.onSearchRequested(); + } + //--------------------------------------------------------------------------- + + @Override + protected void onStart() + { + super.onStart(); + QtApplication.invokeDelegate(); + } + //--------------------------------------------------------------------------- + + @Override + protected void onStop() + { + super.onStop(); + QtApplication.invokeDelegate(); + } + //--------------------------------------------------------------------------- + + @Override + protected void onTitleChanged(CharSequence title, int color) + { + if (!QtApplication.invokeDelegate(title, color).invoked) + super.onTitleChanged(title, color); + } + public void super_onTitleChanged(CharSequence title, int color) + { + super.onTitleChanged(title, color); + } + //--------------------------------------------------------------------------- + + @Override + public boolean onTouchEvent(MotionEvent event) + { + if (QtApplication.m_delegateObject != null && QtApplication.onTouchEvent != null) + return (Boolean) QtApplication.invokeDelegateMethod(QtApplication.onTouchEvent, event); + else + return super.onTouchEvent(event); + } + public boolean super_onTouchEvent(MotionEvent event) + { + return super.onTouchEvent(event); + } + //--------------------------------------------------------------------------- + + @Override + public boolean onTrackballEvent(MotionEvent event) + { + if (QtApplication.m_delegateObject != null && QtApplication.onTrackballEvent != null) + return (Boolean) QtApplication.invokeDelegateMethod(QtApplication.onTrackballEvent, event); + else + return super.onTrackballEvent(event); + } + public boolean super_onTrackballEvent(MotionEvent event) + { + return super.onTrackballEvent(event); + } + //--------------------------------------------------------------------------- + + @Override + public void onUserInteraction() + { + if (!QtApplication.invokeDelegate().invoked) + super.onUserInteraction(); + } + public void super_onUserInteraction() + { + super.onUserInteraction(); + } + //--------------------------------------------------------------------------- + + @Override + protected void onUserLeaveHint() + { + if (!QtApplication.invokeDelegate().invoked) + super.onUserLeaveHint(); + } + public void super_onUserLeaveHint() + { + super.onUserLeaveHint(); + } + //--------------------------------------------------------------------------- + + @Override + public void onWindowAttributesChanged(LayoutParams params) + { + if (!QtApplication.invokeDelegate(params).invoked) + super.onWindowAttributesChanged(params); + } + public void super_onWindowAttributesChanged(LayoutParams params) + { + super.onWindowAttributesChanged(params); + } + //--------------------------------------------------------------------------- + + @Override + public void onWindowFocusChanged(boolean hasFocus) + { + if (!QtApplication.invokeDelegate(hasFocus).invoked) + super.onWindowFocusChanged(hasFocus); + } + public void super_onWindowFocusChanged(boolean hasFocus) + { + super.onWindowFocusChanged(hasFocus); + } + //--------------------------------------------------------------------------- + + //////////////// Activity API 5 ///////////// +//@ANDROID-5 + @Override + public void onAttachedToWindow() + { + if (!QtApplication.invokeDelegate().invoked) + super.onAttachedToWindow(); + } + public void super_onAttachedToWindow() + { + super.onAttachedToWindow(); + } + //--------------------------------------------------------------------------- + + @Override + public void onBackPressed() + { + if (!QtApplication.invokeDelegate().invoked) + super.onBackPressed(); + } + public void super_onBackPressed() + { + super.onBackPressed(); + } + //--------------------------------------------------------------------------- + + @Override + public void onDetachedFromWindow() + { + if (!QtApplication.invokeDelegate().invoked) + super.onDetachedFromWindow(); + } + public void super_onDetachedFromWindow() + { + super.onDetachedFromWindow(); + } + //--------------------------------------------------------------------------- + + @Override + public boolean onKeyLongPress(int keyCode, KeyEvent event) + { + if (QtApplication.m_delegateObject != null && QtApplication.onKeyLongPress != null) + return (Boolean) QtApplication.invokeDelegateMethod(QtApplication.onKeyLongPress, keyCode, event); + else + return super.onKeyLongPress(keyCode, event); + } + public boolean super_onKeyLongPress(int keyCode, KeyEvent event) + { + return super.onKeyLongPress(keyCode, event); + } + //--------------------------------------------------------------------------- +//@ANDROID-5 + +//////////////// Activity API 8 ///////////// +//@ANDROID-8 +@Override + protected Dialog onCreateDialog(int id, Bundle args) + { + QtApplication.InvokeResult res = QtApplication.invokeDelegate(id, args); + if (res.invoked) + return (Dialog)res.methodReturns; + else + return super.onCreateDialog(id, args); + } + public Dialog super_onCreateDialog(int id, Bundle args) + { + return super.onCreateDialog(id, args); + } + //--------------------------------------------------------------------------- + + @Override + protected void onPrepareDialog(int id, Dialog dialog, Bundle args) + { + if (!QtApplication.invokeDelegate(id, dialog, args).invoked) + super.onPrepareDialog(id, dialog, args); + } + public void super_onPrepareDialog(int id, Dialog dialog, Bundle args) + { + super.onPrepareDialog(id, dialog, args); + } + //--------------------------------------------------------------------------- +//@ANDROID-8 + //////////////// Activity API 11 ///////////// + +//@ANDROID-11 + @Override + public boolean dispatchKeyShortcutEvent(KeyEvent event) + { + if (QtApplication.m_delegateObject != null && QtApplication.dispatchKeyShortcutEvent != null) + return (Boolean) QtApplication.invokeDelegateMethod(QtApplication.dispatchKeyShortcutEvent, event); + else + return super.dispatchKeyShortcutEvent(event); + } + public boolean super_dispatchKeyShortcutEvent(KeyEvent event) + { + return super.dispatchKeyShortcutEvent(event); + } + //--------------------------------------------------------------------------- + + @Override + public void onActionModeFinished(ActionMode mode) + { + if (!QtApplication.invokeDelegate(mode).invoked) + super.onActionModeFinished(mode); + } + public void super_onActionModeFinished(ActionMode mode) + { + super.onActionModeFinished(mode); + } + //--------------------------------------------------------------------------- + + @Override + public void onActionModeStarted(ActionMode mode) + { + if (!QtApplication.invokeDelegate(mode).invoked) + super.onActionModeStarted(mode); + } + public void super_onActionModeStarted(ActionMode mode) + { + super.onActionModeStarted(mode); + } + //--------------------------------------------------------------------------- + + @Override + public void onAttachFragment(Fragment fragment) + { + if (!QtApplication.invokeDelegate(fragment).invoked) + super.onAttachFragment(fragment); + } + public void super_onAttachFragment(Fragment fragment) + { + super.onAttachFragment(fragment); + } + //--------------------------------------------------------------------------- + + @Override + public View onCreateView(View parent, String name, Context context, AttributeSet attrs) + { + QtApplication.InvokeResult res = QtApplication.invokeDelegate(parent, name, context, attrs); + if (res.invoked) + return (View)res.methodReturns; + else + return super.onCreateView(parent, name, context, attrs); + } + public View super_onCreateView(View parent, String name, Context context, + AttributeSet attrs) { + return super.onCreateView(parent, name, context, attrs); + } + //--------------------------------------------------------------------------- + + @Override + public boolean onKeyShortcut(int keyCode, KeyEvent event) + { + if (QtApplication.m_delegateObject != null && QtApplication.onKeyShortcut != null) + return (Boolean) QtApplication.invokeDelegateMethod(QtApplication.onKeyShortcut, keyCode,event); + else + return super.onKeyShortcut(keyCode, event); + } + public boolean super_onKeyShortcut(int keyCode, KeyEvent event) + { + return super.onKeyShortcut(keyCode, event); + } + //--------------------------------------------------------------------------- + + @Override + public ActionMode onWindowStartingActionMode(Callback callback) + { + QtApplication.InvokeResult res = QtApplication.invokeDelegate(callback); + if (res.invoked) + return (ActionMode)res.methodReturns; + else + return super.onWindowStartingActionMode(callback); + } + public ActionMode super_onWindowStartingActionMode(Callback callback) + { + return super.onWindowStartingActionMode(callback); + } + //--------------------------------------------------------------------------- +//@ANDROID-11 + //////////////// Activity API 12 ///////////// + +//@ANDROID-12 + @Override + public boolean dispatchGenericMotionEvent(MotionEvent ev) + { + if (QtApplication.m_delegateObject != null && QtApplication.dispatchGenericMotionEvent != null) + return (Boolean) QtApplication.invokeDelegateMethod(QtApplication.dispatchGenericMotionEvent, ev); + else + return super.dispatchGenericMotionEvent(ev); + } + public boolean super_dispatchGenericMotionEvent(MotionEvent event) + { + return super.dispatchGenericMotionEvent(event); + } + //--------------------------------------------------------------------------- + + @Override + public boolean onGenericMotionEvent(MotionEvent event) + { + if (QtApplication.m_delegateObject != null && QtApplication.onGenericMotionEvent != null) + return (Boolean) QtApplication.invokeDelegateMethod(QtApplication.onGenericMotionEvent, event); + else + return super.onGenericMotionEvent(event); + } + public boolean super_onGenericMotionEvent(MotionEvent event) + { + return super.onGenericMotionEvent(event); + } + //--------------------------------------------------------------------------- +//@ANDROID-12 + +} diff --git a/src/android/java/src/org/qtproject/qt5/android/bindings/QtApplication.java b/src/android/java/src/org/qtproject/qt5/android/bindings/QtApplication.java new file mode 100644 index 0000000000..a23c3afb4d --- /dev/null +++ b/src/android/java/src/org/qtproject/qt5/android/bindings/QtApplication.java @@ -0,0 +1,149 @@ +/* + Copyright (c) 2012, BogDan Vatra + Contact: http://www.qt-project.org/legal + + 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 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"; + public static Object m_delegateObject = null; + public static HashMap> m_delegateMethods= new HashMap>(); + public static Method dispatchKeyEvent = null; + public static Method dispatchPopulateAccessibilityEvent = null; + public static Method dispatchTouchEvent = null; + public static Method dispatchTrackballEvent = null; + public static Method onKeyDown = null; + public static Method onKeyMultiple = null; + public static Method onKeyUp = null; + public static Method onTouchEvent = null; + public static Method onTrackballEvent = null; + public static Method onActivityResult = null; + public static Method onCreate = null; + public static Method onKeyLongPress = null; + public static Method dispatchKeyShortcutEvent = null; + public static Method onKeyShortcut = null; + public static Method dispatchGenericMotionEvent = null; + public static Method onGenericMotionEvent = null; + + public static void setQtActivityDelegate(Object listener) + { + QtApplication.m_delegateObject = listener; + + ArrayList delegateMethods = new ArrayList(); + for (Method m : listener.getClass().getMethods()) { + if (m.getDeclaringClass().getName().startsWith("org.qtproject.qt5.android")) + delegateMethods.add(m); + } + + ArrayList applicationFields = new ArrayList(); + for (Field f : QtApplication.class.getFields()) { + if (f.getDeclaringClass().getName().equals(QtApplication.class.getName())) + applicationFields.add(f); + } + + for (Method delegateMethod : delegateMethods) { + try { + QtActivity.class.getDeclaredMethod(delegateMethod.getName(), delegateMethod.getParameterTypes()); + if (QtApplication.m_delegateMethods.containsKey(delegateMethod.getName())) { + QtApplication.m_delegateMethods.get(delegateMethod.getName()).add(delegateMethod); + } else { + ArrayList delegateSet = new ArrayList(); + delegateSet.add(delegateMethod); + QtApplication.m_delegateMethods.put(delegateMethod.getName(), delegateSet); + } + for (Field applicationField:applicationFields) { + if (applicationField.getName().equals(delegateMethod.getName())) { + try { + applicationField.set(null, delegateMethod); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + } catch (Exception e) { + } + } + } + + @Override + public void onTerminate() { + if (m_delegateObject != null && m_delegateMethods.containsKey("onTerminate")) + invokeDelegateMethod(m_delegateMethods.get("onTerminate").get(0)); + super.onTerminate(); + } + + public static class InvokeResult + { + public boolean invoked = false; + public Object methodReturns = null; + } + + private static int stackDeep=-1; + public static InvokeResult invokeDelegate(Object... args) + { + InvokeResult result = new InvokeResult(); + if (m_delegateObject == null) + return result; + StackTraceElement[] elements = Thread.currentThread().getStackTrace(); + if (-1 == stackDeep) { + String activityClassName = QtActivity.class.getCanonicalName(); + for (int it=0;it + + AndroidManifest.xml + libs.xml + logo.png + icon.png + + -- cgit v1.2.3