Android: Simplify Qt for Android hierarchy, less Java reflection!

This changes takes Qt for Android Java code away from the Delegate
classes that uses heavily Java reflection to invoke Activity/Service
calls and overrides. So instead of that, now, we have a QtActivityBase
and a QtServiceBase classes which handle the override logic needed for
Qt directly without reflection.

These Base classes extend Android's Activity and Service directly, and
are inside the internal Qt android package (under Qt6Android.jar).

For example, to handle onConfigurationChanged, instead of the current
way where we need this in QtActivityDelegate:

 public void onConfigurationChanged(Configuration configuration)
 {
     try {
         m_super_onConfigurationChanged.invoke(m_activity, configuration);
     } catch (Exception e) {
         e.printStackTrace();
     }
         handleUiModeChange(configuration.uiMode &
Configuration.UI_MODE_NIGHT_MASK);
 }

And then this in QtActivity:

 @Override
 public void onConfigurationChanged(Configuration newConfig)
     {
         if (!QtLoader.invokeDelegate(newConfig).invoked)
             super.onConfigurationChanged(newConfig);
    }
    public void super_onConfigurationChanged(Configuration newConfig)
    {
        super.onConfigurationChanged(newConfig);
    }

And having to keep it's Method handles around and then use Java
reflection
to call the override behavior done by Qt and the superclass methods.

instead of that, we can do it now in QtActivityBase like:

 @Override
 public void onConfigurationChanged(Configuration newConfig)
 {
     super.onConfigurationChanged(newConfig);
     handleUiModeChange(newConfig.uiMode &
Configuration.UI_MODE_NIGHT_MASK);
 }

Then, we would still have our user facing QtActivity class which extends
QtActivityBase and benefit from the same implementation of Qt logic done
in the base class.

An additional benefit to this approach is that now QtActivity will be
very lightweight and doesn't need to have all the boilerplate code as
before.

[ChangeLog][Android] Simplify Qt for Android public bindings
(QActivity, QtService and QtApplication) by implementing base
classes which use the delegate implementions directly and avoid
reflection.

Task-number: QTBUG-115014
Task-number: QTBUG-114593
Change-Id: Ie1eca74f989627be4468786a27e30b16209fc521
Reviewed-by: Tinja Paavoseppä <tinja.paavoseppa@qt.io>
This commit is contained in:
Assam Boudjelthia 2023-09-15 17:58:02 +03:00
parent beac5a6d72
commit 3b6d288e3b
14 changed files with 548 additions and 957 deletions

View File

@ -8,7 +8,11 @@ set(java_sources
src/org/qtproject/qt/android/EditContextView.java
src/org/qtproject/qt/android/EditPopupMenu.java
src/org/qtproject/qt/android/ExtractStyle.java
src/org/qtproject/qt/android/QtApplicationBase.java
src/org/qtproject/qt/android/QtActivityBase.java
src/org/qtproject/qt/android/QtServiceBase.java
src/org/qtproject/qt/android/QtActivityDelegate.java
src/org/qtproject/qt/android/QtServiceDelegate.java
src/org/qtproject/qt/android/QtLoader.java
src/org/qtproject/qt/android/QtActivityLoader.java
src/org/qtproject/qt/android/QtServiceLoader.java
@ -20,7 +24,6 @@ set(java_sources
src/org/qtproject/qt/android/QtNativeLibrariesDir.java
src/org/qtproject/qt/android/QtSurface.java
src/org/qtproject/qt/android/QtThread.java
src/org/qtproject/qt/android/QtServiceDelegate.java
src/org/qtproject/qt/android/extras//QtAndroidBinder.java
src/org/qtproject/qt/android/extras/QtAndroidServiceConnection.java
src/org/qtproject/qt/android/extras/QtNative.java

View File

@ -0,0 +1,349 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
package org.qtproject.qt.android;
import android.app.Activity;
import android.content.Intent;
import android.content.res.Configuration;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.provider.Browser;
import android.text.method.MetaKeyKeyListener;
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.MotionEvent;
import android.view.View;
public class QtActivityBase extends Activity
{
private long m_metaState;
public boolean m_backKeyPressedSent = false;
private boolean m_optionsMenuIsVisible = false;
// use this variable to pass any parameters to your application,
// the parameters must not contain any white spaces
// and must be separated with "\t"
// e.g "-param1\t-param2=value2\t-param3\tvalue3"
public String APPLICATION_PARAMETERS = null;
// 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_DIALOGS - 1 to use the android native dialogs.
public String ENVIRONMENT_VARIABLES = "QT_USE_ANDROID_NATIVE_DIALOGS=1";
// A list with all themes that your application want to use.
// The name of the theme must be the same with any theme from
// http://developer.android.com/reference/android/R.style.html
// The most used themes are:
// * "Theme_Light" - (default for API <=10) check http://developer.android.com/reference/android/R.style.html#Theme_Light
// * "Theme_Holo" - check http://developer.android.com/reference/android/R.style.html#Theme_Holo
// * "Theme_Holo_Light" - (default for API 11-13) check http://developer.android.com/reference/android/R.style.html#Theme_Holo_Light
public String[] QT_ANDROID_THEMES = null;
public String QT_ANDROID_DEFAULT_THEME = null; // sets the default theme.
private final QtActivityLoader m_loader = new QtActivityLoader(this);
private final QtActivityDelegate m_delegate = new QtActivityDelegate();
protected void onCreateHook(Bundle savedInstanceState) {
m_loader.APPLICATION_PARAMETERS = APPLICATION_PARAMETERS;
m_loader.ENVIRONMENT_VARIABLES = ENVIRONMENT_VARIABLES;
m_loader.QT_ANDROID_THEMES = QT_ANDROID_THEMES;
m_loader.QT_ANDROID_DEFAULT_THEME = QT_ANDROID_DEFAULT_THEME;
m_loader.onCreate(savedInstanceState);
}
public static final String EXTRA_SOURCE_INFO = "org.qtproject.qt.android.sourceInfo";
private void addReferrer(Intent intent)
{
if (intent.getExtras() != null && intent.getExtras().getString(EXTRA_SOURCE_INFO) != null)
return;
String browserApplicationId = "";
if (intent.getExtras() != null)
browserApplicationId = intent.getExtras().getString(Browser.EXTRA_APPLICATION_ID);
String sourceInformation = "";
if (browserApplicationId != null && !browserApplicationId.isEmpty()) {
sourceInformation = browserApplicationId;
} else {
Uri referrer = getReferrer();
if (referrer != null)
sourceInformation = referrer.toString().replaceFirst("android-app://", "");
}
intent.putExtra(EXTRA_SOURCE_INFO, sourceInformation);
}
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
onCreateHook(savedInstanceState);
addReferrer(getIntent());
}
@Override
protected void onStart()
{
super.onStart();
}
@Override
protected void onRestart()
{
super.onRestart();
}
@Override
protected void onPause()
{
super.onPause();
if (Build.VERSION.SDK_INT < 24 || !isInMultiWindowMode())
QtNative.setApplicationState(QtConstants.ApplicationState.ApplicationInactive);
}
@Override
protected void onResume()
{
super.onResume();
QtNative.setApplicationState(QtConstants.ApplicationState.ApplicationActive);
if (m_delegate.isStarted()) {
QtNative.updateWindow();
m_delegate.updateFullScreen(); // Suspending the app clears the immersive mode, so we need to set it again.
}
}
@Override
protected void onStop()
{
super.onStop();
QtNative.setApplicationState(QtConstants.ApplicationState.ApplicationSuspended);
}
@Override
protected void onDestroy()
{
super.onDestroy();
if (m_delegate.isQuitApp()) {
QtNative.terminateQt();
QtNative.setActivity(null, null);
QtNative.m_qtThread.exit();
System.exit(0);
}
}
@Override
public boolean dispatchKeyEvent(KeyEvent event)
{
if (m_delegate.isStarted()
&& event.getAction() == KeyEvent.ACTION_MULTIPLE
&& event.getCharacters() != null
&& event.getCharacters().length() == 1
&& event.getKeyCode() == 0) {
QtNative.keyDown(0, event.getCharacters().charAt(0), event.getMetaState(), event.getRepeatCount() > 0);
QtNative.keyUp(0, event.getCharacters().charAt(0), event.getMetaState(), event.getRepeatCount() > 0);
}
if (QtNative.dispatchKeyEvent(event))
return true;
return super.dispatchKeyEvent(event);
}
@Override
public void onConfigurationChanged(Configuration newConfig)
{
super.onConfigurationChanged(newConfig);
m_delegate.handleUiModeChange(newConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK);
}
@Override
public boolean onContextItemSelected(MenuItem item)
{
m_delegate.setContextMenuVisible(false);
return QtNative.onContextItemSelected(item.getItemId(), item.isChecked());
}
@Override
public void onContextMenuClosed(Menu menu)
{
if (!m_delegate.isContextMenuVisible())
return;
m_delegate.setContextMenuVisible(false);
QtNative.onContextMenuClosed(menu);
}
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo)
{
menu.clearHeader();
QtNative.onCreateContextMenu(menu);
m_delegate.setContextMenuVisible(true);
}
private int m_lastChar = 0;
@Override
public boolean onKeyDown(int keyCode, KeyEvent event)
{
if (!m_delegate.isStarted() || !m_delegate.isPluginRunning())
return false;
m_metaState = MetaKeyKeyListener.handleKeyDown(m_metaState, keyCode, event);
int c = event.getUnicodeChar(MetaKeyKeyListener.getMetaState(m_metaState) | event.getMetaState());
int lc = c;
m_metaState = MetaKeyKeyListener.adjustMetaAfterKeypress(m_metaState);
if ((c & KeyCharacterMap.COMBINING_ACCENT) != 0) {
c = c & KeyCharacterMap.COMBINING_ACCENT_MASK;
c = KeyEvent.getDeadChar(m_lastChar, c);
}
if ((keyCode == KeyEvent.KEYCODE_VOLUME_UP
|| keyCode == KeyEvent.KEYCODE_VOLUME_DOWN
|| keyCode == KeyEvent.KEYCODE_MUTE)
&& System.getenv("QT_ANDROID_VOLUME_KEYS") == null) {
return false;
}
m_lastChar = lc;
if (keyCode == KeyEvent.KEYCODE_BACK) {
m_backKeyPressedSent = !m_delegate.isKeyboardVisible();
if (!m_backKeyPressedSent)
return true;
}
QtNative.keyDown(keyCode, c, event.getMetaState(), event.getRepeatCount() > 0);
return true;
}
@Override
public boolean onKeyUp(int keyCode, KeyEvent event)
{
if (!m_delegate.isStarted() || !m_delegate.isPluginRunning())
return false;
if ((keyCode == KeyEvent.KEYCODE_VOLUME_UP
|| keyCode == KeyEvent.KEYCODE_VOLUME_DOWN
|| keyCode == KeyEvent.KEYCODE_MUTE)
&& System.getenv("QT_ANDROID_VOLUME_KEYS") == null) {
return false;
}
if (keyCode == KeyEvent.KEYCODE_BACK && !m_backKeyPressedSent) {
m_delegate.hideSoftwareKeyboard();
m_delegate.setKeyboardVisibility(false, System.nanoTime());
return true;
}
m_metaState = MetaKeyKeyListener.handleKeyUp(m_metaState, keyCode, event);
QtNative.keyUp(keyCode, event.getUnicodeChar(), event.getMetaState(), event.getRepeatCount() > 0);
return true;
}
@Override
public boolean onCreateOptionsMenu(Menu menu)
{
menu.clear();
return true;
}
@Override
public boolean onPrepareOptionsMenu(Menu menu)
{
m_optionsMenuIsVisible = true;
boolean res = QtNative.onPrepareOptionsMenu(menu);
m_delegate.setActionBarVisibility(res && menu.size() > 0);
return res;
}
@Override
public boolean onOptionsItemSelected(MenuItem item)
{
return QtNative.onOptionsItemSelected(item.getItemId(), item.isChecked());
}
@Override
public void onOptionsMenuClosed(Menu menu)
{
m_optionsMenuIsVisible = false;
QtNative.onOptionsMenuClosed(menu);
}
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState)
{
super.onRestoreInstanceState(savedInstanceState);
m_delegate.setStarted(savedInstanceState.getBoolean("Started"));
// FIXME restore all surfaces
}
@Override
public Object onRetainNonConfigurationInstance()
{
super.onRetainNonConfigurationInstance();
m_delegate.setQuitApp(false);
return true;
}
@Override
protected void onSaveInstanceState(Bundle outState)
{
super.onSaveInstanceState(outState);
outState.putInt("SystemUiVisibility", m_delegate.systemUiVisibility());
outState.putBoolean("Started", m_delegate.isStarted());
}
@Override
public void onWindowFocusChanged(boolean hasFocus)
{
super.onWindowFocusChanged(hasFocus);
if (hasFocus)
m_delegate.updateFullScreen();
}
@Override
public boolean dispatchGenericMotionEvent(MotionEvent ev)
{
if (m_delegate.isStarted() && QtNative.dispatchGenericMotionEvent(ev))
return true;
return super.dispatchGenericMotionEvent(ev);
}
@Override
protected void onNewIntent(Intent intent)
{
QtNative.onNewIntent(intent);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data)
{
super.onActivityResult(requestCode, resultCode, data);
QtNative.onActivityResult(requestCode, resultCode, data);
}
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults)
{
QtNative.sendRequestPermissionsResult(requestCode, permissions, grantResults);
}
QtActivityDelegate getActivityDelegate()
{
return m_delegate;
}
}

View File

@ -1,5 +1,5 @@
// Copyright (C) 2017 BogDan Vatra <bogdan@kde.org>
// Copyright (C) 2022 The Qt Company Ltd.
// Copyright (C) 2023 The Qt Company Ltd.
// Copyright (C) 2016 Olivier Goffart <ogoffart@woboq.com>
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
@ -71,16 +71,6 @@ import static org.qtproject.qt.android.QtConstants.*;
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 Method m_super_onActivityResult = null;
private Method m_super_dispatchGenericMotionEvent = null;
private Method m_super_onWindowFocusChanged = null;
// Keep in sync with QtAndroid::SystemUiVisibility in androidjnimain.h
public static final int SYSTEM_UI_VISIBILITY_NORMAL = 0;
@ -93,8 +83,6 @@ public class QtActivityDelegate
private int m_nativeOrientation = Configuration.ORIENTATION_UNDEFINED;
private String m_mainLib;
private long m_metaState;
private int m_lastChar = 0;
private int m_softInputMode = 0;
private int m_systemUiVisibility = SYSTEM_UI_VISIBILITY_NORMAL;
private boolean m_started = false;
@ -108,7 +96,6 @@ public class QtActivityDelegate
private boolean m_quitApp = true;
private View m_dummyView = null;
private boolean m_keyboardIsVisible = false;
public boolean m_backKeyPressedSent = false;
private long m_showHideTimeStamp = System.nanoTime();
private int m_portraitKeyboardHeight = 0;
private int m_landscapeKeyboardHeight = 0;
@ -122,6 +109,9 @@ public class QtActivityDelegate
private QtAccessibilityDelegate m_accessibilityDelegate = null;
QtActivityDelegate() { }
public void setSystemUiVisibility(int systemUiVisibility)
{
if (m_systemUiVisibility == systemUiVisibility)
@ -442,6 +432,46 @@ public class QtActivityDelegate
});
}
void setStarted(boolean started)
{
m_started = started;
}
boolean isStarted()
{
return m_started;
}
void setQuitApp(boolean quitApp)
{
m_quitApp = quitApp;
}
boolean isQuitApp()
{
return m_quitApp;
}
boolean isPluginRunning()
{
return m_isPluginRunning;
}
int systemUiVisibility()
{
return m_systemUiVisibility;
}
void setContextMenuVisible(boolean contextMenuVisible)
{
m_contextMenuVisible = contextMenuVisible;
}
boolean isContextMenuVisible()
{
return m_contextMenuVisible;
}
int getAppIconSize(Activity a)
{
int size = a.getResources().getDimensionPixelSize(android.R.dimen.app_icon_size);
@ -625,28 +655,6 @@ public class QtActivityDelegate
QtNative.setActivity(m_activity, this);
setActionBarVisibility(false);
Class<?> activityClass = m_activity.getClass();
m_super_dispatchKeyEvent =
activityClass.getMethod("super_dispatchKeyEvent", KeyEvent.class);
m_super_onRestoreInstanceState =
activityClass.getMethod("super_onRestoreInstanceState", Bundle.class);
m_super_onRetainNonConfigurationInstance =
activityClass.getMethod("super_onRetainNonConfigurationInstance");
m_super_onSaveInstanceState =
activityClass.getMethod("super_onSaveInstanceState", Bundle.class);
m_super_onKeyDown =
activityClass.getMethod("super_onKeyDown", Integer.TYPE, KeyEvent.class);
m_super_onKeyUp =
activityClass.getMethod("super_onKeyUp", Integer.TYPE, KeyEvent.class);
m_super_onConfigurationChanged =
activityClass.getMethod("super_onConfigurationChanged", Configuration.class);
m_super_onActivityResult =
activityClass.getMethod("super_onActivityResult", Integer.TYPE, Integer.TYPE, Intent.class);
m_super_onWindowFocusChanged =
activityClass.getMethod("super_onWindowFocusChanged", Boolean.TYPE);
m_super_dispatchGenericMotionEvent =
activityClass.getMethod("super_dispatchGenericMotionEvent", MotionEvent.class);
m_softInputMode = m_activity.getPackageManager().getActivityInfo(m_activity.getComponentName(), 0).softInputMode;
DisplayManager displayManager = (DisplayManager)m_activity.getSystemService(Context.DISPLAY_SERVICE);
@ -948,7 +956,6 @@ public class QtActivityDelegate
m_accessibilityDelegate.notifyScrolledEvent(viewId);
}
public void notifyQtAndroidPluginRunning(boolean running)
{
m_isPluginRunning = running;
@ -959,22 +966,12 @@ public class QtActivityDelegate
m_accessibilityDelegate = new QtAccessibilityDelegate(m_activity, m_layout, this);
}
public void onWindowFocusChanged(boolean hasFocus) {
try {
m_super_onWindowFocusChanged.invoke(m_activity, hasFocus);
} catch (Exception e) {
e.printStackTrace();
}
if (hasFocus)
updateFullScreen();
}
boolean isUiModeDark(Configuration config)
{
return (config.uiMode & Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES;
}
private void handleUiModeChange(int uiMode)
void handleUiModeChange(int uiMode)
{
// QTBUG-108365
if (Build.VERSION.SDK_INT >= 30) {
@ -1001,215 +998,12 @@ public class QtActivityDelegate
}
}
public void onConfigurationChanged(Configuration configuration)
{
try {
m_super_onConfigurationChanged.invoke(m_activity, configuration);
} catch (Exception e) {
e.printStackTrace();
}
handleUiModeChange(configuration.uiMode & Configuration.UI_MODE_NIGHT_MASK);
}
public void onDestroy()
{
if (m_quitApp) {
QtNative.terminateQt();
QtNative.setActivity(null, null);
QtNative.m_qtThread.exit();
System.exit(0);
}
}
public void onPause()
{
if (Build.VERSION.SDK_INT < 24 || !m_activity.isInMultiWindowMode())
QtNative.setApplicationState(ApplicationState.ApplicationInactive);
}
public void onResume()
{
QtNative.setApplicationState(ApplicationState.ApplicationActive);
if (m_started) {
QtNative.updateWindow();
updateFullScreen(); // Suspending the app clears the immersive mode, so we need to set it again.
}
}
public void onNewIntent(Intent data)
{
QtNative.onNewIntent(data);
}
public void onActivityResult(int requestCode, int resultCode, Intent data)
{
try {
m_super_onActivityResult.invoke(m_activity, requestCode, resultCode, data);
} catch (Exception e) {
e.printStackTrace();
}
QtNative.onActivityResult(requestCode, resultCode, data);
}
public void onStop()
{
QtNative.setApplicationState(ApplicationState.ApplicationSuspended);
}
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.putInt("SystemUiVisibility", m_systemUiVisibility);
outState.putBoolean("Started", m_started);
// It should never
}
public void onRestoreInstanceState(Bundle savedInstanceState)
{
try {
m_super_onRestoreInstanceState.invoke(m_activity, savedInstanceState);
} catch (Exception e) {
e.printStackTrace();
}
m_started = savedInstanceState.getBoolean("Started");
// FIXME restore all surfaces
}
public boolean onKeyDown(int keyCode, KeyEvent event)
{
if (!m_started || !m_isPluginRunning)
return false;
m_metaState = MetaKeyKeyListener.handleKeyDown(m_metaState, keyCode, event);
int c = event.getUnicodeChar(MetaKeyKeyListener.getMetaState(m_metaState) | event.getMetaState());
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;
}
if ((keyCode == KeyEvent.KEYCODE_VOLUME_UP
|| keyCode == KeyEvent.KEYCODE_VOLUME_DOWN
|| keyCode == KeyEvent.KEYCODE_MUTE)
&& System.getenv("QT_ANDROID_VOLUME_KEYS") == null) {
return false;
}
m_lastChar = lc;
if (keyCode == KeyEvent.KEYCODE_BACK) {
m_backKeyPressedSent = !m_keyboardIsVisible;
if (!m_backKeyPressedSent)
return true;
}
QtNative.keyDown(keyCode, c, event.getMetaState(), event.getRepeatCount() > 0);
return true;
}
public boolean onKeyUp(int keyCode, KeyEvent event)
{
if (!m_started || !m_isPluginRunning)
return false;
if ((keyCode == KeyEvent.KEYCODE_VOLUME_UP
|| keyCode == KeyEvent.KEYCODE_VOLUME_DOWN
|| keyCode == KeyEvent.KEYCODE_MUTE)
&& System.getenv("QT_ANDROID_VOLUME_KEYS") == null) {
return false;
}
if (keyCode == KeyEvent.KEYCODE_BACK && !m_backKeyPressedSent) {
hideSoftwareKeyboard();
setKeyboardVisibility(false, System.nanoTime());
return true;
}
m_metaState = MetaKeyKeyListener.handleKeyUp(m_metaState, keyCode, event);
QtNative.keyUp(keyCode, event.getUnicodeChar(), event.getMetaState(), event.getRepeatCount() > 0);
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(), event.getRepeatCount() > 0);
QtNative.keyUp(0, event.getCharacters().charAt(0), event.getMetaState(), event.getRepeatCount() > 0);
}
if (QtNative.dispatchKeyEvent(event))
return true;
try {
return (Boolean) m_super_dispatchKeyEvent.invoke(m_activity, event);
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
private boolean m_optionsMenuIsVisible = false;
public boolean onCreateOptionsMenu(Menu menu)
{
menu.clear();
return true;
}
public boolean onPrepareOptionsMenu(Menu menu)
{
m_optionsMenuIsVisible = true;
boolean res = QtNative.onPrepareOptionsMenu(menu);
setActionBarVisibility(res && menu.size() > 0);
return res;
}
public boolean onOptionsItemSelected(MenuItem item)
{
return QtNative.onOptionsItemSelected(item.getItemId(), item.isChecked());
}
public void onOptionsMenuClosed(Menu menu)
{
m_optionsMenuIsVisible = false;
QtNative.onOptionsMenuClosed(menu);
}
public void resetOptionsMenu()
{
m_activity.invalidateOptionsMenu();
}
private boolean m_contextMenuVisible = false;
public void onCreateContextMenu(ContextMenu menu,
View v,
ContextMenuInfo menuInfo)
{
menu.clearHeader();
QtNative.onCreateContextMenu(menu);
m_contextMenuVisible = true;
}
public void onCreatePopupMenu(Menu menu)
{
@ -1217,20 +1011,6 @@ public class QtActivityDelegate
m_contextMenuVisible = true;
}
public void onContextMenuClosed(Menu menu)
{
if (!m_contextMenuVisible)
return;
m_contextMenuVisible = false;
QtNative.onContextMenuClosed(menu);
}
public boolean onContextItemSelected(MenuItem item)
{
m_contextMenuVisible = false;
return QtNative.onContextItemSelected(item.getItemId(), item.isChecked());
}
public void openContextMenu(final int x, final int y, final int w, final int h)
{
m_layout.postDelayed(new Runnable() {
@ -1242,13 +1022,13 @@ public class QtActivityDelegate
popup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem menuItem) {
return QtActivityDelegate.this.onContextItemSelected(menuItem);
return m_activity.onContextItemSelected(menuItem);
}
});
popup.setOnDismissListener(new PopupMenu.OnDismissListener() {
@Override
public void onDismiss(PopupMenu popupMenu) {
QtActivityDelegate.this.onContextMenuClosed(popupMenu.getMenu());
m_activity.onContextMenuClosed(popupMenu.getMenu());
}
});
popup.show();
@ -1261,7 +1041,7 @@ public class QtActivityDelegate
m_activity.closeContextMenu();
}
private void setActionBarVisibility(boolean visible)
void setActionBarVisibility(boolean visible)
{
if (m_activity.getActionBar() == null)
return;
@ -1398,22 +1178,4 @@ public class QtActivityDelegate
m_layout.moveChild(view, index);
}
}
public boolean dispatchGenericMotionEvent (MotionEvent ev)
{
if (m_started && QtNative.dispatchGenericMotionEvent(ev))
return true;
try {
return (Boolean) m_super_dispatchGenericMotionEvent.invoke(m_activity, ev);
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults)
{
QtNative.sendRequestPermissionsResult(requestCode, permissions, grantResults);
}
}

View File

@ -16,22 +16,12 @@ import java.lang.reflect.Field;
public class QtActivityLoader extends QtLoader {
Activity m_activity;
public QtActivityLoader(Activity activity, Class<?> clazz)
public QtActivityLoader(Activity activity)
{
super(activity, clazz);
super(activity);
m_activity = activity;
}
@Override
protected String loaderClassName() {
return "org.qtproject.qt.android.QtActivityDelegate";
}
@Override
protected Class<?> contextClassName() {
return android.app.Activity.class;
}
@Override
protected void finish() {
m_activity.finish();
@ -88,9 +78,7 @@ public class QtActivityLoader extends QtLoader {
Runtime.getRuntime().exit(0);
}
// there can only be a valid delegate object if the QtNative was started.
if (m_delegateObject != null && onCreate != null)
invokeDelegateMethod(onCreate, savedInstanceState);
((QtActivityBase)m_activity).getActivityDelegate().onCreate(savedInstanceState);
return;
}

View File

@ -0,0 +1,17 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
package org.qtproject.qt.android;
import android.app.Application;
public class QtApplicationBase extends Application {
public final static String QtTAG = "Qt";
@Override
public void onTerminate() {
QtNative.terminateQt();
QtNative.m_qtThread.exit();
super.onTerminate();
}
}

View File

@ -36,71 +36,18 @@ public abstract class QtLoader {
// These parameters matter in case of deploying application as system (embedded into firmware)
public static final String SYSTEM_LIB_PATH = "/system/lib/";
public String APPLICATION_PARAMETERS = null; // use this variable to pass any parameters to your application,
// the parameters must not contain any white spaces
// and must be separated with "\t"
// e.g "-param1\t-param2=value2\t-param3\tvalue3"
public String APPLICATION_PARAMETERS = null;
public String ENVIRONMENT_VARIABLES = "QT_USE_ANDROID_NATIVE_DIALOGS=1";
// 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_DIALOGS -1 to use the android native dialogs.
public String[] QT_ANDROID_THEMES = null; // A list with all themes that your application want to use.
// The name of the theme must be the same with any theme from
// http://developer.android.com/reference/android/R.style.html
// The most used themes are:
// * "Theme" - (fallback) check http://developer.android.com/reference/android/R.style.html#Theme
// * "Theme_Black" - check http://developer.android.com/reference/android/R.style.html#Theme_Black
// * "Theme_Light" - (default for API <=10) check http://developer.android.com/reference/android/R.style.html#Theme_Light
// * "Theme_Holo" - check http://developer.android.com/reference/android/R.style.html#Theme_Holo
// * "Theme_Holo_Light" - (default for API 11-13) check http://developer.android.com/reference/android/R.style.html#Theme_Holo_Light
// * "Theme_DeviceDefault" - check http://developer.android.com/reference/android/R.style.html#Theme_DeviceDefault
// * "Theme_DeviceDefault_Light" - (default for API 14+) check http://developer.android.com/reference/android/R.style.html#Theme_DeviceDefault_Light
public String[] QT_ANDROID_THEMES = null;
public String QT_ANDROID_DEFAULT_THEME = null; // sets the default theme.
public ArrayList<String> m_qtLibs = null; // required qt libs
public int m_displayDensity = -1;
private ContextWrapper m_context;
protected ComponentInfo m_contextInfo;
private Class<?> m_delegateClass;
private static ArrayList<FileOutputStream> m_fileOutputStreams = new ArrayList<FileOutputStream>();
// List of open file streams associated with files copied during installation.
public static Object m_delegateObject = null;
public static HashMap<String, ArrayList<Method>> m_delegateMethods= new HashMap<String, ArrayList<Method>>();
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 Method onRequestPermissionsResult = null;
private static String activityClassName;
private static Class qtApplicationClass = null;
public QtLoader(ContextWrapper context, Class<?> clazz) {
public QtLoader(ContextWrapper context) {
m_context = context;
m_delegateClass = clazz;
}
public static void setQtApplicationClass(Class qtAppClass)
{
qtApplicationClass = qtAppClass;
}
public static void setQtTAG(String tag)
@ -108,93 +55,6 @@ public abstract class QtLoader {
QtTAG = tag;
}
public static void setQtContextDelegate(Class<?> clazz, Object listener)
{
m_delegateObject = listener;
activityClassName = clazz.getCanonicalName();
ArrayList<Method> delegateMethods = new ArrayList<Method>();
for (Method m : listener.getClass().getMethods()) {
if (m.getDeclaringClass().getName().startsWith("org.qtproject.qt.android"))
delegateMethods.add(m);
}
ArrayList<Field> applicationFields = new ArrayList<Field>();
for (Field f : qtApplicationClass.getFields()) {
if (f.getDeclaringClass().getName().equals(qtApplicationClass.getName()))
applicationFields.add(f);
}
for (Method delegateMethod : delegateMethods) {
try {
clazz.getDeclaredMethod(delegateMethod.getName(), delegateMethod.getParameterTypes());
if (m_delegateMethods.containsKey(delegateMethod.getName())) {
m_delegateMethods.get(delegateMethod.getName()).add(delegateMethod);
} else {
ArrayList<Method> delegateSet = new ArrayList<Method>();
delegateSet.add(delegateMethod);
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) { }
}
}
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) {
for (int it=0;it<elements.length;it++)
if (elements[it].getClassName().equals(activityClassName)) {
stackDeep = it;
break;
}
}
if (-1 == stackDeep)
return result;
final String methodName=elements[stackDeep].getMethodName();
if (!m_delegateMethods.containsKey(methodName))
return result;
for (Method m : m_delegateMethods.get(methodName)) {
if (m.getParameterTypes().length == args.length) {
result.methodReturns = invokeDelegateMethod(m, args);
result.invoked = true;
return result;
}
}
return result;
}
public static Object invokeDelegateMethod(Method m, Object... args)
{
try {
return m.invoke(m_delegateObject, args);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
// Implement in subclass
protected void finish() {}
@ -206,9 +66,6 @@ public abstract class QtLoader {
run.run();
}
protected abstract String loaderClassName();
protected abstract Class<?> contextClassName();
Intent getIntent()
{
return null;
@ -288,21 +145,21 @@ public abstract class QtLoader {
loaderParams.containsKey(LIB_PATH_KEY) ? loaderParams.getString(LIB_PATH_KEY) : null, // libs folder (if exists)
m_context.getClassLoader()); // parent loader
Class<?> loaderClass = classLoader.loadClass(loaderParams.getString(LOADER_CLASS_NAME_KEY)); // load QtLoader class
Object qtLoader = loaderClass.newInstance(); // create an instance
Method prepareAppMethod = qtLoader.getClass().getMethod("loadApplication",
contextClassName(),
ClassLoader.class,
Bundle.class);
if (!(Boolean)prepareAppMethod.invoke(qtLoader, m_context, classLoader, loaderParams))
throw new Exception("");
setQtContextDelegate(m_delegateClass, qtLoader);
Method startAppMethod=qtLoader.getClass().getMethod("startApplication");
if (!(Boolean)startAppMethod.invoke(qtLoader))
throw new Exception("");
if (m_context instanceof QtActivityBase) {
QtActivityBase activityBase = (QtActivityBase)m_context;
QtActivityDelegate activityDelegate = activityBase.getActivityDelegate();
if (!activityDelegate.loadApplication(activityBase, classLoader, loaderParams))
throw new Exception("");
if (!activityDelegate.startApplication())
throw new Exception("");
} else if (m_context instanceof QtServiceBase) {
QtServiceBase serviceBase = (QtServiceBase)m_context;
QtServiceDelegate serviceDelegate = serviceBase.getServiceDelegate();
if (!serviceDelegate.loadApplication(serviceBase, classLoader, loaderParams))
throw new Exception("");
if (!serviceDelegate.startApplication())
throw new Exception("");
}
} catch (Exception e) {
e.printStackTrace();
AlertDialog errorDialog = new AlertDialog.Builder(m_context).create();
@ -412,7 +269,6 @@ public abstract class QtLoader {
Bundle loaderParams = new Bundle();
loaderParams.putInt(ERROR_CODE_KEY, 0);
loaderParams.putString(DEX_PATH_KEY, new String());
loaderParams.putString(LOADER_CLASS_NAME_KEY, loaderClassName());
id = resources.getIdentifier("static_init_classes", "string", packageName);
loaderParams.putStringArray(STATIC_INIT_CLASSES_KEY, resources.getString(id)

View File

@ -0,0 +1,58 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
package org.qtproject.qt.android;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;
public class QtServiceBase extends Service {
private final QtServiceDelegate m_delegate = new QtServiceDelegate(this);
QtServiceLoader m_loader = new QtServiceLoader(this);
protected void onCreateHook() {
// the application has already started
// do not reload everything again
if (QtNative.isStarted()) {
m_loader = null;
Log.w(QtNative.QtTAG,
"A QtService tried to start in the same process as an initiated " +
"QtActivity. That is not supported. This results in the service " +
"functioning as an Android Service detached from Qt.");
} else {
m_loader.onCreate();
}
}
@Override
public void onCreate()
{
super.onCreate();
onCreateHook();
}
@Override
public void onDestroy()
{
super.onDestroy();
QtNative.quitQtCoreApplication();
QtNative.terminateQt();
QtNative.setService(null, null);
QtNative.m_qtThread.exit();
System.exit(0);
}
@Override
public IBinder onBind(Intent intent) {
synchronized (this) {
return QtNative.onBind(intent);
}
}
QtServiceDelegate getServiceDelegate()
{
return m_delegate;
}
}

View File

@ -1,5 +1,5 @@
// Copyright (C) 2016 BogDan Vatra <bogdan@kde.org>
// Copyright (C) 2016 The Qt Company Ltd.
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
package org.qtproject.qt.android;
@ -58,6 +58,11 @@ public class QtServiceDelegate
private Service m_service = null;
private static String m_applicationParameters = null;
QtServiceDelegate(Service service)
{
m_service = service;
}
public boolean loadApplication(Service service, ClassLoader classLoader, Bundle loaderParams)
{
/// check parameters integrity
@ -136,20 +141,4 @@ public class QtServiceDelegate
return false;
}
}
public void onDestroy()
{
QtNative.quitQtCoreApplication();
QtNative.terminateQt();
QtNative.setService(null, null);
QtNative.m_qtThread.exit();
System.exit(0);
}
public IBinder onBind(Intent intent)
{
synchronized (this) {
return QtNative.onBind(intent);
}
}
}

View File

@ -14,8 +14,8 @@ import java.security.Provider;
public class QtServiceLoader extends QtLoader {
Service m_service;
public QtServiceLoader(Service service, Class<?> clazz) {
super(service, clazz);
public QtServiceLoader(Service service) {
super(service);
m_service = service;
}
@ -29,10 +29,6 @@ public class QtServiceLoader extends QtLoader {
return;
}
if (m_delegateObject != null && onCreate != null) {
Bundle bundle = null;
invokeDelegateMethod(onCreate, bundle);
}
startApp(true);
}
@ -40,14 +36,4 @@ public class QtServiceLoader extends QtLoader {
protected void finish() {
m_service.stopSelf();
}
@Override
protected String loaderClassName() {
return "org.qtproject.qt.android.QtServiceDelegate";
}
@Override
protected Class<?> contextClassName() {
return android.app.Service.class;
}
}

View File

@ -4,11 +4,6 @@
package org.qtproject.qt.android.bindings;
import android.app.Activity;
import android.content.Intent;
import android.content.res.Configuration;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.provider.Browser;
import android.view.ContextMenu;
@ -18,44 +13,41 @@ import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.os.Build;
import org.qtproject.qt.android.QtActivityLoader;
import org.qtproject.qt.android.QtLoader;
import org.qtproject.qt.android.QtActivityBase;
public class QtActivity extends Activity
public class QtActivity extends QtActivityBase
{
public static final String EXTRA_SOURCE_INFO = "org.qtproject.qt.android.sourceInfo";
public String APPLICATION_PARAMETERS = null; // use this variable to pass any parameters to your application,
// the parameters must not contain any white spaces
// and must be separated with "\t"
// e.g "-param1\t-param2=value2\t-param3\tvalue3"
public String ENVIRONMENT_VARIABLES = "QT_USE_ANDROID_NATIVE_DIALOGS=1";
// 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_DIALOGS - 1 to use the android native dialogs.
public String[] QT_ANDROID_THEMES = null; // A list with all themes that your application want to use.
// The name of the theme must be the same with any theme from
// http://developer.android.com/reference/android/R.style.html
// The most used themes are:
// * "Theme" - (fallback) check http://developer.android.com/reference/android/R.style.html#Theme
// * "Theme_Black" - check http://developer.android.com/reference/android/R.style.html#Theme_Black
// * "Theme_Light" - (default for API <=10) check http://developer.android.com/reference/android/R.style.html#Theme_Light
// * "Theme_Holo" - check http://developer.android.com/reference/android/R.style.html#Theme_Holo
// * "Theme_Holo_Light" - (default for API 11-13) check http://developer.android.com/reference/android/R.style.html#Theme_Holo_Light
// * "Theme_DeviceDefault" - check http://developer.android.com/reference/android/R.style.html#Theme_DeviceDefault
// * "Theme_DeviceDefault_Light" - (default for API 14+) check http://developer.android.com/reference/android/R.style.html#Theme_DeviceDefault_Light
public String QT_ANDROID_DEFAULT_THEME = null; // sets the default theme.
private QtActivityLoader m_loader;
public QtActivity()
@Override
public void onCreate(Bundle savedInstanceState)
{
m_loader = new QtActivityLoader(this, QtActivity.class);
setAppDetails();
super.onCreate(savedInstanceState);
}
private void setAppDetails()
{
// 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"
APPLICATION_PARAMETERS = "";
// 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_DIALOGS - 1 to use the android native dialogs.
ENVIRONMENT_VARIABLES = "QT_USE_ANDROID_NATIVE_DIALOGS=1";
// A list with all themes that your application want to use.
// The name of the theme must be the same with any theme from
// http://developer.android.com/reference/android/R.style.html
// The most used themes are:
// * "Theme_Light"
// * "Theme_Holo"
// * "Theme_Holo_Light"
if (Build.VERSION.SDK_INT < 29) {
QT_ANDROID_THEMES = new String[] {"Theme_Holo_Light"};
@ -65,326 +57,4 @@ public class QtActivity extends Activity
QT_ANDROID_DEFAULT_THEME = "Theme_DeviceDefault_DayNight";
}
}
/////////////////////////// forward all notifications ////////////////////////////
/////////////////////////// Super class calls ////////////////////////////////////
/////////////// PLEASE DO NOT CHANGE THE FOLLOWING CODE //////////////////////////
//////////////////////////////////////////////////////////////////////////////////
protected void onCreateHook(Bundle savedInstanceState) {
m_loader.APPLICATION_PARAMETERS = APPLICATION_PARAMETERS;
m_loader.ENVIRONMENT_VARIABLES = ENVIRONMENT_VARIABLES;
m_loader.QT_ANDROID_THEMES = QT_ANDROID_THEMES;
m_loader.QT_ANDROID_DEFAULT_THEME = QT_ANDROID_DEFAULT_THEME;
m_loader.onCreate(savedInstanceState);
}
private void addReferrer(Intent intent)
{
if (intent.getExtras() != null && intent.getExtras().getString(EXTRA_SOURCE_INFO) != null)
return;
String browserApplicationId = "";
if (intent.getExtras() != null)
browserApplicationId = intent.getExtras().getString(Browser.EXTRA_APPLICATION_ID);
String sourceInformation = "";
if (browserApplicationId != null && !browserApplicationId.isEmpty()) {
sourceInformation = browserApplicationId;
} else {
Uri referrer = getReferrer();
if (referrer != null)
sourceInformation = referrer.toString().replaceFirst("android-app://", "");
}
intent.putExtra(EXTRA_SOURCE_INFO, sourceInformation);
}
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
QtLoader.setQtApplicationClass(QtApplication.class);
onCreateHook(savedInstanceState);
addReferrer(getIntent());
}
@Override
public boolean dispatchKeyEvent(KeyEvent event)
{
if (QtLoader.m_delegateObject != null && QtLoader.dispatchKeyEvent != null)
return (Boolean) QtLoader.invokeDelegateMethod(QtLoader.dispatchKeyEvent, event);
else
return super.dispatchKeyEvent(event);
}
public boolean super_dispatchKeyEvent(KeyEvent event)
{
return super.dispatchKeyEvent(event);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data)
{
if (QtLoader.m_delegateObject != null && QtLoader.onActivityResult != null) {
QtLoader.invokeDelegateMethod(QtLoader.onActivityResult, requestCode, resultCode, data);
return;
}
super.onActivityResult(requestCode, resultCode, data);
}
public void super_onActivityResult(int requestCode, int resultCode, Intent data)
{
super.onActivityResult(requestCode, resultCode, data);
}
@Override
public void onConfigurationChanged(Configuration newConfig)
{
if (!QtLoader.invokeDelegate(newConfig).invoked)
super.onConfigurationChanged(newConfig);
}
public void super_onConfigurationChanged(Configuration newConfig)
{
super.onConfigurationChanged(newConfig);
}
@Override
public boolean onContextItemSelected(MenuItem item)
{
QtLoader.InvokeResult res = QtLoader.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 (!QtLoader.invokeDelegate(menu).invoked)
super.onContextMenuClosed(menu);
}
public void super_onContextMenuClosed(Menu menu)
{
super.onContextMenuClosed(menu);
}
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo)
{
if (!QtLoader.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 boolean onCreateOptionsMenu(Menu menu)
{
QtLoader.InvokeResult res = QtLoader.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
protected void onDestroy()
{
super.onDestroy();
QtLoader.invokeDelegate();
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event)
{
if (QtLoader.m_delegateObject != null && QtLoader.onKeyDown != null)
return (Boolean) QtLoader.invokeDelegateMethod(QtLoader.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 onKeyUp(int keyCode, KeyEvent event)
{
if (QtLoader.m_delegateObject != null && QtLoader.onKeyUp != null)
return (Boolean) QtLoader.invokeDelegateMethod(QtLoader.onKeyUp, keyCode, event);
else
return super.onKeyUp(keyCode, event);
}
public boolean super_onKeyUp(int keyCode, KeyEvent event)
{
return super.onKeyUp(keyCode, event);
}
@Override
protected void onNewIntent(Intent intent)
{
addReferrer(intent);
if (!QtLoader.invokeDelegate(intent).invoked)
super.onNewIntent(intent);
}
public void super_onNewIntent(Intent intent)
{
super.onNewIntent(intent);
}
@Override
public boolean onOptionsItemSelected(MenuItem item)
{
QtLoader.InvokeResult res = QtLoader.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 (!QtLoader.invokeDelegate(menu).invoked)
super.onOptionsMenuClosed(menu);
}
public void super_onOptionsMenuClosed(Menu menu)
{
super.onOptionsMenuClosed(menu);
}
@Override
protected void onPause()
{
super.onPause();
QtLoader.invokeDelegate();
}
@Override
public boolean onPrepareOptionsMenu(Menu menu)
{
QtLoader.InvokeResult res = QtLoader.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
protected void onRestart()
{
super.onRestart();
QtLoader.invokeDelegate();
}
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState)
{
if (!QtLoader.invokeDelegate(savedInstanceState).invoked)
super.onRestoreInstanceState(savedInstanceState);
}
public void super_onRestoreInstanceState(Bundle savedInstanceState)
{
super.onRestoreInstanceState(savedInstanceState);
}
@Override
protected void onResume()
{
super.onResume();
QtLoader.invokeDelegate();
}
@Override
public Object onRetainNonConfigurationInstance()
{
QtLoader.InvokeResult res = QtLoader.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 (!QtLoader.invokeDelegate(outState).invoked)
super.onSaveInstanceState(outState);
}
public void super_onSaveInstanceState(Bundle outState)
{
super.onSaveInstanceState(outState);
}
@Override
protected void onStart()
{
super.onStart();
QtLoader.invokeDelegate();
}
@Override
protected void onStop()
{
super.onStop();
QtLoader.invokeDelegate();
}
@Override
public void onWindowFocusChanged(boolean hasFocus)
{
if (!QtLoader.invokeDelegate(hasFocus).invoked)
super.onWindowFocusChanged(hasFocus);
}
public void super_onWindowFocusChanged(boolean hasFocus)
{
super.onWindowFocusChanged(hasFocus);
}
@Override
public boolean dispatchGenericMotionEvent(MotionEvent ev)
{
if (QtLoader.m_delegateObject != null && QtLoader.dispatchGenericMotionEvent != null)
return (Boolean) QtLoader.invokeDelegateMethod(QtLoader.dispatchGenericMotionEvent, ev);
else
return super.dispatchGenericMotionEvent(ev);
}
public boolean super_dispatchGenericMotionEvent(MotionEvent event)
{
return super.dispatchGenericMotionEvent(event);
}
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults)
{
if (QtLoader.m_delegateObject != null && QtLoader.onRequestPermissionsResult != null) {
QtLoader.invokeDelegateMethod(QtLoader.onRequestPermissionsResult, requestCode ,
permissions, grantResults);
}
}
//---------------------------------------------------------------------------
}

View File

@ -4,30 +4,12 @@
package org.qtproject.qt.android.bindings;
import android.app.Application;
import org.qtproject.qt.android.QtLoader;
import org.qtproject.qt.android.QtApplicationBase;
public class QtApplication extends Application
public class QtApplication extends QtApplicationBase
{
public final static String QtTAG = "Qt";
@Override
public void onTerminate() {
if (QtLoader.m_delegateObject != null && QtLoader.m_delegateMethods.containsKey("onTerminate"))
QtLoader.invokeDelegateMethod(QtLoader.m_delegateMethods.get("onTerminate").get(0));
super.onTerminate();
}
// TODO: only keep around for avoid build errors, will be removed.
public static class InvokeResult
{
public boolean invoked = false;
public Object methodReturns = null;
}
public static InvokeResult invokeDelegate(Object... args)
{
InvokeResult result = new InvokeResult();
return result;
}
}

View File

@ -4,85 +4,13 @@
package org.qtproject.qt.android.bindings;
import android.app.Service;
import android.util.Log;
import android.content.Intent;
import android.content.res.Configuration;
import android.os.IBinder;
import org.qtproject.qt.android.QtServiceBase;
import org.qtproject.qt.android.QtNative;
import org.qtproject.qt.android.QtServiceLoader;
import org.qtproject.qt.android.QtLoader;
public class QtService extends Service
public class QtService extends QtServiceBase
{
QtServiceLoader m_loader = new QtServiceLoader(this, QtService.class);
/////////////////////////// forward all notifications ////////////////////////////
/////////////////////////// Super class calls ////////////////////////////////////
/////////////// PLEASE DO NOT CHANGE THE FOLLOWING CODE //////////////////////////
//////////////////////////////////////////////////////////////////////////////////
protected void onCreateHook() {
// the application has already started
// do not reload everything again
if (QtNative.isStarted()) {
m_loader = null;
Log.w(QtNative.QtTAG,
"A QtService tried to start in the same process as an initiated " +
"QtActivity. That is not supported. This results in the service " +
"functioning as an Android Service detached from Qt.");
} else {
m_loader.onCreate();
}
}
@Override
public void onCreate()
{
super.onCreate();
onCreateHook();
}
@Override
public void onDestroy()
{
super.onDestroy();
QtLoader.invokeDelegate();
}
@Override
public IBinder onBind(Intent intent)
{
QtLoader.InvokeResult res = QtLoader.invokeDelegate(intent);
if (res.invoked)
return (IBinder)res.methodReturns;
else
return null;
}
@Override
public void onConfigurationChanged(Configuration newConfig)
{
if (!QtLoader.invokeDelegate(newConfig).invoked)
super.onConfigurationChanged(newConfig);
}
public void super_onConfigurationChanged(Configuration newConfig)
{
super.onConfigurationChanged(newConfig);
}
@Override
public boolean onUnbind(Intent intent)
{
QtLoader.InvokeResult res = QtLoader.invokeDelegate(intent);
if (res.invoked)
return (Boolean) res.methodReturns;
else
return super.onUnbind(intent);
}
public boolean super_onUnbind(Intent intent)
{
return super.onUnbind(intent);
}
//---------------------------------------------------------------------------
}

View File

@ -20,6 +20,9 @@ public class QtJniObjectTestClass
// --------------------------------------------------------------------------------------------
final int INT_FIELD = 123;
final boolean BOOL_FIELD = true;
byte BYTE_VAR;
short SHORT_VAR;
int INT_VAR;

View File

@ -1034,19 +1034,19 @@ void tst_QJniObject::getStaticCharField()
void tst_QJniObject::getBooleanField()
{
QJniObject obj("org/qtproject/qt/android/QtActivityDelegate");
QJniObject obj(testClassName);
QVERIFY(obj.isValid());
QVERIFY(!obj.getField<jboolean>("m_backKeyPressedSent"));
QVERIFY(obj.getField<jboolean>("BOOL_FIELD"));
}
void tst_QJniObject::getIntField()
{
QJniObject obj("org/qtproject/qt/android/QtActivityDelegate");
QJniObject obj(testClassName);
QVERIFY(obj.isValid());
jint res = obj.getField<jint>("m_currentRotation");
QCOMPARE(res, -1);
jint res = obj.getField<jint>("INT_FIELD");
QCOMPARE(res, 123);
}
template <typename T>