diff options
Diffstat (limited to 'src/android/jar')
21 files changed, 460 insertions, 331 deletions
diff --git a/src/android/jar/CMakeLists.txt b/src/android/jar/CMakeLists.txt index c36bbdf75b..b786354a21 100644 --- a/src/android/jar/CMakeLists.txt +++ b/src/android/jar/CMakeLists.txt @@ -35,11 +35,17 @@ set(java_sources src/org/qtproject/qt/android/QtWindow.java src/org/qtproject/qt/android/QtActivityDelegateBase.java src/org/qtproject/qt/android/QtEmbeddedDelegate.java - src/org/qtproject/qt/android/QtEmbeddedDelegateFactory.java + src/org/qtproject/qt/android/QtEmbeddedViewInterfaceFactory.java src/org/qtproject/qt/android/QtEmbeddedLoader.java src/org/qtproject/qt/android/QtView.java src/org/qtproject/qt/android/QtEmbeddedViewInterface.java src/org/qtproject/qt/android/QtServiceEmbeddedDelegate.java + src/org/qtproject/qt/android/BackendRegister.java + src/org/qtproject/qt/android/QtWindowInterface.java + src/org/qtproject/qt/android/QtAccessibilityInterface.java + src/org/qtproject/qt/android/QtMenuInterface.java + src/org/qtproject/qt/android/QtLayoutInterface.java + src/org/qtproject/qt/android/QtInputInterface.java ) qt_internal_add_jar(Qt${QtBase_VERSION_MAJOR}Android diff --git a/src/android/jar/build.gradle b/src/android/jar/build.gradle index 452d4ad780..99ad69aeac 100644 --- a/src/android/jar/build.gradle +++ b/src/android/jar/build.gradle @@ -27,7 +27,7 @@ android { namespace "org.qtproject.qt.android" defaultConfig { - minSdkVersion 23 + minSdkVersion 28 } sourceSets { diff --git a/src/android/jar/src/org/qtproject/qt/android/BackendRegister.java b/src/android/jar/src/org/qtproject/qt/android/BackendRegister.java new file mode 100644 index 0000000000..b66a593ec6 --- /dev/null +++ b/src/android/jar/src/org/qtproject/qt/android/BackendRegister.java @@ -0,0 +1,9 @@ +// Copyright (C) 2024 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; + +class BackendRegister +{ + static native void registerBackend(Class interfaceType, Object interfaceObject); + static native void unregisterBackend(Class interfaceType); +} diff --git a/src/android/jar/src/org/qtproject/qt/android/QtAccessibilityDelegate.java b/src/android/jar/src/org/qtproject/qt/android/QtAccessibilityDelegate.java index d23c87e792..8558e42c3b 100644 --- a/src/android/jar/src/org/qtproject/qt/android/QtAccessibilityDelegate.java +++ b/src/android/jar/src/org/qtproject/qt/android/QtAccessibilityDelegate.java @@ -61,6 +61,8 @@ class QtAccessibilityDelegate extends View.AccessibilityDelegate } // TODO do we want to have one QtAccessibilityDelegate for the whole app (QtRootLayout) or // e.g. one per window? + // FIXME make QtAccessibilityDelegate window based or verify current way works + // also for child windows: QTBUG-120685 public QtAccessibilityDelegate(QtLayout layout) { m_layout = layout; diff --git a/src/android/jar/src/org/qtproject/qt/android/QtAccessibilityInterface.java b/src/android/jar/src/org/qtproject/qt/android/QtAccessibilityInterface.java new file mode 100644 index 0000000000..41d0c4c612 --- /dev/null +++ b/src/android/jar/src/org/qtproject/qt/android/QtAccessibilityInterface.java @@ -0,0 +1,14 @@ +// Copyright (C) 2024 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; + +@UsedFromNativeCode +public interface QtAccessibilityInterface { + default void initializeAccessibility() { } + default void notifyLocationChange(int viewId) { } + default void notifyObjectHide(int viewId, int parentId) { } + default void notifyObjectFocus(int viewId) { } + default void notifyScrolledEvent(int viewId) { } + default void notifyValueChanged(int viewId, String value) { } + default void notifyObjectShow(int parentId) { } +} diff --git a/src/android/jar/src/org/qtproject/qt/android/QtActivityBase.java b/src/android/jar/src/org/qtproject/qt/android/QtActivityBase.java index 3cb6ba220e..c11130ebd7 100644 --- a/src/android/jar/src/org/qtproject/qt/android/QtActivityBase.java +++ b/src/android/jar/src/org/qtproject/qt/android/QtActivityBase.java @@ -19,7 +19,7 @@ import android.view.MotionEvent; import android.view.View; import android.view.Window; -public class QtActivityBase extends Activity +public class QtActivityBase extends Activity implements QtNative.AppStateDetailsListener { private String m_applicationParams = ""; private boolean m_isCustomThemeSet = false; @@ -96,6 +96,8 @@ public class QtActivityBase extends Activity m_delegate = new QtActivityDelegate(this); + QtNative.registerAppStateListener(this); + handleActivityRestart(); addReferrer(getIntent()); @@ -108,6 +110,14 @@ public class QtActivityBase extends Activity } @Override + public void onAppStateDetailsChanged(QtNative.ApplicationStateDetails details) { + if (details.isStarted) + m_delegate.registerBackends(); + else + m_delegate.unregisterBackends(); + } + + @Override protected void onStart() { super.onStart(); @@ -153,6 +163,7 @@ public class QtActivityBase extends Activity { super.onDestroy(); if (!m_retainNonConfigurationInstance) { + QtNative.unregisterAppStateListener(this); QtNative.terminateQt(); QtNative.setActivity(null); QtNative.getQtThread().exit(); diff --git a/src/android/jar/src/org/qtproject/qt/android/QtActivityDelegate.java b/src/android/jar/src/org/qtproject/qt/android/QtActivityDelegate.java index 1e1a36be3c..10e40c98df 100644 --- a/src/android/jar/src/org/qtproject/qt/android/QtActivityDelegate.java +++ b/src/android/jar/src/org/qtproject/qt/android/QtActivityDelegate.java @@ -26,24 +26,24 @@ import android.view.Menu; import android.view.View; import android.view.ViewConfiguration; import android.view.ViewGroup; -import android.view.Window; -import android.view.WindowInsetsController; import android.widget.ImageView; import android.widget.PopupMenu; import java.util.HashMap; class QtActivityDelegate extends QtActivityDelegateBase + implements QtWindowInterface, QtAccessibilityInterface, QtMenuInterface, QtLayoutInterface { private static final String QtTAG = "QtActivityDelegate"; private QtRootLayout m_layout = null; private ImageView m_splashScreen = null; private boolean m_splashScreenSticky = false; + private boolean m_backendsRegistered = false; private View m_dummyView = null; private HashMap<Integer, View> m_nativeViews = new HashMap<Integer, View>(); - + private QtAccessibilityDelegate m_accessibilityDelegate = null; QtActivityDelegate(Activity activity) { @@ -53,17 +53,43 @@ class QtActivityDelegate extends QtActivityDelegateBase setActivityBackgroundDrawable(); } + void registerBackends() + { + if (!m_backendsRegistered) { + m_backendsRegistered = true; + BackendRegister.registerBackend(QtWindowInterface.class, + (QtWindowInterface)QtActivityDelegate.this); + BackendRegister.registerBackend(QtAccessibilityInterface.class, + (QtAccessibilityInterface)QtActivityDelegate.this); + BackendRegister.registerBackend(QtMenuInterface.class, + (QtMenuInterface)QtActivityDelegate.this); + BackendRegister.registerBackend(QtLayoutInterface.class, + (QtLayoutInterface)QtActivityDelegate.this); + BackendRegister.registerBackend(QtInputInterface.class, + (QtInputInterface)m_inputDelegate); + } + } + + void unregisterBackends() + { + if (m_backendsRegistered) { + m_backendsRegistered = false; + BackendRegister.unregisterBackend(QtWindowInterface.class); + BackendRegister.unregisterBackend(QtAccessibilityInterface.class); + BackendRegister.unregisterBackend(QtMenuInterface.class); + BackendRegister.unregisterBackend(QtLayoutInterface.class); + BackendRegister.unregisterBackend(QtInputInterface.class); + } + } - @UsedFromNativeCode @Override - QtLayout getQtLayout() + public QtLayout getQtLayout() { return m_layout; } - @UsedFromNativeCode @Override - void setSystemUiVisibility(int systemUiVisibility) + public void setSystemUiVisibility(int systemUiVisibility) { QtNative.runAction(() -> { m_displayManager.setSystemUiVisibility(systemUiVisibility); @@ -212,7 +238,55 @@ class QtActivityDelegate extends QtActivityDelegateBase }); } - @UsedFromNativeCode + @Override + public void notifyLocationChange(int viewId) + { + if (m_accessibilityDelegate == null) + return; + m_accessibilityDelegate.notifyLocationChange(viewId); + } + + @Override + public void notifyObjectHide(int viewId, int parentId) + { + if (m_accessibilityDelegate == null) + return; + m_accessibilityDelegate.notifyObjectHide(viewId, parentId); + } + + @Override + public void notifyObjectShow(int parentId) + { + if (m_accessibilityDelegate == null) + return; + m_accessibilityDelegate.notifyObjectShow(parentId); + } + + @Override + public void notifyObjectFocus(int viewId) + { + if (m_accessibilityDelegate == null) + return; + m_accessibilityDelegate.notifyObjectFocus(viewId); + } + + @Override + public void notifyValueChanged(int viewId, String value) + { + if (m_accessibilityDelegate == null) + return; + m_accessibilityDelegate.notifyValueChanged(viewId, value); + } + + @Override + public void notifyScrolledEvent(int viewId) + { + if (m_accessibilityDelegate == null) + return; + m_accessibilityDelegate.notifyScrolledEvent(viewId); + } + + @Override public void initializeAccessibility() { QtNative.runAction(() -> { @@ -224,27 +298,25 @@ class QtActivityDelegate extends QtActivityDelegateBase }); } - @UsedFromNativeCode + // QtMenuInterface implementation begin + @Override public void resetOptionsMenu() { QtNative.runAction(() -> m_activity.invalidateOptionsMenu()); } - @UsedFromNativeCode + @Override public void openOptionsMenu() { QtNative.runAction(() -> m_activity.openOptionsMenu()); } - private boolean m_contextMenuVisible = false; - - public void onCreatePopupMenu(Menu menu) + @Override + public void closeContextMenu() { - QtNative.fillContextMenu(menu); - m_contextMenuVisible = true; + QtNative.runAction(() -> m_activity.closeContextMenu()); } - @UsedFromNativeCode @Override public void openContextMenu(final int x, final int y, final int w, final int h) { @@ -264,11 +336,14 @@ class QtActivityDelegate extends QtActivityDelegateBase popup.show(); }, 100); } + // QtMenuInterface implementation end - @UsedFromNativeCode - public void closeContextMenu() + private boolean m_contextMenuVisible = false; + + public void onCreatePopupMenu(Menu menu) { - QtNative.runAction(() -> m_activity.closeContextMenu()); + QtNative.fillContextMenu(menu); + m_contextMenuVisible = true; } @Override @@ -310,7 +385,7 @@ class QtActivityDelegate extends QtActivityDelegateBase @UsedFromNativeCode @Override - void removeTopLevelWindow(final int id) + public void removeTopLevelWindow(final int id) { QtNative.runAction(()-> { if (m_topLevelWindows.containsKey(id)) { @@ -328,7 +403,7 @@ class QtActivityDelegate extends QtActivityDelegateBase @UsedFromNativeCode @Override - void bringChildToFront(final int id) + public void bringChildToFront(final int id) { QtNative.runAction(() -> { QtWindow window = m_topLevelWindows.get(id); @@ -339,7 +414,7 @@ class QtActivityDelegate extends QtActivityDelegateBase @UsedFromNativeCode @Override - void bringChildToBack(int id) + public void bringChildToBack(int id) { QtNative.runAction(() -> { QtWindow window = m_topLevelWindows.get(id); @@ -348,16 +423,6 @@ class QtActivityDelegate extends QtActivityDelegateBase }); } - @Override - QtAccessibilityDelegate createAccessibilityDelegate() - { - if (m_layout != null) - return new QtAccessibilityDelegate(m_layout); - - Log.w(QtTAG, "Null layout, failed to initialize accessibility delegate."); - return null; - } - private void setActivityBackgroundDrawable() { TypedValue attr = new TypedValue(); diff --git a/src/android/jar/src/org/qtproject/qt/android/QtActivityDelegateBase.java b/src/android/jar/src/org/qtproject/qt/android/QtActivityDelegateBase.java index 6fd539d8dd..4980a47d08 100644 --- a/src/android/jar/src/org/qtproject/qt/android/QtActivityDelegateBase.java +++ b/src/android/jar/src/org/qtproject/qt/android/QtActivityDelegateBase.java @@ -37,7 +37,6 @@ abstract class QtActivityDelegateBase { protected Activity m_activity; protected HashMap<Integer, QtWindow> m_topLevelWindows; - protected QtAccessibilityDelegate m_accessibilityDelegate = null; protected QtDisplayManager m_displayManager = null; protected QtInputDelegate m_inputDelegate = null; @@ -46,20 +45,12 @@ abstract class QtActivityDelegateBase // Subclass must implement these abstract void startNativeApplicationImpl(String appParams, String mainLib); - abstract QtAccessibilityDelegate createAccessibilityDelegate(); - abstract QtLayout getQtLayout(); // With these we are okay with default implementation doing nothing void setUpLayout() {} void setUpSplashScreen(int orientation) {} void hideSplashScreen(final int duration) {} - void openContextMenu(final int x, final int y, final int w, final int h) {} void setActionBarVisibility(boolean visible) {} - void addTopLevelWindow(final QtWindow window) {} - void removeTopLevelWindow(final int id) {} - void bringChildToFront(final int id) {} - void bringChildToBack(int id) {} - void setSystemUiVisibility(int systemUiVisibility) {} QtActivityDelegateBase(Activity activity) { @@ -72,7 +63,6 @@ abstract class QtActivityDelegateBase return m_displayManager; } - @UsedFromNativeCode QtInputDelegate getInputDelegate() { return m_inputDelegate; } @@ -158,62 +148,6 @@ abstract class QtActivityDelegateBase hideSplashScreen(0); } - @UsedFromNativeCode - public void notifyLocationChange(int viewId) - { - if (m_accessibilityDelegate == null) - return; - m_accessibilityDelegate.notifyLocationChange(viewId); - } - - @UsedFromNativeCode - public void notifyObjectHide(int viewId, int parentId) - { - if (m_accessibilityDelegate == null) - return; - m_accessibilityDelegate.notifyObjectHide(viewId, parentId); - } - - @UsedFromNativeCode - public void notifyObjectShow(int parentId) - { - if (m_accessibilityDelegate == null) - return; - m_accessibilityDelegate.notifyObjectShow(parentId); - } - - @UsedFromNativeCode - public void notifyObjectFocus(int viewId) - { - if (m_accessibilityDelegate == null) - return; - m_accessibilityDelegate.notifyObjectFocus(viewId); - } - - @UsedFromNativeCode - public void notifyValueChanged(int viewId, String value) - { - if (m_accessibilityDelegate == null) - return; - m_accessibilityDelegate.notifyValueChanged(viewId, value); - } - - @UsedFromNativeCode - public void notifyScrolledEvent(int viewId) - { - if (m_accessibilityDelegate == null) - return; - m_accessibilityDelegate.notifyScrolledEvent(viewId); - } - - @UsedFromNativeCode - public void initializeAccessibility() - { - QtNative.runAction(() -> { - m_accessibilityDelegate = createAccessibilityDelegate(); - }); - } - void handleUiModeChange(int uiMode) { // QTBUG-108365 @@ -240,28 +174,4 @@ abstract class QtActivityDelegateBase break; } } - - @UsedFromNativeCode - public void resetOptionsMenu() - { - QtNative.runAction(() -> m_activity.invalidateOptionsMenu()); - } - - @UsedFromNativeCode - public void openOptionsMenu() - { - QtNative.runAction(() -> m_activity.openOptionsMenu()); - } - - public void onCreatePopupMenu(Menu menu) - { - QtNative.fillContextMenu(menu); - m_contextMenuVisible = true; - } - - @UsedFromNativeCode - public void closeContextMenu() - { - QtNative.runAction(() -> m_activity.closeContextMenu()); - } } diff --git a/src/android/jar/src/org/qtproject/qt/android/QtEmbeddedDelegate.java b/src/android/jar/src/org/qtproject/qt/android/QtEmbeddedDelegate.java index ff694777d5..5298ac02bd 100644 --- a/src/android/jar/src/org/qtproject/qt/android/QtEmbeddedDelegate.java +++ b/src/android/jar/src/org/qtproject/qt/android/QtEmbeddedDelegate.java @@ -15,19 +15,24 @@ import android.os.Handler; import android.os.Looper; import android.util.DisplayMetrics; import android.util.Log; +import android.view.Menu; import android.view.View; import android.view.ViewGroup; +import android.widget.PopupMenu; import java.util.ArrayList; import java.util.HashMap; class QtEmbeddedDelegate extends QtActivityDelegateBase - implements QtNative.AppStateDetailsListener, QtEmbeddedViewInterface + implements QtNative.AppStateDetailsListener, QtEmbeddedViewInterface, QtWindowInterface, + QtMenuInterface, QtLayoutInterface { + private static final String QtTAG = "QtEmbeddedDelegate"; // TODO simplistic implementation with one QtView, expand to support multiple views QTBUG-117649 private QtView m_view; private QtNative.ApplicationStateDetails m_stateDetails; private boolean m_windowLoaded = false; + private boolean m_backendsRegistered = false; public QtEmbeddedDelegate(Activity context) { super(context); @@ -76,7 +81,7 @@ class QtEmbeddedDelegate extends QtActivityDelegateBase if (m_activity == activity && m_stateDetails.isStarted) { m_activity.getApplication().unregisterActivityLifecycleCallbacks(this); QtNative.unregisterAppStateListener(QtEmbeddedDelegate.this); - QtEmbeddedDelegateFactory.remove(m_activity); + QtEmbeddedViewInterfaceFactory.remove(m_activity); QtNative.terminateQt(); QtNative.setActivity(null); QtNative.getQtThread().exit(); @@ -89,6 +94,17 @@ class QtEmbeddedDelegate extends QtActivityDelegateBase public void onAppStateDetailsChanged(QtNative.ApplicationStateDetails details) { synchronized (this) { m_stateDetails = details; + if (details.isStarted && !m_backendsRegistered) { + m_backendsRegistered = true; + BackendRegister.registerBackend(QtWindowInterface.class, (QtWindowInterface)this); + BackendRegister.registerBackend(QtMenuInterface.class, (QtMenuInterface)this); + BackendRegister.registerBackend(QtLayoutInterface.class, (QtLayoutInterface)this); + } else if (!details.isStarted && m_backendsRegistered) { + m_backendsRegistered = false; + BackendRegister.unregisterBackend(QtWindowInterface.class); + BackendRegister.unregisterBackend(QtMenuInterface.class); + BackendRegister.unregisterBackend(QtLayoutInterface.class); + } } } @@ -115,16 +131,7 @@ class QtEmbeddedDelegate extends QtActivityDelegateBase } @Override - QtAccessibilityDelegate createAccessibilityDelegate() - { - // FIXME make QtAccessibilityDelegate window based or verify current way works - // also for child windows: QTBUG-120685 - return null; - } - - @UsedFromNativeCode - @Override - QtLayout getQtLayout() + public QtLayout getQtLayout() { // TODO verify if returning m_view here works, this is used by the androidjniinput // when e.g. showing a keyboard, so depends on getting the keyboard focus working @@ -175,4 +182,36 @@ class QtEmbeddedDelegate extends QtActivityDelegateBase m_windowLoaded = true; } } + + // QtMenuInterface implementation begin + @Override + public void resetOptionsMenu() { QtNative.runAction(() -> m_activity.invalidateOptionsMenu()); } + + @Override + public void openOptionsMenu() { QtNative.runAction(() -> m_activity.openOptionsMenu()); } + + @Override + public void closeContextMenu() { QtNative.runAction(() -> m_activity.closeContextMenu()); } + + @Override + public void openContextMenu(final int x, final int y, final int w, final int h) + { + QtLayout layout = getQtLayout(); + layout.postDelayed(() -> { + final QtEditText focusedEditText = m_inputDelegate.getCurrentQtEditText(); + if (focusedEditText == null) { + Log.w(QtTAG, "No focused view when trying to open context menu"); + return; + } + layout.setLayoutParams(focusedEditText, new QtLayout.LayoutParams(w, h, x, y), false); + PopupMenu popup = new PopupMenu(m_activity, focusedEditText); + QtNative.fillContextMenu(popup.getMenu()); + popup.setOnMenuItemClickListener(menuItem -> + m_activity.onContextItemSelected(menuItem)); + popup.setOnDismissListener(popupMenu -> + m_activity.onContextMenuClosed(popupMenu.getMenu())); + popup.show(); + }, 100); + } + // QtMenuInterface implementation end } diff --git a/src/android/jar/src/org/qtproject/qt/android/QtEmbeddedDelegateFactory.java b/src/android/jar/src/org/qtproject/qt/android/QtEmbeddedDelegateFactory.java deleted file mode 100644 index 8cf89e5bc3..0000000000 --- a/src/android/jar/src/org/qtproject/qt/android/QtEmbeddedDelegateFactory.java +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright (C) 2024 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.app.Application; -import android.os.Bundle; - -import java.util.HashMap; - -class QtEmbeddedDelegateFactory { - private static final HashMap<Activity, QtEmbeddedDelegate> m_delegates = new HashMap<>(); - private static final Object m_delegateLock = new Object(); - - @UsedFromNativeCode - public static QtActivityDelegateBase getActivityDelegate(Activity activity) { - synchronized (m_delegateLock) { - return m_delegates.get(activity); - } - } - - public static QtEmbeddedDelegate create(Activity activity) { - synchronized (m_delegateLock) { - if (!m_delegates.containsKey(activity)) - m_delegates.put(activity, new QtEmbeddedDelegate(activity)); - - return m_delegates.get(activity); - } - } - - public static void remove(Activity activity) { - synchronized (m_delegateLock) { - m_delegates.remove(activity); - } - } -} diff --git a/src/android/jar/src/org/qtproject/qt/android/QtEmbeddedLoader.java b/src/android/jar/src/org/qtproject/qt/android/QtEmbeddedLoader.java index 0c6c4b49f0..69ecced7ff 100644 --- a/src/android/jar/src/org/qtproject/qt/android/QtEmbeddedLoader.java +++ b/src/android/jar/src/org/qtproject/qt/android/QtEmbeddedLoader.java @@ -48,6 +48,6 @@ class QtEmbeddedLoader extends QtLoader { protected void finish() { // Called when loading fails - clear the delegate to make sure we don't hold reference // to the embedding Context - QtEmbeddedDelegateFactory.remove((Activity)m_context.getBaseContext()); + QtEmbeddedViewInterfaceFactory.remove((Activity)m_context.getBaseContext()); } } diff --git a/src/android/jar/src/org/qtproject/qt/android/QtEmbeddedViewInterfaceFactory.java b/src/android/jar/src/org/qtproject/qt/android/QtEmbeddedViewInterfaceFactory.java new file mode 100644 index 0000000000..8a5764e93f --- /dev/null +++ b/src/android/jar/src/org/qtproject/qt/android/QtEmbeddedViewInterfaceFactory.java @@ -0,0 +1,34 @@ +// Copyright (C) 2024 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.content.Context; +import android.app.Activity; +import android.app.Service; + +import java.util.HashMap; + +class QtEmbeddedViewInterfaceFactory { + private static final HashMap<Context, QtEmbeddedViewInterface> m_interfaces = new HashMap<>(); + private static final Object m_interfaceLock = new Object(); + + public static QtEmbeddedViewInterface create(Context context) { + synchronized (m_interfaceLock) { + if (!m_interfaces.containsKey(context)) { + if (context instanceof Activity) + m_interfaces.put(context, new QtEmbeddedDelegate((Activity)context)); + else if (context instanceof Service) + m_interfaces.put(context, new QtServiceEmbeddedDelegate((Service)context)); + } + + return m_interfaces.get(context); + } + } + + public static void remove(Context context) { + synchronized (m_interfaceLock) { + m_interfaces.remove(context); + } + } +} diff --git a/src/android/jar/src/org/qtproject/qt/android/QtInputConnection.java b/src/android/jar/src/org/qtproject/qt/android/QtInputConnection.java index 1bfe05e7ac..b95f817d33 100644 --- a/src/android/jar/src/org/qtproject/qt/android/QtInputConnection.java +++ b/src/android/jar/src/org/qtproject/qt/android/QtInputConnection.java @@ -77,6 +77,10 @@ class QtInputConnection extends BaseInputConnection Log.w(QtTAG, "HideKeyboardRunnable: The activity reference is null"); return; } + if (m_qtInputConnectionListener == null) { + Log.w(QtTAG, "HideKeyboardRunnable: QtInputConnectionListener is null"); + return; + } Rect r = new Rect(); activity.getWindow().getDecorView().getWindowVisibleDisplayFrame(r); @@ -109,7 +113,7 @@ class QtInputConnection extends BaseInputConnection { if (closing) m_view.postDelayed(new HideKeyboardRunnable(), 100); - else + else if (m_qtInputConnectionListener != null) m_qtInputConnectionListener.onSetClosing(false); } @@ -297,7 +301,8 @@ class QtInputConnection extends BaseInputConnection restartImmInput(); break; default: - m_qtInputConnectionListener.onSendKeyEventDefaultCase(); + if (m_qtInputConnectionListener != null) + m_qtInputConnectionListener.onSendKeyEventDefaultCase(); break; } } diff --git a/src/android/jar/src/org/qtproject/qt/android/QtInputDelegate.java b/src/android/jar/src/org/qtproject/qt/android/QtInputDelegate.java index cfa273e410..bf5578285a 100644 --- a/src/android/jar/src/org/qtproject/qt/android/QtInputDelegate.java +++ b/src/android/jar/src/org/qtproject/qt/android/QtInputDelegate.java @@ -21,7 +21,8 @@ import android.view.inputmethod.InputMethodManager; import org.qtproject.qt.android.QtInputConnection.QtInputConnectionListener; /** @noinspection FieldCanBeLocal*/ -class QtInputDelegate implements QtInputConnection.QtInputConnectionListener { +class QtInputDelegate implements QtInputConnection.QtInputConnectionListener, QtInputInterface +{ // keyboard methods public static native void keyDown(int key, int unicode, int modifier, boolean autoRepeat); @@ -90,6 +91,140 @@ class QtInputDelegate implements QtInputConnection.QtInputConnectionListener { m_imm = (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE); } + // QtInputInterface implementation begin + @Override + public void updateSelection(final int selStart, final int selEnd, + final int candidatesStart, final int candidatesEnd) + { + QtNative.runAction(() -> { + if (m_imm == null) + return; + + m_imm.updateSelection(m_currentEditText, selStart, selEnd, candidatesStart, candidatesEnd); + }); + } + + @Override + public void showSoftwareKeyboard(Activity activity, QtLayout layout, + final int x, final int y, final int width, final int height, + final int inputHints, final int enterKeyType) + { + QtNative.runAction(() -> { + if (m_imm == null || m_currentEditText == null) + return; + + if (updateSoftInputMode(activity, height)) + return; + + m_currentEditText.setEditTextOptions(enterKeyType, inputHints); + + m_currentEditText.postDelayed(() -> { + m_imm.showSoftInput(m_currentEditText, 0, new ResultReceiver(new Handler()) { + @Override + protected void onReceiveResult(int resultCode, Bundle resultData) { + switch (resultCode) { + case InputMethodManager.RESULT_SHOWN: + QtNativeInputConnection.updateCursorPosition(); + //FALLTHROUGH + case InputMethodManager.RESULT_UNCHANGED_SHOWN: + setKeyboardVisibility(true, System.nanoTime()); + if (m_softInputMode == 0) { + probeForKeyboardHeight(layout, activity, + x, y, width, height, inputHints, enterKeyType); + } + break; + case InputMethodManager.RESULT_HIDDEN: + case InputMethodManager.RESULT_UNCHANGED_HIDDEN: + setKeyboardVisibility(false, System.nanoTime()); + break; + } + } + }); + if (m_currentEditText.m_optionsChanged) { + m_imm.restartInput(m_currentEditText); + m_currentEditText.m_optionsChanged = false; + } + }, 15); + }); + } + + @Override + public int getSelectHandleWidth() + { + int width = 0; + if (m_leftSelectionHandle != null && m_rightSelectionHandle != null) { + width = Math.max(m_leftSelectionHandle.width(), m_rightSelectionHandle.width()); + } else if (m_cursorHandle != null) { + width = m_cursorHandle.width(); + } + return width; + } + + /* called from the C++ code when the position of the cursor or selection handles needs to + be adjusted. + mode is one of QAndroidInputContext::CursorHandleShowMode + */ + @Override + public void updateHandles(Activity activity, QtLayout layout, int mode, + int editX, int editY, int editButtons, + int x1, int y1, int x2, int y2, boolean rtl) + { + QtNative.runAction(() -> updateHandleImpl(activity, layout, mode, editX, editY, editButtons, + x1, y1, x2, y2, rtl)); + } + + @Override + public QtInputConnection.QtInputConnectionListener getInputConnectionListener() + { + return this; + } + + @Override + public void resetSoftwareKeyboard() + { + if (m_imm == null || m_currentEditText == null) + return; + m_currentEditText.postDelayed(() -> { + m_imm.restartInput(m_currentEditText); + m_currentEditText.m_optionsChanged = false; + }, 5); + } + + @Override + public void hideSoftwareKeyboard() + { + m_isKeyboardHidingAnimationOngoing = true; + QtNative.runAction(() -> { + if (m_imm == null || m_currentEditText == null) + return; + + m_imm.hideSoftInputFromWindow(m_currentEditText.getWindowToken(), 0, + new ResultReceiver(new Handler()) { + @Override + protected void onReceiveResult(int resultCode, Bundle resultData) { + switch (resultCode) { + case InputMethodManager.RESULT_SHOWN: + case InputMethodManager.RESULT_UNCHANGED_SHOWN: + setKeyboardVisibility(true, System.nanoTime()); + break; + case InputMethodManager.RESULT_HIDDEN: + case InputMethodManager.RESULT_UNCHANGED_HIDDEN: + setKeyboardVisibility(false, System.nanoTime()); + break; + } + } + }); + }); + } + + // Is the keyboard fully visible i.e. visible and no ongoing animation + @Override + public boolean isSoftwareKeyboardVisible() + { + return isKeyboardVisible() && !m_isKeyboardHidingAnimationOngoing; + } + // QtInputInterface implementation end + // QtInputConnectionListener methods @Override public void onSetClosing(boolean closing) { @@ -113,13 +248,6 @@ class QtInputDelegate implements QtInputConnection.QtInputConnectionListener { return m_keyboardIsVisible; } - // Is the keyboard fully visible i.e. visible and no ongoing animation - @UsedFromNativeCode - public boolean isSoftwareKeyboardVisible() - { - return isKeyboardVisible() && !m_isKeyboardHidingAnimationOngoing; - } - void setSoftInputMode(int inputMode) { m_softInputMode = inputMode; @@ -158,65 +286,11 @@ class QtInputDelegate implements QtInputConnection.QtInputConnectionListener { } - @UsedFromNativeCode - public void resetSoftwareKeyboard() - { - if (m_imm == null || m_currentEditText == null) - return; - m_currentEditText.postDelayed(() -> { - m_imm.restartInput(m_currentEditText); - m_currentEditText.m_optionsChanged = false; - }, 5); - } - void setFocusedView(QtEditText currentEditText) { m_currentEditText = currentEditText; } - public void showSoftwareKeyboard(Activity activity, QtLayout layout, - final int x, final int y, final int width, final int height, - final int inputHints, final int enterKeyType) - { - QtNative.runAction(() -> { - if (m_imm == null || m_currentEditText == null) - return; - - if (updateSoftInputMode(activity, height)) - return; - - m_currentEditText.setEditTextOptions(enterKeyType, inputHints); - - m_currentEditText.postDelayed(() -> { - m_imm.showSoftInput(m_currentEditText, 0, new ResultReceiver(new Handler()) { - @Override - protected void onReceiveResult(int resultCode, Bundle resultData) { - switch (resultCode) { - case InputMethodManager.RESULT_SHOWN: - QtNativeInputConnection.updateCursorPosition(); - //FALLTHROUGH - case InputMethodManager.RESULT_UNCHANGED_SHOWN: - setKeyboardVisibility(true, System.nanoTime()); - if (m_softInputMode == 0) { - probeForKeyboardHeight(layout, activity, - x, y, width, height, inputHints, enterKeyType); - } - break; - case InputMethodManager.RESULT_HIDDEN: - case InputMethodManager.RESULT_UNCHANGED_HIDDEN: - setKeyboardVisibility(false, System.nanoTime()); - break; - } - } - }); - if (m_currentEditText.m_optionsChanged) { - m_imm.restartInput(m_currentEditText); - m_currentEditText.m_optionsChanged = false; - } - }, 15); - }); - } - private boolean updateSoftInputMode(Activity activity, int height) { DisplayMetrics metrics = new DisplayMetrics(); @@ -284,69 +358,6 @@ class QtInputDelegate implements QtInputConnection.QtInputConnectionListener { }, m_probeKeyboardHeightDelayMs); } - public void hideSoftwareKeyboard() - { - m_isKeyboardHidingAnimationOngoing = true; - QtNative.runAction(() -> { - if (m_imm == null || m_currentEditText == null) - return; - - m_imm.hideSoftInputFromWindow(m_currentEditText.getWindowToken(), 0, - new ResultReceiver(new Handler()) { - @Override - protected void onReceiveResult(int resultCode, Bundle resultData) { - switch (resultCode) { - case InputMethodManager.RESULT_SHOWN: - case InputMethodManager.RESULT_UNCHANGED_SHOWN: - setKeyboardVisibility(true, System.nanoTime()); - break; - case InputMethodManager.RESULT_HIDDEN: - case InputMethodManager.RESULT_UNCHANGED_HIDDEN: - setKeyboardVisibility(false, System.nanoTime()); - break; - } - } - }); - }); - } - - @UsedFromNativeCode - public void updateSelection(final int selStart, final int selEnd, - final int candidatesStart, final int candidatesEnd) - { - QtNative.runAction(() -> { - if (m_imm == null) - return; - - m_imm.updateSelection(m_currentEditText, selStart, selEnd, candidatesStart, candidatesEnd); - }); - } - - @UsedFromNativeCode - public int getSelectHandleWidth() - { - int width = 0; - if (m_leftSelectionHandle != null && m_rightSelectionHandle != null) { - width = Math.max(m_leftSelectionHandle.width(), m_rightSelectionHandle.width()); - } else if (m_cursorHandle != null) { - width = m_cursorHandle.width(); - } - return width; - } - - /* called from the C++ code when the position of the cursor or selection handles needs to - be adjusted. - mode is one of QAndroidInputContext::CursorHandleShowMode - */ - @UsedFromNativeCode - public void updateHandles(Activity activity, QtLayout layout, int mode, - int editX, int editY, int editButtons, - int x1, int y1, int x2, int y2, boolean rtl) - { - QtNative.runAction(() -> updateHandleImpl(activity, layout, mode, editX, editY, editButtons, - x1, y1, x2, y2, rtl)); - } - private void updateHandleImpl(Activity activity, QtLayout layout, int mode, int editX, int editY, int editButtons, int x1, int y1, int x2, int y2, boolean rtl) diff --git a/src/android/jar/src/org/qtproject/qt/android/QtInputInterface.java b/src/android/jar/src/org/qtproject/qt/android/QtInputInterface.java new file mode 100644 index 0000000000..1dc4d5fd7f --- /dev/null +++ b/src/android/jar/src/org/qtproject/qt/android/QtInputInterface.java @@ -0,0 +1,21 @@ +// Copyright (C) 2024 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; + +@UsedFromNativeCode +interface QtInputInterface { + void updateSelection(final int selStart, final int selEnd, final int candidatesStart, + final int candidatesEnd); + void showSoftwareKeyboard(Activity activity, QtLayout layout, final int x, final int y, + final int width, final int height, final int inputHints, + final int enterKeyType); + void resetSoftwareKeyboard(); + void hideSoftwareKeyboard(); + boolean isSoftwareKeyboardVisible(); + int getSelectHandleWidth(); + void updateHandles(Activity activity, QtLayout layout, int mode, int editX, int editY, + int editButtons, int x1, int y1, int x2, int y2, boolean rtl); + QtInputConnection.QtInputConnectionListener getInputConnectionListener(); +} diff --git a/src/android/jar/src/org/qtproject/qt/android/QtLayoutInterface.java b/src/android/jar/src/org/qtproject/qt/android/QtLayoutInterface.java new file mode 100644 index 0000000000..8444266893 --- /dev/null +++ b/src/android/jar/src/org/qtproject/qt/android/QtLayoutInterface.java @@ -0,0 +1,8 @@ +// Copyright (C) 2024 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; + +@UsedFromNativeCode +interface QtLayoutInterface { + QtLayout getQtLayout(); +} diff --git a/src/android/jar/src/org/qtproject/qt/android/QtMenuInterface.java b/src/android/jar/src/org/qtproject/qt/android/QtMenuInterface.java new file mode 100644 index 0000000000..556b9a57b9 --- /dev/null +++ b/src/android/jar/src/org/qtproject/qt/android/QtMenuInterface.java @@ -0,0 +1,11 @@ +// Copyright (C) 2024 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; + +@UsedFromNativeCode +interface QtMenuInterface { + void resetOptionsMenu(); + void openOptionsMenu(); + void closeContextMenu(); + void openContextMenu(final int x, final int y, final int w, final int h); +} diff --git a/src/android/jar/src/org/qtproject/qt/android/QtServiceEmbeddedDelegate.java b/src/android/jar/src/org/qtproject/qt/android/QtServiceEmbeddedDelegate.java index 29f1d1790f..d8af626ca0 100644 --- a/src/android/jar/src/org/qtproject/qt/android/QtServiceEmbeddedDelegate.java +++ b/src/android/jar/src/org/qtproject/qt/android/QtServiceEmbeddedDelegate.java @@ -28,13 +28,8 @@ class QtServiceEmbeddedDelegate implements QtEmbeddedViewInterface, QtNative.App m_service = service; QtNative.registerAppStateListener(this); QtNative.setService(service); - } - - @UsedFromNativeCode - QtInputDelegate getInputDelegate() - { - // TODO Implement text input (QTBUG-122552) - return null; + // QTBUG-122920 TODO Implement accessibility for service UIs + // QTBUG-122552 TODO Implement text input } @Override @@ -107,6 +102,7 @@ class QtServiceEmbeddedDelegate implements QtEmbeddedViewInterface, QtNative.App { QtNative.setApplicationState(ApplicationSuspended); QtNative.unregisterAppStateListener(QtServiceEmbeddedDelegate.this); + QtEmbeddedViewInterfaceFactory.remove(m_service); QtNative.terminateQt(); QtNative.setService(null); diff --git a/src/android/jar/src/org/qtproject/qt/android/QtView.java b/src/android/jar/src/org/qtproject/qt/android/QtView.java index ddf70b3b5b..b4fa0382ed 100644 --- a/src/android/jar/src/org/qtproject/qt/android/QtView.java +++ b/src/android/jar/src/org/qtproject/qt/android/QtView.java @@ -44,24 +44,15 @@ abstract class QtView extends ViewGroup { private static native void resizeWindow(long windowReference, int x, int y, int width, int height); - /** - * Create QtView for embedding a QWindow. Instantiating a QtView will load the Qt libraries - * if they have not already been loaded, including the app library specified by appName, and - * starting the said Qt app. - * @param context the hosting Context - * @param appLibName the name of the Qt app library to load and start. This corresponds to the - target name set in Qt app's CMakeLists.txt - **/ - public QtView(Context context, String appLibName) throws InvalidParameterException { + /** + * Create a QtView for embedding a QWindow without loading the Qt libraries or starting + * the Qt app. + * @param context the hosting Context + **/ + public QtView(Context context) { super(context); - if (appLibName == null || appLibName.isEmpty()) { - throw new InvalidParameterException("QtView: argument 'appLibName' may not be empty "+ - "or null"); - } - QtEmbeddedLoader loader = new QtEmbeddedLoader(context); - m_viewInterface = QtEmbeddedDelegateFactory.create((Activity)context); - loader.setMainLibraryName(appLibName); + m_viewInterface = QtEmbeddedViewInterfaceFactory.create(context); addOnLayoutChangeListener(new View.OnLayoutChangeListener() { @Override public void onLayoutChange(View v, int left, int top, int right, int bottom, @@ -78,10 +69,22 @@ abstract class QtView extends ViewGroup { } } }); - loader.loadQtLibraries(); - // Start Native Qt application - m_viewInterface.startQtApplication(loader.getApplicationParameters(), - loader.getMainLibraryPath()); + } + /** + * Create a QtView for embedding a QWindow, and load the Qt libraries if they have not already + * been loaded, including the app library specified by appName, and starting the said Qt app. + * @param context the hosting Context + * @param appLibName the name of the Qt app library to load and start. This corresponds to the + target name set in Qt app's CMakeLists.txt + **/ + public QtView(Context context, String appLibName) throws InvalidParameterException { + this(context); + if (appLibName == null || appLibName.isEmpty()) { + throw new InvalidParameterException("QtView: argument 'appLibName' may not be empty "+ + "or null"); + } + + loadQtLibraries(appLibName); } @Override @@ -139,6 +142,15 @@ abstract class QtView extends ViewGroup { m_windowListener = listener; } + void loadQtLibraries(String appLibName) { + QtEmbeddedLoader loader = new QtEmbeddedLoader(getContext()); + loader.setMainLibraryName(appLibName); + loader.loadQtLibraries(); + // Start Native Qt application + m_viewInterface.startQtApplication(loader.getApplicationParameters(), + loader.getMainLibraryPath()); + } + void setWindowReference(long windowReference) { m_windowReference = windowReference; } diff --git a/src/android/jar/src/org/qtproject/qt/android/QtWindow.java b/src/android/jar/src/org/qtproject/qt/android/QtWindow.java index d72e69d32a..e88309e47e 100644 --- a/src/android/jar/src/org/qtproject/qt/android/QtWindow.java +++ b/src/android/jar/src/org/qtproject/qt/android/QtWindow.java @@ -26,11 +26,12 @@ class QtWindow extends QtLayout implements QtSurfaceInterface { private static native void setSurface(int windowId, Surface surface); static native void windowFocusChanged(boolean hasFocus, int id); - public QtWindow(Context context, QtWindow parentWindow, QtInputDelegate delegate) + public QtWindow(Context context, QtWindow parentWindow, + QtInputConnection.QtInputConnectionListener listener) { super(context); setId(View.generateViewId()); - m_editText = new QtEditText(context, delegate); + m_editText = new QtEditText(context, listener); setParent(parentWindow); setFocusableInTouchMode(true); addView(m_editText, new QtLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, diff --git a/src/android/jar/src/org/qtproject/qt/android/QtWindowInterface.java b/src/android/jar/src/org/qtproject/qt/android/QtWindowInterface.java new file mode 100644 index 0000000000..1fb312786f --- /dev/null +++ b/src/android/jar/src/org/qtproject/qt/android/QtWindowInterface.java @@ -0,0 +1,11 @@ +// Copyright (C) 2024 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; +@UsedFromNativeCode +interface QtWindowInterface { + default void addTopLevelWindow(final QtWindow window) { } + default void removeTopLevelWindow(final int id) { } + default void bringChildToFront(final int id) { } + default void bringChildToBack(int id) { } + default void setSystemUiVisibility(int systemUiVisibility) { } +} |