diff options
author | Paul Olav Tvete <paul.tvete@digia.com> | 2013-03-04 10:16:42 +0100 |
---|---|---|
committer | The Qt Project <gerrit-noreply@qt-project.org> | 2013-03-05 08:31:23 +0100 |
commit | 97fcf3bc987a18f32233fea550eb4a5a22e2b822 (patch) | |
tree | 31d75557fdc4a525af8f5053db514066c63bd1bd /src/android/jar | |
parent | 1b582d64eb6d13e60a02ebc40956302a4864eb6c (diff) |
Introducing the Qt Android port
Based on the Necessitas project by Bogdan Vatra.
Contributors to the Qt5 project:
BogDan Vatra <bogdan@kde.org>
Eskil Abrahamsen Blomfeldt <eskil.abrahamsen-blomfeldt@digia.com>
hjk <hjk121@nokiamail.com>
Oswald Buddenhagen <oswald.buddenhagen@digia.com>
Paul Olav Tvete <paul.tvete@digia.com>
Robin Burchell <robin+qt@viroteck.net>
Samuel Rødal <samuel.rodal@digia.com>
Yoann Lopes <yoann.lopes@digia.com>
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 <oswald.buddenhagen@digia.com>
Reviewed-by: Lars Knoll <lars.knoll@digia.com>
Diffstat (limited to 'src/android/jar')
9 files changed, 2142 insertions, 0 deletions
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 @@ +<?xml version='1.0' encoding='utf-8'?> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1" android:versionName="1.0" package="org.qtproject.qt5.android"> + <supports-screens android:largeScreens="true" android:normalScreens="true" android:anyDensity="true" android:smallScreens="true"/> +</manifest> 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 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <string name="app_name">QtJar</string> +</resources> 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 <bogdan@kde.org> +** 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<String> 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<Runnable> 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 <bogdan@kde.org> +** 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 <bogdan@kde.org> +** 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 <bogdan@kde.org> +** 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 <bogdan@kde.org> +** 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<Runnable> m_lostActions = new ArrayList<Runnable>(); // 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<String> 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<String> 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<Runnable> 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<event.getPointerCount();i++) { + touchAdd(id, + event.getPointerId(i), + getAction(i, event), + i == 0, + (int)event.getX(i), + (int)event.getY(i), + event.getSize(i), + event.getPressure(i)); + } + + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + touchEnd(id,0); + break; + + case MotionEvent.ACTION_UP: + touchEnd(id,2); + break; + + default: + touchEnd(id,1); + } + //@ANDROID-5 + + 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) > 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 <bogdan@kde.org> +** 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 <bogdan@kde.org> +** 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; + } +} |