diff options
Diffstat (limited to 'src/android/jar/src')
26 files changed, 706 insertions, 505 deletions
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..690b1ae248 --- /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 +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..dccaf45a05 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; @@ -63,25 +63,19 @@ public class QtActivityBase extends Activity m_applicationParams += params; } - private void handleActivityRestart() { - if (QtNative.getStateDetails().isStarted) { - boolean updated = m_delegate.updateActivityAfterRestart(this); - if (!updated) { - // could not update the activity so restart the application - Intent intent = Intent.makeRestartActivityTask(getComponentName()); - startActivity(intent); - QtNative.quitApp(); - Runtime.getRuntime().exit(0); - } - } - } - @Override public void setTheme(int resId) { super.setTheme(resId); m_isCustomThemeSet = true; } + private void restartApplication() { + Intent intent = Intent.makeRestartActivityTask(getComponentName()); + startActivity(intent); + QtNative.quitApp(); + Runtime.getRuntime().exit(0); + } + @Override protected void onCreate(Bundle savedInstanceState) { @@ -94,9 +88,17 @@ public class QtActivityBase extends Activity android.R.style.Theme_Holo_Light); } + if (QtNative.getStateDetails().isStarted) { + // We don't yet have a reliable way to keep the app + // running properly in case of an Activity only restart, + // so for now restart the whole app. + restartApplication(); + } + m_delegate = new QtActivityDelegate(this); - handleActivityRestart(); + QtNative.registerAppStateListener(this); + addReferrer(getIntent()); QtActivityLoader loader = new QtActivityLoader(this); @@ -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(); @@ -295,6 +306,7 @@ public class QtActivityBase extends Activity @Override protected void onNewIntent(Intent intent) { + addReferrer(intent); QtNative.onNewIntent(intent); } 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..d78c059094 100644 --- a/src/android/jar/src/org/qtproject/qt/android/QtActivityDelegate.java +++ b/src/android/jar/src/org/qtproject/qt/android/QtActivityDelegate.java @@ -9,7 +9,6 @@ import android.app.Activity; import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.content.res.Configuration; -import android.graphics.Color; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.graphics.Rect; @@ -26,24 +25,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 final HashMap<Integer, View> m_nativeViews = new HashMap<>(); + private QtAccessibilityDelegate m_accessibilityDelegate = null; QtActivityDelegate(Activity activity) { @@ -53,17 +52,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); @@ -73,21 +98,6 @@ class QtActivityDelegate extends QtActivityDelegateBase } @Override - public boolean updateActivityAfterRestart(Activity activity) { - boolean updated = super.updateActivityAfterRestart(activity); - // TODO verify whether this is even needed, the last I checked the initMembers - // recreates the layout anyway - // update the new activity content view to old layout - ViewGroup layoutParent = (ViewGroup)m_layout.getParent(); - if (layoutParent != null) - layoutParent.removeView(m_layout); - - m_activity.setContentView(m_layout); - - return updated; - } - - @Override void startNativeApplicationImpl(String appParams, String mainLib) { m_layout.getViewTreeObserver().addOnGlobalLayoutListener( @@ -212,7 +222,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 +282,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 +320,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 +369,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 +387,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 +398,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 +407,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(); @@ -408,7 +457,8 @@ class QtActivityDelegate extends QtActivityDelegateBase QtNative.runAction(() -> { if (m_nativeViews.containsKey(id)) { View view = m_nativeViews.get(id); - view.setLayoutParams(new QtLayout.LayoutParams(w, h, x, y)); + if (view != null) + view.setLayoutParams(new QtLayout.LayoutParams(w, h, x, y)); } else { Log.e(QtTAG, "View " + id + " not found!"); } 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..84a5961b8a 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; } @@ -87,21 +77,6 @@ abstract class QtActivityDelegateBase return m_contextMenuVisible; } - public boolean updateActivityAfterRestart(Activity activity) { - try { - // set new activity - m_activity = activity; - QtNative.setActivity(m_activity); - - // force c++ native activity object to update - return QtNative.updateNativeActivity(); - } catch (Exception e) { - Log.w(QtNative.QtTAG, "Failed to update the activity."); - e.printStackTrace(); - return false; - } - } - public void startNativeApplication(String appParams, String mainLib) { if (m_membersInitialized) @@ -158,62 +133,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 +159,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/QtEditText.java b/src/android/jar/src/org/qtproject/qt/android/QtEditText.java index 4524887242..71b44a81e5 100644 --- a/src/android/jar/src/org/qtproject/qt/android/QtEditText.java +++ b/src/android/jar/src/org/qtproject/qt/android/QtEditText.java @@ -61,7 +61,7 @@ class QtEditText extends View { if (m_imeOptions == imeOptions) return; - m_imeOptions = m_imeOptions; + m_imeOptions = imeOptions; m_optionsChanged = true; } @@ -78,7 +78,7 @@ class QtEditText extends View { if (m_inputType == inputType) return; - m_inputType = m_inputType; + m_inputType = inputType; m_optionsChanged = true; } 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 1c0fd0f7d8..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,21 +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 { +class QtEmbeddedDelegate extends QtActivityDelegateBase + 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 long m_rootWindowRef = 0L; private QtNative.ApplicationStateDetails m_stateDetails; private boolean m_windowLoaded = false; - - private static native void createRootWindow(View rootView, int x, int y, int width, int height); - static native void deleteWindow(long windowReference); + private boolean m_backendsRegistered = false; public QtEmbeddedDelegate(Activity context) { super(context); @@ -78,11 +81,10 @@ class QtEmbeddedDelegate extends QtActivityDelegateBase implements QtNative.AppS 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(); - onDestroy(); } } }); @@ -92,11 +94,28 @@ class QtEmbeddedDelegate extends QtActivityDelegateBase implements QtNative.AppS public void onAppStateDetailsChanged(QtNative.ApplicationStateDetails details) { synchronized (this) { m_stateDetails = details; - if (m_stateDetails.nativePluginIntegrationReady) { + 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); + } + } + } + + @Override + public void onNativePluginIntegrationReadyChanged(boolean ready) + { + synchronized (this) { + if (ready) { QtNative.runAction(() -> { DisplayMetrics metrics = Resources.getSystem().getDisplayMetrics(); - QtDisplayManager.setApplicationDisplayMetrics(m_activity, - metrics.widthPixels, + QtDisplayManager.setApplicationDisplayMetrics(m_activity, metrics.widthPixels, metrics.heightPixels); }); @@ -112,16 +131,7 @@ class QtEmbeddedDelegate extends QtActivityDelegateBase implements QtNative.AppS } @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 @@ -131,6 +141,14 @@ class QtEmbeddedDelegate extends QtActivityDelegateBase implements QtNative.AppS return m_view.getQtWindow(); } + // QtEmbeddedViewInterface implementation begin + @Override + public void startQtApplication(String appParams, String mainLib) + { + super.startNativeApplication(appParams, mainLib); + } + + @Override public void queueLoadWindow() { synchronized (this) { @@ -139,12 +157,15 @@ class QtEmbeddedDelegate extends QtActivityDelegateBase implements QtNative.AppS } } - void setView(QtView view) { + @Override + public void setView(QtView view) + { m_view = view; updateInputDelegate(); if (m_view != null) registerGlobalFocusChangeListener(m_view); } + // QtEmbeddedViewInterface implementation end private void updateInputDelegate() { if (m_view == null) { @@ -154,21 +175,43 @@ class QtEmbeddedDelegate extends QtActivityDelegateBase implements QtNative.AppS m_inputDelegate.setEditPopupMenu(new EditPopupMenu(m_activity, m_view)); } - - public void setRootWindowRef(long ref) { - m_rootWindowRef = ref; - } - - public void onDestroy() { - if (m_rootWindowRef != 0L) - deleteWindow(m_rootWindowRef); - m_rootWindowRef = 0L; - } - private void createRootWindow() { if (m_view != null && !m_windowLoaded) { - createRootWindow(m_view, m_view.getLeft(), m_view.getTop(), m_view.getWidth(), m_view.getHeight()); + QtView.createRootWindow(m_view, m_view.getLeft(), m_view.getTop(), m_view.getWidth(), + m_view.getHeight()); 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 65cfcbeef1..69ecced7ff 100644 --- a/src/android/jar/src/org/qtproject/qt/android/QtEmbeddedLoader.java +++ b/src/android/jar/src/org/qtproject/qt/android/QtEmbeddedLoader.java @@ -41,12 +41,13 @@ class QtEmbeddedLoader extends QtLoader { setEnvironmentVariable("QT_ANDROID_THEME_DISPLAY_DPI", String.valueOf(displayDensity)); String stylePath = ExtractStyle.setup(m_context, "minimal", displayDensity); setEnvironmentVariable("ANDROID_STYLE_PATH", stylePath); + setEnvironmentVariable("QT_ANDROID_NO_EXIT_CALL", String.valueOf(true)); } @Override 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/QtEmbeddedViewInterface.java b/src/android/jar/src/org/qtproject/qt/android/QtEmbeddedViewInterface.java new file mode 100644 index 0000000000..a83a65e32c --- /dev/null +++ b/src/android/jar/src/org/qtproject/qt/android/QtEmbeddedViewInterface.java @@ -0,0 +1,15 @@ +// 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; + +/** + * QtEmbeddedViewInterface is intended to encapsulate the needs of QtView, so that the Activity and + * Service implementations of these functions may be split clearly, and the interface can be stored + * and used conveniently in QtView. +**/ +interface QtEmbeddedViewInterface { + void startQtApplication(String appParams, String mainLib); + void setView(QtView view); + void queueLoadWindow(); +}; 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/QtNative.java b/src/android/jar/src/org/qtproject/qt/android/QtNative.java index 97a45ef8fa..17e1386efb 100644 --- a/src/android/jar/src/org/qtproject/qt/android/QtNative.java +++ b/src/android/jar/src/org/qtproject/qt/android/QtNative.java @@ -30,15 +30,16 @@ import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManagerFactory; import javax.net.ssl.X509TrustManager; -class QtNative +// ### Qt7: make private and find new API for onNewIntent() +public class QtNative { private static WeakReference<Activity> m_activity = null; private static WeakReference<Service> m_service = null; - public static final Object m_mainActivityMutex = new Object(); // mutex used to synchronize runnable operations + private static final Object m_mainActivityMutex = new Object(); // mutex used to synchronize runnable operations private static final ApplicationStateDetails m_stateDetails = new ApplicationStateDetails(); - public static final String QtTAG = "Qt JAVA"; + static final String QtTAG = "Qt JAVA"; // a list containing all actions which could not be performed (e.g. the main activity is destroyed, etc.) private static final ArrayList<Runnable> m_lostActions = new ArrayList<>(); @@ -51,24 +52,24 @@ class QtNative private static final Object m_appStateListenersLock = new Object(); @UsedFromNativeCode - public static ClassLoader classLoader() + static ClassLoader classLoader() { return m_classLoader; } - public static void setClassLoader(ClassLoader classLoader) + static void setClassLoader(ClassLoader classLoader) { m_classLoader = classLoader; } - public static void setActivity(Activity qtMainActivity) + static void setActivity(Activity qtMainActivity) { synchronized (m_mainActivityMutex) { m_activity = new WeakReference<>(qtMainActivity); } } - public static void setService(Service qtMainService) + static void setService(Service qtMainService) { synchronized (m_mainActivityMutex) { m_service = new WeakReference<>(qtMainService); @@ -76,40 +77,40 @@ class QtNative } @UsedFromNativeCode - public static Activity activity() + static Activity activity() { synchronized (m_mainActivityMutex) { return m_activity != null ? m_activity.get() : null; } } - public static boolean isActivityValid() + static boolean isActivityValid() { return m_activity != null && m_activity.get() != null; } @UsedFromNativeCode - public static Service service() + static Service service() { synchronized (m_mainActivityMutex) { return m_service != null ? m_service.get() : null; } } - public static boolean isServiceValid() + static boolean isServiceValid() { return m_service != null && m_service.get() != null; } @UsedFromNativeCode - public static Context getContext() { + static Context getContext() { if (isActivityValid()) return m_activity.get(); return service(); } @UsedFromNativeCode - public static String[] getStringArray(String joinedString) + static String[] getStringArray(String joinedString) { return joinedString.split(","); } @@ -162,7 +163,7 @@ class QtNative } @UsedFromNativeCode - public static boolean openURL(Context context, String url, String mime) + static boolean openURL(Context context, String url, String mime) { final Uri uri = getUriWithValidPermission(context, url, "r"); if (uri == null) { @@ -196,42 +197,44 @@ class QtNative } interface AppStateDetailsListener { - void onAppStateDetailsChanged(ApplicationStateDetails details); + default void onAppStateDetailsChanged(ApplicationStateDetails details) {} + default void onNativePluginIntegrationReadyChanged(boolean ready) {} } // Keep in sync with src/corelib/global/qnamespace.h - public static class ApplicationState { + static class ApplicationState { static final int ApplicationSuspended = 0x0; static final int ApplicationHidden = 0x1; static final int ApplicationInactive = 0x2; static final int ApplicationActive = 0x4; } - public static class ApplicationStateDetails { + static class ApplicationStateDetails { int state = ApplicationState.ApplicationSuspended; boolean nativePluginIntegrationReady = false; boolean isStarted = false; } - public static ApplicationStateDetails getStateDetails() + static ApplicationStateDetails getStateDetails() { return m_stateDetails; } - public static void setStarted(boolean started) + static void setStarted(boolean started) { m_stateDetails.isStarted = started; notifyAppStateDetailsChanged(m_stateDetails); } @UsedFromNativeCode - public static void notifyNativePluginIntegrationReady(boolean ready) + static void notifyNativePluginIntegrationReady(boolean ready) { m_stateDetails.nativePluginIntegrationReady = ready; + notifyNativePluginIntegrationReadyChanged(ready); notifyAppStateDetailsChanged(m_stateDetails); } - public static void setApplicationState(int state) + static void setApplicationState(int state) { synchronized (m_mainActivityMutex) { m_stateDetails.state = state; @@ -258,6 +261,13 @@ class QtNative } } + static void notifyNativePluginIntegrationReadyChanged(boolean ready) { + synchronized (m_appStateListenersLock) { + for (final AppStateDetailsListener listener : m_appStateListeners) + listener.onNativePluginIntegrationReadyChanged(ready); + } + } + static void notifyAppStateDetailsChanged(ApplicationStateDetails details) { synchronized (m_appStateListenersLock) { for (AppStateDetailsListener listener : m_appStateListeners) @@ -267,12 +277,12 @@ class QtNative // Post a runnable to Main (UI) Thread if the app is active, // otherwise, queue it to be posted when the the app is active again - public static void runAction(Runnable action) + static void runAction(Runnable action) { runAction(action, true); } - public static void runAction(Runnable action, boolean queueWhenInactive) + static void runAction(Runnable action, boolean queueWhenInactive) { synchronized (m_mainActivityMutex) { final Looper mainLooper = Looper.getMainLooper(); @@ -319,7 +329,7 @@ class QtNative runAction(() -> view.setVisibility(visible ? View.VISIBLE : View.GONE)); } - public static void startApplication(String params, String mainLib) + static void startApplication(String params, String mainLib) { synchronized (m_mainActivityMutex) { m_qtThread.run(() -> { @@ -334,7 +344,7 @@ class QtNative } } - public static void quitApp() + static void quitApp() { runAction(() -> { quitQtAndroidPlugin(); @@ -343,12 +353,12 @@ class QtNative if (isServiceValid()) m_service.get().stopSelf(); m_stateDetails.isStarted = false; - // Likely no use to call notifyAppStateDetailsChanged at this point since we are exiting + notifyAppStateDetailsChanged(m_stateDetails); }); } @UsedFromNativeCode - public static int checkSelfPermission(String permission) + static int checkSelfPermission(String permission) { synchronized (m_mainActivityMutex) { Context context = getContext(); @@ -410,47 +420,43 @@ class QtNative } // application methods - public static native boolean startQtAndroidPlugin(String params); - public static native void startQtApplication(); - public static native void waitForServiceSetup(); - public static native void quitQtCoreApplication(); - public static native void quitQtAndroidPlugin(); - public static native void terminateQt(); - public static native boolean updateNativeActivity(); + static native boolean startQtAndroidPlugin(String params); + static native void startQtApplication(); + static native void waitForServiceSetup(); + static native void quitQtCoreApplication(); + static native void quitQtAndroidPlugin(); + static native void terminateQt(); + static native boolean updateNativeActivity(); // application methods - // surface methods - public static native void setSurface(int id, Object surface); - // surface methods - // window methods - public static native void updateWindow(); + static native void updateWindow(); // window methods // application methods - public static native void updateApplicationState(int state); + static native void updateApplicationState(int state); // menu methods - public static native boolean onPrepareOptionsMenu(Menu menu); - public static native boolean onOptionsItemSelected(int itemId, boolean checked); - public static native void onOptionsMenuClosed(Menu menu); - - public static native void onCreateContextMenu(ContextMenu menu); - public static native void fillContextMenu(Menu menu); - public static native boolean onContextItemSelected(int itemId, boolean checked); - public static native void onContextMenuClosed(Menu menu); + static native boolean onPrepareOptionsMenu(Menu menu); + static native boolean onOptionsItemSelected(int itemId, boolean checked); + static native void onOptionsMenuClosed(Menu menu); + + static native void onCreateContextMenu(ContextMenu menu); + static native void fillContextMenu(Menu menu); + static native boolean onContextItemSelected(int itemId, boolean checked); + static native void onContextMenuClosed(Menu menu); // menu methods // activity methods - public static native void onActivityResult(int requestCode, int resultCode, Intent data); + static native void onActivityResult(int requestCode, int resultCode, Intent data); public static native void onNewIntent(Intent data); - public static native void runPendingCppRunnables(); + static native void runPendingCppRunnables(); - public static native void sendRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults); + static native void sendRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults); // activity methods // service methods - public static native IBinder onBind(Intent intent); + static native IBinder onBind(Intent intent); // service methods } diff --git a/src/android/jar/src/org/qtproject/qt/android/QtRootLayout.java b/src/android/jar/src/org/qtproject/qt/android/QtRootLayout.java index 3dae587a71..b8743d5ce0 100644 --- a/src/android/jar/src/org/qtproject/qt/android/QtRootLayout.java +++ b/src/android/jar/src/org/qtproject/qt/android/QtRootLayout.java @@ -16,11 +16,8 @@ import android.view.Surface; A layout which corresponds to one Activity, i.e. is the root layout where the top level window and handles orientation changes. */ -public class QtRootLayout extends QtLayout +class QtRootLayout extends QtLayout { - private int m_activityDisplayRotation = -1; - private int m_ownDisplayRotation = -1; - private int m_nativeOrientation = -1; private int m_previousRotation = -1; public QtRootLayout(Context context) @@ -28,21 +25,6 @@ public class QtRootLayout extends QtLayout super(context); } - public void setActivityDisplayRotation(int rotation) - { - m_activityDisplayRotation = rotation; - } - - public void setNativeOrientation(int orientation) - { - m_nativeOrientation = orientation; - } - - public int displayRotation() - { - return m_ownDisplayRotation; - } - @Override protected void onSizeChanged (int w, int h, int oldw, int oldh) { @@ -50,25 +32,6 @@ public class QtRootLayout extends QtLayout if (activity == null) return; - DisplayMetrics realMetrics = new DisplayMetrics(); - Display display = (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) - ? activity.getWindowManager().getDefaultDisplay() - : activity.getDisplay(); - - if (display == null) - return; - - display.getRealMetrics(realMetrics); - if ((realMetrics.widthPixels > realMetrics.heightPixels) != (w > h)) { - // This is an intermediate state during display rotation. - // The new size is still reported for old orientation, while - // realMetrics contain sizes for new orientation. Setting - // such parameters will produce inconsistent results, so - // we just skip them. - // We will have another onSizeChanged() with normal values - // a bit later. - return; - } QtDisplayManager.setApplicationDisplayMetrics(activity, w, h); QtDisplayManager.handleOrientationChanges(activity); } diff --git a/src/android/jar/src/org/qtproject/qt/android/QtServiceEmbeddedDelegate.java b/src/android/jar/src/org/qtproject/qt/android/QtServiceEmbeddedDelegate.java new file mode 100644 index 0000000000..d8af626ca0 --- /dev/null +++ b/src/android/jar/src/org/qtproject/qt/android/QtServiceEmbeddedDelegate.java @@ -0,0 +1,111 @@ +// 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 static org.qtproject.qt.android.QtNative.ApplicationState.ApplicationSuspended; + +import android.app.Service; +import android.content.Context; +import android.content.res.Resources; +import android.hardware.display.DisplayManager; +import android.view.Display; +import android.view.View; +import android.util.DisplayMetrics; + +/** + * QtServiceEmbeddedDelegate is used for embedding QML into Android Service contexts. Implements + * {@link QtEmbeddedViewInterface} so it can be used by QtView to communicate with the Qt layer. + */ +class QtServiceEmbeddedDelegate implements QtEmbeddedViewInterface, QtNative.AppStateDetailsListener +{ + private final Service m_service; + private QtView m_view; + private boolean m_windowLoaded = false; + + QtServiceEmbeddedDelegate(Service service) + { + m_service = service; + QtNative.registerAppStateListener(this); + QtNative.setService(service); + // QTBUG-122920 TODO Implement accessibility for service UIs + // QTBUG-122552 TODO Implement text input + } + + @Override + public void onNativePluginIntegrationReadyChanged(boolean ready) + { + synchronized (this) { + if (ready) { + QtNative.runAction(() -> { + if (m_view == null) + return; + + final DisplayMetrics metrics = Resources.getSystem().getDisplayMetrics(); + + final int maxWidth = m_view.getWidth(); + final int maxHeight = m_view.getHeight(); + final int width = maxWidth; + final int height = maxHeight; + final int insetLeft = m_view.getLeft(); + final int insetTop = m_view.getTop(); + + final DisplayManager dm = m_service.getSystemService(DisplayManager.class); + QtDisplayManager.setDisplayMetrics( + maxWidth, maxHeight, insetLeft, insetTop, width, height, + QtDisplayManager.getXDpi(metrics), QtDisplayManager.getYDpi(metrics), + metrics.scaledDensity, metrics.density, + QtDisplayManager.getRefreshRate( + dm.getDisplay(Display.DEFAULT_DISPLAY))); + }); + createRootWindow(); + } + } + } + + // QtEmbeddedViewInterface implementation begin + @Override + public void startQtApplication(String appParams, String mainLib) + { + QtNative.startApplication(appParams, mainLib); + } + + @Override + public void setView(QtView view) + { + m_view = view; + // If the embedded view is destroyed, do cleanup: + if (view == null) + cleanup(); + } + + @Override + public void queueLoadWindow() + { + synchronized (this) { + if (QtNative.getStateDetails().nativePluginIntegrationReady) + createRootWindow(); + } + } + // QtEmbeddedViewInterface implementation end + + private void createRootWindow() + { + if (m_view != null && !m_windowLoaded) { + QtView.createRootWindow(m_view, m_view.getLeft(), m_view.getTop(), m_view.getWidth(), + m_view.getHeight()); + m_windowLoaded = true; + } + } + + private void cleanup() + { + QtNative.setApplicationState(ApplicationSuspended); + QtNative.unregisterAppStateListener(QtServiceEmbeddedDelegate.this); + QtEmbeddedViewInterfaceFactory.remove(m_service); + + QtNative.terminateQt(); + QtNative.setService(null); + QtNative.getQtThread().exit(); + } +} diff --git a/src/android/jar/src/org/qtproject/qt/android/QtSurface.java b/src/android/jar/src/org/qtproject/qt/android/QtSurface.java index 3165de4811..e20974eeac 100644 --- a/src/android/jar/src/org/qtproject/qt/android/QtSurface.java +++ b/src/android/jar/src/org/qtproject/qt/android/QtSurface.java @@ -14,7 +14,7 @@ import android.view.SurfaceView; @SuppressLint("ViewConstructor") class QtSurface extends SurfaceView implements SurfaceHolder.Callback { - private QtSurfaceInterface m_surfaceCallback; + private final QtSurfaceInterface m_surfaceCallback; public QtSurface(Context context, QtSurfaceInterface surfaceCallback, boolean onTop, int imageDepth) { diff --git a/src/android/jar/src/org/qtproject/qt/android/QtSurfaceInterface.java b/src/android/jar/src/org/qtproject/qt/android/QtSurfaceInterface.java index 8df442f730..5850f2e3a1 100644 --- a/src/android/jar/src/org/qtproject/qt/android/QtSurfaceInterface.java +++ b/src/android/jar/src/org/qtproject/qt/android/QtSurfaceInterface.java @@ -6,8 +6,7 @@ package org.qtproject.qt.android; import android.view.Surface; - -public interface QtSurfaceInterface +interface QtSurfaceInterface { void onSurfaceChanged(Surface surface); } diff --git a/src/android/jar/src/org/qtproject/qt/android/QtTextureView.java b/src/android/jar/src/org/qtproject/qt/android/QtTextureView.java index 828838a9f0..95370f3e4b 100644 --- a/src/android/jar/src/org/qtproject/qt/android/QtTextureView.java +++ b/src/android/jar/src/org/qtproject/qt/android/QtTextureView.java @@ -5,15 +5,14 @@ package org.qtproject.qt.android; import android.content.Context; -import android.graphics.PixelFormat; import android.graphics.SurfaceTexture; import android.util.Log; import android.view.Surface; import android.view.TextureView; -public class QtTextureView extends TextureView implements TextureView.SurfaceTextureListener +class QtTextureView extends TextureView implements TextureView.SurfaceTextureListener { - private QtSurfaceInterface m_surfaceCallback; + private final QtSurfaceInterface m_surfaceCallback; private boolean m_staysOnTop; private Surface m_surface; 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 6836171187..b4fa0382ed 100644 --- a/src/android/jar/src/org/qtproject/qt/android/QtView.java +++ b/src/android/jar/src/org/qtproject/qt/android/QtView.java @@ -29,36 +29,30 @@ abstract class QtView extends ViewGroup { protected QtWindow m_window; protected long m_windowReference; + protected long m_parentWindowReference; protected QtWindowListener m_windowListener; - protected QtEmbeddedDelegate m_delegate; + protected QtEmbeddedViewInterface m_viewInterface; // Implement in subclass to handle the creation of the QWindow and its parent container. // TODO could we take care of the parent window creation and parenting outside of the // window creation method to simplify things if user would extend this? Preferably without // too much JNI back and forth. Related to parent window creation, so handle with QTBUG-121511. abstract protected void createWindow(long parentWindowRef); + static native void createRootWindow(View rootView, int x, int y, int width, int height); + static native void deleteWindow(long windowReference); private static native void setWindowVisible(long windowReference, boolean visible); 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_delegate = 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, @@ -75,24 +69,36 @@ abstract class QtView extends ViewGroup { } } }); - loader.loadQtLibraries(); - // Start Native Qt application - m_delegate.startNativeApplication(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 protected void onAttachedToWindow() { super.onAttachedToWindow(); - m_delegate.setView(this); - m_delegate.queueLoadWindow(); + m_viewInterface.setView(this); + m_viewInterface.queueLoadWindow(); } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); destroyWindow(); - m_delegate.setView(null); + m_viewInterface.setView(null); } @Override @@ -136,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; } @@ -156,7 +171,7 @@ abstract class QtView extends ViewGroup { // viewReference - the reference to the created QQuickView void addQtWindow(QtWindow window, long viewReference, long parentWindowRef) { setWindowReference(viewReference); - m_delegate.setRootWindowRef(parentWindowRef); + m_parentWindowReference = parentWindowRef; final Handler handler = new Handler(Looper.getMainLooper()); handler.post(new Runnable() { @Override @@ -176,9 +191,9 @@ abstract class QtView extends ViewGroup { // Destroy the underlying QWindow void destroyWindow() { - if (m_windowReference != 0L) - QtEmbeddedDelegate.deleteWindow(m_windowReference); - m_windowReference = 0L; + if (m_parentWindowReference != 0L) + deleteWindow(m_parentWindowReference); + m_parentWindowReference = 0L; } QtWindow getQtWindow() { 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..2a9daa5d02 100644 --- a/src/android/jar/src/org/qtproject/qt/android/QtWindow.java +++ b/src/android/jar/src/org/qtproject/qt/android/QtWindow.java @@ -6,7 +6,6 @@ package org.qtproject.qt.android; import android.content.Context; import android.view.GestureDetector; import android.view.MotionEvent; -import android.util.Log; import android.view.Surface; import android.view.View; import android.view.ViewGroup; @@ -14,11 +13,9 @@ import android.view.ViewGroup; import java.util.HashMap; class QtWindow extends QtLayout implements QtSurfaceInterface { - private final static String TAG = "QtWindow"; - private View m_surfaceContainer; private View m_nativeView; - private HashMap<Integer, QtWindow> m_childWindows = new HashMap<Integer, QtWindow>(); + private final HashMap<Integer, QtWindow> m_childWindows = new HashMap<>(); private QtWindow m_parentWindow; private GestureDetector m_gestureDetector; private final QtEditText m_editText; @@ -26,11 +23,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, @@ -39,6 +37,7 @@ class QtWindow extends QtLayout implements QtSurfaceInterface { QtNative.runAction(() -> { m_gestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() { + @Override public void onLongPress(MotionEvent event) { QtInputDelegate.longPress(getId(), (int) event.getX(), (int) event.getY()); } @@ -47,6 +46,7 @@ class QtWindow extends QtLayout implements QtSurfaceInterface { }); } + @UsedFromNativeCode void setVisible(boolean visible) { QtNative.runAction(() -> { if (visible) @@ -85,12 +85,14 @@ class QtWindow extends QtLayout implements QtSurfaceInterface { return QtInputDelegate.sendGenericMotionEvent(event, getId()); } + @UsedFromNativeCode public void removeWindow() { if (m_parentWindow != null) m_parentWindow.removeChildWindow(getId()); } + @UsedFromNativeCode public void createSurface(final boolean onTop, final int x, final int y, final int w, final int h, final int imageDepth, final boolean isOpaque, @@ -116,6 +118,7 @@ class QtWindow extends QtLayout implements QtSurfaceInterface { }); } + @UsedFromNativeCode public void destroySurface() { QtNative.runAction(()-> { @@ -126,6 +129,7 @@ class QtWindow extends QtLayout implements QtSurfaceInterface { }, false); } + @UsedFromNativeCode public void setGeometry(final int x, final int y, final int w, final int h) { QtNative.runAction(()-> { @@ -150,6 +154,7 @@ class QtWindow extends QtLayout implements QtSurfaceInterface { }); } + @UsedFromNativeCode public void setNativeView(final View view, final int x, final int y, final int w, final int h) { @@ -165,6 +170,7 @@ class QtWindow extends QtLayout implements QtSurfaceInterface { }); } + @UsedFromNativeCode public void bringChildToFront(int id) { QtNative.runAction(()-> { @@ -176,6 +182,7 @@ class QtWindow extends QtLayout implements QtSurfaceInterface { }); } + @UsedFromNativeCode public void bringChildToBack(int id) { QtNative.runAction(()-> { View view = m_childWindows.get(id); @@ -185,6 +192,7 @@ class QtWindow extends QtLayout implements QtSurfaceInterface { }); } + @UsedFromNativeCode public void removeNativeView() { QtNative.runAction(()-> { @@ -207,9 +215,4 @@ class QtWindow extends QtLayout implements QtSurfaceInterface { if (m_parentWindow != null) m_parentWindow.addChildWindow(this); } - - QtWindow parent() - { - return m_parentWindow; - } } 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) { } +} |