summaryrefslogtreecommitdiffstats
path: root/src/android/jar
diff options
context:
space:
mode:
Diffstat (limited to 'src/android/jar')
-rw-r--r--src/android/jar/.gitignore3
-rw-r--r--src/android/jar/CMakeLists.txt40
-rw-r--r--src/android/jar/build.gradle7
-rw-r--r--src/android/jar/src/org/qtproject/qt/android/CursorHandle.java131
-rw-r--r--src/android/jar/src/org/qtproject/qt/android/EditContextView.java102
-rw-r--r--src/android/jar/src/org/qtproject/qt/android/EditPopupMenu.java100
-rw-r--r--src/android/jar/src/org/qtproject/qt/android/ExtractStyle.java116
-rw-r--r--src/android/jar/src/org/qtproject/qt/android/QtAccessibilityDelegate.java (renamed from src/android/jar/src/org/qtproject/qt/android/accessibility/QtAccessibilityDelegate.java)324
-rw-r--r--src/android/jar/src/org/qtproject/qt/android/QtActivityBase.java325
-rw-r--r--src/android/jar/src/org/qtproject/qt/android/QtActivityDelegate.java1467
-rw-r--r--src/android/jar/src/org/qtproject/qt/android/QtActivityDelegateBase.java267
-rw-r--r--src/android/jar/src/org/qtproject/qt/android/QtActivityLoader.java151
-rw-r--r--src/android/jar/src/org/qtproject/qt/android/QtApplicationBase.java15
-rw-r--r--src/android/jar/src/org/qtproject/qt/android/QtClipboardManager.java232
-rw-r--r--src/android/jar/src/org/qtproject/qt/android/QtDisplayManager.java287
-rw-r--r--src/android/jar/src/org/qtproject/qt/android/QtEditText.java249
-rw-r--r--src/android/jar/src/org/qtproject/qt/android/QtEmbeddedDelegate.java174
-rw-r--r--src/android/jar/src/org/qtproject/qt/android/QtEmbeddedDelegateFactory.java37
-rw-r--r--src/android/jar/src/org/qtproject/qt/android/QtEmbeddedLoader.java52
-rw-r--r--src/android/jar/src/org/qtproject/qt/android/QtInputConnection.java164
-rw-r--r--src/android/jar/src/org/qtproject/qt/android/QtInputDelegate.java654
-rw-r--r--src/android/jar/src/org/qtproject/qt/android/QtLayout.java118
-rw-r--r--src/android/jar/src/org/qtproject/qt/android/QtLoader.java558
-rw-r--r--src/android/jar/src/org/qtproject/qt/android/QtMessageDialogHelper.java414
-rw-r--r--src/android/jar/src/org/qtproject/qt/android/QtNative.java1328
-rw-r--r--src/android/jar/src/org/qtproject/qt/android/QtNativeAccessibility.java22
-rw-r--r--src/android/jar/src/org/qtproject/qt/android/QtNativeLibrariesDir.java60
-rw-r--r--src/android/jar/src/org/qtproject/qt/android/QtRootLayout.java98
-rw-r--r--src/android/jar/src/org/qtproject/qt/android/QtServiceBase.java51
-rw-r--r--src/android/jar/src/org/qtproject/qt/android/QtServiceDelegate.java206
-rw-r--r--src/android/jar/src/org/qtproject/qt/android/QtServiceLoader.java28
-rw-r--r--src/android/jar/src/org/qtproject/qt/android/QtSurface.java101
-rw-r--r--src/android/jar/src/org/qtproject/qt/android/QtSurfaceInterface.java13
-rw-r--r--src/android/jar/src/org/qtproject/qt/android/QtTextureView.java52
-rw-r--r--src/android/jar/src/org/qtproject/qt/android/QtThread.java65
-rw-r--r--src/android/jar/src/org/qtproject/qt/android/QtView.java187
-rw-r--r--src/android/jar/src/org/qtproject/qt/android/QtWindow.java215
-rw-r--r--src/android/jar/src/org/qtproject/qt/android/UsedFromNativeCode.java10
-rw-r--r--src/android/jar/src/org/qtproject/qt/android/accessibility/QtNativeAccessibility.java58
-rw-r--r--src/android/jar/src/org/qtproject/qt/android/extras/QtAndroidBinder.java45
-rw-r--r--src/android/jar/src/org/qtproject/qt/android/extras/QtAndroidServiceConnection.java45
-rw-r--r--src/android/jar/src/org/qtproject/qt/android/extras/QtNative.java44
42 files changed, 4825 insertions, 3790 deletions
diff --git a/src/android/jar/.gitignore b/src/android/jar/.gitignore
index 364420a59a..e2339f3385 100644
--- a/src/android/jar/.gitignore
+++ b/src/android/jar/.gitignore
@@ -1,6 +1,9 @@
.gradle/
+.settings/
+.project
build/
gradle/
gradlew
gradlew.bat
local.properties
+
diff --git a/src/android/jar/CMakeLists.txt b/src/android/jar/CMakeLists.txt
index 4336c58ae5..698853588c 100644
--- a/src/android/jar/CMakeLists.txt
+++ b/src/android/jar/CMakeLists.txt
@@ -1,32 +1,54 @@
-# Generated from jar.pro.
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
set(java_sources
- src/org/qtproject/qt/android/accessibility/QtAccessibilityDelegate.java
- src/org/qtproject/qt/android/accessibility/QtNativeAccessibility.java
+ src/org/qtproject/qt/android/QtAccessibilityDelegate.java
+ src/org/qtproject/qt/android/QtNativeAccessibility.java
src/org/qtproject/qt/android/CursorHandle.java
src/org/qtproject/qt/android/EditContextView.java
src/org/qtproject/qt/android/EditPopupMenu.java
src/org/qtproject/qt/android/ExtractStyle.java
+ src/org/qtproject/qt/android/QtApplicationBase.java
+ src/org/qtproject/qt/android/QtActivityBase.java
+ src/org/qtproject/qt/android/QtServiceBase.java
src/org/qtproject/qt/android/QtActivityDelegate.java
+ src/org/qtproject/qt/android/QtInputDelegate.java
+ src/org/qtproject/qt/android/QtLoader.java
+ src/org/qtproject/qt/android/QtActivityLoader.java
+ src/org/qtproject/qt/android/QtServiceLoader.java
src/org/qtproject/qt/android/QtEditText.java
src/org/qtproject/qt/android/QtInputConnection.java
src/org/qtproject/qt/android/QtLayout.java
src/org/qtproject/qt/android/QtMessageDialogHelper.java
src/org/qtproject/qt/android/QtNative.java
- src/org/qtproject/qt/android/QtNativeLibrariesDir.java
+ src/org/qtproject/qt/android/QtSurfaceInterface.java
src/org/qtproject/qt/android/QtSurface.java
+ src/org/qtproject/qt/android/QtTextureView.java
src/org/qtproject/qt/android/QtThread.java
- src/org/qtproject/qt/android/QtServiceDelegate.java # special case
+ src/org/qtproject/qt/android/extras/QtAndroidBinder.java
+ src/org/qtproject/qt/android/extras/QtAndroidServiceConnection.java
+ src/org/qtproject/qt/android/extras/QtNative.java
+ src/org/qtproject/qt/android/QtClipboardManager.java
+ src/org/qtproject/qt/android/QtDisplayManager.java
+ src/org/qtproject/qt/android/UsedFromNativeCode.java
+ src/org/qtproject/qt/android/QtRootLayout.java
+ 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/QtEmbeddedLoader.java
+ src/org/qtproject/qt/android/QtView.java
)
-qt_internal_add_jar(Qt${QtBase_VERSION_MAJOR}Android # special case
+qt_internal_add_jar(Qt${QtBase_VERSION_MAJOR}Android
INCLUDE_JARS ${QT_ANDROID_JAR}
SOURCES ${java_sources}
OUTPUT_DIR "${QT_BUILD_DIR}/jar"
)
-install_jar(Qt${QtBase_VERSION_MAJOR}Android # special case
- DESTINATION jar
+qt_path_join(destination ${INSTALL_DATADIR} "jar")
+
+install_jar(Qt${QtBase_VERSION_MAJOR}Android
+ DESTINATION ${destination}
COMPONENT Devel
)
-
diff --git a/src/android/jar/build.gradle b/src/android/jar/build.gradle
index c947852f79..452d4ad780 100644
--- a/src/android/jar/build.gradle
+++ b/src/android/jar/build.gradle
@@ -7,7 +7,7 @@ buildscript {
}
dependencies {
- classpath 'com.android.tools.build:gradle:7.0.2'
+ classpath 'com.android.tools.build:gradle:8.0.2'
}
}
@@ -23,12 +23,11 @@ repositories {
}
android {
- compileSdkVersion 31
- buildToolsVersion "31.0.3"
+ compileSdk 34
+ namespace "org.qtproject.qt.android"
defaultConfig {
minSdkVersion 23
- targetSdkVersion 31
}
sourceSets {
diff --git a/src/android/jar/src/org/qtproject/qt/android/CursorHandle.java b/src/android/jar/src/org/qtproject/qt/android/CursorHandle.java
index 980b172444..7e601c0551 100644
--- a/src/android/jar/src/org/qtproject/qt/android/CursorHandle.java
+++ b/src/android/jar/src/org/qtproject/qt/android/CursorHandle.java
@@ -1,64 +1,28 @@
-/****************************************************************************
-**
-** Copyright (C) 2016 Olivier Goffart <ogoffart@woboq.com>
-** Contact: http://www.qt.io/licensing/
-**
-** This file is part of the Android port of the Qt Toolkit.
-**
-** $QT_BEGIN_LICENSE:LGPL$
-** Commercial License Usage
-** Licensees holding valid commercial Qt licenses may use this file in
-** accordance with the commercial license agreement provided with the
-** Software or, alternatively, in accordance with the terms contained in
-** a written agreement between you and The Qt Company. For licensing terms
-** and conditions see https://www.qt.io/terms-conditions. For further
-** information use the contact form at https://www.qt.io/contact-us.
-**
-** GNU Lesser General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU Lesser
-** General Public License version 3 as published by the Free Software
-** Foundation and appearing in the file LICENSE.LGPL3 included in the
-** packaging of this file. Please review the following information to
-** ensure the GNU Lesser General Public License version 3 requirements
-** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
-**
-** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 2.0 or (at your option) the GNU General
-** Public license version 3 or any later version approved by the KDE Free
-** Qt Foundation. The licenses are as published by the Free Software
-** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
-** included in the packaging of this file. Please review the following
-** information to ensure the GNU General Public License requirements will
-** be met: https://www.gnu.org/licenses/gpl-2.0.html and
-** https://www.gnu.org/licenses/gpl-3.0.html.
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
+// Copyright (C) 2016 Olivier Goffart <ogoffart@woboq.com>
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
package org.qtproject.qt.android;
+import android.annotation.SuppressLint;
+import android.app.Activity;
import android.content.Context;
-import android.os.Bundle;
-import android.util.DisplayMetrics;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.widget.LinearLayout;
-import android.widget.ImageView;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
-import android.view.MotionEvent;
-import android.widget.PopupWindow;
-import android.app.Activity;
+import android.util.DisplayMetrics;
+import android.util.Log;
import android.util.TypedValue;
+import android.view.MotionEvent;
+import android.view.View;
import android.view.ViewTreeObserver;
+import android.widget.ImageView;
+import android.widget.PopupWindow;
/* This view represents one of the handle (selection or cursor handle) */
+@SuppressLint("ViewConstructor")
class CursorView extends ImageView
{
- private CursorHandle mHandle;
- // The coordinare which where clicked
+ private final CursorHandle mHandle;
+ // The coordinate which where clicked
private float m_offsetX;
private float m_offsetY;
private boolean m_pressed = false;
@@ -68,7 +32,7 @@ class CursorView extends ImageView
mHandle = handle;
}
- // Called when the handle was moved programatically , with the delta amount in pixels
+ // Called when the handle was moved programmatically , with the delta amount in pixels
public void adjusted(int dx, int dy) {
m_offsetX += dx;
m_offsetY += dy;
@@ -79,7 +43,7 @@ class CursorView extends ImageView
switch (ev.getActionMasked()) {
case MotionEvent.ACTION_DOWN: {
m_offsetX = ev.getRawX();
- m_offsetY = ev.getRawY() + getHeight() / 2;
+ m_offsetY = ev.getRawY() + (float) getHeight() / 2;
m_pressed = true;
break;
}
@@ -88,7 +52,7 @@ class CursorView extends ImageView
if (!m_pressed)
return false;
mHandle.updatePosition(Math.round(ev.getRawX() - m_offsetX),
- Math.round(ev.getRawY() - m_offsetY));
+ Math.round(ev.getRawY() - m_offsetY));
break;
}
@@ -99,24 +63,24 @@ class CursorView extends ImageView
}
return true;
}
-
}
// Helper class that manages a cursor or selection handle
-public class CursorHandle implements ViewTreeObserver.OnPreDrawListener
+class CursorHandle implements ViewTreeObserver.OnPreDrawListener
{
- private View m_layout = null;
+ private static final String QtTag = "QtCursorHandle";
+ private final View m_layout;
private CursorView m_cursorView = null;
private PopupWindow m_popup = null;
- private int m_id;
- private int m_attr;
- private Activity m_activity;
+ private final int m_id;
+ private final int m_attr;
+ private final Activity m_activity;
private int m_posX = 0;
private int m_posY = 0;
private int m_lastX;
private int m_lastY;
int tolerance;
- private boolean m_rtl;
+ private final boolean m_rtl;
int m_yShift;
public CursorHandle(Activity activity, View layout, int id, int attr, boolean rtl) {
@@ -124,35 +88,38 @@ public class CursorHandle implements ViewTreeObserver.OnPreDrawListener
m_id = id;
m_attr = attr;
m_layout = layout;
- DisplayMetrics metrics = new DisplayMetrics();
- activity.getWindowManager().getDefaultDisplay().getMetrics(metrics);
+ DisplayMetrics metrics = activity.getResources().getDisplayMetrics();
m_yShift = (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_MM, 1f, metrics);
tolerance = Math.min(1, (int)(m_yShift / 2f));
m_lastX = m_lastY = -1 - tolerance;
m_rtl = rtl;
}
- private boolean initOverlay(){
- if (m_popup == null){
+ private void initOverlay(){
+ if (m_popup != null)
+ return;
- Context context = m_layout.getContext();
- int[] attrs = {m_attr};
- TypedArray a = context.getTheme().obtainStyledAttributes(attrs);
- Drawable drawable = a.getDrawable(0);
+ Context context = m_layout.getContext();
+ int[] attrs = {m_attr};
+ TypedArray a = context.getTheme().obtainStyledAttributes(attrs);
+ Drawable drawable = a.getDrawable(0);
- m_cursorView = new CursorView(context, this);
- m_cursorView.setImageDrawable(drawable);
+ m_cursorView = new CursorView(context, this);
+ m_cursorView.setImageDrawable(drawable);
- m_popup = new PopupWindow(context, null, android.R.attr.textSelectHandleWindowStyle);
- m_popup.setSplitTouchEnabled(true);
- m_popup.setClippingEnabled(false);
- m_popup.setContentView(m_cursorView);
+ m_popup = new PopupWindow(context, null, android.R.attr.textSelectHandleWindowStyle);
+ m_popup.setSplitTouchEnabled(true);
+ m_popup.setClippingEnabled(false);
+ m_popup.setContentView(m_cursorView);
+ if (drawable != null) {
m_popup.setWidth(drawable.getIntrinsicWidth());
m_popup.setHeight(drawable.getIntrinsicHeight());
-
- m_layout.getViewTreeObserver().addOnPreDrawListener(this);
+ } else {
+ Log.w(QtTag, "initOverlay(): cannot get width/height for popup " +
+ "from null drawable for attribute " + m_attr);
}
- return true;
+
+ m_layout.getViewTreeObserver().addOnPreDrawListener(this);
}
// Show the handle at a given position (or move it if it is already shown)
@@ -162,16 +129,18 @@ public class CursorHandle implements ViewTreeObserver.OnPreDrawListener
final int[] layoutLocation = new int[2];
m_layout.getLocationOnScreen(layoutLocation);
- // This value is used for handling split screen case
+ // These values are used for handling split screen case
final int[] activityLocation = new int[2];
+ final int[] activityLocationInWindow = new int[2];
m_activity.getWindow().getDecorView().getLocationOnScreen(activityLocation);
+ m_activity.getWindow().getDecorView().getLocationInWindow(activityLocationInWindow);
int x2 = x + layoutLocation[0] - activityLocation[0];
- int y2 = y + layoutLocation[1] + m_yShift - activityLocation[1];
+ int y2 = y + layoutLocation[1] + m_yShift + (activityLocationInWindow[1] - activityLocation[1]);
- if (m_id == QtNative.IdCursorHandle) {
+ if (m_id == QtInputDelegate.IdCursorHandle) {
x2 -= m_popup.getWidth() / 2 ;
- } else if ((m_id == QtNative.IdLeftHandle && !m_rtl) || (m_id == QtNative.IdRightHandle && m_rtl)) {
+ } else if ((m_id == QtInputDelegate.IdLeftHandle && !m_rtl) || (m_id == QtInputDelegate.IdRightHandle && m_rtl)) {
x2 -= m_popup.getWidth() * 3 / 4;
} else {
x2 -= m_popup.getWidth() / 4;
@@ -211,7 +180,7 @@ public class CursorHandle implements ViewTreeObserver.OnPreDrawListener
public void updatePosition(int x, int y) {
y -= m_yShift;
if (Math.abs(m_lastX - x) > tolerance || Math.abs(m_lastY - y) > tolerance) {
- QtNative.handleLocationChanged(m_id, x + m_posX, y + m_posY);
+ QtInputDelegate.handleLocationChanged(m_id, x + m_posX, y + m_posY);
m_lastX = x;
m_lastY = y;
}
diff --git a/src/android/jar/src/org/qtproject/qt/android/EditContextView.java b/src/android/jar/src/org/qtproject/qt/android/EditContextView.java
index c96ab11ffa..fbd32ed98b 100644
--- a/src/android/jar/src/org/qtproject/qt/android/EditContextView.java
+++ b/src/android/jar/src/org/qtproject/qt/android/EditContextView.java
@@ -1,64 +1,30 @@
-/****************************************************************************
-**
-** Copyright (C) 2018 BogDan Vatra <bogdan@kde.org>
-** Contact: http://www.qt.io/licensing/
-**
-** This file is part of the Android port of the Qt Toolkit.
-**
-** $QT_BEGIN_LICENSE:LGPL$
-** Commercial License Usage
-** Licensees holding valid commercial Qt licenses may use this file in
-** accordance with the commercial license agreement provided with the
-** Software or, alternatively, in accordance with the terms contained in
-** a written agreement between you and The Qt Company. For licensing terms
-** and conditions see https://www.qt.io/terms-conditions. For further
-** information use the contact form at https://www.qt.io/contact-us.
-**
-** GNU Lesser General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU Lesser
-** General Public License version 3 as published by the Free Software
-** Foundation and appearing in the file LICENSE.LGPL3 included in the
-** packaging of this file. Please review the following information to
-** ensure the GNU Lesser General Public License version 3 requirements
-** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
-**
-** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 2.0 or (at your option) the GNU General
-** Public license version 3 or any later version approved by the KDE Free
-** Qt Foundation. The licenses are as published by the Free Software
-** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
-** included in the packaging of this file. Please review the following
-** information to ensure the GNU General Public License requirements will
-** be met: https://www.gnu.org/licenses/gpl-2.0.html and
-** https://www.gnu.org/licenses/gpl-3.0.html.
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
+// Copyright (C) 2018 BogDan Vatra <bogdan@kde.org>
+// 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.annotation.SuppressLint;
import android.content.Context;
+import android.graphics.Point;
import android.text.TextUtils;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.TextView;
-import android.R;
import java.util.HashMap;
-public class EditContextView extends LinearLayout implements View.OnClickListener
+@SuppressLint("ViewConstructor")
+class EditContextView extends LinearLayout implements View.OnClickListener
{
- public static final int CUT_BUTTON = 1 << 0;
+ public static final int CUT_BUTTON = 1;
public static final int COPY_BUTTON = 1 << 1;
public static final int PASTE_BUTTON = 1 << 2;
- public static final int SALL_BUTTON = 1 << 3;
+ public static final int SELECT_ALL_BUTTON = 1 << 3;
- HashMap<Integer, ContextButton> m_buttons = new HashMap<Integer, ContextButton>(4);
+ HashMap<Integer, ContextButton> m_buttons = new HashMap<>(4);
OnClickListener m_onClickListener;
public interface OnClickListener
@@ -76,8 +42,10 @@ public class EditContextView extends LinearLayout implements View.OnClickListene
setLayoutParams(new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT, 1));
setGravity(Gravity.CENTER);
- setTextColor(getResources().getColor(R.color.widget_edittext_dark));
- EditContextView.this.setBackground(getResources().getDrawable(R.drawable.editbox_background_normal));
+ setTextColor(getResources().getColor(
+ android.R.color.widget_edittext_dark, context.getTheme()));
+ EditContextView.this.setBackground(getResources().getDrawable(
+ android.R.drawable.editbox_background_normal, context.getTheme()));
float scale = getResources().getDisplayMetrics().density;
int hPadding = (int)(16 * scale + 0.5f);
int vPadding = (int)(8 * scale + 0.5f);
@@ -104,10 +72,38 @@ public class EditContextView extends LinearLayout implements View.OnClickListene
public void updateButtons(int buttonsLayout)
{
- m_buttons.get(R.string.cut).setVisibility((buttonsLayout & CUT_BUTTON) != 0 ? View.VISIBLE : View.GONE);
- m_buttons.get(R.string.copy).setVisibility((buttonsLayout & COPY_BUTTON) != 0 ? View.VISIBLE : View.GONE);
- m_buttons.get(R.string.paste).setVisibility((buttonsLayout & PASTE_BUTTON) != 0 ? View.VISIBLE : View.GONE);
- m_buttons.get(R.string.selectAll).setVisibility((buttonsLayout & SALL_BUTTON) != 0 ? View.VISIBLE : View.GONE);
+ ContextButton button = m_buttons.get(android.R.string.cut);
+ if (button != null)
+ button.setVisibility((buttonsLayout & CUT_BUTTON) != 0 ? View.VISIBLE : View.GONE);
+
+ button = m_buttons.get(android.R.string.copy);
+ if (button != null)
+ button.setVisibility((buttonsLayout & COPY_BUTTON) != 0 ? View.VISIBLE : View.GONE);
+
+ button = m_buttons.get(android.R.string.paste);
+ if (button != null)
+ button.setVisibility((buttonsLayout & PASTE_BUTTON) != 0 ? View.VISIBLE : View.GONE);
+
+ button = m_buttons.get(android.R.string.selectAll);
+ if (button != null)
+ button.setVisibility((buttonsLayout & SELECT_ALL_BUTTON) != 0 ? View.VISIBLE : View.GONE);
+ }
+
+ public Point getCalculatedSize()
+ {
+ Point size = new Point(0, 0);
+ for (ContextButton b : m_buttons.values()) {
+ if (b.getVisibility() == View.VISIBLE) {
+ b.measure(0, 0);
+ size.x += b.getMeasuredWidth();
+ size.y = Math.max(size.y, b.getMeasuredHeight());
+ }
+ }
+
+ size.x += getPaddingLeft() + getPaddingRight();
+ size.y += getPaddingTop() + getPaddingBottom();
+
+ return size;
}
public EditContextView(Context context, OnClickListener onClickListener) {
@@ -115,9 +111,9 @@ public class EditContextView extends LinearLayout implements View.OnClickListene
m_onClickListener = onClickListener;
setLayoutParams(new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
- addButton(R.string.cut);
- addButton(R.string.copy);
- addButton(R.string.paste);
- addButton(R.string.selectAll);
+ addButton(android.R.string.cut);
+ addButton(android.R.string.copy);
+ addButton(android.R.string.paste);
+ addButton(android.R.string.selectAll);
}
}
diff --git a/src/android/jar/src/org/qtproject/qt/android/EditPopupMenu.java b/src/android/jar/src/org/qtproject/qt/android/EditPopupMenu.java
index 99940bbd02..25be522c48 100644
--- a/src/android/jar/src/org/qtproject/qt/android/EditPopupMenu.java
+++ b/src/android/jar/src/org/qtproject/qt/android/EditPopupMenu.java
@@ -1,70 +1,25 @@
-/****************************************************************************
-**
-** Copyright (C) 2018 BogDan Vatra <bogdan@kde.org>
-** Copyright (C) 2016 Olivier Goffart <ogoffart@woboq.com>
-** Contact: http://www.qt.io/licensing/
-**
-** This file is part of the Android port of the Qt Toolkit.
-**
-** $QT_BEGIN_LICENSE:LGPL$
-** Commercial License Usage
-** Licensees holding valid commercial Qt licenses may use this file in
-** accordance with the commercial license agreement provided with the
-** Software or, alternatively, in accordance with the terms contained in
-** a written agreement between you and The Qt Company. For licensing terms
-** and conditions see https://www.qt.io/terms-conditions. For further
-** information use the contact form at https://www.qt.io/contact-us.
-**
-** GNU Lesser General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU Lesser
-** General Public License version 3 as published by the Free Software
-** Foundation and appearing in the file LICENSE.LGPL3 included in the
-** packaging of this file. Please review the following information to
-** ensure the GNU Lesser General Public License version 3 requirements
-** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
-**
-** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 2.0 or (at your option) the GNU General
-** Public license version 3 or any later version approved by the KDE Free
-** Qt Foundation. The licenses are as published by the Free Software
-** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
-** included in the packaging of this file. Please review the following
-** information to ensure the GNU General Public License requirements will
-** be met: https://www.gnu.org/licenses/gpl-2.0.html and
-** https://www.gnu.org/licenses/gpl-3.0.html.
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
+// Copyright (C) 2018 BogDan Vatra <bogdan@kde.org>
+// Copyright (C) 2016 Olivier Goffart <ogoffart@woboq.com>
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
package org.qtproject.qt.android;
+import android.app.Activity;
import android.content.Context;
-import android.os.Bundle;
-import android.util.Log;
-import android.view.LayoutInflater;
+import android.graphics.Point;
import android.view.View;
-import android.widget.LinearLayout;
-import android.widget.ImageView;
-import android.content.res.TypedArray;
-import android.graphics.drawable.Drawable;
-import android.view.MotionEvent;
-import android.widget.PopupWindow;
-import android.app.Activity;
-import android.view.ViewTreeObserver;
-import android.view.View.OnClickListener;
-import android.view.ViewGroup.LayoutParams;
import android.view.ViewGroup;
-import android.R;
+import android.view.ViewTreeObserver;
+import android.widget.PopupWindow;
// Helper class that manages a cursor or selection handle
-public class EditPopupMenu implements ViewTreeObserver.OnPreDrawListener, View.OnLayoutChangeListener,
+class EditPopupMenu implements ViewTreeObserver.OnPreDrawListener, View.OnLayoutChangeListener,
EditContextView.OnClickListener
{
- private View m_layout = null;
- private EditContextView m_view = null;
+ private final View m_layout;
+ private final EditContextView m_view;
private PopupWindow m_popup = null;
+ private final Activity m_activity;
private int m_posX;
private int m_posY;
private int m_buttons;
@@ -74,6 +29,7 @@ public class EditPopupMenu implements ViewTreeObserver.OnPreDrawListener, View.O
public EditPopupMenu(Activity activity, View layout)
{
+ m_activity = activity;
m_view = new EditContextView(activity, this);
m_view.addOnLayoutChangeListener(this);
@@ -103,15 +59,23 @@ public class EditPopupMenu implements ViewTreeObserver.OnPreDrawListener, View.O
initOverlay();
m_view.updateButtons(buttons);
- final int[] location = new int[2];
- m_layout.getLocationOnScreen(location);
+ Point viewSize = m_view.getCalculatedSize();
+
+ final int[] layoutLocation = new int[2];
+ m_layout.getLocationOnScreen(layoutLocation);
+
+ // These values are used for handling split screen case
+ final int[] activityLocation = new int[2];
+ final int[] activityLocationInWindow = new int[2];
+ m_activity.getWindow().getDecorView().getLocationOnScreen(activityLocation);
+ m_activity.getWindow().getDecorView().getLocationInWindow(activityLocationInWindow);
- int x2 = x + location[0];
- int y2 = y + location[1];
+ int x2 = x + layoutLocation[0] - activityLocation[0];
+ int y2 = y + layoutLocation[1] + (activityLocationInWindow[1] - activityLocation[1]);
- x2 -= m_view.getWidth() / 2 ;
+ x2 -= viewSize.x / 2 ;
- y2 -= m_view.getHeight();
+ y2 -= viewSize.y;
if (y2 < 0) {
if (cursorHandle != null) {
y2 = cursorHandle.bottom();
@@ -122,8 +86,8 @@ public class EditPopupMenu implements ViewTreeObserver.OnPreDrawListener, View.O
}
}
- if (m_layout.getWidth() < x + m_view.getWidth() / 2)
- x2 = m_layout.getWidth() - m_view.getWidth();
+ if (m_layout.getWidth() < x + viewSize.x / 2)
+ x2 = m_layout.getWidth() - viewSize.x;
if (x2 < 0)
x2 = 0;
@@ -171,16 +135,16 @@ public class EditPopupMenu implements ViewTreeObserver.OnPreDrawListener, View.O
@Override
public void contextButtonClicked(int buttonId) {
switch (buttonId) {
- case R.string.cut:
+ case android.R.string.cut:
QtNativeInputConnection.cut();
break;
- case R.string.copy:
+ case android.R.string.copy:
QtNativeInputConnection.copy();
break;
- case R.string.paste:
+ case android.R.string.paste:
QtNativeInputConnection.paste();
break;
- case R.string.selectAll:
+ case android.R.string.selectAll:
QtNativeInputConnection.selectAll();
break;
}
diff --git a/src/android/jar/src/org/qtproject/qt/android/ExtractStyle.java b/src/android/jar/src/org/qtproject/qt/android/ExtractStyle.java
index 8c391a7e7b..6780634317 100644
--- a/src/android/jar/src/org/qtproject/qt/android/ExtractStyle.java
+++ b/src/android/jar/src/org/qtproject/qt/android/ExtractStyle.java
@@ -1,48 +1,14 @@
-/****************************************************************************
-**
-** Copyright (C) 2021 The Qt Company Ltd.
-** Copyright (C) 2014 BogDan Vatra <bogdan@kde.org>
-** Contact: https://www.qt.io/licensing/
-**
-** This file is part of the Android port of the Qt Toolkit.
-**
-** $QT_BEGIN_LICENSE:LGPL$
-** Commercial License Usage
-** Licensees holding valid commercial Qt licenses may use this file in
-** accordance with the commercial license agreement provided with the
-** Software or, alternatively, in accordance with the terms contained in
-** a written agreement between you and The Qt Company. For licensing terms
-** and conditions see https://www.qt.io/terms-conditions. For further
-** information use the contact form at https://www.qt.io/contact-us.
-**
-** GNU Lesser General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU Lesser
-** General Public License version 3 as published by the Free Software
-** Foundation and appearing in the file LICENSE.LGPL3 included in the
-** packaging of this file. Please review the following information to
-** ensure the GNU Lesser General Public License version 3 requirements
-** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
-**
-** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 2.0 or (at your option) the GNU General
-** Public license version 3 or any later version approved by the KDE Free
-** Qt Foundation. The licenses are as published by the Free Software
-** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
-** included in the packaging of this file. Please review the following
-** information to ensure the GNU General Public License requirements will
-** be met: https://www.gnu.org/licenses/gpl-2.0.html and
-** https://www.gnu.org/licenses/gpl-3.0.html.
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
+// Copyright (C) 2021 The Qt Company Ltd.
+// Copyright (C) 2014 BogDan Vatra <bogdan@kde.org>
+// 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.annotation.SuppressLint;
+import android.app.Activity;
import android.content.Context;
import android.content.res.ColorStateList;
+import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
@@ -95,7 +61,7 @@ import java.util.Map;
import java.util.Objects;
-public class ExtractStyle {
+class ExtractStyle {
// This used to be retrieved from android.R.styleable.ViewDrawableStates field via reflection,
// but since the access to that is restricted, we need to have hard-coded here.
@@ -171,6 +137,72 @@ public class ExtractStyle {
Context m_context;
private final HashMap<String, DrawableCache> m_drawableCache = new HashMap<>();
+ private static boolean m_missingNormalStyle = false;
+ private static boolean m_missingDarkStyle = false;
+ private static String m_stylePath = null;
+ private static boolean m_extractMinimal = false;
+
+ private static final String QtTAG = "QtExtractStyle";
+
+ private static boolean isUiModeDark(Configuration config)
+ {
+ return (config.uiMode & Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES;
+ }
+
+ public static String setup(Context context, String extractOption, int dpi) {
+
+ String dataDir = context.getApplicationInfo().dataDir;
+ m_stylePath = dataDir + "/qt-reserved-files/android-style/" + dpi + "/";
+
+ if (extractOption.equals("none"))
+ return m_stylePath;
+
+ if (extractOption.isEmpty())
+ extractOption = "minimal";
+
+ if (!extractOption.equals("default") && !extractOption.equals("full")
+ && !extractOption.equals("minimal") && !extractOption.equals("none")) {
+ Log.e(QtTAG, "Invalid extract_android_style option \"" + extractOption
+ + "\", defaulting to \"minimal\"");
+ extractOption = "minimal";
+ }
+
+ // QTBUG-69810: The extraction code will trigger compatibility warnings on Android
+ // SDK version >= 28 when the target SDK version is set to something lower then 28,
+ // so default to "none" and issue a warning if that is the case.
+ if (extractOption.equals("default")) {
+ int targetSdk = context.getApplicationInfo().targetSdkVersion;
+ if (targetSdk < 28 && Build.VERSION.SDK_INT >= 28) {
+ Log.e(QtTAG, "extract_android_style option set to \"none\" when " +
+ "targetSdkVersion is less then 28");
+ extractOption = "none";
+ }
+ }
+
+ boolean darkModeFileMissing = !(new File(m_stylePath + "darkUiMode/style.json").exists());
+ m_missingDarkStyle = Build.VERSION.SDK_INT > 28 && darkModeFileMissing;
+ m_missingNormalStyle = !(new File(m_stylePath + "style.json").exists());
+ m_extractMinimal = extractOption.equals("minimal");
+
+ ExtractStyle.runIfNeeded(context, isUiModeDark(context.getResources().getConfiguration()));
+
+ return m_stylePath;
+ }
+
+ public static void runIfNeeded(Context context, boolean extractDarkMode) {
+ if (m_stylePath == null)
+ return;
+ if (extractDarkMode) {
+ if (m_missingDarkStyle) {
+ new ExtractStyle(context, m_stylePath + "darkUiMode/", m_extractMinimal);
+ m_missingDarkStyle = false;
+ }
+ } else if (m_missingNormalStyle) {
+ new ExtractStyle(context, m_stylePath, m_extractMinimal);
+ m_missingNormalStyle = false;
+ }
+ }
+
public ExtractStyle(Context context, String extractPath, boolean minimal) {
m_minimal = minimal;
m_extractPath = extractPath + "/";
@@ -1103,10 +1135,6 @@ public class ExtractStyle {
return json;
}
- public JSONObject extractTextAppearanceInformation(int styleName, String qtClass, AttributeSet attributeSet) {
- return extractTextAppearanceInformation(styleName, qtClass, android.R.attr.textAppearance, attributeSet);
- }
-
public JSONObject extractTextAppearanceInformation(int styleName, String qtClass) {
return extractTextAppearanceInformation(styleName, qtClass, android.R.attr.textAppearance, null);
}
diff --git a/src/android/jar/src/org/qtproject/qt/android/accessibility/QtAccessibilityDelegate.java b/src/android/jar/src/org/qtproject/qt/android/QtAccessibilityDelegate.java
index e75682c654..d23c87e792 100644
--- a/src/android/jar/src/org/qtproject/qt/android/accessibility/QtAccessibilityDelegate.java
+++ b/src/android/jar/src/org/qtproject/qt/android/QtAccessibilityDelegate.java
@@ -1,71 +1,32 @@
-/****************************************************************************
-**
-** Copyright (C) 2016 The Qt Company Ltd.
-** Contact: https://www.qt.io/licensing/
-**
-** This file is part of the Android port of the Qt Toolkit.
-**
-** $QT_BEGIN_LICENSE:LGPL$
-** Commercial License Usage
-** Licensees holding valid commercial Qt licenses may use this file in
-** accordance with the commercial license agreement provided with the
-** Software or, alternatively, in accordance with the terms contained in
-** a written agreement between you and The Qt Company. For licensing terms
-** and conditions see https://www.qt.io/terms-conditions. For further
-** information use the contact form at https://www.qt.io/contact-us.
-**
-** GNU Lesser General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU Lesser
-** General Public License version 3 as published by the Free Software
-** Foundation and appearing in the file LICENSE.LGPL3 included in the
-** packaging of this file. Please review the following information to
-** ensure the GNU Lesser General Public License version 3 requirements
-** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
-**
-** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 2.0 or (at your option) the GNU General
-** Public license version 3 or any later version approved by the KDE Free
-** Qt Foundation. The licenses are as published by the Free Software
-** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
-** included in the packaging of this file. Please review the following
-** information to ensure the GNU General Public License requirements will
-** be met: https://www.gnu.org/licenses/gpl-2.0.html and
-** https://www.gnu.org/licenses/gpl-3.0.html.
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
-
-
-package org.qtproject.qt.android.accessibility;
-
-import android.accessibilityservice.AccessibilityService;
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+
+package org.qtproject.qt.android;
+
import android.app.Activity;
+import android.content.Context;
import android.graphics.Rect;
+import android.os.Build;
import android.os.Bundle;
+import android.system.Os;
+import android.text.TextUtils;
import android.util.Log;
+import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
-import android.text.TextUtils;
-
-import android.view.accessibility.*;
-import android.view.MotionEvent;
-import android.view.View.OnHoverListener;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityNodeInfo.CollectionInfo;
+import android.view.accessibility.AccessibilityNodeProvider;
-import android.content.Context;
-
-import java.util.LinkedList;
-import java.util.List;
-
-import org.qtproject.qt.android.QtActivityDelegate;
-
-public class QtAccessibilityDelegate extends View.AccessibilityDelegate
+class QtAccessibilityDelegate extends View.AccessibilityDelegate
{
private static final String TAG = "Qt A11Y";
- // Qt uses the upper half of the unsiged integers
+ // Qt uses the upper half of the unsigned integers
// all low positive ints should be fine.
public static final int INVALID_ID = 333; // half evil
@@ -74,10 +35,8 @@ public class QtAccessibilityDelegate extends View.AccessibilityDelegate
private static final String DEFAULT_CLASS_NAME = "$VirtualChild";
private View m_view = null;
- private AccessibilityManager m_manager;
- private QtActivityDelegate m_activityDelegate;
- private Activity m_activity;
- private ViewGroup m_layout;
+ private final AccessibilityManager m_manager;
+ private final QtLayout m_layout;
// The accessible object that currently has the "accessibility focus"
// usually indicated by a yellow rectangle on screen.
@@ -89,6 +48,8 @@ public class QtAccessibilityDelegate extends View.AccessibilityDelegate
// this is because the Android platform window does not take
// the offset of the view on screen into account (eg status bar on top)
private final int[] m_globalOffset = new int[2];
+ private int m_oldOffsetX = 0;
+ private int m_oldOffsetY = 0;
private class HoverEventListener implements View.OnHoverListener
{
@@ -98,14 +59,13 @@ public class QtAccessibilityDelegate extends View.AccessibilityDelegate
return dispatchHoverEvent(event);
}
}
-
- public QtAccessibilityDelegate(Activity activity, ViewGroup layout, QtActivityDelegate activityDelegate)
+ // TODO do we want to have one QtAccessibilityDelegate for the whole app (QtRootLayout) or
+ // e.g. one per window?
+ public QtAccessibilityDelegate(QtLayout layout)
{
- m_activity = activity;
m_layout = layout;
- m_activityDelegate = activityDelegate;
- m_manager = (AccessibilityManager) m_activity.getSystemService(Context.ACCESSIBILITY_SERVICE);
+ m_manager = (AccessibilityManager) m_layout.getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
if (m_manager != null) {
AccessibilityManagerListener accServiceListener = new AccessibilityManagerListener();
if (!m_manager.addAccessibilityStateChangeListener(accServiceListener))
@@ -120,24 +80,29 @@ public class QtAccessibilityDelegate extends View.AccessibilityDelegate
@Override
public void onAccessibilityStateChanged(boolean enabled)
{
+ if (Os.getenv("QT_ANDROID_DISABLE_ACCESSIBILITY") != null)
+ return;
if (enabled) {
- try {
+ try {
View view = m_view;
if (view == null) {
- view = new View(m_activity);
+ view = new View(m_layout.getContext());
view.setId(View.NO_ID);
}
// ### Keep this for debugging for a while. It allows us to visually see that our View
// ### is on top of the surface(s)
- // ColorDrawable color = new ColorDrawable(0x80ff8080); //0xAARRGGBB
- // view.setBackground(color);
+ //noinspection CommentedOutCode
+ {
+ // ColorDrawable color = new ColorDrawable(0x80ff8080); //0xAARRGGBB
+ // view.setBackground(color);
+ }
view.setAccessibilityDelegate(QtAccessibilityDelegate.this);
// if all is fine, add it to the layout
if (m_view == null) {
//m_layout.addAccessibilityView(view);
- m_layout.addView(view, m_activityDelegate.getSurfaceCount(),
+ m_layout.addView(view, m_layout.getChildCount(),
new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
}
m_view = view;
@@ -145,7 +110,7 @@ public class QtAccessibilityDelegate extends View.AccessibilityDelegate
m_view.setOnHoverListener(new HoverEventListener());
} catch (Exception e) {
// Unknown exception means something went wrong.
- Log.w("Qt A11y", "Unknown exception: " + e.toString());
+ Log.w("Qt A11y", "Unknown exception: " + e);
}
} else {
if (m_view != null) {
@@ -181,8 +146,6 @@ public class QtAccessibilityDelegate extends View.AccessibilityDelegate
switch (event.getAction()) {
case MotionEvent.ACTION_HOVER_ENTER:
case MotionEvent.ACTION_HOVER_MOVE:
- setHoveredVirtualViewId(virtualViewId);
- break;
case MotionEvent.ACTION_HOVER_EXIT:
setHoveredVirtualViewId(virtualViewId);
break;
@@ -191,61 +154,126 @@ public class QtAccessibilityDelegate extends View.AccessibilityDelegate
return true;
}
- public void notifyLocationChange()
+ public void notifyScrolledEvent(int viewId)
{
- invalidateVirtualViewId(m_focusedVirtualViewId);
+ QtNative.runAction(() -> sendEventForVirtualViewId(viewId,
+ AccessibilityEvent.TYPE_VIEW_SCROLLED));
}
- public void notifyObjectHide(int viewId)
+ public void notifyLocationChange(int viewId)
{
- // If the object had accessibility focus, we need to clear it.
- // Note: This code is mostly copied from
- // AccessibilityNodeProvider::performAction, but we remove the
- // focus only if the focused view id matches the one that was hidden.
- if (m_focusedVirtualViewId == viewId) {
- m_focusedVirtualViewId = INVALID_ID;
+ QtNative.runAction(() -> {
+ if (m_focusedVirtualViewId == viewId)
+ invalidateVirtualViewId(m_focusedVirtualViewId);
+ });
+ }
+
+ public void notifyObjectHide(int viewId, int parentId)
+ {
+ QtNative.runAction(() -> {
+ // If the object had accessibility focus, we need to clear it.
+ // Note: This code is mostly copied from
+ // AccessibilityNodeProvider::performAction, but we remove the
+ // focus only if the focused view id matches the one that was hidden.
+ if (m_focusedVirtualViewId == viewId) {
+ m_focusedVirtualViewId = INVALID_ID;
+ m_view.invalidate();
+ sendEventForVirtualViewId(viewId,
+ AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED);
+ }
+ // When the object is hidden, we need to notify its parent about
+ // content change, not the hidden object itself
+ invalidateVirtualViewId(parentId);
+ });
+ }
+
+ public void notifyObjectShow(int parentId)
+ {
+ QtNative.runAction(() -> {
+ // When the object is shown, we need to notify its parent about
+ // content change, not the shown object itself
+ invalidateVirtualViewId(parentId);
+ });
+ }
+
+ public void notifyObjectFocus(int viewId)
+ {
+ QtNative.runAction(() -> {
+ if (m_view == null)
+ return;
+ m_focusedVirtualViewId = viewId;
m_view.invalidate();
sendEventForVirtualViewId(viewId,
- AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED);
- }
- // When the object is hidden, we need to notify its parent about
- // content change, not the hidden object itself
- final int parentId = QtNativeAccessibility.parentId(viewId);
- invalidateVirtualViewId(parentId);
+ AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED);
+ });
}
- public void notifyObjectFocus(int viewId)
+ public void notifyValueChanged(int viewId, String value)
{
- if (m_view == null)
- return;
- m_focusedVirtualViewId = viewId;
- m_view.invalidate();
- sendEventForVirtualViewId(viewId,
- AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED);
+ QtNative.runAction(() -> {
+ // Send a TYPE_ANNOUNCEMENT event with the new value
+
+ if ((viewId == INVALID_ID) || !m_manager.isEnabled()) {
+ Log.w(TAG, "notifyValueChanged() for invalid view");
+ return;
+ }
+
+ final ViewGroup group = (ViewGroup) m_view.getParent();
+ if (group == null) {
+ Log.w(TAG, "Could not announce value because ViewGroup was null.");
+ return;
+ }
+
+ final AccessibilityEvent event =
+ AccessibilityEvent.obtain(AccessibilityEvent.TYPE_ANNOUNCEMENT);
+
+ event.setEnabled(true);
+ event.setClassName(m_view.getClass().getName() + DEFAULT_CLASS_NAME);
+
+ event.setContentDescription(value);
+
+ if (event.getText().isEmpty() && TextUtils.isEmpty(event.getContentDescription())) {
+ Log.w(TAG, "No value to announce for " + event.getClassName());
+ return;
+ }
+
+ event.setPackageName(m_view.getContext().getPackageName());
+ event.setSource(m_view, viewId);
+
+ if (!group.requestSendAccessibilityEvent(m_view, event))
+ Log.w(TAG, "Failed to send value change announcement for " + event.getClassName());
+ });
}
- public boolean sendEventForVirtualViewId(int virtualViewId, int eventType)
+ public void sendEventForVirtualViewId(int virtualViewId, int eventType)
{
- if ((virtualViewId == INVALID_ID) || !m_manager.isEnabled()) {
- Log.w(TAG, "sendEventForVirtualViewId for invalid view");
- return false;
- }
+ final AccessibilityEvent event = getEventForVirtualViewId(virtualViewId, eventType);
+ sendAccessibilityEvent(event);
+ }
+
+ public void sendAccessibilityEvent(AccessibilityEvent event)
+ {
+ if (event == null)
+ return;
final ViewGroup group = (ViewGroup) m_view.getParent();
if (group == null) {
Log.w(TAG, "Could not send AccessibilityEvent because group was null. This should really not happen.");
- return false;
+ return;
}
- final AccessibilityEvent event;
- event = getEventForVirtualViewId(virtualViewId, eventType);
- return group.requestSendAccessibilityEvent(m_view, event);
+ group.requestSendAccessibilityEvent(m_view, event);
}
public void invalidateVirtualViewId(int virtualViewId)
{
- if (virtualViewId != INVALID_ID)
- sendEventForVirtualViewId(virtualViewId, AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
+ final AccessibilityEvent event = getEventForVirtualViewId(virtualViewId, AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
+
+ if (event == null)
+ return;
+
+ event.setContentChangeTypes(AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE);
+ sendAccessibilityEvent(event);
}
private void setHoveredVirtualViewId(int virtualViewId)
@@ -262,6 +290,14 @@ public class QtAccessibilityDelegate extends View.AccessibilityDelegate
private AccessibilityEvent getEventForVirtualViewId(int virtualViewId, int eventType)
{
+ if ((virtualViewId == INVALID_ID) || !m_manager.isEnabled()) {
+ Log.w(TAG, "getEventForVirtualViewId for invalid view");
+ return null;
+ }
+
+ if (m_layout.getChildCount() == 0)
+ return null;
+
final AccessibilityEvent event = AccessibilityEvent.obtain(eventType);
event.setEnabled(true);
@@ -276,15 +312,17 @@ public class QtAccessibilityDelegate extends View.AccessibilityDelegate
return event;
}
+ // This can be used for debug by performActionForVirtualViewId()
+ /** @noinspection unused*/
private void dumpNodes(int parentId)
{
Log.i(TAG, "A11Y hierarchy: " + parentId + " parent: " + QtNativeAccessibility.parentId(parentId));
Log.i(TAG, " desc: " + QtNativeAccessibility.descriptionForAccessibleObject(parentId) + " rect: " + QtNativeAccessibility.screenRect(parentId));
Log.i(TAG, " NODE: " + getNodeForVirtualViewId(parentId));
int[] ids = QtNativeAccessibility.childIdListForAccessibleObject(parentId);
- for (int i = 0; i < ids.length; ++i) {
- Log.i(TAG, parentId + " has child: " + ids[i]);
- dumpNodes(ids[i]);
+ for (int id : ids) {
+ Log.i(TAG, parentId + " has child: " + id);
+ dumpNodes(id);
}
}
@@ -321,12 +359,30 @@ public class QtAccessibilityDelegate extends View.AccessibilityDelegate
result.setPackageName(source.getPackageName());
result.setClassName(source.getClassName());
-// Spit out the entire hierarchy for debugging purposes
-// dumpNodes(-1);
+ // Spit out the entire hierarchy for debugging purposes
+ // dumpNodes(-1);
- int[] ids = QtNativeAccessibility.childIdListForAccessibleObject(-1);
- for (int i = 0; i < ids.length; ++i)
- result.addChild(m_view, ids[i]);
+ if (m_layout.getChildCount() != 0) {
+ int[] ids = QtNativeAccessibility.childIdListForAccessibleObject(-1);
+ for (int id : ids)
+ result.addChild(m_view, id);
+ }
+
+ // The offset values have changed, so we need to re-focus the
+ // currently focused item, otherwise it will have an incorrect
+ // focus frame
+ if ((m_oldOffsetX != offsetX) || (m_oldOffsetY != offsetY)) {
+ m_oldOffsetX = offsetX;
+ m_oldOffsetY = offsetY;
+ if (m_focusedVirtualViewId != INVALID_ID) {
+ m_nodeProvider.performAction(m_focusedVirtualViewId,
+ AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS,
+ new Bundle());
+ m_nodeProvider.performAction(m_focusedVirtualViewId,
+ AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS,
+ new Bundle());
+ }
+ }
return result;
}
@@ -338,8 +394,9 @@ public class QtAccessibilityDelegate extends View.AccessibilityDelegate
node.setClassName(m_view.getClass().getName() + DEFAULT_CLASS_NAME);
node.setPackageName(m_view.getContext().getPackageName());
- if (!QtNativeAccessibility.populateNode(virtualViewId, node))
+ if (m_layout.getChildCount() == 0 || !QtNativeAccessibility.populateNode(virtualViewId, node)) {
return node;
+ }
// set only if valid, otherwise we return a node that is invalid and will crash when accessed
node.setSource(m_view, virtualViewId);
@@ -356,29 +413,39 @@ public class QtAccessibilityDelegate extends View.AccessibilityDelegate
screenRect.offset(offsetX, offsetY);
node.setBoundsInScreen(screenRect);
- Rect rectInParent = screenRect;
Rect parentScreenRect = QtNativeAccessibility.screenRect(parentId);
- rectInParent.offset(-parentScreenRect.left, -parentScreenRect.top);
- node.setBoundsInParent(rectInParent);
+ screenRect.offset(-parentScreenRect.left, -parentScreenRect.top);
+ node.setBoundsInParent(screenRect);
// Manage internal accessibility focus state.
if (m_focusedVirtualViewId == virtualViewId) {
node.setAccessibilityFocused(true);
- node.addAction(AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS);
+ node.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_CLEAR_ACCESSIBILITY_FOCUS);
} else {
node.setAccessibilityFocused(false);
- node.addAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS);
+ node.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_ACCESSIBILITY_FOCUS);
+ }
+
+ int[] ids = QtNativeAccessibility.childIdListForAccessibleObject(virtualViewId);
+ for (int id : ids)
+ node.addChild(m_view, id);
+ if (node.isScrollable()) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+ node.setCollectionInfo(new CollectionInfo(ids.length, 1, false));
+ } else {
+ node.setCollectionInfo(CollectionInfo.obtain(ids.length, 1, false));
+ }
}
return node;
}
- private AccessibilityNodeProvider m_nodeProvider = new AccessibilityNodeProvider()
+ private final AccessibilityNodeProvider m_nodeProvider = new AccessibilityNodeProvider()
{
@Override
public AccessibilityNodeInfo createAccessibilityNodeInfo(int virtualViewId)
{
- if (virtualViewId == View.NO_ID) {
+ if (virtualViewId == View.NO_ID || m_layout.getChildCount() == 0) {
return getNodeForView();
}
return getNodeForVirtualViewId(virtualViewId);
@@ -420,16 +487,19 @@ public class QtAccessibilityDelegate extends View.AccessibilityDelegate
return m_view.performAccessibilityAction(action, arguments);
}
}
- handled |= performActionForVirtualViewId(virtualViewId, action, arguments);
+ handled |= performActionForVirtualViewId(virtualViewId, action);
return handled;
}
};
- protected boolean performActionForVirtualViewId(int virtualViewId, int action, Bundle arguments)
+ protected boolean performActionForVirtualViewId(int virtualViewId, int action)
{
-// Log.i(TAG, "ACTION " + action + " on " + virtualViewId);
-// dumpNodes(virtualViewId);
+ //noinspection CommentedOutCode
+ {
+ // Log.i(TAG, "ACTION " + action + " on " + virtualViewId);
+ // dumpNodes(virtualViewId);
+ }
boolean success = false;
switch (action) {
case AccessibilityNodeInfo.ACTION_CLICK:
diff --git a/src/android/jar/src/org/qtproject/qt/android/QtActivityBase.java b/src/android/jar/src/org/qtproject/qt/android/QtActivityBase.java
new file mode 100644
index 0000000000..3cb6ba220e
--- /dev/null
+++ b/src/android/jar/src/org/qtproject/qt/android/QtActivityBase.java
@@ -0,0 +1,325 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+package org.qtproject.qt.android;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.content.res.Configuration;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.provider.Browser;
+import android.view.ContextMenu;
+import android.view.ContextMenu.ContextMenuInfo;
+import android.view.KeyEvent;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.Window;
+
+public class QtActivityBase extends Activity
+{
+ private String m_applicationParams = "";
+ private boolean m_isCustomThemeSet = false;
+ private boolean m_retainNonConfigurationInstance = false;
+
+ private QtActivityDelegate m_delegate;
+
+ public static final String EXTRA_SOURCE_INFO = "org.qtproject.qt.android.sourceInfo";
+
+ private void addReferrer(Intent intent)
+ {
+ if (intent.getExtras() != null && intent.getExtras().getString(EXTRA_SOURCE_INFO) != null)
+ return;
+
+ String browserApplicationId = "";
+ if (intent.getExtras() != null)
+ browserApplicationId = intent.getExtras().getString(Browser.EXTRA_APPLICATION_ID);
+
+ String sourceInformation = "";
+ if (browserApplicationId != null && !browserApplicationId.isEmpty()) {
+ sourceInformation = browserApplicationId;
+ } else {
+ Uri referrer = getReferrer();
+ if (referrer != null)
+ sourceInformation = referrer.toString().replaceFirst("android-app://", "");
+ }
+
+ intent.putExtra(EXTRA_SOURCE_INFO, sourceInformation);
+ }
+
+ // Append any parameters to your application.
+ // Either a whitespace or a tab is accepted as a separator between parameters.
+ /** @noinspection unused*/
+ public void appendApplicationParameters(String params)
+ {
+ if (params == null || params.isEmpty())
+ return;
+
+ if (!m_applicationParams.isEmpty())
+ m_applicationParams += " ";
+ 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;
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState)
+ {
+ super.onCreate(savedInstanceState);
+ requestWindowFeature(Window.FEATURE_ACTION_BAR);
+
+ if (!m_isCustomThemeSet) {
+ setTheme(Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q ?
+ android.R.style.Theme_DeviceDefault_DayNight :
+ android.R.style.Theme_Holo_Light);
+ }
+
+ m_delegate = new QtActivityDelegate(this);
+
+ handleActivityRestart();
+ addReferrer(getIntent());
+
+ QtActivityLoader loader = new QtActivityLoader(this);
+ loader.appendApplicationParameters(m_applicationParams);
+
+ loader.loadQtLibraries();
+ m_delegate.startNativeApplication(loader.getApplicationParameters(),
+ loader.getMainLibraryPath());
+ }
+
+ @Override
+ protected void onStart()
+ {
+ super.onStart();
+ }
+
+ @Override
+ protected void onRestart()
+ {
+ super.onRestart();
+ }
+
+ @Override
+ protected void onPause()
+ {
+ super.onPause();
+ if (Build.VERSION.SDK_INT < 24 || !isInMultiWindowMode())
+ QtNative.setApplicationState(QtNative.ApplicationState.ApplicationInactive);
+ m_delegate.displayManager().unregisterDisplayListener();
+ }
+
+ @Override
+ protected void onResume()
+ {
+ super.onResume();
+ QtNative.setApplicationState(QtNative.ApplicationState.ApplicationActive);
+ if (QtNative.getStateDetails().isStarted) {
+ m_delegate.displayManager().registerDisplayListener();
+ QtNative.updateWindow();
+ // Suspending the app clears the immersive mode, so we need to set it again.
+ m_delegate.displayManager().updateFullScreen();
+ }
+ }
+
+ @Override
+ protected void onStop()
+ {
+ super.onStop();
+ QtNative.setApplicationState(QtNative.ApplicationState.ApplicationSuspended);
+ }
+
+ @Override
+ protected void onDestroy()
+ {
+ super.onDestroy();
+ if (!m_retainNonConfigurationInstance) {
+ QtNative.terminateQt();
+ QtNative.setActivity(null);
+ QtNative.getQtThread().exit();
+ System.exit(0);
+ }
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfig)
+ {
+ super.onConfigurationChanged(newConfig);
+ m_delegate.handleUiModeChange(newConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK);
+ }
+
+ @Override
+ public boolean onContextItemSelected(MenuItem item)
+ {
+ m_delegate.setContextMenuVisible(false);
+ return QtNative.onContextItemSelected(item.getItemId(), item.isChecked());
+ }
+
+ @Override
+ public void onContextMenuClosed(Menu menu)
+ {
+ if (!m_delegate.isContextMenuVisible())
+ return;
+ m_delegate.setContextMenuVisible(false);
+ QtNative.onContextMenuClosed(menu);
+ }
+
+ @Override
+ public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo)
+ {
+ menu.clearHeader();
+ QtNative.onCreateContextMenu(menu);
+ m_delegate.setContextMenuVisible(true);
+ }
+
+ @Override
+ public boolean dispatchKeyEvent(KeyEvent event)
+ {
+ boolean handleResult = m_delegate.getInputDelegate().handleDispatchKeyEvent(event);
+ if (QtNative.getStateDetails().isStarted && handleResult)
+ return true;
+
+ return super.dispatchKeyEvent(event);
+ }
+
+ @Override
+ public boolean dispatchGenericMotionEvent(MotionEvent event)
+ {
+ boolean handled = m_delegate.getInputDelegate().handleDispatchGenericMotionEvent(event);
+ if (QtNative.getStateDetails().isStarted && handled)
+ return true;
+
+ return super.dispatchGenericMotionEvent(event);
+ }
+
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event)
+ {
+ QtNative.ApplicationStateDetails stateDetails = QtNative.getStateDetails();
+ if (!stateDetails.isStarted || !stateDetails.nativePluginIntegrationReady)
+ return false;
+
+ return m_delegate.getInputDelegate().onKeyDown(keyCode, event);
+ }
+
+ @Override
+ public boolean onKeyUp(int keyCode, KeyEvent event)
+ {
+ QtNative.ApplicationStateDetails stateDetails = QtNative.getStateDetails();
+ if (!stateDetails.isStarted || !stateDetails.nativePluginIntegrationReady)
+ return false;
+
+ return m_delegate.getInputDelegate().onKeyUp(keyCode, event);
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu)
+ {
+ menu.clear();
+ return true;
+ }
+
+ @Override
+ public boolean onPrepareOptionsMenu(Menu menu)
+ {
+ boolean res = QtNative.onPrepareOptionsMenu(menu);
+ m_delegate.setActionBarVisibility(res && menu.size() > 0);
+ return res;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item)
+ {
+ return QtNative.onOptionsItemSelected(item.getItemId(), item.isChecked());
+ }
+
+ @Override
+ public void onOptionsMenuClosed(Menu menu)
+ {
+ QtNative.onOptionsMenuClosed(menu);
+ }
+
+ @Override
+ protected void onRestoreInstanceState(Bundle savedInstanceState)
+ {
+ super.onRestoreInstanceState(savedInstanceState);
+ QtNative.setStarted(savedInstanceState.getBoolean("Started"));
+ int savedSystemUiVisibility = savedInstanceState.getInt("SystemUiVisibility");
+ m_delegate.displayManager().setSystemUiVisibility(savedSystemUiVisibility);
+ // FIXME restore all surfaces
+ }
+
+ @Override
+ public Object onRetainNonConfigurationInstance()
+ {
+ super.onRetainNonConfigurationInstance();
+ m_retainNonConfigurationInstance = true;
+ return true;
+ }
+
+ @Override
+ protected void onSaveInstanceState(Bundle outState)
+ {
+ super.onSaveInstanceState(outState);
+ outState.putInt("SystemUiVisibility", m_delegate.displayManager().systemUiVisibility());
+ outState.putBoolean("Started", QtNative.getStateDetails().isStarted);
+ }
+
+ @Override
+ public void onWindowFocusChanged(boolean hasFocus)
+ {
+ super.onWindowFocusChanged(hasFocus);
+ if (hasFocus)
+ m_delegate.displayManager().updateFullScreen();
+ }
+
+ @Override
+ protected void onNewIntent(Intent intent)
+ {
+ QtNative.onNewIntent(intent);
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data)
+ {
+ super.onActivityResult(requestCode, resultCode, data);
+ QtNative.onActivityResult(requestCode, resultCode, data);
+ }
+
+ @Override
+ public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults)
+ {
+ QtNative.sendRequestPermissionsResult(requestCode, permissions, grantResults);
+ }
+
+ @UsedFromNativeCode
+ public void hideSplashScreen(final int duration)
+ {
+ m_delegate.hideSplashScreen(duration);
+ }
+
+ @UsedFromNativeCode
+ QtActivityDelegateBase getActivityDelegate()
+ {
+ return m_delegate;
+ }
+}
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 8cf71597e4..1e1a36be3c 100644
--- a/src/android/jar/src/org/qtproject/qt/android/QtActivityDelegate.java
+++ b/src/android/jar/src/org/qtproject/qt/android/QtActivityDelegate.java
@@ -1,802 +1,155 @@
-/****************************************************************************
-**
-** Copyright (C) 2017 BogDan Vatra <bogdan@kde.org>
-** Copyright (C) 2016 The Qt Company Ltd.
-** Copyright (C) 2016 Olivier Goffart <ogoffart@woboq.com>
-** Contact: https://www.qt.io/licensing/
-**
-** This file is part of the Android port of the Qt Toolkit.
-**
-** $QT_BEGIN_LICENSE:LGPL$
-** Commercial License Usage
-** Licensees holding valid commercial Qt licenses may use this file in
-** accordance with the commercial license agreement provided with the
-** Software or, alternatively, in accordance with the terms contained in
-** a written agreement between you and The Qt Company. For licensing terms
-** and conditions see https://www.qt.io/terms-conditions. For further
-** information use the contact form at https://www.qt.io/contact-us.
-**
-** GNU Lesser General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU Lesser
-** General Public License version 3 as published by the Free Software
-** Foundation and appearing in the file LICENSE.LGPL3 included in the
-** packaging of this file. Please review the following information to
-** ensure the GNU Lesser General Public License version 3 requirements
-** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
-**
-** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 2.0 or (at your option) the GNU General
-** Public license version 3 or any later version approved by the KDE Free
-** Qt Foundation. The licenses are as published by the Free Software
-** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
-** included in the packaging of this file. Please review the following
-** information to ensure the GNU General Public License requirements will
-** be met: https://www.gnu.org/licenses/gpl-2.0.html and
-** https://www.gnu.org/licenses/gpl-3.0.html.
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
+// Copyright (C) 2017 BogDan Vatra <bogdan@kde.org>
+// Copyright (C) 2023 The Qt Company Ltd.
+// Copyright (C) 2016 Olivier Goffart <ogoffart@woboq.com>
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
package org.qtproject.qt.android;
import android.app.Activity;
-import android.content.Context;
-import android.content.Intent;
import android.content.pm.ActivityInfo;
-import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.res.AssetManager;
import android.content.res.Configuration;
+import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.Rect;
-import android.net.LocalServerSocket;
-import android.net.LocalSocket;
import android.os.Build;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.ResultReceiver;
-import android.text.method.MetaKeyKeyListener;
-import android.util.Base64;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.TypedValue;
+import android.view.Display;
+import android.view.ViewTreeObserver;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
-import android.view.ContextMenu;
-import android.view.ContextMenu.ContextMenuInfo;
-import android.view.Display;
-import android.view.KeyCharacterMap;
-import android.view.KeyEvent;
import android.view.Menu;
-import android.view.MenuItem;
-import android.view.MotionEvent;
-import android.view.Surface;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
-import android.view.WindowManager;
-import android.view.inputmethod.InputMethodManager;
-import android.view.ViewTreeObserver;
+import android.view.Window;
+import android.view.WindowInsetsController;
import android.widget.ImageView;
import android.widget.PopupMenu;
-import android.hardware.display.DisplayManager;
-import java.io.BufferedReader;
-import java.io.DataOutputStream;
-import java.io.File;
-import java.io.FileWriter;
-import java.io.InputStreamReader;
-import java.io.IOException;
-import java.lang.reflect.Constructor;
-import java.lang.reflect.Method;
-import java.util.ArrayList;
import java.util.HashMap;
-import java.util.Objects;
-import org.qtproject.qt.android.accessibility.QtAccessibilityDelegate;
-
-public class QtActivityDelegate
+class QtActivityDelegate extends QtActivityDelegateBase
{
- private Activity m_activity = null;
- private Method m_super_dispatchKeyEvent = null;
- private Method m_super_onRestoreInstanceState = null;
- private Method m_super_onRetainNonConfigurationInstance = null;
- private Method m_super_onSaveInstanceState = null;
- private Method m_super_onKeyDown = null;
- private Method m_super_onKeyUp = null;
- private Method m_super_onConfigurationChanged = null;
- private Method m_super_onActivityResult = null;
- private Method m_super_dispatchGenericMotionEvent = null;
- private Method m_super_onWindowFocusChanged = null;
-
- private static final String NATIVE_LIBRARIES_KEY = "native.libraries";
- private static final String BUNDLED_LIBRARIES_KEY = "bundled.libraries";
- private static final String MAIN_LIBRARY_KEY = "main.library";
- private static final String ENVIRONMENT_VARIABLES_KEY = "environment.variables";
- private static final String APPLICATION_PARAMETERS_KEY = "application.parameters";
- private static final String STATIC_INIT_CLASSES_KEY = "static.init.classes";
- private static final String EXTRACT_STYLE_KEY = "extract.android.style";
- private static final String EXTRACT_STYLE_MINIMAL_KEY = "extract.android.style.option";
-
- public static final int SYSTEM_UI_VISIBILITY_NORMAL = 0;
- public static final int SYSTEM_UI_VISIBILITY_FULLSCREEN = 1;
- public static final int SYSTEM_UI_VISIBILITY_TRANSLUCENT = 2;
+ private static final String QtTAG = "QtActivityDelegate";
- private static String m_environmentVariables = null;
- private static String m_applicationParameters = null;
-
- private int m_currentRotation = -1; // undefined
- private int m_nativeOrientation = Configuration.ORIENTATION_UNDEFINED;
-
- private String m_mainLib;
- private long m_metaState;
- private int m_lastChar = 0;
- private int m_softInputMode = 0;
- private int m_systemUiVisibility = SYSTEM_UI_VISIBILITY_NORMAL;
- private boolean m_started = false;
- private HashMap<Integer, QtSurface> m_surfaces = null;
- private HashMap<Integer, View> m_nativeViews = null;
- private QtLayout m_layout = null;
+ private QtRootLayout m_layout = null;
private ImageView m_splashScreen = null;
private boolean m_splashScreenSticky = false;
- private QtEditText m_editText = null;
- private InputMethodManager m_imm = null;
- private boolean m_quitApp = true;
- private View m_dummyView = null;
- private boolean m_keyboardIsVisible = false;
- public boolean m_backKeyPressedSent = false;
- private long m_showHideTimeStamp = System.nanoTime();
- private int m_portraitKeyboardHeight = 0;
- private int m_landscapeKeyboardHeight = 0;
- private int m_probeKeyboardHeightDelay = 50; // ms
- private CursorHandle m_cursorHandle;
- private CursorHandle m_leftSelectionHandle;
- private CursorHandle m_rightSelectionHandle;
- private EditPopupMenu m_editPopupMenu;
- private boolean m_isPluginRunning = false;
- private QtAccessibilityDelegate m_accessibilityDelegate = null;
+ private View m_dummyView = null;
+ private HashMap<Integer, View> m_nativeViews = new HashMap<Integer, View>();
- public void setSystemUiVisibility(int systemUiVisibility)
+ QtActivityDelegate(Activity activity)
{
- if (m_systemUiVisibility == systemUiVisibility)
- return;
-
- m_systemUiVisibility = systemUiVisibility;
+ super(activity);
- int systemUiVisibilityFlags = 0;
- switch (m_systemUiVisibility) {
- case SYSTEM_UI_VISIBILITY_NORMAL:
- m_activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN);
- m_activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
- systemUiVisibilityFlags = View.SYSTEM_UI_FLAG_VISIBLE;
- break;
- case SYSTEM_UI_VISIBILITY_FULLSCREEN:
- m_activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
- m_activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN);
- systemUiVisibilityFlags = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
- | View.SYSTEM_UI_FLAG_LAYOUT_STABLE
- | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
- | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
- | View.SYSTEM_UI_FLAG_FULLSCREEN
- | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
- | View.INVISIBLE;
- break;
- case SYSTEM_UI_VISIBILITY_TRANSLUCENT:
- m_activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN
- | WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION
- | WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
- m_activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
- systemUiVisibilityFlags = View.SYSTEM_UI_FLAG_VISIBLE;
- break;
- };
-
- m_activity.getWindow().getDecorView().setSystemUiVisibility(systemUiVisibilityFlags);
-
- m_layout.requestLayout();
+ setActionBarVisibility(false);
+ setActivityBackgroundDrawable();
}
- public void updateFullScreen()
- {
- if (m_systemUiVisibility == SYSTEM_UI_VISIBILITY_FULLSCREEN) {
- m_systemUiVisibility = SYSTEM_UI_VISIBILITY_NORMAL;
- setSystemUiVisibility(SYSTEM_UI_VISIBILITY_FULLSCREEN);
- }
- }
- public boolean isKeyboardVisible()
+ @UsedFromNativeCode
+ @Override
+ QtLayout getQtLayout()
{
- return m_keyboardIsVisible;
+ return m_layout;
}
- // input method hints - must be kept in sync with QTDIR/src/corelib/global/qnamespace.h
- private final int ImhHiddenText = 0x1;
- private final int ImhSensitiveData = 0x2;
- private final int ImhNoAutoUppercase = 0x4;
- private final int ImhPreferNumbers = 0x8;
- private final int ImhPreferUppercase = 0x10;
- private final int ImhPreferLowercase = 0x20;
- private final int ImhNoPredictiveText = 0x40;
-
- private final int ImhDate = 0x80;
- private final int ImhTime = 0x100;
-
- private final int ImhPreferLatin = 0x200;
-
- private final int ImhMultiLine = 0x400;
-
- private final int ImhDigitsOnly = 0x10000;
- private final int ImhFormattedNumbersOnly = 0x20000;
- private final int ImhUppercaseOnly = 0x40000;
- private final int ImhLowercaseOnly = 0x80000;
- private final int ImhDialableCharactersOnly = 0x100000;
- private final int ImhEmailCharactersOnly = 0x200000;
- private final int ImhUrlCharactersOnly = 0x400000;
- private final int ImhLatinOnly = 0x800000;
-
- // enter key type - must be kept in sync with QTDIR/src/corelib/global/qnamespace.h
- private final int EnterKeyDefault = 0;
- private final int EnterKeyReturn = 1;
- private final int EnterKeyDone = 2;
- private final int EnterKeyGo = 3;
- private final int EnterKeySend = 4;
- private final int EnterKeySearch = 5;
- private final int EnterKeyNext = 6;
- private final int EnterKeyPrevious = 7;
-
- // application state
- public static final int ApplicationSuspended = 0x0;
- public static final int ApplicationHidden = 0x1;
- public static final int ApplicationInactive = 0x2;
- public static final int ApplicationActive = 0x4;
-
-
- public boolean setKeyboardVisibility(boolean visibility, long timeStamp)
+ @UsedFromNativeCode
+ @Override
+ void setSystemUiVisibility(int systemUiVisibility)
{
- if (m_showHideTimeStamp > timeStamp)
- return false;
- m_showHideTimeStamp = timeStamp;
+ QtNative.runAction(() -> {
+ m_displayManager.setSystemUiVisibility(systemUiVisibility);
+ m_layout.requestLayout();
+ QtNative.updateWindow();
+ });
+ }
- if (m_keyboardIsVisible == visibility)
- return false;
- m_keyboardIsVisible = visibility;
- QtNative.keyboardVisibilityUpdated(m_keyboardIsVisible);
+ @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);
- if (visibility == false)
- updateFullScreen(); // Hiding the keyboard clears the immersive mode, so we need to set it again.
+ m_activity.setContentView(m_layout);
- return true;
- }
- public void resetSoftwareKeyboard()
- {
- if (m_imm == null)
- return;
- m_editText.postDelayed(new Runnable() {
- @Override
- public void run() {
- m_imm.restartInput(m_editText);
- m_editText.m_optionsChanged = false;
- }
- }, 5);
+ return updated;
}
- public void showSoftwareKeyboard(final int x, final int y, final int width, final int height, final int inputHints, final int enterKeyType)
+ @Override
+ void startNativeApplicationImpl(String appParams, String mainLib)
{
- if (m_imm == null)
- return;
-
- DisplayMetrics metrics = new DisplayMetrics();
- m_activity.getWindowManager().getDefaultDisplay().getMetrics(metrics);
-
- // If the screen is in portrait mode than we estimate that keyboard height will not be higher than 2/5 of the screen.
- // else than we estimate that keyboard height will not be higher than 2/3 of the screen
- final int visibleHeight;
- if (metrics.widthPixels < metrics.heightPixels)
- visibleHeight = m_portraitKeyboardHeight != 0 ? m_portraitKeyboardHeight : metrics.heightPixels * 3 / 5;
- else
- visibleHeight = m_landscapeKeyboardHeight != 0 ? m_landscapeKeyboardHeight : metrics.heightPixels / 3;
-
- if (m_softInputMode != 0) {
- m_activity.getWindow().setSoftInputMode(m_softInputMode);
- final boolean softInputIsHidden = (m_softInputMode & WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN) != 0;
- if (softInputIsHidden)
- return;
- } else {
- if (height > visibleHeight)
- m_activity.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
- else
- m_activity.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN);
- }
-
- int initialCapsMode = 0;
-
- int imeOptions = android.view.inputmethod.EditorInfo.IME_ACTION_DONE;
-
- switch (enterKeyType) {
- case EnterKeyReturn:
- imeOptions = android.view.inputmethod.EditorInfo.IME_FLAG_NO_ENTER_ACTION;
- break;
- case EnterKeyGo:
- imeOptions = android.view.inputmethod.EditorInfo.IME_ACTION_GO;
- break;
- case EnterKeySend:
- imeOptions = android.view.inputmethod.EditorInfo.IME_ACTION_SEND;
- break;
- case EnterKeySearch:
- imeOptions = android.view.inputmethod.EditorInfo.IME_ACTION_SEARCH;
- break;
- case EnterKeyNext:
- imeOptions = android.view.inputmethod.EditorInfo.IME_ACTION_NEXT;
- break;
- case EnterKeyPrevious:
- imeOptions = android.view.inputmethod.EditorInfo.IME_ACTION_PREVIOUS;
- break;
- }
-
- int inputType = android.text.InputType.TYPE_CLASS_TEXT;
-
- if ((inputHints & (ImhPreferNumbers | ImhDigitsOnly | ImhFormattedNumbersOnly)) != 0) {
- inputType = android.text.InputType.TYPE_CLASS_NUMBER;
- if ((inputHints & ImhFormattedNumbersOnly) != 0) {
- inputType |= (android.text.InputType.TYPE_NUMBER_FLAG_DECIMAL
- | android.text.InputType.TYPE_NUMBER_FLAG_SIGNED);
- }
-
- if ((inputHints & ImhHiddenText) != 0)
- inputType |= android.text.InputType.TYPE_NUMBER_VARIATION_PASSWORD;
- } else if ((inputHints & ImhDialableCharactersOnly) != 0) {
- inputType = android.text.InputType.TYPE_CLASS_PHONE;
- } else if ((inputHints & (ImhDate | ImhTime)) != 0) {
- inputType = android.text.InputType.TYPE_CLASS_DATETIME;
- if ((inputHints & (ImhDate | ImhTime)) != (ImhDate | ImhTime)) {
- if ((inputHints & ImhDate) != 0)
- inputType |= android.text.InputType.TYPE_DATETIME_VARIATION_DATE;
- else
- inputType |= android.text.InputType.TYPE_DATETIME_VARIATION_TIME;
- } // else { TYPE_DATETIME_VARIATION_NORMAL(0) }
- } else { // CLASS_TEXT
- if ((inputHints & ImhHiddenText) != 0) {
- inputType |= android.text.InputType.TYPE_TEXT_VARIATION_PASSWORD;
- } else if ((inputHints & ImhSensitiveData) != 0 ||
- ((inputHints & ImhNoPredictiveText) != 0 &&
- System.getenv("QT_ANDROID_ENABLE_WORKAROUND_TO_DISABLE_PREDICTIVE_TEXT") != null)) {
- inputType |= android.text.InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD;
- } else if ((inputHints & ImhUrlCharactersOnly) != 0) {
- inputType |= android.text.InputType.TYPE_TEXT_VARIATION_URI;
- if (enterKeyType == 0) // not explicitly overridden
- imeOptions = android.view.inputmethod.EditorInfo.IME_ACTION_GO;
- } else if ((inputHints & ImhEmailCharactersOnly) != 0) {
- inputType |= android.text.InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS;
- }
-
- if ((inputHints & ImhMultiLine) != 0) {
- inputType |= android.text.InputType.TYPE_TEXT_FLAG_MULTI_LINE;
- // Clear imeOptions for Multi-Line Type
- // User should be able to insert new line in such case
- imeOptions = android.view.inputmethod.EditorInfo.IME_ACTION_DONE;
- }
- if ((inputHints & (ImhNoPredictiveText | ImhSensitiveData | ImhHiddenText)) != 0)
- inputType |= android.text.InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS;
-
- if ((inputHints & ImhUppercaseOnly) != 0) {
- initialCapsMode |= android.text.TextUtils.CAP_MODE_CHARACTERS;
- inputType |= android.text.InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS;
- } else if ((inputHints & ImhLowercaseOnly) == 0 && (inputHints & ImhNoAutoUppercase) == 0) {
- initialCapsMode |= android.text.TextUtils.CAP_MODE_SENTENCES;
- inputType |= android.text.InputType.TYPE_TEXT_FLAG_CAP_SENTENCES;
- }
- }
-
- if (enterKeyType == 0 && (inputHints & ImhMultiLine) != 0)
- imeOptions = android.view.inputmethod.EditorInfo.IME_FLAG_NO_ENTER_ACTION;
-
- m_editText.setInitialCapsMode(initialCapsMode);
- m_editText.setImeOptions(imeOptions);
- m_editText.setInputType(inputType);
-
- m_layout.setLayoutParams(m_editText, new QtLayout.LayoutParams(width, height, x, y), false);
- m_editText.requestFocus();
- m_editText.postDelayed(new Runnable() {
- @Override
- public void run() {
- m_imm.showSoftInput(m_editText, 0, new ResultReceiver(new Handler()) {
+ m_layout.getViewTreeObserver().addOnGlobalLayoutListener(
+ new ViewTreeObserver.OnGlobalLayoutListener() {
@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) {
- // probe for real keyboard height
- m_layout.postDelayed(new Runnable() {
- @Override
- public void run() {
- if (!m_keyboardIsVisible)
- return;
- DisplayMetrics metrics = new DisplayMetrics();
- m_activity.getWindowManager().getDefaultDisplay().getMetrics(metrics);
- Rect r = new Rect();
- m_activity.getWindow().getDecorView().getWindowVisibleDisplayFrame(r);
- if (metrics.heightPixels != r.bottom) {
- if (metrics.widthPixels > metrics.heightPixels) { // landscape
- if (m_landscapeKeyboardHeight != r.bottom) {
- m_landscapeKeyboardHeight = r.bottom;
- showSoftwareKeyboard(x, y, width, height, inputHints, enterKeyType);
- }
- } else {
- if (m_portraitKeyboardHeight != r.bottom) {
- m_portraitKeyboardHeight = r.bottom;
- showSoftwareKeyboard(x, y, width, height, inputHints, enterKeyType);
- }
- }
- } else {
- // no luck ?
- // maybe the delay was too short, so let's make it longer
- if (m_probeKeyboardHeightDelay < 1000)
- m_probeKeyboardHeightDelay *= 2;
- }
- }
- }, m_probeKeyboardHeightDelay);
- }
- break;
- case InputMethodManager.RESULT_HIDDEN:
- case InputMethodManager.RESULT_UNCHANGED_HIDDEN:
- setKeyboardVisibility(false, System.nanoTime());
- break;
- }
+ public void onGlobalLayout() {
+ QtNative.startApplication(appParams, mainLib);
+ m_layout.getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
});
- if (m_editText.m_optionsChanged) {
- m_imm.restartInput(m_editText);
- m_editText.m_optionsChanged = false;
- }
- }
- }, 15);
- }
-
- public void hideSoftwareKeyboard()
- {
- if (m_imm == null)
- return;
- m_imm.hideSoftInputFromWindow(m_editText.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;
- }
- }
- });
- }
-
- String getAppIconSize(Activity a)
- {
- int size = a.getResources().getDimensionPixelSize(android.R.dimen.app_icon_size);
- if (size < 36 || size > 512) { // check size sanity
- DisplayMetrics metrics = new DisplayMetrics();
- a.getWindowManager().getDefaultDisplay().getMetrics(metrics);
- size = metrics.densityDpi / 10 * 3;
- if (size < 36)
- size = 36;
-
- if (size > 512)
- size = 512;
- }
- return "\tQT_ANDROID_APP_ICON_SIZE=" + size;
- }
-
- public void updateSelection(int selStart, int selEnd, int candidatesStart, int candidatesEnd)
- {
- if (m_imm == null)
- return;
-
- m_imm.updateSelection(m_editText, selStart, selEnd, candidatesStart, candidatesEnd);
- }
-
- // Values coming from QAndroidInputContext::CursorHandleShowMode
- private static final int CursorHandleNotShown = 0;
- private static final int CursorHandleShowNormal = 1;
- private static final int CursorHandleShowSelection = 2;
- private static final int CursorHandleShowEdit = 0x100;
-
- 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
- */
- public void updateHandles(int mode, int editX, int editY, int editButtons, int x1, int y1, int x2, int y2, boolean rtl)
- {
- switch (mode & 0xff)
- {
- case CursorHandleNotShown:
- if (m_cursorHandle != null) {
- m_cursorHandle.hide();
- m_cursorHandle = null;
- }
- if (m_rightSelectionHandle != null) {
- m_rightSelectionHandle.hide();
- m_leftSelectionHandle.hide();
- m_rightSelectionHandle = null;
- m_leftSelectionHandle = null;
- }
- if (m_editPopupMenu != null)
- m_editPopupMenu.hide();
- break;
-
- case CursorHandleShowNormal:
- if (m_cursorHandle == null) {
- m_cursorHandle = new CursorHandle(m_activity, m_layout, QtNative.IdCursorHandle,
- android.R.attr.textSelectHandle, false);
- }
- m_cursorHandle.setPosition(x1, y1);
- if (m_rightSelectionHandle != null) {
- m_rightSelectionHandle.hide();
- m_leftSelectionHandle.hide();
- m_rightSelectionHandle = null;
- m_leftSelectionHandle = null;
- }
- break;
-
- case CursorHandleShowSelection:
- if (m_rightSelectionHandle == null) {
- m_leftSelectionHandle = new CursorHandle(m_activity, m_layout, QtNative.IdLeftHandle,
- !rtl ? android.R.attr.textSelectHandleLeft :
- android.R.attr.textSelectHandleRight,
- rtl);
- m_rightSelectionHandle = new CursorHandle(m_activity, m_layout, QtNative.IdRightHandle,
- !rtl ? android.R.attr.textSelectHandleRight :
- android.R.attr.textSelectHandleLeft,
- rtl);
- }
- m_leftSelectionHandle.setPosition(x1,y1);
- m_rightSelectionHandle.setPosition(x2,y2);
- if (m_cursorHandle != null) {
- m_cursorHandle.hide();
- m_cursorHandle = null;
- }
- mode |= CursorHandleShowEdit;
- break;
- }
-
- if (!QtNative.hasClipboardText())
- editButtons &= ~EditContextView.PASTE_BUTTON;
-
- if ((mode & CursorHandleShowEdit) == CursorHandleShowEdit && editButtons != 0) {
- m_editPopupMenu.setPosition(editX, editY, editButtons, m_cursorHandle, m_leftSelectionHandle,
- m_rightSelectionHandle);
- } else {
- if (m_editPopupMenu != null)
- m_editPopupMenu.hide();
- }
}
- public boolean loadApplication(Activity activity, ClassLoader classLoader, Bundle loaderParams)
+ @Override
+ protected void setUpLayout()
{
- /// check parameters integrity
- if (!loaderParams.containsKey(NATIVE_LIBRARIES_KEY)
- || !loaderParams.containsKey(BUNDLED_LIBRARIES_KEY)
- || !loaderParams.containsKey(ENVIRONMENT_VARIABLES_KEY)) {
- return false;
- }
-
- m_activity = activity;
- setActionBarVisibility(false);
- QtNative.setActivity(m_activity, this);
- QtNative.setClassLoader(classLoader);
- if (loaderParams.containsKey(STATIC_INIT_CLASSES_KEY)) {
- for (String className: Objects.requireNonNull(loaderParams.getStringArray(STATIC_INIT_CLASSES_KEY))) {
- if (className.length() == 0)
- continue;
-
- try {
- Class<?> initClass = classLoader.loadClass(className);
- Object staticInitDataObject = initClass.newInstance(); // create an instance
- try {
- Method m = initClass.getMethod("setActivity", Activity.class, Object.class);
- m.invoke(staticInitDataObject, m_activity, this);
- } catch (Exception e) {
- Log.d(QtNative.QtTAG, "Class " + className + " does not implement setActivity method");
- }
-
- // For modules that don't need/have setActivity
- try {
- Method m = initClass.getMethod("setContext", Context.class);
- m.invoke(staticInitDataObject, (Context)m_activity);
- } catch (Exception e) {
- e.printStackTrace();
- }
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- }
- QtNative.loadQtLibraries(loaderParams.getStringArrayList(NATIVE_LIBRARIES_KEY));
- ArrayList<String> libraries = loaderParams.getStringArrayList(BUNDLED_LIBRARIES_KEY);
- String nativeLibsDir = QtNativeLibrariesDir.nativeLibrariesDir(m_activity);
- QtNative.loadBundledLibraries(libraries, nativeLibsDir);
- m_mainLib = loaderParams.getString(MAIN_LIBRARY_KEY);
- // older apps provide the main library as the last bundled library; look for this if the main library isn't provided
- if (null == m_mainLib && libraries.size() > 0) {
- m_mainLib = libraries.get(libraries.size() - 1);
- libraries.remove(libraries.size() - 1);
- }
-
- if (loaderParams.containsKey(EXTRACT_STYLE_KEY)) {
- String path = loaderParams.getString(EXTRACT_STYLE_KEY);
- new ExtractStyle(m_activity, path, loaderParams.containsKey(EXTRACT_STYLE_MINIMAL_KEY) &&
- loaderParams.getBoolean(EXTRACT_STYLE_MINIMAL_KEY));
- }
-
- try {
- m_super_dispatchKeyEvent = m_activity.getClass().getMethod("super_dispatchKeyEvent", KeyEvent.class);
- m_super_onRestoreInstanceState = m_activity.getClass().getMethod("super_onRestoreInstanceState", Bundle.class);
- m_super_onRetainNonConfigurationInstance = m_activity.getClass().getMethod("super_onRetainNonConfigurationInstance");
- m_super_onSaveInstanceState = m_activity.getClass().getMethod("super_onSaveInstanceState", Bundle.class);
- m_super_onKeyDown = m_activity.getClass().getMethod("super_onKeyDown", Integer.TYPE, KeyEvent.class);
- m_super_onKeyUp = m_activity.getClass().getMethod("super_onKeyUp", Integer.TYPE, KeyEvent.class);
- m_super_onConfigurationChanged = m_activity.getClass().getMethod("super_onConfigurationChanged", Configuration.class);
- m_super_onActivityResult = m_activity.getClass().getMethod("super_onActivityResult", Integer.TYPE, Integer.TYPE, Intent.class);
- m_super_onWindowFocusChanged = m_activity.getClass().getMethod("super_onWindowFocusChanged", Boolean.TYPE);
- m_super_dispatchGenericMotionEvent = m_activity.getClass().getMethod("super_dispatchGenericMotionEvent", MotionEvent.class);
- } catch (Exception e) {
- e.printStackTrace();
- return false;
- }
-
- m_environmentVariables = loaderParams.getString(ENVIRONMENT_VARIABLES_KEY);
- String additionalEnvironmentVariables = "QT_ANDROID_FONTS_MONOSPACE=Droid Sans Mono;Droid Sans;Droid Sans Fallback"
- + "\tQT_ANDROID_FONTS_SERIF=Droid Serif"
- + "\tHOME=" + m_activity.getFilesDir().getAbsolutePath()
- + "\tTMPDIR=" + m_activity.getFilesDir().getAbsolutePath();
-
- additionalEnvironmentVariables += "\tQT_ANDROID_FONTS=Roboto;Droid Sans;Droid Sans Fallback";
-
- additionalEnvironmentVariables += getAppIconSize(activity);
+ int orientation = m_activity.getResources().getConfiguration().orientation;
+ m_layout = new QtRootLayout(m_activity);
- if (m_environmentVariables != null && m_environmentVariables.length() > 0)
- m_environmentVariables = additionalEnvironmentVariables + "\t" + m_environmentVariables;
- else
- m_environmentVariables = additionalEnvironmentVariables;
+ setUpSplashScreen(orientation);
+ m_activity.registerForContextMenu(m_layout);
+ m_activity.setContentView(m_layout,
+ new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT));
+ QtDisplayManager.handleOrientationChanges(m_activity);
- if (loaderParams.containsKey(APPLICATION_PARAMETERS_KEY))
- m_applicationParameters = loaderParams.getString(APPLICATION_PARAMETERS_KEY);
- else
- m_applicationParameters = "";
+ handleUiModeChange(m_activity.getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK);
- try {
- m_softInputMode = m_activity.getPackageManager().getActivityInfo(m_activity.getComponentName(), 0).softInputMode;
- } catch (Exception e) {
- e.printStackTrace();
- }
+ Display display = (Build.VERSION.SDK_INT < Build.VERSION_CODES.R)
+ ? m_activity.getWindowManager().getDefaultDisplay()
+ : m_activity.getDisplay();
+ QtDisplayManager.handleRefreshRateChanged(QtDisplayManager.getRefreshRate(display));
- DisplayManager.DisplayListener displayListener = new DisplayManager.DisplayListener() {
- @Override
- public void onDisplayAdded(int displayId) { }
+ m_layout.getViewTreeObserver().addOnPreDrawListener(() -> {
+ if (!m_inputDelegate.isKeyboardVisible())
+ return true;
- @Override
- public void onDisplayChanged(int displayId) {
- Display display = (Build.VERSION.SDK_INT < Build.VERSION_CODES.R)
- ? m_activity.getWindowManager().getDefaultDisplay()
- : m_activity.getDisplay();
- m_currentRotation = display.getRotation();
- QtNative.handleOrientationChanged(m_currentRotation, m_nativeOrientation);
- float refreshRate = display.getRefreshRate();
- QtNative.handleRefreshRateChanged(refreshRate);
+ Rect r = new Rect();
+ m_activity.getWindow().getDecorView().getWindowVisibleDisplayFrame(r);
+ DisplayMetrics metrics = new DisplayMetrics();
+ m_activity.getWindowManager().getDefaultDisplay().getMetrics(metrics);
+ final int kbHeight = metrics.heightPixels - r.bottom;
+ if (kbHeight < 0) {
+ m_inputDelegate.setKeyboardVisibility(false, System.nanoTime());
+ return true;
}
-
- @Override
- public void onDisplayRemoved(int displayId) { }
- };
-
- try {
- DisplayManager displayManager = (DisplayManager) m_activity.getSystemService(Context.DISPLAY_SERVICE);
- displayManager.registerDisplayListener(displayListener, null);
- } catch (Exception e) {
- e.printStackTrace();
- }
-
- m_mainLib = QtNative.loadMainLibrary(m_mainLib, nativeLibsDir);
- return m_mainLib != null;
- }
-
- public boolean startApplication()
- {
- // start application
- try {
-
- Bundle extras = m_activity.getIntent().getExtras();
- if (extras != null) {
- try {
- final boolean isDebuggable = (m_activity.getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
- if (!isDebuggable)
- throw new Exception();
-
- if (extras.containsKey("extraenvvars")) {
- try {
- m_environmentVariables += "\t" + new String(Base64.decode(extras.getString("extraenvvars"), Base64.DEFAULT), "UTF-8");
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
-
- if (extras.containsKey("extraappparams")) {
- try {
- m_applicationParameters += "\t" + new String(Base64.decode(extras.getString("extraappparams"), Base64.DEFAULT), "UTF-8");
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- } catch (Exception e) {
- Log.e(QtNative.QtTAG, "Not in debug mode! It is not allowed to use " +
- "extra arguments in non-debug mode.");
- // This is not an error, so keep it silent
- // e.printStackTrace();
- }
- } // extras != null
-
- if (null == m_surfaces)
- onCreate(null);
+ final int[] location = new int[2];
+ m_layout.getLocationOnScreen(location);
+ QtInputDelegate.keyboardGeometryChanged(location[0], r.bottom - location[1],
+ r.width(), kbHeight);
return true;
- } catch (Exception e) {
- e.printStackTrace();
- return false;
- }
- }
-
- public void onTerminate()
- {
- QtNative.terminateQt();
- QtNative.m_qtThread.exit();
+ });
+ registerGlobalFocusChangeListener(m_layout);
+ m_inputDelegate.setEditPopupMenu(new EditPopupMenu(m_activity, m_layout));
}
- public void onCreate(Bundle savedInstanceState)
+ @Override
+ protected void setUpSplashScreen(int orientation)
{
- m_quitApp = true;
- Runnable startApplication = null;
- if (null == savedInstanceState) {
- startApplication = new Runnable() {
- @Override
- public void run() {
- try {
- QtNative.startApplication(m_applicationParameters, m_environmentVariables, m_mainLib);
- m_started = true;
- } catch (Exception e) {
- e.printStackTrace();
- m_activity.finish();
- }
- }
- };
- }
- m_layout = new QtLayout(m_activity, startApplication);
-
- int orientation = m_activity.getResources().getConfiguration().orientation;
-
try {
- ActivityInfo info = m_activity.getPackageManager().getActivityInfo(m_activity.getComponentName(), PackageManager.GET_META_DATA);
+ ActivityInfo info = m_activity.getPackageManager().getActivityInfo(
+ m_activity.getComponentName(),
+ PackageManager.GET_META_DATA);
String splashScreenKey = "android.app.splash_screen_drawable_"
+ (orientation == Configuration.ORIENTATION_LANDSCAPE ? "landscape" : "portrait");
@@ -804,351 +157,86 @@ public class QtActivityDelegate
splashScreenKey = "android.app.splash_screen_drawable";
if (info.metaData.containsKey(splashScreenKey)) {
- m_splashScreenSticky = info.metaData.containsKey("android.app.splash_screen_sticky") && info.metaData.getBoolean("android.app.splash_screen_sticky");
+ m_splashScreenSticky =
+ info.metaData.containsKey("android.app.splash_screen_sticky") &&
+ info.metaData.getBoolean("android.app.splash_screen_sticky");
+
int id = info.metaData.getInt(splashScreenKey);
m_splashScreen = new ImageView(m_activity);
- m_splashScreen.setImageDrawable(m_activity.getResources().getDrawable(id));
+ m_splashScreen.setImageDrawable(m_activity.getResources().getDrawable(
+ id, m_activity.getTheme()));
m_splashScreen.setScaleType(ImageView.ScaleType.FIT_XY);
- m_splashScreen.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
+ m_splashScreen.setLayoutParams(new ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT));
m_layout.addView(m_splashScreen);
}
} catch (Exception e) {
e.printStackTrace();
}
-
- m_editText = new QtEditText(m_activity, this);
- m_imm = (InputMethodManager)m_activity.getSystemService(Context.INPUT_METHOD_SERVICE);
- m_surfaces = new HashMap<Integer, QtSurface>();
- m_nativeViews = new HashMap<Integer, View>();
- m_activity.registerForContextMenu(m_layout);
- m_activity.setContentView(m_layout,
- new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
- ViewGroup.LayoutParams.MATCH_PARENT));
-
- int rotation = m_activity.getWindowManager().getDefaultDisplay().getRotation();
- boolean rot90 = (rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270);
- boolean currentlyLandscape = (orientation == Configuration.ORIENTATION_LANDSCAPE);
- if ((currentlyLandscape && !rot90) || (!currentlyLandscape && rot90))
- m_nativeOrientation = Configuration.ORIENTATION_LANDSCAPE;
- else
- m_nativeOrientation = Configuration.ORIENTATION_PORTRAIT;
-
- QtNative.handleOrientationChanged(rotation, m_nativeOrientation);
- m_currentRotation = rotation;
-
- float refreshRate = (Build.VERSION.SDK_INT < Build.VERSION_CODES.R)
- ? m_activity.getWindowManager().getDefaultDisplay().getRefreshRate()
- : m_activity.getDisplay().getRefreshRate();
- QtNative.handleRefreshRateChanged(refreshRate);
-
- m_layout.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
- @Override
- public boolean onPreDraw() {
- if (!m_keyboardIsVisible)
- return true;
-
- Rect r = new Rect();
- m_activity.getWindow().getDecorView().getWindowVisibleDisplayFrame(r);
- DisplayMetrics metrics = new DisplayMetrics();
- m_activity.getWindowManager().getDefaultDisplay().getMetrics(metrics);
- final int kbHeight = metrics.heightPixels - r.bottom;
- if (kbHeight < 0) {
- setKeyboardVisibility(false, System.nanoTime());
- return true;
- }
- final int[] location = new int[2];
- m_layout.getLocationOnScreen(location);
- QtNative.keyboardGeometryChanged(location[0], r.bottom - location[1],
- r.width(), kbHeight);
- return true;
- }
- });
- m_editPopupMenu = new EditPopupMenu(m_activity, m_layout);
}
- public void hideSplashScreen()
+ @Override
+ protected void hideSplashScreen(final int duration)
{
- hideSplashScreen(0);
- }
+ QtNative.runAction(() -> {
+ if (m_splashScreen == null)
+ return;
- public void hideSplashScreen(final int duration)
- {
- if (m_splashScreen == null)
- return;
+ if (duration <= 0) {
+ m_layout.removeView(m_splashScreen);
+ m_splashScreen = null;
+ return;
+ }
- if (duration <= 0) {
- m_layout.removeView(m_splashScreen);
- m_splashScreen = null;
- return;
- }
+ final Animation fadeOut = new AlphaAnimation(1, 0);
+ fadeOut.setInterpolator(new AccelerateInterpolator());
+ fadeOut.setDuration(duration);
- final Animation fadeOut = new AlphaAnimation(1, 0);
- fadeOut.setInterpolator(new AccelerateInterpolator());
- fadeOut.setDuration(duration);
+ fadeOut.setAnimationListener(new Animation.AnimationListener() {
+ @Override
+ public void onAnimationEnd(Animation animation) {
+ hideSplashScreen(0);
+ }
- fadeOut.setAnimationListener(new Animation.AnimationListener() {
- @Override
- public void onAnimationEnd(Animation animation) { hideSplashScreen(0); }
+ @Override
+ public void onAnimationRepeat(Animation animation) {
+ }
- @Override
- public void onAnimationRepeat(Animation animation) {}
+ @Override
+ public void onAnimationStart(Animation animation) {
+ }
+ });
- @Override
- public void onAnimationStart(Animation animation) {}
+ m_splashScreen.startAnimation(fadeOut);
});
-
- m_splashScreen.startAnimation(fadeOut);
- }
-
- public void notifyAccessibilityLocationChange()
- {
- if (m_accessibilityDelegate == null)
- return;
- m_accessibilityDelegate.notifyLocationChange();
- }
-
- public void notifyObjectHide(int viewId)
- {
- if (m_accessibilityDelegate == null)
- return;
- m_accessibilityDelegate.notifyObjectHide(viewId);
- }
-
- public void notifyObjectFocus(int viewId)
- {
- if (m_accessibilityDelegate == null)
- return;
- m_accessibilityDelegate.notifyObjectFocus(viewId);
- }
-
- public void notifyQtAndroidPluginRunning(boolean running)
- {
- m_isPluginRunning = running;
}
+ @UsedFromNativeCode
public void initializeAccessibility()
{
- m_accessibilityDelegate = new QtAccessibilityDelegate(m_activity, m_layout, this);
- }
-
- public void onWindowFocusChanged(boolean hasFocus) {
- try {
- m_super_onWindowFocusChanged.invoke(m_activity, hasFocus);
- } catch (Exception e) {
- e.printStackTrace();
- }
- if (hasFocus)
- updateFullScreen();
- }
-
- public void onConfigurationChanged(Configuration configuration)
- {
- try {
- m_super_onConfigurationChanged.invoke(m_activity, configuration);
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
-
- public void onDestroy()
- {
- if (m_quitApp) {
- QtNative.terminateQt();
- QtNative.setActivity(null, null);
- QtNative.m_qtThread.exit();
- System.exit(0);
- }
- }
-
- public void onPause()
- {
- if (Build.VERSION.SDK_INT < 24 || !m_activity.isInMultiWindowMode())
- QtNative.setApplicationState(ApplicationInactive);
- }
-
- public void onResume()
- {
- QtNative.setApplicationState(ApplicationActive);
- if (m_started) {
- QtNative.updateWindow();
- updateFullScreen(); // Suspending the app clears the immersive mode, so we need to set it again.
- }
- }
-
- public void onNewIntent(Intent data)
- {
- QtNative.onNewIntent(data);
- }
-
- public void onActivityResult(int requestCode, int resultCode, Intent data)
- {
- try {
- m_super_onActivityResult.invoke(m_activity, requestCode, resultCode, data);
- } catch (Exception e) {
- e.printStackTrace();
- }
-
- QtNative.onActivityResult(requestCode, resultCode, data);
- }
-
-
- public void onStop()
- {
- QtNative.setApplicationState(ApplicationSuspended);
- }
-
- public Object onRetainNonConfigurationInstance()
- {
- try {
- m_super_onRetainNonConfigurationInstance.invoke(m_activity);
- } catch (Exception e) {
- e.printStackTrace();
- }
- m_quitApp = false;
- return true;
- }
-
- public void onSaveInstanceState(Bundle outState) {
- try {
- m_super_onSaveInstanceState.invoke(m_activity, outState);
- } catch (Exception e) {
- e.printStackTrace();
- }
- outState.putInt("SystemUiVisibility", m_systemUiVisibility);
- outState.putBoolean("Started", m_started);
- // It should never
- }
-
- public void onRestoreInstanceState(Bundle savedInstanceState)
- {
- try {
- m_super_onRestoreInstanceState.invoke(m_activity, savedInstanceState);
- } catch (Exception e) {
- e.printStackTrace();
- }
- m_started = savedInstanceState.getBoolean("Started");
- // FIXME restore all surfaces
-
- }
-
- public boolean onKeyDown(int keyCode, KeyEvent event)
- {
- if (!m_started || !m_isPluginRunning)
- return false;
-
- m_metaState = MetaKeyKeyListener.handleKeyDown(m_metaState, keyCode, event);
- int c = event.getUnicodeChar(MetaKeyKeyListener.getMetaState(m_metaState) | event.getMetaState());
- int lc = c;
- m_metaState = MetaKeyKeyListener.adjustMetaAfterKeypress(m_metaState);
-
- if ((c & KeyCharacterMap.COMBINING_ACCENT) != 0) {
- c = c & KeyCharacterMap.COMBINING_ACCENT_MASK;
- int composed = KeyEvent.getDeadChar(m_lastChar, c);
- c = composed;
- }
-
- if ((keyCode == KeyEvent.KEYCODE_VOLUME_UP
- || keyCode == KeyEvent.KEYCODE_VOLUME_DOWN
- || keyCode == KeyEvent.KEYCODE_MUTE)
- && System.getenv("QT_ANDROID_VOLUME_KEYS") == null) {
- return false;
- }
-
- m_lastChar = lc;
- if (keyCode == KeyEvent.KEYCODE_BACK) {
- m_backKeyPressedSent = !m_keyboardIsVisible;
- if (!m_backKeyPressedSent)
- return true;
- }
- QtNative.keyDown(keyCode, c, event.getMetaState(), event.getRepeatCount() > 0);
-
- return true;
- }
-
- public boolean onKeyUp(int keyCode, KeyEvent event)
- {
- if (!m_started || !m_isPluginRunning)
- return false;
-
- if ((keyCode == KeyEvent.KEYCODE_VOLUME_UP
- || keyCode == KeyEvent.KEYCODE_VOLUME_DOWN
- || keyCode == KeyEvent.KEYCODE_MUTE)
- && System.getenv("QT_ANDROID_VOLUME_KEYS") == null) {
- return false;
- }
-
- if (keyCode == KeyEvent.KEYCODE_BACK && !m_backKeyPressedSent) {
- hideSoftwareKeyboard();
- setKeyboardVisibility(false, System.nanoTime());
- return true;
- }
-
- m_metaState = MetaKeyKeyListener.handleKeyUp(m_metaState, keyCode, event);
- QtNative.keyUp(keyCode, event.getUnicodeChar(), event.getMetaState(), event.getRepeatCount() > 0);
- return true;
- }
-
- public boolean dispatchKeyEvent(KeyEvent event)
- {
- if (m_started
- && event.getAction() == KeyEvent.ACTION_MULTIPLE
- && event.getCharacters() != null
- && event.getCharacters().length() == 1
- && event.getKeyCode() == 0) {
- QtNative.keyDown(0, event.getCharacters().charAt(0), event.getMetaState(), event.getRepeatCount() > 0);
- QtNative.keyUp(0, event.getCharacters().charAt(0), event.getMetaState(), event.getRepeatCount() > 0);
- }
-
- if (QtNative.dispatchKeyEvent(event))
- return true;
-
- try {
- return (Boolean) m_super_dispatchKeyEvent.invoke(m_activity, event);
- } catch (Exception e) {
- e.printStackTrace();
- }
- return false;
- }
-
- private boolean m_optionsMenuIsVisible = false;
- public boolean onCreateOptionsMenu(Menu menu)
- {
- menu.clear();
- return true;
- }
- public boolean onPrepareOptionsMenu(Menu menu)
- {
- m_optionsMenuIsVisible = true;
- boolean res = QtNative.onPrepareOptionsMenu(menu);
- setActionBarVisibility(res && menu.size() > 0);
- return res;
- }
-
- public boolean onOptionsItemSelected(MenuItem item)
- {
- return QtNative.onOptionsItemSelected(item.getItemId(), item.isChecked());
+ QtNative.runAction(() -> {
+ // FIXME make QtAccessibilityDelegate window based
+ if (m_layout != null)
+ m_accessibilityDelegate = new QtAccessibilityDelegate(m_layout);
+ else
+ Log.w(QtTAG, "Null layout, failed to initialize accessibility delegate.");
+ });
}
- public void onOptionsMenuClosed(Menu menu)
+ @UsedFromNativeCode
+ public void resetOptionsMenu()
{
- m_optionsMenuIsVisible = false;
- QtNative.onOptionsMenuClosed(menu);
+ QtNative.runAction(() -> m_activity.invalidateOptionsMenu());
}
- public void resetOptionsMenu()
+ @UsedFromNativeCode
+ public void openOptionsMenu()
{
- m_activity.invalidateOptionsMenu();
+ QtNative.runAction(() -> m_activity.openOptionsMenu());
}
private boolean m_contextMenuVisible = false;
- public void onCreateContextMenu(ContextMenu menu,
- View v,
- ContextMenuInfo menuInfo)
- {
- menu.clearHeader();
- QtNative.onCreateContextMenu(menu);
- m_contextMenuVisible = true;
- }
public void onCreatePopupMenu(Menu menu)
{
@@ -1156,51 +244,35 @@ public class QtActivityDelegate
m_contextMenuVisible = true;
}
- public void onContextMenuClosed(Menu menu)
- {
- if (!m_contextMenuVisible)
- return;
- m_contextMenuVisible = false;
- QtNative.onContextMenuClosed(menu);
- }
-
- public boolean onContextItemSelected(MenuItem item)
- {
- m_contextMenuVisible = false;
- return QtNative.onContextItemSelected(item.getItemId(), item.isChecked());
- }
-
+ @UsedFromNativeCode
+ @Override
public void openContextMenu(final int x, final int y, final int w, final int h)
{
- m_layout.postDelayed(new Runnable() {
- @Override
- public void run() {
- m_layout.setLayoutParams(m_editText, new QtLayout.LayoutParams(w, h, x, y), false);
- PopupMenu popup = new PopupMenu(m_activity, m_editText);
- QtActivityDelegate.this.onCreatePopupMenu(popup.getMenu());
- popup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
- @Override
- public boolean onMenuItemClick(MenuItem menuItem) {
- return QtActivityDelegate.this.onContextItemSelected(menuItem);
- }
- });
- popup.setOnDismissListener(new PopupMenu.OnDismissListener() {
- @Override
- public void onDismiss(PopupMenu popupMenu) {
- QtActivityDelegate.this.onContextMenuClosed(popupMenu.getMenu());
- }
- });
- popup.show();
- }
- }, 100);
- }
-
+ m_layout.postDelayed(() -> {
+ final QtEditText focusedEditText = m_inputDelegate.getCurrentQtEditText();
+ if (focusedEditText == null) {
+ Log.w(QtTAG, "No focused view when trying to open context menu");
+ return;
+ }
+ m_layout.setLayoutParams(focusedEditText, new QtLayout.LayoutParams(w, h, x, y), false);
+ PopupMenu popup = new PopupMenu(m_activity, focusedEditText);
+ QtActivityDelegate.this.onCreatePopupMenu(popup.getMenu());
+ popup.setOnMenuItemClickListener(menuItem ->
+ m_activity.onContextItemSelected(menuItem));
+ popup.setOnDismissListener(popupMenu ->
+ m_activity.onContextMenuClosed(popupMenu.getMenu()));
+ popup.show();
+ }, 100);
+ }
+
+ @UsedFromNativeCode
public void closeContextMenu()
{
- m_activity.closeContextMenu();
+ QtNative.runAction(() -> m_activity.closeContextMenu());
}
- private void setActionBarVisibility(boolean visible)
+ @Override
+ void setActionBarVisibility(boolean visible)
{
if (m_activity.getActionBar() == null)
return;
@@ -1210,149 +282,136 @@ public class QtActivityDelegate
m_activity.getActionBar().show();
}
- public void insertNativeView(int id, View view, int x, int y, int w, int h) {
- if (m_dummyView != null) {
- m_layout.removeView(m_dummyView);
- m_dummyView = null;
- }
-
- if (m_nativeViews.containsKey(id))
- m_layout.removeView(m_nativeViews.remove(id));
-
- if (w < 0 || h < 0) {
- view.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
- ViewGroup.LayoutParams.MATCH_PARENT));
- } else {
- view.setLayoutParams(new QtLayout.LayoutParams(w, h, x, y));
- }
-
- view.setId(id);
- m_layout.addView(view);
- m_nativeViews.put(id, view);
- }
+ @UsedFromNativeCode
+ @Override
+ public void addTopLevelWindow(final QtWindow window)
+ {
+ if (window == null)
+ return;
- public void createSurface(int id, boolean onTop, int x, int y, int w, int h, int imageDepth) {
- if (m_surfaces.size() == 0) {
- TypedValue attr = new TypedValue();
- m_activity.getTheme().resolveAttribute(android.R.attr.windowBackground, attr, true);
- if (attr.type >= TypedValue.TYPE_FIRST_COLOR_INT && attr.type <= TypedValue.TYPE_LAST_COLOR_INT) {
- m_activity.getWindow().setBackgroundDrawable(new ColorDrawable(attr.data));
- } else {
- m_activity.getWindow().setBackgroundDrawable(m_activity.getResources().getDrawable(attr.resourceId));
- }
- if (m_dummyView != null) {
- m_layout.removeView(m_dummyView);
- m_dummyView = null;
+ QtNative.runAction(()-> {
+ if (m_topLevelWindows.size() == 0) {
+ if (m_dummyView != null) {
+ m_layout.removeView(m_dummyView);
+ m_dummyView = null;
+ }
}
- }
- if (m_surfaces.containsKey(id))
- m_layout.removeView(m_surfaces.remove(id));
+ window.setLayoutParams(new ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT));
- QtSurface surface = new QtSurface(m_activity, id, onTop, imageDepth);
- if (w < 0 || h < 0) {
- surface.setLayoutParams( new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
- ViewGroup.LayoutParams.MATCH_PARENT));
- } else {
- surface.setLayoutParams( new QtLayout.LayoutParams(w, h, x, y));
- }
-
- // Native views are always inserted in the end of the stack (i.e., on top).
- // All other views are stacked based on the order they are created.
- final int surfaceCount = getSurfaceCount();
- m_layout.addView(surface, surfaceCount);
-
- m_surfaces.put(id, surface);
- if (!m_splashScreenSticky)
- hideSplashScreen();
+ m_layout.addView(window, m_topLevelWindows.size());
+ m_topLevelWindows.put(window.getId(), window);
+ if (!m_splashScreenSticky)
+ hideSplashScreen();
+ });
}
- public void setSurfaceGeometry(int id, int x, int y, int w, int h) {
- if (m_surfaces.containsKey(id)) {
- QtSurface surface = m_surfaces.get(id);
- surface.setLayoutParams(new QtLayout.LayoutParams(w, h, x, y));
- } else if (m_nativeViews.containsKey(id)) {
- View view = m_nativeViews.get(id);
- view.setLayoutParams(new QtLayout.LayoutParams(w, h, x, y));
- } else {
- Log.e(QtNative.QtTAG, "Surface " + id +" not found!");
- return;
- }
+ @UsedFromNativeCode
+ @Override
+ void removeTopLevelWindow(final int id)
+ {
+ QtNative.runAction(()-> {
+ if (m_topLevelWindows.containsKey(id)) {
+ QtWindow window = m_topLevelWindows.remove(id);
+ if (m_topLevelWindows.isEmpty()) {
+ // Keep last frame in stack until it is replaced to get correct
+ // shutdown transition
+ m_dummyView = window;
+ } else {
+ m_layout.removeView(window);
+ }
+ }
+ });
}
- public void destroySurface(int id) {
- View view = null;
-
- if (m_surfaces.containsKey(id)) {
- view = m_surfaces.remove(id);
- } else if (m_nativeViews.containsKey(id)) {
- view = m_nativeViews.remove(id);
- } else {
- Log.e(QtNative.QtTAG, "Surface " + id +" not found!");
- }
-
- if (view == null)
- return;
-
- // Keep last frame in stack until it is replaced to get correct
- // shutdown transition
- if (m_surfaces.size() == 0 && m_nativeViews.size() == 0) {
- m_dummyView = view;
- } else {
- m_layout.removeView(view);
- }
+ @UsedFromNativeCode
+ @Override
+ void bringChildToFront(final int id)
+ {
+ QtNative.runAction(() -> {
+ QtWindow window = m_topLevelWindows.get(id);
+ if (window != null)
+ m_layout.moveChild(window, m_topLevelWindows.size() - 1);
+ });
}
- public int getSurfaceCount()
+ @UsedFromNativeCode
+ @Override
+ void bringChildToBack(int id)
{
- return m_surfaces.size();
+ QtNative.runAction(() -> {
+ QtWindow window = m_topLevelWindows.get(id);
+ if (window != null)
+ m_layout.moveChild(window, 0);
+ });
}
- public void bringChildToFront(int id)
+ @Override
+ QtAccessibilityDelegate createAccessibilityDelegate()
{
- View view = m_surfaces.get(id);
- if (view != null) {
- final int surfaceCount = getSurfaceCount();
- if (surfaceCount > 0)
- m_layout.moveChild(view, surfaceCount - 1);
- return;
- }
+ if (m_layout != null)
+ return new QtAccessibilityDelegate(m_layout);
- view = m_nativeViews.get(id);
- if (view != null)
- m_layout.moveChild(view, -1);
+ Log.w(QtTAG, "Null layout, failed to initialize accessibility delegate.");
+ return null;
}
- public void bringChildToBack(int id)
+ private void setActivityBackgroundDrawable()
{
- View view = m_surfaces.get(id);
- if (view != null) {
- m_layout.moveChild(view, 0);
- return;
+ TypedValue attr = new TypedValue();
+ m_activity.getTheme().resolveAttribute(android.R.attr.windowBackground,
+ attr, true);
+ Drawable backgroundDrawable;
+ if (attr.type >= TypedValue.TYPE_FIRST_COLOR_INT &&
+ attr.type <= TypedValue.TYPE_LAST_COLOR_INT) {
+ backgroundDrawable = new ColorDrawable(attr.data);
+ } else {
+ backgroundDrawable = m_activity.getResources().
+ getDrawable(attr.resourceId, m_activity.getTheme());
}
- view = m_nativeViews.get(id);
- if (view != null) {
- final int index = getSurfaceCount();
- m_layout.moveChild(view, index);
- }
+ m_activity.getWindow().setBackgroundDrawable(backgroundDrawable);
}
- public boolean dispatchGenericMotionEvent (MotionEvent ev)
+ // TODO: QTBUG-122761 To be removed after QtAndroidAutomotive does not depend on it.
+ @UsedFromNativeCode
+ public void insertNativeView(int id, View view, int x, int y, int w, int h)
{
- if (m_started && QtNative.dispatchGenericMotionEvent(ev))
- return true;
+ QtNative.runAction(()-> {
+ if (m_dummyView != null) {
+ m_layout.removeView(m_dummyView);
+ m_dummyView = null;
+ }
- try {
- return (Boolean) m_super_dispatchGenericMotionEvent.invoke(m_activity, ev);
- } catch (Exception e) {
- e.printStackTrace();
- }
- return false;
+ if (m_nativeViews.containsKey(id))
+ m_layout.removeView(m_nativeViews.remove(id));
+
+ if (w < 0 || h < 0) {
+ view.setLayoutParams(new ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
+ } else {
+ view.setLayoutParams(new QtLayout.LayoutParams(w, h, x, y));
+ }
+
+ view.setId(id);
+ m_layout.addView(view);
+ m_nativeViews.put(id, view);
+ });
}
- public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults)
+ // TODO: QTBUG-122761 To be removed after QtAndroidAutomotive does not depend on it.
+ @UsedFromNativeCode
+ public void setNativeViewGeometry(int id, int x, int y, int w, int h)
{
- QtNative.sendRequestPermissionsResult(requestCode, permissions, grantResults);
+ QtNative.runAction(() -> {
+ if (m_nativeViews.containsKey(id)) {
+ View view = m_nativeViews.get(id);
+ 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
new file mode 100644
index 0000000000..6fd539d8dd
--- /dev/null
+++ b/src/android/jar/src/org/qtproject/qt/android/QtActivityDelegateBase.java
@@ -0,0 +1,267 @@
+// Copyright (C) 2017 BogDan Vatra <bogdan@kde.org>
+// Copyright (C) 2023 The Qt Company Ltd.
+// Copyright (C) 2016 Olivier Goffart <ogoffart@woboq.com>
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+package org.qtproject.qt.android;
+
+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;
+import android.os.Build;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.util.TypedValue;
+import android.view.Display;
+import android.view.ViewTreeObserver;
+import android.view.animation.AccelerateInterpolator;
+import android.view.animation.AlphaAnimation;
+import android.view.animation.Animation;
+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;
+
+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;
+
+ private boolean m_membersInitialized = false;
+ private boolean m_contextMenuVisible = false;
+
+ // 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)
+ {
+ m_activity = activity;
+ // Set native context
+ QtNative.setActivity(m_activity);
+ }
+
+ QtDisplayManager displayManager() {
+ return m_displayManager;
+ }
+
+ @UsedFromNativeCode
+ QtInputDelegate getInputDelegate() {
+ return m_inputDelegate;
+ }
+
+ void setContextMenuVisible(boolean contextMenuVisible)
+ {
+ m_contextMenuVisible = contextMenuVisible;
+ }
+
+ boolean isContextMenuVisible()
+ {
+ 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)
+ return;
+ initMembers();
+ startNativeApplicationImpl(appParams, mainLib);
+ }
+
+ void initMembers()
+ {
+ m_membersInitialized = true;
+ m_topLevelWindows = new HashMap<Integer, QtWindow>();
+
+ m_displayManager = new QtDisplayManager(m_activity);
+ m_displayManager.registerDisplayListener();
+
+ QtInputDelegate.KeyboardVisibilityListener keyboardVisibilityListener =
+ () -> m_displayManager.updateFullScreen();
+ m_inputDelegate = new QtInputDelegate(m_activity, keyboardVisibilityListener);
+
+ try {
+ PackageManager pm = m_activity.getPackageManager();
+ ActivityInfo activityInfo = pm.getActivityInfo(m_activity.getComponentName(), 0);
+ m_inputDelegate.setSoftInputMode(activityInfo.softInputMode);
+ } catch (PackageManager.NameNotFoundException e) {
+ e.printStackTrace();
+ }
+
+ setUpLayout();
+ }
+
+ protected void registerGlobalFocusChangeListener(final View view) {
+ view.getViewTreeObserver().addOnGlobalFocusChangeListener(this::onGlobalFocusChanged);
+ }
+
+ private void onGlobalFocusChanged(View oldFocus, View newFocus) {
+ if (newFocus instanceof QtEditText) {
+ final QtWindow newWindow = (QtWindow) newFocus.getParent();
+ QtWindow.windowFocusChanged(true, newWindow.getId());
+ m_inputDelegate.setFocusedView((QtEditText) newFocus);
+ } else {
+ int id = -1;
+ if (oldFocus instanceof QtEditText) {
+ final QtWindow oldWindow = (QtWindow) oldFocus.getParent();
+ id = oldWindow.getId();
+ }
+ QtWindow.windowFocusChanged(false, id);
+ m_inputDelegate.setFocusedView(null);
+ }
+ }
+
+ public void hideSplashScreen()
+ {
+ 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
+ if (Build.VERSION.SDK_INT >= 30) {
+ // Since 29 version we are using Theme_DeviceDefault_DayNight
+ Window window = m_activity.getWindow();
+ WindowInsetsController controller = window.getInsetsController();
+ if (controller != null) {
+ // set APPEARANCE_LIGHT_STATUS_BARS if needed
+ int appearanceLight = Color.luminance(window.getStatusBarColor()) > 0.5 ?
+ WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS : 0;
+ controller.setSystemBarsAppearance(appearanceLight,
+ WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS);
+ }
+ }
+ switch (uiMode) {
+ case Configuration.UI_MODE_NIGHT_NO:
+ ExtractStyle.runIfNeeded(m_activity, false);
+ QtDisplayManager.handleUiDarkModeChanged(0);
+ break;
+ case Configuration.UI_MODE_NIGHT_YES:
+ ExtractStyle.runIfNeeded(m_activity, true);
+ QtDisplayManager.handleUiDarkModeChanged(1);
+ 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/QtActivityLoader.java b/src/android/jar/src/org/qtproject/qt/android/QtActivityLoader.java
new file mode 100644
index 0000000000..1b2e8e49a0
--- /dev/null
+++ b/src/android/jar/src/org/qtproject/qt/android/QtActivityLoader.java
@@ -0,0 +1,151 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// Copyright (c) 2016, BogDan Vatra <bogdan@kde.org>
+// 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.annotation.SuppressLint;
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.ContextWrapper;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.os.Bundle;
+import android.system.Os;
+import android.util.Base64;
+import android.util.DisplayMetrics;
+import android.util.Log;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.nio.charset.StandardCharsets;
+
+class QtActivityLoader extends QtLoader {
+ private final Activity m_activity;
+
+ public QtActivityLoader(Activity activity)
+ {
+ super(new ContextWrapper(activity));
+ m_activity = activity;
+
+ extractContextMetaData();
+ }
+
+ private void showErrorDialog() {
+ if (m_activity == null) {
+ Log.w(QtTAG, "cannot show the error dialog from a null activity object");
+ return;
+ }
+ Resources resources = m_activity.getResources();
+ String packageName = m_activity.getPackageName();
+ AlertDialog errorDialog = new AlertDialog.Builder(m_activity).create();
+ @SuppressLint("DiscouragedApi") int id = resources.getIdentifier(
+ "fatal_error_msg", "string", packageName);
+ errorDialog.setMessage(resources.getString(id));
+ errorDialog.setButton(Dialog.BUTTON_POSITIVE, resources.getString(android.R.string.ok),
+ (dialog, which) -> finish());
+ errorDialog.show();
+ }
+
+ @Override
+ protected void finish() {
+ if (m_activity == null) {
+ Log.w(QtTAG, "finish() called when activity object is null");
+ return;
+ }
+ showErrorDialog();
+ m_activity.finish();
+ }
+
+ private String getDecodedUtfString(String str)
+ {
+ byte[] decodedExtraEnvVars = Base64.decode(str, Base64.DEFAULT);
+ return new String(decodedExtraEnvVars, StandardCharsets.UTF_8);
+ }
+
+ int getAppIconSize()
+ {
+ int size = m_activity.getResources().getDimensionPixelSize(android.R.dimen.app_icon_size);
+ if (size < 36 || size > 512) { // check size sanity
+ DisplayMetrics metrics = new DisplayMetrics();
+ m_activity.getWindowManager().getDefaultDisplay().getMetrics(metrics);
+ size = metrics.densityDpi / 10 * 3;
+ if (size < 36)
+ size = 36;
+
+ if (size > 512)
+ size = 512;
+ }
+
+ return size;
+ }
+
+ private void setupStyleExtraction()
+ {
+ int displayDensity = m_activity.getResources().getDisplayMetrics().densityDpi;
+ setEnvironmentVariable("QT_ANDROID_THEME_DISPLAY_DPI", String.valueOf(displayDensity));
+
+ String extractOption = getMetaData("android.app.extract_android_style");
+ if (extractOption.equals("full"))
+ setEnvironmentVariable("QT_USE_ANDROID_NATIVE_STYLE", String.valueOf(1));
+
+ String stylePath = ExtractStyle.setup(m_activity, extractOption, displayDensity);
+ setEnvironmentVariable("ANDROID_STYLE_PATH", stylePath);
+ }
+
+ @Override
+ protected void extractContextMetaData()
+ {
+ super.extractContextMetaData();
+
+ setEnvironmentVariable("QT_USE_ANDROID_NATIVE_DIALOGS", String.valueOf(1));
+ setEnvironmentVariable("QT_ANDROID_APP_ICON_SIZE", String.valueOf(getAppIconSize()));
+
+ setupStyleExtraction();
+
+ Intent intent = m_activity.getIntent();
+ if (intent == null) {
+ Log.w(QtTAG, "Null Intent from the current Activity.");
+ return;
+ }
+
+ String intentArgs = intent.getStringExtra("applicationArguments");
+ if (intentArgs != null)
+ appendApplicationParameters(intentArgs);
+
+ Bundle extras = intent.getExtras();
+ if (extras == null) {
+ Log.w(QtTAG, "Null extras from the Activity's intent.");
+ return;
+ }
+
+ int flags = m_activity.getApplicationInfo().flags;
+ boolean isDebuggable = (flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
+
+ if (isDebuggable) {
+ if (extras.containsKey("extraenvvars")) {
+ String extraEnvVars = extras.getString("extraenvvars");
+ setEnvironmentVariables(getDecodedUtfString(extraEnvVars));
+ }
+
+ if (extras.containsKey("extraappparams")) {
+ String extraAppParams = extras.getString("extraappparams");
+ appendApplicationParameters(getDecodedUtfString(extraAppParams));
+ }
+
+ m_debuggerSleepMs = 3000;
+ if (Os.getenv("QT_ANDROID_DEBUGGER_MAIN_THREAD_SLEEP_MS") != null) {
+ try {
+ m_debuggerSleepMs = Integer.parseInt(Os.getenv("QT_ANDROID_DEBUGGER_MAIN_THREAD_SLEEP_MS"));
+ } catch (NumberFormatException ignored) {
+ }
+ }
+ } else {
+ Log.d(QtNative.QtTAG, "Not in debug mode! It is not allowed to use extra arguments " +
+ "in non-debug mode.");
+ }
+ }
+}
diff --git a/src/android/jar/src/org/qtproject/qt/android/QtApplicationBase.java b/src/android/jar/src/org/qtproject/qt/android/QtApplicationBase.java
new file mode 100644
index 0000000000..de572266b9
--- /dev/null
+++ b/src/android/jar/src/org/qtproject/qt/android/QtApplicationBase.java
@@ -0,0 +1,15 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+package org.qtproject.qt.android;
+
+import android.app.Application;
+
+public class QtApplicationBase extends Application {
+ @Override
+ public void onTerminate() {
+ QtNative.terminateQt();
+ QtNative.getQtThread().exit();
+ super.onTerminate();
+ }
+}
diff --git a/src/android/jar/src/org/qtproject/qt/android/QtClipboardManager.java b/src/android/jar/src/org/qtproject/qt/android/QtClipboardManager.java
new file mode 100644
index 0000000000..ac0d4e1890
--- /dev/null
+++ b/src/android/jar/src/org/qtproject/qt/android/QtClipboardManager.java
@@ -0,0 +1,232 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+package org.qtproject.qt.android;
+
+import android.content.ClipData;
+import android.content.ClipDescription;
+import android.content.ClipboardManager;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Build;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.Objects;
+import java.util.concurrent.Semaphore;
+
+class QtClipboardManager
+{
+ public static native void onClipboardDataChanged(long nativePointer);
+
+ private final static String TAG = "QtClipboardManager";
+ private ClipboardManager m_clipboardManager = null;
+ private boolean m_usePrimaryClip = false;
+ private final long m_nativePointer;
+
+ public QtClipboardManager(Context context, long nativePointer)
+ {
+ m_nativePointer = nativePointer;
+ registerClipboardManager(context);
+ }
+
+ private void registerClipboardManager(Context context)
+ {
+ if (context != null) {
+ final Semaphore semaphore = new Semaphore(0);
+ QtNative.runAction(() -> {
+ m_clipboardManager =
+ (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
+ if (m_clipboardManager != null) {
+ m_clipboardManager.addPrimaryClipChangedListener(
+ () -> onClipboardDataChanged(m_nativePointer));
+ }
+ semaphore.release();
+ });
+ try {
+ semaphore.acquire();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ @UsedFromNativeCode
+ public void clearClipData()
+ {
+ if (m_clipboardManager != null) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
+ m_clipboardManager.clearPrimaryClip();
+ } else {
+ String[] mimeTypes = { "application/octet-stream" };
+ ClipData data = new ClipData("", mimeTypes, new ClipData.Item(new Intent()));
+ m_clipboardManager.setPrimaryClip(data);
+ }
+ }
+ m_usePrimaryClip = false;
+ }
+
+ @UsedFromNativeCode
+ public void setClipboardText(Context context, String text)
+ {
+ if (m_clipboardManager != null) {
+ ClipData clipData = ClipData.newPlainText("text/plain", text);
+ updatePrimaryClip(clipData, context);
+ }
+ }
+
+ public static boolean hasClipboardText(Context context)
+ {
+ ClipboardManager clipboardManager =
+ (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
+
+ if (clipboardManager == null)
+ return false;
+
+ ClipDescription description = clipboardManager.getPrimaryClipDescription();
+ // getPrimaryClipDescription can fail if the app does not have input focus
+ if (description == null)
+ return false;
+
+ for (int i = 0; i < description.getMimeTypeCount(); ++i) {
+ String itemMimeType = description.getMimeType(i);
+ if (itemMimeType.matches("text/(.*)"))
+ return true;
+ }
+ return false;
+ }
+
+ @UsedFromNativeCode
+ public boolean hasClipboardText()
+ {
+ return hasClipboardMimeType("text/(.*)");
+ }
+
+ @UsedFromNativeCode
+ public String getClipboardText()
+ {
+ try {
+ if (m_clipboardManager != null && m_clipboardManager.hasPrimaryClip()) {
+ ClipData primaryClip = m_clipboardManager.getPrimaryClip();
+ if (primaryClip != null) {
+ for (int i = 0; i < primaryClip.getItemCount(); ++i)
+ if (primaryClip.getItemAt(i).getText() != null)
+ return primaryClip.getItemAt(i).getText().toString();
+ }
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to get clipboard data", e);
+ }
+ return "";
+ }
+
+ private void updatePrimaryClip(ClipData clipData, Context context)
+ {
+ try {
+ if (m_usePrimaryClip) {
+ ClipData clip = m_clipboardManager.getPrimaryClip();
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ Objects.requireNonNull(clip).addItem(context.getContentResolver(),
+ clipData.getItemAt(0));
+ } else {
+ Objects.requireNonNull(clip).addItem(clipData.getItemAt(0));
+ }
+ m_clipboardManager.setPrimaryClip(clip);
+ } else {
+ m_clipboardManager.setPrimaryClip(clipData);
+ m_usePrimaryClip = true;
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to set clipboard data", e);
+ }
+ }
+
+ @UsedFromNativeCode
+ public void setClipboardHtml(Context context, String text, String html)
+ {
+ if (m_clipboardManager != null) {
+ ClipData clipData = ClipData.newHtmlText("text/html", text, html);
+ updatePrimaryClip(clipData, context);
+ }
+ }
+
+ private boolean hasClipboardMimeType(String mimeType)
+ {
+ if (m_clipboardManager == null)
+ return false;
+
+ ClipDescription description = m_clipboardManager.getPrimaryClipDescription();
+ // getPrimaryClipDescription can fail if the app does not have input focus
+ if (description == null)
+ return false;
+
+ for (int i = 0; i < description.getMimeTypeCount(); ++i) {
+ String itemMimeType = description.getMimeType(i);
+ if (itemMimeType.matches(mimeType))
+ return true;
+ }
+ return false;
+ }
+
+ @UsedFromNativeCode
+ public boolean hasClipboardHtml()
+ {
+ return hasClipboardMimeType("text/html");
+ }
+
+ @UsedFromNativeCode
+ public String getClipboardHtml()
+ {
+ try {
+ if (m_clipboardManager != null && m_clipboardManager.hasPrimaryClip()) {
+ ClipData primaryClip = m_clipboardManager.getPrimaryClip();
+ if (primaryClip != null) {
+ for (int i = 0; i < primaryClip.getItemCount(); ++i)
+ if (primaryClip.getItemAt(i).getHtmlText() != null)
+ return primaryClip.getItemAt(i).getHtmlText();
+ }
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to get clipboard data", e);
+ }
+ return "";
+ }
+
+ @UsedFromNativeCode
+ public void setClipboardUri(Context context, String uriString)
+ {
+ if (m_clipboardManager != null) {
+ ClipData clipData = ClipData.newUri(context.getContentResolver(), "text/uri-list",
+ Uri.parse(uriString));
+ updatePrimaryClip(clipData, context);
+ }
+ }
+
+ @UsedFromNativeCode
+ public boolean hasClipboardUri()
+ {
+ return hasClipboardMimeType("text/uri-list");
+ }
+
+ @UsedFromNativeCode
+ private String[] getClipboardUris()
+ {
+ ArrayList<String> uris = new ArrayList<>();
+ try {
+ if (m_clipboardManager != null && m_clipboardManager.hasPrimaryClip()) {
+ ClipData primaryClip = m_clipboardManager.getPrimaryClip();
+ if (primaryClip != null) {
+ for (int i = 0; i < primaryClip.getItemCount(); ++i)
+ if (primaryClip.getItemAt(i).getUri() != null)
+ uris.add(primaryClip.getItemAt(i).getUri().toString());
+ }
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to get clipboard data", e);
+ }
+ String[] strings = new String[uris.size()];
+ strings = uris.toArray(strings);
+ return strings;
+ }
+}
diff --git a/src/android/jar/src/org/qtproject/qt/android/QtDisplayManager.java b/src/android/jar/src/org/qtproject/qt/android/QtDisplayManager.java
new file mode 100644
index 0000000000..b6a52fb22f
--- /dev/null
+++ b/src/android/jar/src/org/qtproject/qt/android/QtDisplayManager.java
@@ -0,0 +1,287 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+package org.qtproject.qt.android;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.graphics.Rect;
+import android.hardware.display.DisplayManager;
+import android.os.Build;
+import android.util.DisplayMetrics;
+import android.util.Size;
+import android.view.Display;
+import android.view.Surface;
+import android.view.View;
+import android.view.WindowInsets;
+import android.view.WindowManager;
+import android.view.WindowMetrics;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+class QtDisplayManager {
+
+ // screen methods
+ public static native void setDisplayMetrics(int screenWidthPixels, int screenHeightPixels,
+ int availableLeftPixels, int availableTopPixels,
+ int availableWidthPixels, int availableHeightPixels,
+ double XDpi, double YDpi, double scaledDensity,
+ double density, float refreshRate);
+ public static native void handleOrientationChanged(int newRotation, int nativeOrientation);
+ public static native void handleRefreshRateChanged(float refreshRate);
+ public static native void handleUiDarkModeChanged(int newUiMode);
+ public static native void handleScreenAdded(int displayId);
+ public static native void handleScreenChanged(int displayId);
+ public static native void handleScreenRemoved(int displayId);
+ // screen methods
+
+ // Keep in sync with QtAndroid::SystemUiVisibility in androidjnimain.h
+ public static final int SYSTEM_UI_VISIBILITY_NORMAL = 0;
+ public static final int SYSTEM_UI_VISIBILITY_FULLSCREEN = 1;
+ public static final int SYSTEM_UI_VISIBILITY_TRANSLUCENT = 2;
+ private int m_systemUiVisibility = SYSTEM_UI_VISIBILITY_NORMAL;
+
+ private static int m_previousRotation = -1;
+
+ private DisplayManager.DisplayListener m_displayListener = null;
+ private final Activity m_activity;
+
+ QtDisplayManager(Activity activity)
+ {
+ m_activity = activity;
+ initDisplayListener();
+ }
+
+ private void initDisplayListener() {
+ m_displayListener = new DisplayManager.DisplayListener() {
+ @Override
+ public void onDisplayAdded(int displayId) {
+ QtDisplayManager.handleScreenAdded(displayId);
+ }
+
+ @Override
+ public void onDisplayChanged(int displayId) {
+ Display display = (Build.VERSION.SDK_INT < Build.VERSION_CODES.R)
+ ? m_activity.getWindowManager().getDefaultDisplay()
+ : m_activity.getDisplay();
+ float refreshRate = getRefreshRate(display);
+ QtDisplayManager.handleRefreshRateChanged(refreshRate);
+ QtDisplayManager.handleScreenChanged(displayId);
+ }
+
+ @Override
+ public void onDisplayRemoved(int displayId) {
+ QtDisplayManager.handleScreenRemoved(displayId);
+ }
+ };
+ }
+
+ static void handleOrientationChanges(Activity activity)
+ {
+ int currentRotation = getDisplayRotation(activity);
+ if (m_previousRotation == currentRotation)
+ return;
+ int nativeOrientation = getNativeOrientation(activity, currentRotation);
+ QtDisplayManager.handleOrientationChanged(currentRotation, nativeOrientation);
+ m_previousRotation = currentRotation;
+ }
+
+ public static int getDisplayRotation(Activity activity) {
+ Display display = Build.VERSION.SDK_INT < Build.VERSION_CODES.R ?
+ activity.getWindowManager().getDefaultDisplay() :
+ activity.getDisplay();
+
+ return display != null ? display.getRotation() : 0;
+ }
+
+ private static int getNativeOrientation(Activity activity, int rotation)
+ {
+ int orientation = activity.getResources().getConfiguration().orientation;
+ boolean rot90 = (rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270);
+ boolean isLandscape = (orientation == Configuration.ORIENTATION_LANDSCAPE);
+ if ((isLandscape && !rot90) || (!isLandscape && rot90))
+ return Configuration.ORIENTATION_LANDSCAPE;
+
+ return Configuration.ORIENTATION_PORTRAIT;
+ }
+
+ static float getRefreshRate(Display display)
+ {
+ return display != null ? display.getRefreshRate() : 60.0f;
+ }
+
+ public void registerDisplayListener()
+ {
+ DisplayManager displayManager =
+ (DisplayManager) m_activity.getSystemService(Context.DISPLAY_SERVICE);
+ displayManager.registerDisplayListener(m_displayListener, null);
+ }
+
+ public void unregisterDisplayListener()
+ {
+ DisplayManager displayManager =
+ (DisplayManager) m_activity.getSystemService(Context.DISPLAY_SERVICE);
+ displayManager.unregisterDisplayListener(m_displayListener);
+ }
+
+ public void setSystemUiVisibility(int systemUiVisibility)
+ {
+ if (m_systemUiVisibility == systemUiVisibility)
+ return;
+
+ m_systemUiVisibility = systemUiVisibility;
+
+ int systemUiVisibilityFlags = View.SYSTEM_UI_FLAG_VISIBLE;
+ switch (m_systemUiVisibility) {
+ case SYSTEM_UI_VISIBILITY_NORMAL:
+ m_activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN);
+ m_activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
+ m_activity.getWindow().getAttributes().layoutInDisplayCutoutMode =
+ WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER;
+ }
+ break;
+ case SYSTEM_UI_VISIBILITY_FULLSCREEN:
+ m_activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
+ m_activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN);
+ systemUiVisibilityFlags = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
+ | View.SYSTEM_UI_FLAG_LAYOUT_STABLE
+ | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
+ | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
+ | View.SYSTEM_UI_FLAG_FULLSCREEN
+ | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
+ | View.INVISIBLE;
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
+ m_activity.getWindow().getAttributes().layoutInDisplayCutoutMode =
+ WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT;
+ }
+ break;
+ case SYSTEM_UI_VISIBILITY_TRANSLUCENT:
+ m_activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN
+ | WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION
+ | WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
+ m_activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+ m_activity.getWindow().getAttributes().layoutInDisplayCutoutMode =
+ WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+ }
+ break;
+ }
+ m_activity.getWindow().getDecorView().setSystemUiVisibility(systemUiVisibilityFlags);
+ }
+
+ public int systemUiVisibility()
+ {
+ return m_systemUiVisibility;
+ }
+
+ public void updateFullScreen()
+ {
+ if (m_systemUiVisibility == SYSTEM_UI_VISIBILITY_FULLSCREEN) {
+ m_systemUiVisibility = SYSTEM_UI_VISIBILITY_NORMAL;
+ setSystemUiVisibility(SYSTEM_UI_VISIBILITY_FULLSCREEN);
+ }
+ }
+
+ @UsedFromNativeCode
+ public static Display getDisplay(Context context, int displayId)
+ {
+ DisplayManager displayManager =
+ (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE);
+ if (displayManager != null) {
+ return displayManager.getDisplay(displayId);
+ }
+ return null;
+ }
+
+ @UsedFromNativeCode
+ public static List<Display> getAvailableDisplays(Context context)
+ {
+ DisplayManager displayManager =
+ (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE);
+ if (displayManager != null) {
+ Display[] displays = displayManager.getDisplays();
+ return Arrays.asList(displays);
+ }
+ return new ArrayList<>();
+ }
+
+ @UsedFromNativeCode
+ public static Size getDisplaySize(Context displayContext, Display display)
+ {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
+ DisplayMetrics realMetrics = new DisplayMetrics();
+ display.getRealMetrics(realMetrics);
+ return new Size(realMetrics.widthPixels, realMetrics.heightPixels);
+ }
+
+ Context windowsContext = displayContext.createWindowContext(
+ WindowManager.LayoutParams.TYPE_APPLICATION, null);
+ WindowManager windowManager =
+ (WindowManager) windowsContext.getSystemService(Context.WINDOW_SERVICE);
+ WindowMetrics windowsMetrics = windowManager.getCurrentWindowMetrics();
+ Rect bounds = windowsMetrics.getBounds();
+ return new Size(bounds.width(), bounds.height());
+ }
+
+ public static void setApplicationDisplayMetrics(Activity activity, int width, int height)
+ {
+ if (activity == null)
+ return;
+
+ final WindowInsets rootInsets = activity.getWindow().getDecorView().getRootWindowInsets();
+ final WindowManager windowManager = activity.getWindowManager();
+ Display display;
+
+ int insetLeft;
+ int insetTop;
+
+ int maxWidth;
+ int maxHeight;
+
+ if (android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
+ display = windowManager.getDefaultDisplay();
+
+ final DisplayMetrics maxMetrics = new DisplayMetrics();
+ display.getRealMetrics(maxMetrics);
+ maxWidth = maxMetrics.widthPixels;
+ maxHeight = maxMetrics.heightPixels;
+
+ insetLeft = rootInsets.getStableInsetLeft();
+ insetTop = rootInsets.getStableInsetTop();
+ } else {
+ display = activity.getDisplay();
+
+ final WindowMetrics maxMetrics = windowManager.getMaximumWindowMetrics();
+ maxWidth = maxMetrics.getBounds().width();
+ maxHeight = maxMetrics.getBounds().height();
+
+ insetLeft = rootInsets.getInsetsIgnoringVisibility(WindowInsets.Type.systemBars()).left;
+ insetTop = rootInsets.getInsetsIgnoringVisibility(WindowInsets.Type.systemBars()).top;
+ }
+
+ final DisplayMetrics displayMetrics = activity.getResources().getDisplayMetrics();
+
+ double density = displayMetrics.density;
+ double scaledDensity = displayMetrics.scaledDensity;
+
+ setDisplayMetrics(maxWidth, maxHeight, insetLeft, insetTop,
+ width, height, getXDpi(displayMetrics), getYDpi(displayMetrics),
+ scaledDensity, density, getRefreshRate(display));
+ }
+
+ public static float getXDpi(final DisplayMetrics metrics) {
+ if (metrics.xdpi < android.util.DisplayMetrics.DENSITY_LOW)
+ return android.util.DisplayMetrics.DENSITY_LOW;
+ return metrics.xdpi;
+ }
+
+ public static float getYDpi(final DisplayMetrics metrics) {
+ if (metrics.ydpi < android.util.DisplayMetrics.DENSITY_LOW)
+ return android.util.DisplayMetrics.DENSITY_LOW;
+ return metrics.ydpi;
+ }
+}
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 ff58b6b27f..4524887242 100644
--- a/src/android/jar/src/org/qtproject/qt/android/QtEditText.java
+++ b/src/android/jar/src/org/qtproject/qt/android/QtEditText.java
@@ -1,110 +1,221 @@
-/****************************************************************************
-**
-** Copyright (C) 2016 The Qt Company Ltd.
-** Copyright (C) 2012 BogDan Vatra <bogdan@kde.org>
-** Contact: https://www.qt.io/licensing/
-**
-** This file is part of the Android port of the Qt Toolkit.
-**
-** $QT_BEGIN_LICENSE:LGPL$
-** Commercial License Usage
-** Licensees holding valid commercial Qt licenses may use this file in
-** accordance with the commercial license agreement provided with the
-** Software or, alternatively, in accordance with the terms contained in
-** a written agreement between you and The Qt Company. For licensing terms
-** and conditions see https://www.qt.io/terms-conditions. For further
-** information use the contact form at https://www.qt.io/contact-us.
-**
-** GNU Lesser General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU Lesser
-** General Public License version 3 as published by the Free Software
-** Foundation and appearing in the file LICENSE.LGPL3 included in the
-** packaging of this file. Please review the following information to
-** ensure the GNU Lesser General Public License version 3 requirements
-** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
-**
-** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 2.0 or (at your option) the GNU General
-** Public license version 3 or any later version approved by the KDE Free
-** Qt Foundation. The licenses are as published by the Free Software
-** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
-** included in the packaging of this file. Please review the following
-** information to ensure the GNU General Public License requirements will
-** be met: https://www.gnu.org/licenses/gpl-2.0.html and
-** https://www.gnu.org/licenses/gpl-3.0.html.
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
+// Copyright (C) 2016 The Qt Company Ltd.
+// Copyright (C) 2012 BogDan Vatra <bogdan@kde.org>
+// 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.graphics.Canvas;
import android.text.InputType;
import android.view.View;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
+import android.view.KeyEvent;
-public class QtEditText extends View
+import org.qtproject.qt.android.QtInputConnection.QtInputConnectionListener;
+
+class QtEditText extends View
{
int m_initialCapsMode = 0;
int m_imeOptions = 0;
int m_inputType = InputType.TYPE_CLASS_TEXT;
boolean m_optionsChanged = false;
- QtActivityDelegate m_activityDelegate;
+ QtInputConnection m_inputConnection = null;
+
+ // input method hints - must be kept in sync with QTDIR/src/corelib/global/qnamespace.h
+ private final int ImhHiddenText = 0x1;
+ private final int ImhSensitiveData = 0x2;
+ private final int ImhNoAutoUppercase = 0x4;
+ private final int ImhPreferNumbers = 0x8;
+ private final int ImhPreferUppercase = 0x10;
+ private final int ImhPreferLowercase = 0x20;
+ private final int ImhNoPredictiveText = 0x40;
+
+ private final int ImhDate = 0x80;
+ private final int ImhTime = 0x100;
+
+ private final int ImhPreferLatin = 0x200;
+
+ private final int ImhMultiLine = 0x400;
+
+ private final int ImhDigitsOnly = 0x10000;
+ private final int ImhFormattedNumbersOnly = 0x20000;
+ private final int ImhUppercaseOnly = 0x40000;
+ private final int ImhLowercaseOnly = 0x80000;
+ private final int ImhDialableCharactersOnly = 0x100000;
+ private final int ImhEmailCharactersOnly = 0x200000;
+ private final int ImhUrlCharactersOnly = 0x400000;
+ private final int ImhLatinOnly = 0x800000;
+
+ private final QtInputConnectionListener m_qtInputConnectionListener;
+
+ public QtEditText(Context context, QtInputConnectionListener listener)
+ {
+ super(context);
+ setFocusable(true);
+ setFocusableInTouchMode(true);
+ m_qtInputConnectionListener = listener;
+ }
- public void setImeOptions(int m_imeOptions)
+ private void setImeOptions(int imeOptions)
{
- if (m_imeOptions == this.m_imeOptions)
+ if (m_imeOptions == imeOptions)
return;
- this.m_imeOptions = m_imeOptions;
+ m_imeOptions = m_imeOptions;
m_optionsChanged = true;
}
- public void setInitialCapsMode(int m_initialCapsMode)
+ private void setInitialCapsMode(int initialCapsMode)
{
- if (m_initialCapsMode == this.m_initialCapsMode)
+ if (m_initialCapsMode == initialCapsMode)
return;
- this.m_initialCapsMode = m_initialCapsMode;
+ m_initialCapsMode = initialCapsMode;
m_optionsChanged = true;
}
- public void setInputType(int m_inputType)
+ private void setInputType(int inputType)
{
- if (m_inputType == this.m_inputType)
+ if (m_inputType == inputType)
return;
- this.m_inputType = m_inputType;
+ m_inputType = m_inputType;
m_optionsChanged = true;
}
- public QtEditText(Context context, QtActivityDelegate activityDelegate)
+ @Override
+ public InputConnection onCreateInputConnection(EditorInfo outAttrs)
{
- super(context);
- setFocusable(true);
- setFocusableInTouchMode(true);
- m_activityDelegate = activityDelegate;
+ outAttrs.inputType = m_inputType;
+ outAttrs.imeOptions = m_imeOptions;
+ outAttrs.initialCapsMode = m_initialCapsMode;
+ m_inputConnection = new QtInputConnection(this,m_qtInputConnectionListener);
+ return m_inputConnection;
}
- public QtActivityDelegate getActivityDelegate()
+
+ @Override
+ public boolean onCheckIsTextEditor ()
{
- return m_activityDelegate;
+ return true;
}
@Override
- public InputConnection onCreateInputConnection(EditorInfo outAttrs)
+ public boolean onKeyDown (int keyCode, KeyEvent event)
{
- outAttrs.inputType = m_inputType;
- outAttrs.imeOptions = m_imeOptions;
- outAttrs.initialCapsMode = m_initialCapsMode;
- outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_EXTRACT_UI;
- return new QtInputConnection(this);
+ if (null != m_inputConnection)
+ m_inputConnection.restartImmInput();
+
+ return super.onKeyDown(keyCode, event);
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ // DEBUG CODE
+ // canvas.drawARGB(127, 255, 0, 255);
+ super.onDraw(canvas);
}
-// // DEBUG CODE
-// @Override
-// protected void onDraw(Canvas canvas) {
-// canvas.drawARGB(127, 255, 0, 255);
-// super.onDraw(canvas);
-// }
+
+ public void setEditTextOptions(int enterKeyType, int inputHints)
+ {
+ int initialCapsMode = 0;
+ int imeOptions = imeOptionsFromEnterKeyType(enterKeyType);
+ int inputType = android.text.InputType.TYPE_CLASS_TEXT;
+
+ if ((inputHints & (ImhPreferNumbers | ImhDigitsOnly | ImhFormattedNumbersOnly)) != 0) {
+ inputType = android.text.InputType.TYPE_CLASS_NUMBER;
+ if ((inputHints & ImhFormattedNumbersOnly) != 0) {
+ inputType |= (android.text.InputType.TYPE_NUMBER_FLAG_DECIMAL
+ | android.text.InputType.TYPE_NUMBER_FLAG_SIGNED);
+ }
+
+ if ((inputHints & ImhHiddenText) != 0)
+ inputType |= android.text.InputType.TYPE_NUMBER_VARIATION_PASSWORD;
+ } else if ((inputHints & ImhDialableCharactersOnly) != 0) {
+ inputType = android.text.InputType.TYPE_CLASS_PHONE;
+ } else if ((inputHints & (ImhDate | ImhTime)) != 0) {
+ inputType = android.text.InputType.TYPE_CLASS_DATETIME;
+ if ((inputHints & (ImhDate | ImhTime)) != (ImhDate | ImhTime)) {
+ if ((inputHints & ImhDate) != 0)
+ inputType |= android.text.InputType.TYPE_DATETIME_VARIATION_DATE;
+ else
+ inputType |= android.text.InputType.TYPE_DATETIME_VARIATION_TIME;
+ } // else { TYPE_DATETIME_VARIATION_NORMAL(0) }
+ } else { // CLASS_TEXT
+ if ((inputHints & ImhHiddenText) != 0) {
+ inputType |= android.text.InputType.TYPE_TEXT_VARIATION_PASSWORD;
+ } else if ((inputHints & ImhSensitiveData) != 0 ||
+ isDisablePredictiveTextWorkaround(inputHints)) {
+ inputType |= android.text.InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD;
+ } else if ((inputHints & ImhUrlCharactersOnly) != 0) {
+ inputType |= android.text.InputType.TYPE_TEXT_VARIATION_URI;
+ if (enterKeyType == 0) // not explicitly overridden
+ imeOptions = android.view.inputmethod.EditorInfo.IME_ACTION_GO;
+ } else if ((inputHints & ImhEmailCharactersOnly) != 0) {
+ inputType |= android.text.InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS;
+ }
+
+ if ((inputHints & ImhMultiLine) != 0) {
+ inputType |= android.text.InputType.TYPE_TEXT_FLAG_MULTI_LINE;
+ // Clear imeOptions for Multi-Line Type
+ // User should be able to insert new line in such case
+ imeOptions = android.view.inputmethod.EditorInfo.IME_ACTION_DONE;
+ }
+ if ((inputHints & (ImhNoPredictiveText | ImhSensitiveData | ImhHiddenText)) != 0)
+ inputType |= android.text.InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS;
+
+ if ((inputHints & ImhUppercaseOnly) != 0) {
+ initialCapsMode |= android.text.TextUtils.CAP_MODE_CHARACTERS;
+ inputType |= android.text.InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS;
+ } else if ((inputHints & ImhLowercaseOnly) == 0
+ && (inputHints & ImhNoAutoUppercase) == 0) {
+ initialCapsMode |= android.text.TextUtils.CAP_MODE_SENTENCES;
+ inputType |= android.text.InputType.TYPE_TEXT_FLAG_CAP_SENTENCES;
+ }
+ }
+
+ if (enterKeyType == 0 && (inputHints & ImhMultiLine) != 0)
+ imeOptions = android.view.inputmethod.EditorInfo.IME_FLAG_NO_ENTER_ACTION;
+
+ setInitialCapsMode(initialCapsMode);
+ setImeOptions(imeOptions);
+ setInputType(inputType);
+ }
+
+ private int imeOptionsFromEnterKeyType(int enterKeyType)
+ {
+ int imeOptions = android.view.inputmethod.EditorInfo.IME_ACTION_DONE;
+
+ // enter key type - must be kept in sync with QTDIR/src/corelib/global/qnamespace.h
+ switch (enterKeyType) {
+ case 0: // EnterKeyDefault
+ break;
+ case 1: // EnterKeyReturn
+ imeOptions = android.view.inputmethod.EditorInfo.IME_FLAG_NO_ENTER_ACTION;
+ break;
+ case 2: // EnterKeyDone
+ break;
+ case 3: // EnterKeyGo
+ imeOptions = android.view.inputmethod.EditorInfo.IME_ACTION_GO;
+ break;
+ case 4: // EnterKeySend
+ imeOptions = android.view.inputmethod.EditorInfo.IME_ACTION_SEND;
+ break;
+ case 5: // EnterKeySearch
+ imeOptions = android.view.inputmethod.EditorInfo.IME_ACTION_SEARCH;
+ break;
+ case 6: // EnterKeyNext
+ imeOptions = android.view.inputmethod.EditorInfo.IME_ACTION_NEXT;
+ break;
+ case 7: // EnterKeyPrevious
+ imeOptions = android.view.inputmethod.EditorInfo.IME_ACTION_PREVIOUS;
+ break;
+ }
+ return imeOptions;
+ }
+
+ private boolean isDisablePredictiveTextWorkaround(int inputHints)
+ {
+ return (inputHints & ImhNoPredictiveText) != 0 &&
+ System.getenv("QT_ANDROID_ENABLE_WORKAROUND_TO_DISABLE_PREDICTIVE_TEXT") != null;
+ }
}
diff --git a/src/android/jar/src/org/qtproject/qt/android/QtEmbeddedDelegate.java b/src/android/jar/src/org/qtproject/qt/android/QtEmbeddedDelegate.java
new file mode 100644
index 0000000000..1c0fd0f7d8
--- /dev/null
+++ b/src/android/jar/src/org/qtproject/qt/android/QtEmbeddedDelegate.java
@@ -0,0 +1,174 @@
+// 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.*;
+
+import android.app.Activity;
+import android.app.Application;
+import android.content.Context;
+import android.content.res.Resources;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+class QtEmbeddedDelegate extends QtActivityDelegateBase implements QtNative.AppStateDetailsListener {
+ // 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);
+
+ public QtEmbeddedDelegate(Activity context) {
+ super(context);
+
+ m_stateDetails = QtNative.getStateDetails();
+ QtNative.registerAppStateListener(this);
+
+ m_activity.getApplication().registerActivityLifecycleCallbacks(
+ new Application.ActivityLifecycleCallbacks() {
+ @Override
+ public void onActivityCreated(Activity activity, Bundle savedInstanceState) {}
+
+ @Override
+ public void onActivityStarted(Activity activity) {}
+
+ @Override
+ public void onActivityResumed(Activity activity) {
+ if (m_activity == activity && m_stateDetails.isStarted) {
+ QtNative.setApplicationState(ApplicationActive);
+ QtNative.updateWindow();
+ }
+ }
+
+ @Override
+ public void onActivityPaused(Activity activity) {
+ if (m_activity == activity && m_stateDetails.isStarted) {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N ||
+ !activity.isInMultiWindowMode()) {
+ QtNative.setApplicationState(ApplicationInactive);
+ }
+ }
+ }
+
+ @Override
+ public void onActivityStopped(Activity activity) {
+ if (m_activity == activity && m_stateDetails.isStarted) {
+ QtNative.setApplicationState(ApplicationSuspended);
+ }
+ }
+
+ @Override
+ public void onActivitySaveInstanceState(Activity activity, Bundle outState) {}
+
+ @Override
+ public void onActivityDestroyed(Activity activity) {
+ if (m_activity == activity && m_stateDetails.isStarted) {
+ m_activity.getApplication().unregisterActivityLifecycleCallbacks(this);
+ QtNative.unregisterAppStateListener(QtEmbeddedDelegate.this);
+ QtEmbeddedDelegateFactory.remove(m_activity);
+ QtNative.terminateQt();
+ QtNative.setActivity(null);
+ QtNative.getQtThread().exit();
+ onDestroy();
+ }
+ }
+ });
+ }
+
+ @Override
+ public void onAppStateDetailsChanged(QtNative.ApplicationStateDetails details) {
+ synchronized (this) {
+ m_stateDetails = details;
+ if (m_stateDetails.nativePluginIntegrationReady) {
+ QtNative.runAction(() -> {
+ DisplayMetrics metrics = Resources.getSystem().getDisplayMetrics();
+ QtDisplayManager.setApplicationDisplayMetrics(m_activity,
+ metrics.widthPixels,
+ metrics.heightPixels);
+
+ });
+ createRootWindow();
+ }
+ }
+ }
+
+ @Override
+ void startNativeApplicationImpl(String appParams, String mainLib)
+ {
+ QtNative.startApplication(appParams, mainLib);
+ }
+
+ @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()
+ {
+ // 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
+ // QTBUG-118873
+ if (m_view == null)
+ return null;
+ return m_view.getQtWindow();
+ }
+
+ public void queueLoadWindow()
+ {
+ synchronized (this) {
+ if (m_stateDetails.nativePluginIntegrationReady)
+ createRootWindow();
+ }
+ }
+
+ void setView(QtView view) {
+ m_view = view;
+ updateInputDelegate();
+ if (m_view != null)
+ registerGlobalFocusChangeListener(m_view);
+ }
+
+ private void updateInputDelegate() {
+ if (m_view == null) {
+ m_inputDelegate.setEditPopupMenu(null);
+ return;
+ }
+ 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());
+ m_windowLoaded = true;
+ }
+ }
+}
diff --git a/src/android/jar/src/org/qtproject/qt/android/QtEmbeddedDelegateFactory.java b/src/android/jar/src/org/qtproject/qt/android/QtEmbeddedDelegateFactory.java
new file mode 100644
index 0000000000..8cf89e5bc3
--- /dev/null
+++ b/src/android/jar/src/org/qtproject/qt/android/QtEmbeddedDelegateFactory.java
@@ -0,0 +1,37 @@
+// 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
new file mode 100644
index 0000000000..65cfcbeef1
--- /dev/null
+++ b/src/android/jar/src/org/qtproject/qt/android/QtEmbeddedLoader.java
@@ -0,0 +1,52 @@
+// 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.Service;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.os.Build;
+import android.os.Bundle;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.SurfaceView;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Objects;
+
+import dalvik.system.DexClassLoader;
+import android.content.res.Resources;
+
+class QtEmbeddedLoader extends QtLoader {
+ private static final String TAG = "QtEmbeddedLoader";
+
+ public QtEmbeddedLoader(Context context) {
+ super(new ContextWrapper(context));
+ // TODO Service context handling QTBUG-118874
+ int displayDensity = m_context.getResources().getDisplayMetrics().densityDpi;
+ setEnvironmentVariable("QT_ANDROID_THEME_DISPLAY_DPI", String.valueOf(displayDensity));
+ String stylePath = ExtractStyle.setup(m_context, "minimal", displayDensity);
+ setEnvironmentVariable("ANDROID_STYLE_PATH", stylePath);
+ }
+
+ @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());
+ }
+}
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 6cf1aca5bf..1bfe05e7ac 100644
--- a/src/android/jar/src/org/qtproject/qt/android/QtInputConnection.java
+++ b/src/android/jar/src/org/qtproject/qt/android/QtInputConnection.java
@@ -1,46 +1,13 @@
-/****************************************************************************
-**
-** Copyright (C) 2016 The Qt Company Ltd.
-** Copyright (C) 2012 BogDan Vatra <bogdan@kde.org>
-** Contact: https://www.qt.io/licensing/
-**
-** This file is part of the Android port of the Qt Toolkit.
-**
-** $QT_BEGIN_LICENSE:LGPL$
-** Commercial License Usage
-** Licensees holding valid commercial Qt licenses may use this file in
-** accordance with the commercial license agreement provided with the
-** Software or, alternatively, in accordance with the terms contained in
-** a written agreement between you and The Qt Company. For licensing terms
-** and conditions see https://www.qt.io/terms-conditions. For further
-** information use the contact form at https://www.qt.io/contact-us.
-**
-** GNU Lesser General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU Lesser
-** General Public License version 3 as published by the Free Software
-** Foundation and appearing in the file LICENSE.LGPL3 included in the
-** packaging of this file. Please review the following information to
-** ensure the GNU Lesser General Public License version 3 requirements
-** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
-**
-** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 2.0 or (at your option) the GNU General
-** Public license version 3 or any later version approved by the KDE Free
-** Qt Foundation. The licenses are as published by the Free Software
-** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
-** included in the packaging of this file. Please review the following
-** information to ensure the GNU General Public License requirements will
-** be met: https://www.gnu.org/licenses/gpl-2.0.html and
-** https://www.gnu.org/licenses/gpl-3.0.html.
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
+// Copyright (C) 2016 The Qt Company Ltd.
+// Copyright (C) 2012 BogDan Vatra <bogdan@kde.org>
+// 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.os.Build;
+import android.util.Log;
+import android.view.WindowMetrics;
import android.view.inputmethod.BaseInputConnection;
import android.view.inputmethod.CompletionInfo;
import android.view.inputmethod.ExtractedText;
@@ -83,26 +50,11 @@ class QtNativeInputConnection
static native boolean copyURL();
static native boolean paste();
static native boolean updateCursorPosition();
+ static native void reportFullscreenMode(boolean enabled);
+ static native boolean fullscreenMode();
}
-class HideKeyboardRunnable implements Runnable {
- private long m_hideTimeStamp = System.nanoTime();
-
- @Override
- public void run() {
- // Check that the keyboard is really no longer there.
- Activity activity = QtNative.activity();
- Rect r = new Rect();
- activity.getWindow().getDecorView().getWindowVisibleDisplayFrame(r);
- DisplayMetrics metrics = new DisplayMetrics();
- activity.getWindowManager().getDefaultDisplay().getMetrics(metrics);
- final int kbHeight = metrics.heightPixels - r.bottom;
- if (kbHeight < 100)
- QtNative.activityDelegate().setKeyboardVisibility(false, m_hideTimeStamp);
- }
-}
-
-public class QtInputConnection extends BaseInputConnection
+class QtInputConnection extends BaseInputConnection
{
private static final int ID_SELECT_ALL = android.R.id.selectAll;
private static final int ID_CUT = android.R.id.cut;
@@ -112,21 +64,71 @@ public class QtInputConnection extends BaseInputConnection
private static final int ID_SWITCH_INPUT_METHOD = android.R.id.switchInputMethod;
private static final int ID_ADD_TO_DICTIONARY = android.R.id.addToDictionary;
- private QtEditText m_view = null;
+ private static final String QtTAG = "QtInputConnection";
+
+ private final QtInputConnectionListener m_qtInputConnectionListener;
+
+ class HideKeyboardRunnable implements Runnable {
+ @Override
+ public void run() {
+ // Check that the keyboard is really no longer there.
+ Activity activity = QtNative.activity();
+ if (activity == null) {
+ Log.w(QtTAG, "HideKeyboardRunnable: The activity reference is null");
+ return;
+ }
+
+ Rect r = new Rect();
+ activity.getWindow().getDecorView().getWindowVisibleDisplayFrame(r);
+
+ int screenHeight;
+ if (android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
+ DisplayMetrics metrics = new DisplayMetrics();
+ activity.getWindowManager().getDefaultDisplay().getMetrics(metrics);
+ screenHeight = metrics.heightPixels;
+ } else {
+ final WindowMetrics maximumWindowMetrics = activity.getWindowManager().getMaximumWindowMetrics();
+ screenHeight = maximumWindowMetrics.getBounds().height();
+ }
+ final int kbHeight = screenHeight - r.bottom;
+ if (kbHeight < 100)
+ m_qtInputConnectionListener.onHideKeyboardRunnableDone(false, System.nanoTime());
+ }
+ }
+
+ public interface QtInputConnectionListener {
+ void onSetClosing(boolean closing);
+ void onHideKeyboardRunnableDone(boolean visibility, long hideTimeStamp);
+ void onSendKeyEventDefaultCase();
+ }
+
+ private final QtEditText m_view;
+ private final InputMethodManager m_imm;
private void setClosing(boolean closing)
{
- if (closing) {
+ if (closing)
m_view.postDelayed(new HideKeyboardRunnable(), 100);
- } else {
- QtNative.activityDelegate().setKeyboardVisibility(true, System.nanoTime());
- }
+ else
+ m_qtInputConnectionListener.onSetClosing(false);
}
- public QtInputConnection(QtEditText targetView)
+ public QtInputConnection(QtEditText targetView, QtInputConnectionListener listener)
{
super(targetView, true);
m_view = targetView;
+ m_imm = (InputMethodManager)m_view.getContext().getSystemService(
+ Context.INPUT_METHOD_SERVICE);
+ m_qtInputConnectionListener = listener;
+ }
+
+ public void restartImmInput()
+ {
+ if (QtNativeInputConnection.fullscreenMode()) {
+ if (m_imm != null)
+ m_imm.restartInput(m_view);
+ }
+
}
@Override
@@ -137,6 +139,18 @@ public class QtInputConnection extends BaseInputConnection
}
@Override
+ public boolean reportFullscreenMode (boolean enabled)
+ {
+ QtNativeInputConnection.reportFullscreenMode(enabled);
+ // Always ignored on calling editor.
+ // Always false on Android 8 and later, true with earlier.
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
+ return false;
+
+ return true;
+ }
+
+ @Override
public boolean endBatchEdit()
{
setClosing(false);
@@ -154,6 +168,7 @@ public class QtInputConnection extends BaseInputConnection
public boolean commitText(CharSequence text, int newCursorPosition)
{
setClosing(false);
+ restartImmInput();
return QtNativeInputConnection.commitText(text.toString(), newCursorPosition);
}
@@ -219,23 +234,25 @@ public class QtInputConnection extends BaseInputConnection
{
switch (id) {
case ID_SELECT_ALL:
+ restartImmInput();
return QtNativeInputConnection.selectAll();
case ID_COPY:
+ restartImmInput();
return QtNativeInputConnection.copy();
case ID_COPY_URL:
+ restartImmInput();
return QtNativeInputConnection.copyURL();
case ID_CUT:
+ restartImmInput();
return QtNativeInputConnection.cut();
case ID_PASTE:
+ restartImmInput();
return QtNativeInputConnection.paste();
-
case ID_SWITCH_INPUT_METHOD:
- InputMethodManager imm = (InputMethodManager)m_view.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
- if (imm != null)
- imm.showInputMethodPicker();
+ if (m_imm != null)
+ m_imm.showInputMethodPicker();
return true;
-
case ID_ADD_TO_DICTIONARY:
// TODO
// String word = m_editable.subSequence(0, m_editable.length()).toString();
@@ -268,8 +285,7 @@ public class QtInputConnection extends BaseInputConnection
event.getRepeatCount(),
event.getMetaState());
return super.sendKeyEvent(fakeEvent);
-
- case android.view.inputmethod.EditorInfo.IME_ACTION_PREVIOUS:
+ case android.view.inputmethod.EditorInfo.IME_ACTION_PREVIOUS:
fakeEvent = new KeyEvent(event.getDownTime(),
event.getEventTime(),
event.getAction(),
@@ -277,12 +293,14 @@ public class QtInputConnection extends BaseInputConnection
event.getRepeatCount(),
KeyEvent.META_SHIFT_ON);
return super.sendKeyEvent(fakeEvent);
-
+ case android.view.inputmethod.EditorInfo.IME_FLAG_NO_ENTER_ACTION:
+ restartImmInput();
+ break;
default:
- break;
+ m_qtInputConnectionListener.onSendKeyEventDefaultCase();
+ break;
}
}
-
return super.sendKeyEvent(event);
}
diff --git a/src/android/jar/src/org/qtproject/qt/android/QtInputDelegate.java b/src/android/jar/src/org/qtproject/qt/android/QtInputDelegate.java
new file mode 100644
index 0000000000..cfa273e410
--- /dev/null
+++ b/src/android/jar/src/org/qtproject/qt/android/QtInputDelegate.java
@@ -0,0 +1,654 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+package org.qtproject.qt.android;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.ResultReceiver;
+import android.text.method.MetaKeyKeyListener;
+import android.util.DisplayMetrics;
+import android.view.InputDevice;
+import android.view.KeyCharacterMap;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.WindowManager;
+import android.view.inputmethod.InputMethodManager;
+
+import org.qtproject.qt.android.QtInputConnection.QtInputConnectionListener;
+
+/** @noinspection FieldCanBeLocal*/
+class QtInputDelegate implements QtInputConnection.QtInputConnectionListener {
+
+ // keyboard methods
+ public static native void keyDown(int key, int unicode, int modifier, boolean autoRepeat);
+ public static native void keyUp(int key, int unicode, int modifier, boolean autoRepeat);
+ public static native void keyboardVisibilityChanged(boolean visibility);
+ public static native void keyboardGeometryChanged(int x, int y, int width, int height);
+ // keyboard methods
+
+ // dispatch events methods
+ public static native boolean dispatchGenericMotionEvent(MotionEvent event);
+ public static native boolean dispatchKeyEvent(KeyEvent event);
+ // dispatch events methods
+
+ // handle methods
+ public static native void handleLocationChanged(int id, int x, int y);
+ // handle methods
+
+ private QtEditText m_currentEditText = null;
+ private final InputMethodManager m_imm;
+
+ private boolean m_keyboardIsVisible = false;
+ private boolean m_isKeyboardHidingAnimationOngoing = false;
+ private long m_showHideTimeStamp = System.nanoTime();
+ private int m_portraitKeyboardHeight = 0;
+ private int m_landscapeKeyboardHeight = 0;
+ private int m_probeKeyboardHeightDelayMs = 50;
+ private CursorHandle m_cursorHandle;
+ private CursorHandle m_leftSelectionHandle;
+ private CursorHandle m_rightSelectionHandle;
+ private EditPopupMenu m_editPopupMenu;
+
+ private int m_softInputMode = 0;
+
+ // Values coming from QAndroidInputContext::CursorHandleShowMode
+ private static final int CursorHandleNotShown = 0;
+ private static final int CursorHandleShowNormal = 1;
+ private static final int CursorHandleShowSelection = 2;
+ private static final int CursorHandleShowEdit = 0x100;
+
+ // Handle IDs
+ public static final int IdCursorHandle = 1;
+ public static final int IdLeftHandle = 2;
+ public static final int IdRightHandle = 3;
+
+ private static Boolean m_tabletEventSupported = null;
+
+ private static int m_oldX, m_oldY;
+
+
+ private long m_metaState;
+ private int m_lastChar = 0;
+ private boolean m_backKeyPressedSent = false;
+
+ // Note: because of the circular call to updateFullScreen() from the delegate, we need
+ // a listener to be able to do that call from the delegate, because that's where that
+ // logic lives
+ public interface KeyboardVisibilityListener {
+ void onKeyboardVisibilityChange();
+ }
+
+ private final KeyboardVisibilityListener m_keyboardVisibilityListener;
+
+ QtInputDelegate(Activity activity, KeyboardVisibilityListener listener)
+ {
+ this.m_keyboardVisibilityListener = listener;
+ m_imm = (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE);
+ }
+
+ // QtInputConnectionListener methods
+ @Override
+ public void onSetClosing(boolean closing) {
+ if (!closing)
+ setKeyboardVisibility(true, System.nanoTime());
+ }
+
+ @Override
+ public void onHideKeyboardRunnableDone(boolean visibility, long hideTimeStamp) {
+ setKeyboardVisibility(visibility, hideTimeStamp);
+ }
+
+ @Override
+ public void onSendKeyEventDefaultCase() {
+ hideSoftwareKeyboard();
+ }
+ // QtInputConnectionListener methods
+
+ public boolean isKeyboardVisible()
+ {
+ 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;
+ }
+
+ QtEditText getCurrentQtEditText()
+ {
+ return m_currentEditText;
+ }
+
+ void setEditPopupMenu(EditPopupMenu editPopupMenu)
+ {
+ m_editPopupMenu = editPopupMenu;
+ }
+
+ private void keyboardVisibilityUpdated(boolean visibility)
+ {
+ m_isKeyboardHidingAnimationOngoing = false;
+ QtInputDelegate.keyboardVisibilityChanged(visibility);
+ }
+
+ public void setKeyboardVisibility(boolean visibility, long timeStamp)
+ {
+ if (m_showHideTimeStamp > timeStamp)
+ return;
+ m_showHideTimeStamp = timeStamp;
+
+ if (m_keyboardIsVisible == visibility)
+ return;
+ m_keyboardIsVisible = visibility;
+ keyboardVisibilityUpdated(m_keyboardIsVisible);
+
+ // Hiding the keyboard clears the immersive mode, so we need to set it again.
+ if (!visibility)
+ m_keyboardVisibilityListener.onKeyboardVisibilityChange();
+
+ }
+
+ @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();
+ activity.getWindowManager().getDefaultDisplay().getMetrics(metrics);
+
+ // If the screen is in portrait mode than we estimate that keyboard height
+ // will not be higher than 2/5 of the screen. Otherwise we estimate that keyboard height
+ // will not be higher than 2/3 of the screen
+ final int visibleHeight;
+ if (metrics.widthPixels < metrics.heightPixels) {
+ visibleHeight = m_portraitKeyboardHeight != 0 ?
+ m_portraitKeyboardHeight : metrics.heightPixels * 3 / 5;
+ } else {
+ visibleHeight = m_landscapeKeyboardHeight != 0 ?
+ m_landscapeKeyboardHeight : metrics.heightPixels / 3;
+ }
+
+ if (m_softInputMode != 0) {
+ activity.getWindow().setSoftInputMode(m_softInputMode);
+ int stateHidden = WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN;
+ return (m_softInputMode & stateHidden) != 0;
+ } else {
+ int stateUnchanged = WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED;
+ if (height > visibleHeight) {
+ int adjustResize = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
+ activity.getWindow().setSoftInputMode(stateUnchanged | adjustResize);
+ } else {
+ int adjustPan = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN;
+ activity.getWindow().setSoftInputMode(stateUnchanged | adjustPan);
+ }
+ }
+ return false;
+ }
+
+ private void probeForKeyboardHeight(QtLayout layout, Activity activity, int x, int y,
+ int width, int height, int inputHints, int enterKeyType)
+ {
+ layout.postDelayed(() -> {
+ if (!m_keyboardIsVisible)
+ return;
+ DisplayMetrics metrics = new DisplayMetrics();
+ activity.getWindowManager().getDefaultDisplay().getMetrics(metrics);
+ Rect r = new Rect();
+ activity.getWindow().getDecorView().getWindowVisibleDisplayFrame(r);
+ if (metrics.heightPixels != r.bottom) {
+ if (metrics.widthPixels > metrics.heightPixels) { // landscape
+ if (m_landscapeKeyboardHeight != r.bottom) {
+ m_landscapeKeyboardHeight = r.bottom;
+ showSoftwareKeyboard(activity, layout, x, y, width, height,
+ inputHints, enterKeyType);
+ }
+ } else {
+ if (m_portraitKeyboardHeight != r.bottom) {
+ m_portraitKeyboardHeight = r.bottom;
+ showSoftwareKeyboard(activity, layout, x, y, width, height,
+ inputHints, enterKeyType);
+ }
+ }
+ } else {
+ // no luck ?
+ // maybe the delay was too short, so let's make it longer
+ if (m_probeKeyboardHeightDelayMs < 1000)
+ m_probeKeyboardHeightDelayMs *= 2;
+ }
+ }, 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)
+ {
+ switch (mode & 0xff)
+ {
+ case CursorHandleNotShown:
+ if (m_cursorHandle != null) {
+ m_cursorHandle.hide();
+ m_cursorHandle = null;
+ }
+ if (m_rightSelectionHandle != null) {
+ m_rightSelectionHandle.hide();
+ m_leftSelectionHandle.hide();
+ m_rightSelectionHandle = null;
+ m_leftSelectionHandle = null;
+ }
+ if (m_editPopupMenu != null)
+ m_editPopupMenu.hide();
+ break;
+
+ case CursorHandleShowNormal:
+ if (m_cursorHandle == null) {
+ m_cursorHandle = new CursorHandle(activity, layout, IdCursorHandle,
+ android.R.attr.textSelectHandle, false);
+ }
+ m_cursorHandle.setPosition(x1, y1);
+ if (m_rightSelectionHandle != null) {
+ m_rightSelectionHandle.hide();
+ m_leftSelectionHandle.hide();
+ m_rightSelectionHandle = null;
+ m_leftSelectionHandle = null;
+ }
+ break;
+
+ case CursorHandleShowSelection:
+ if (m_rightSelectionHandle == null) {
+ m_leftSelectionHandle = new CursorHandle(activity, layout, IdLeftHandle,
+ !rtl ? android.R.attr.textSelectHandleLeft :
+ android.R.attr.textSelectHandleRight,
+ rtl);
+ m_rightSelectionHandle = new CursorHandle(activity, layout, IdRightHandle,
+ !rtl ? android.R.attr.textSelectHandleRight :
+ android.R.attr.textSelectHandleLeft,
+ rtl);
+ }
+ m_leftSelectionHandle.setPosition(x1,y1);
+ m_rightSelectionHandle.setPosition(x2,y2);
+ if (m_cursorHandle != null) {
+ m_cursorHandle.hide();
+ m_cursorHandle = null;
+ }
+ mode |= CursorHandleShowEdit;
+ break;
+ }
+
+ if (!QtClipboardManager.hasClipboardText(activity))
+ editButtons &= ~EditContextView.PASTE_BUTTON;
+
+ if (m_editPopupMenu != null) {
+ if ((mode & CursorHandleShowEdit) == CursorHandleShowEdit && editButtons != 0) {
+ m_editPopupMenu.setPosition(editX, editY, editButtons,
+ m_cursorHandle, m_leftSelectionHandle, m_rightSelectionHandle);
+ } else {
+ m_editPopupMenu.hide();
+ }
+ }
+ }
+
+ public boolean onKeyDown(int keyCode, KeyEvent event)
+ {
+ m_metaState = MetaKeyKeyListener.handleKeyDown(m_metaState, keyCode, event);
+ int metaState = MetaKeyKeyListener.getMetaState(m_metaState) | event.getMetaState();
+ int c = event.getUnicodeChar(metaState);
+ int lc = c;
+ m_metaState = MetaKeyKeyListener.adjustMetaAfterKeypress(m_metaState);
+
+ if ((c & KeyCharacterMap.COMBINING_ACCENT) != 0) {
+ c = c & KeyCharacterMap.COMBINING_ACCENT_MASK;
+ c = KeyEvent.getDeadChar(m_lastChar, c);
+ }
+
+ if ((keyCode == KeyEvent.KEYCODE_VOLUME_UP
+ || keyCode == KeyEvent.KEYCODE_VOLUME_DOWN
+ || keyCode == KeyEvent.KEYCODE_MUTE)
+ && System.getenv("QT_ANDROID_VOLUME_KEYS") == null) {
+ return false;
+ }
+
+ m_lastChar = lc;
+ if (keyCode == KeyEvent.KEYCODE_BACK) {
+ m_backKeyPressedSent = !isKeyboardVisible();
+ if (!m_backKeyPressedSent)
+ return true;
+ }
+
+ QtInputDelegate.keyDown(keyCode, c, event.getMetaState(), event.getRepeatCount() > 0);
+
+ return true;
+ }
+
+ public boolean onKeyUp(int keyCode, KeyEvent event)
+ {
+ if ((keyCode == KeyEvent.KEYCODE_VOLUME_UP
+ || keyCode == KeyEvent.KEYCODE_VOLUME_DOWN
+ || keyCode == KeyEvent.KEYCODE_MUTE)
+ && System.getenv("QT_ANDROID_VOLUME_KEYS") == null) {
+ return false;
+ }
+
+ if (keyCode == KeyEvent.KEYCODE_BACK && !m_backKeyPressedSent) {
+ hideSoftwareKeyboard();
+ setKeyboardVisibility(false, System.nanoTime());
+ return true;
+ }
+
+ m_metaState = MetaKeyKeyListener.handleKeyUp(m_metaState, keyCode, event);
+ boolean autoRepeat = event.getRepeatCount() > 0;
+ QtInputDelegate.keyUp(keyCode, event.getUnicodeChar(), event.getMetaState(), autoRepeat);
+
+ return true;
+ }
+
+ public boolean handleDispatchKeyEvent(KeyEvent event)
+ {
+ if (event.getAction() == KeyEvent.ACTION_MULTIPLE
+ && event.getCharacters() != null
+ && event.getCharacters().length() == 1
+ && event.getKeyCode() == 0) {
+ keyDown(0, event.getCharacters().charAt(0), event.getMetaState(),
+ event.getRepeatCount() > 0);
+ keyUp(0, event.getCharacters().charAt(0), event.getMetaState(),
+ event.getRepeatCount() > 0);
+ }
+
+ return dispatchKeyEvent(event);
+ }
+
+ public boolean handleDispatchGenericMotionEvent(MotionEvent event)
+ {
+ return dispatchGenericMotionEvent(event);
+ }
+
+ //////////////////////////////
+ // Mouse and Touch Input //
+ //////////////////////////////
+
+ // tablet methods
+ public static native boolean isTabletEventSupported();
+ public static native void tabletEvent(int winId, int deviceId, long time, int action,
+ int pointerType, int buttonState, float x, float y,
+ float pressure);
+ // tablet methods
+
+ // pointer methods
+ public static native void mouseDown(int winId, int x, int y, int mouseButtonState);
+ public static native void mouseUp(int winId, int x, int y, int mouseButtonState);
+ public static native void mouseMove(int winId, int x, int y);
+ public static native void mouseWheel(int winId, int x, int y, float hDelta, float vDelta);
+ public static native void touchBegin(int winId);
+ public static native void touchAdd(int winId, int pointerId, int action, boolean primary,
+ int x, int y, float major, float minor, float rotation,
+ float pressure);
+ public static native void touchEnd(int winId, int action);
+ public static native void touchCancel(int winId);
+ public static native void longPress(int winId, int x, int y);
+ // pointer methods
+
+ static private int getAction(int index, MotionEvent event)
+ {
+ int action = event.getActionMasked();
+ if (action == MotionEvent.ACTION_MOVE) {
+ int hsz = event.getHistorySize();
+ if (hsz > 0) {
+ float x = event.getX(index);
+ float y = event.getY(index);
+ for (int h = 0; h < hsz; ++h) {
+ if ( event.getHistoricalX(index, h) != x ||
+ event.getHistoricalY(index, h) != y )
+ return 1;
+ }
+ return 2;
+ }
+ return 1;
+ }
+ if (action == MotionEvent.ACTION_DOWN
+ || action == MotionEvent.ACTION_POINTER_DOWN && index == event.getActionIndex()) {
+ return 0;
+ } else if (action == MotionEvent.ACTION_UP
+ || action == MotionEvent.ACTION_POINTER_UP && index == event.getActionIndex()) {
+ return 3;
+ }
+ return 2;
+ }
+
+ static public void sendTouchEvent(MotionEvent event, int id)
+ {
+ int pointerType = 0;
+
+ if (m_tabletEventSupported == null)
+ m_tabletEventSupported = isTabletEventSupported();
+
+ switch (event.getToolType(0)) {
+ case MotionEvent.TOOL_TYPE_STYLUS:
+ pointerType = 1; // QTabletEvent::Pen
+ break;
+ case MotionEvent.TOOL_TYPE_ERASER:
+ pointerType = 3; // QTabletEvent::Eraser
+ break;
+ }
+
+ if (event.getToolType(0) == MotionEvent.TOOL_TYPE_MOUSE) {
+ sendMouseEvent(event, id);
+ } else if (m_tabletEventSupported && pointerType != 0) {
+ tabletEvent(id, event.getDeviceId(), event.getEventTime(), event.getActionMasked(),
+ pointerType, event.getButtonState(),
+ event.getX(), event.getY(), event.getPressure());
+ } else {
+ touchBegin(id);
+ for (int i = 0; i < event.getPointerCount(); ++i) {
+ touchAdd(id,
+ event.getPointerId(i),
+ getAction(i, event),
+ i == 0,
+ (int)event.getX(i),
+ (int)event.getY(i),
+ event.getTouchMajor(i),
+ event.getTouchMinor(i),
+ event.getOrientation(i),
+ event.getPressure(i));
+ }
+
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ touchEnd(id, 0);
+ break;
+
+ case MotionEvent.ACTION_UP:
+ touchEnd(id, 2);
+ break;
+
+ case MotionEvent.ACTION_CANCEL:
+ touchCancel(id);
+ break;
+
+ default:
+ touchEnd(id, 1);
+ }
+ }
+ }
+
+ static public void sendTrackballEvent(MotionEvent event, int id)
+ {
+ sendMouseEvent(event,id);
+ }
+
+ static public boolean sendGenericMotionEvent(MotionEvent event, int id)
+ {
+ int scrollOrHoverMove = MotionEvent.ACTION_SCROLL | MotionEvent.ACTION_HOVER_MOVE;
+ int pointerDeviceModifier = (event.getSource() & InputDevice.SOURCE_CLASS_POINTER);
+ boolean isPointerDevice = pointerDeviceModifier == InputDevice.SOURCE_CLASS_POINTER;
+
+ if ((event.getAction() & scrollOrHoverMove) == 0 || !isPointerDevice )
+ return false;
+
+ return sendMouseEvent(event, id);
+ }
+
+ static public boolean sendMouseEvent(MotionEvent event, int id)
+ {
+ switch (event.getActionMasked()) {
+ case MotionEvent.ACTION_UP:
+ mouseUp(id, (int) event.getX(), (int) event.getY(), event.getButtonState());
+ break;
+
+ case MotionEvent.ACTION_DOWN:
+ mouseDown(id, (int) event.getX(), (int) event.getY(), event.getButtonState());
+ m_oldX = (int) event.getX();
+ m_oldY = (int) event.getY();
+ break;
+ case MotionEvent.ACTION_HOVER_MOVE:
+ case MotionEvent.ACTION_MOVE:
+ if (event.getToolType(0) == MotionEvent.TOOL_TYPE_MOUSE) {
+ mouseMove(id, (int) event.getX(), (int) event.getY());
+ } else {
+ int dx = (int) (event.getX() - m_oldX);
+ int dy = (int) (event.getY() - m_oldY);
+ if (Math.abs(dx) > 5 || Math.abs(dy) > 5) {
+ mouseMove(id, (int) event.getX(), (int) event.getY());
+ m_oldX = (int) event.getX();
+ m_oldY = (int) event.getY();
+ }
+ }
+ break;
+ case MotionEvent.ACTION_SCROLL:
+ mouseWheel(id, (int) event.getX(), (int) event.getY(),
+ event.getAxisValue(MotionEvent.AXIS_HSCROLL),
+ event.getAxisValue(MotionEvent.AXIS_VSCROLL));
+ break;
+ default:
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/src/android/jar/src/org/qtproject/qt/android/QtLayout.java b/src/android/jar/src/org/qtproject/qt/android/QtLayout.java
index 2e52770cc5..aedc845014 100644
--- a/src/android/jar/src/org/qtproject/qt/android/QtLayout.java
+++ b/src/android/jar/src/org/qtproject/qt/android/QtLayout.java
@@ -1,42 +1,6 @@
-/****************************************************************************
-**
-** Copyright (C) 2016 The Qt Company Ltd.
-** Copyright (C) 2012 BogDan Vatra <bogdan@kde.org>
-** Contact: https://www.qt.io/licensing/
-**
-** This file is part of the Android port of the Qt Toolkit.
-**
-** $QT_BEGIN_LICENSE:LGPL$
-** Commercial License Usage
-** Licensees holding valid commercial Qt licenses may use this file in
-** accordance with the commercial license agreement provided with the
-** Software or, alternatively, in accordance with the terms contained in
-** a written agreement between you and The Qt Company. For licensing terms
-** and conditions see https://www.qt.io/terms-conditions. For further
-** information use the contact form at https://www.qt.io/contact-us.
-**
-** GNU Lesser General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU Lesser
-** General Public License version 3 as published by the Free Software
-** Foundation and appearing in the file LICENSE.LGPL3 included in the
-** packaging of this file. Please review the following information to
-** ensure the GNU Lesser General Public License version 3 requirements
-** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
-**
-** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 2.0 or (at your option) the GNU General
-** Public license version 3 or any later version approved by the KDE Free
-** Qt Foundation. The licenses are as published by the Free Software
-** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
-** included in the packaging of this file. Please review the following
-** information to ensure the GNU General Public License requirements will
-** be met: https://www.gnu.org/licenses/gpl-2.0.html and
-** https://www.gnu.org/licenses/gpl-3.0.html.
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
+// Copyright (C) 2023 The Qt Company Ltd.
+// Copyright (C) 2012 BogDan Vatra <bogdan@kde.org>
+// 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;
@@ -46,17 +10,15 @@ import android.os.Build;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.Display;
+import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
-import android.view.WindowInsets;
-public class QtLayout extends ViewGroup
-{
- private Runnable m_startApplicationRunnable;
- public QtLayout(Context context, Runnable startRunnable)
+class QtLayout extends ViewGroup {
+
+ public QtLayout(Context context)
{
super(context);
- m_startApplicationRunnable = startRunnable;
}
public QtLayout(Context context, AttributeSet attrs)
@@ -70,38 +32,6 @@ public class QtLayout extends ViewGroup
}
@Override
- protected void onSizeChanged (int w, int h, int oldw, int oldh)
- {
- WindowInsets insets = getRootWindowInsets();
-
- DisplayMetrics realMetrics = new DisplayMetrics();
- Display display = (Build.VERSION.SDK_INT < Build.VERSION_CODES.R)
- ? ((Activity)getContext()).getWindowManager().getDefaultDisplay()
- : ((Activity)getContext()).getDisplay();
- display.getRealMetrics(realMetrics);
-
- boolean isFullScreenView = h == realMetrics.heightPixels;
-
- int insetLeft = isFullScreenView ? insets.getSystemWindowInsetLeft() : 0;
- int insetTop = isFullScreenView ? insets.getSystemWindowInsetTop() : 0;
- int insetRight = isFullScreenView ? insets.getSystemWindowInsetRight() : 0;
- int insetBottom = isFullScreenView ? insets.getSystemWindowInsetBottom() : 0;
-
- int usableAreaWidth = w - insetLeft - insetRight;
- int usableAreaHeight = h - insetTop - insetBottom;
-
- QtNative.setApplicationDisplayMetrics(
- realMetrics.widthPixels, realMetrics.heightPixels, insetLeft, insetTop,
- usableAreaWidth, usableAreaHeight, realMetrics.xdpi, realMetrics.ydpi,
- realMetrics.scaledDensity, realMetrics.density, display.getRefreshRate());
-
- if (m_startApplicationRunnable != null) {
- m_startApplicationRunnable.run();
- m_startApplicationRunnable = null;
- }
- }
-
- @Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
int count = getChildCount();
@@ -119,11 +49,15 @@ public class QtLayout extends ViewGroup
int childRight;
int childBottom;
- QtLayout.LayoutParams lp
- = (QtLayout.LayoutParams) child.getLayoutParams();
-
- childRight = lp.x + child.getMeasuredWidth();
- childBottom = lp.y + child.getMeasuredHeight();
+ if (child.getLayoutParams() instanceof QtLayout.LayoutParams) {
+ QtLayout.LayoutParams lp
+ = (QtLayout.LayoutParams) child.getLayoutParams();
+ childRight = lp.x + child.getMeasuredWidth();
+ childBottom = lp.y + child.getMeasuredHeight();
+ } else {
+ childRight = child.getMeasuredWidth();
+ childBottom = child.getMeasuredHeight();
+ }
maxWidth = Math.max(maxWidth, childRight);
maxHeight = Math.max(maxHeight, childBottom);
@@ -157,7 +91,6 @@ public class QtLayout extends ViewGroup
protected void onLayout(boolean changed, int l, int t, int r, int b)
{
int count = getChildCount();
-
for (int i = 0; i < count; i++) {
View child = getChildAt(i);
if (child.getVisibility() != GONE) {
@@ -166,10 +99,11 @@ public class QtLayout extends ViewGroup
int childLeft = lp.x;
int childTop = lp.y;
- child.layout(childLeft, childTop,
- childLeft + child.getMeasuredWidth(),
- childTop + child.getMeasuredHeight());
-
+ int childRight = (lp.width == ViewGroup.LayoutParams.MATCH_PARENT) ?
+ r - l : childLeft + child.getMeasuredWidth();
+ int childBottom = (lp.height == ViewGroup.LayoutParams.MATCH_PARENT) ?
+ b - t : childTop + child.getMeasuredHeight();
+ child.layout(childLeft, childTop, childRight, childBottom);
}
}
}
@@ -189,8 +123,7 @@ public class QtLayout extends ViewGroup
/**
* Per-child layout information associated with AbsoluteLayout.
- * See
- * {@link android.R.styleable#AbsoluteLayout_Layout Absolute Layout Attributes}
+ * See {android.R.styleable#AbsoluteLayout_Layout Absolute Layout Attributes}
* for a list of all child view attributes that this class supports.
*/
public static class LayoutParams extends ViewGroup.LayoutParams
@@ -222,6 +155,11 @@ public class QtLayout extends ViewGroup
this.y = y;
}
+ public LayoutParams(int width, int height)
+ {
+ super(width, height);
+ }
+
/**
* {@inheritDoc}
*/
@@ -247,7 +185,7 @@ public class QtLayout extends ViewGroup
/**
* set the layout params on a child view.
- *
+ * <p>
* Note: This function adds the child view if it's not in the
* layout already.
*/
diff --git a/src/android/jar/src/org/qtproject/qt/android/QtLoader.java b/src/android/jar/src/org/qtproject/qt/android/QtLoader.java
new file mode 100644
index 0000000000..a00c4795f7
--- /dev/null
+++ b/src/android/jar/src/org/qtproject/qt/android/QtLoader.java
@@ -0,0 +1,558 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// Copyright (c) 2019, BogDan Vatra <bogdan@kde.org>
+// 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.annotation.SuppressLint;
+import android.app.Activity;
+import android.app.Service;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ComponentInfo;
+import android.content.res.Resources;
+import android.os.Build;
+import android.os.Bundle;
+import android.util.Log;
+
+import java.io.File;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Objects;
+
+import dalvik.system.DexClassLoader;
+
+abstract class QtLoader {
+
+ protected static final String QtTAG = "QtLoader";
+
+ private final Resources m_resources;
+ private final String m_packageName;
+ private String m_preferredAbi = null;
+ private String m_nativeLibrariesDir = null;
+ private ClassLoader m_classLoader;
+
+ protected final ContextWrapper m_context;
+ protected ComponentInfo m_contextInfo;
+
+ protected String m_mainLibPath;
+ protected String m_mainLibName;
+ protected String m_applicationParameters = "";
+ protected HashMap<String, String> m_environmentVariables = new HashMap<>();
+
+ protected int m_debuggerSleepMs = 0;
+
+ /**
+ * Sets and initialize the basic pieces.
+ * Initializes the class loader since it doesn't rely on anything
+ * other than the context.
+ * Also, we can already initialize the static classes contexts here.
+ **/
+ public QtLoader(ContextWrapper context) {
+ m_context = context;
+ m_resources = m_context.getResources();
+ m_packageName = m_context.getPackageName();
+
+ initClassLoader();
+ initStaticClasses();
+ initContextInfo();
+ }
+
+ /**
+ * Implements the logic for finish the extended context, mostly called
+ * in error cases.
+ **/
+ abstract protected void finish();
+
+ /**
+ * Initializes the context info instance which is used to retrieve
+ * the app metadata from the AndroidManifest.xml or other xml resources.
+ * Some values are dependent on the context being an Activity or Service.
+ **/
+ protected void initContextInfo() {
+ try {
+ Context context = m_context.getBaseContext();
+ if (context instanceof Activity) {
+ m_contextInfo = context.getPackageManager().getActivityInfo(
+ ((Activity)context).getComponentName(), PackageManager.GET_META_DATA);
+ } else if (context instanceof Service) {
+ m_contextInfo = context.getPackageManager().getServiceInfo(
+ new ComponentName(context, context.getClass()),
+ PackageManager.GET_META_DATA);
+ } else {
+ Log.w(QtTAG, "Context is not an instance of Activity or Service, could not get " +
+ "context info for it");
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ finish();
+ }
+ }
+
+ /**
+ * Extract the common metadata in the base implementation. And the extended methods
+ * call context specific metadata extraction. This also sets the various environment
+ * variables and application parameters.
+ **/
+ protected void extractContextMetaData() {
+ setEnvironmentVariable("QT_ANDROID_FONTS", "Roboto;Droid Sans;Droid Sans Fallback");
+ String monospaceFonts = "Droid Sans Mono;Droid Sans;Droid Sans Fallback";
+ setEnvironmentVariable("QT_ANDROID_FONTS_MONOSPACE", monospaceFonts);
+ setEnvironmentVariable("QT_ANDROID_FONTS_SERIF", "Droid Serif");
+ setEnvironmentVariable("HOME", m_context.getFilesDir().getAbsolutePath());
+ setEnvironmentVariable("TMPDIR", m_context.getCacheDir().getAbsolutePath());
+ String backgroundRunning = getMetaData("android.app.background_running");
+ setEnvironmentVariable("QT_BLOCK_EVENT_LOOPS_WHEN_SUSPENDED", backgroundRunning);
+ setEnvironmentVariable("QTRACE_LOCATION", getMetaData("android.app.trace_location"));
+ appendApplicationParameters(getMetaData("android.app.arguments"));
+ }
+
+ private ArrayList<String> preferredAbiLibs(String[] libs) {
+ HashMap<String, ArrayList<String>> abiLibs = new HashMap<>();
+ for (String lib : libs) {
+ String[] archLib = lib.split(";", 2);
+ if (m_preferredAbi != null && !archLib[0].equals(m_preferredAbi))
+ continue;
+ if (!abiLibs.containsKey(archLib[0]))
+ abiLibs.put(archLib[0], new ArrayList<>());
+ Objects.requireNonNull(abiLibs.get(archLib[0])).add(archLib[1]);
+ }
+
+ if (m_preferredAbi != null) {
+ if (abiLibs.containsKey(m_preferredAbi)) {
+ return abiLibs.get(m_preferredAbi);
+ }
+ return new ArrayList<>();
+ }
+
+ for (String abi : Build.SUPPORTED_ABIS) {
+ if (abiLibs.containsKey(abi)) {
+ m_preferredAbi = abi;
+ return abiLibs.get(abi);
+ }
+ }
+ return new ArrayList<>();
+ }
+
+ private void initStaticClasses() {
+ Context context = m_context.getBaseContext();
+ boolean isActivity = context instanceof Activity;
+ for (String className : getStaticInitClasses()) {
+ try {
+ Class<?> initClass = m_classLoader.loadClass(className);
+ Object staticInitDataObject = initClass.newInstance(); // create an instance
+
+ if (isActivity) {
+ try {
+ Method m = initClass.getMethod("setActivity", Activity.class, Object.class);
+ m.invoke(staticInitDataObject, (Activity) context, this);
+ } catch (InvocationTargetException | NoSuchMethodException e) {
+ Log.d(QtTAG, "Class " + initClass.getName() + " does not implement " +
+ "setActivity method");
+ }
+ } else {
+ try {
+ Method m = initClass.getMethod("setService", Service.class, Object.class);
+ m.invoke(staticInitDataObject, (Service) context, this);
+ } catch (InvocationTargetException | NoSuchMethodException e) {
+ Log.d(QtTAG, "Class " + initClass.getName() + " does not implement " +
+ "setService method");
+ }
+ }
+
+ try {
+ // For modules that don't need/have setActivity/setService
+ Method m = initClass.getMethod("setContext", Context.class);
+ m.invoke(staticInitDataObject, context);
+ } catch (InvocationTargetException | NoSuchMethodException e) {
+ Log.d(QtTAG, "Class " + initClass.getName() + " does not implement " +
+ "setContext method");
+ }
+ } catch (IllegalAccessException | ClassNotFoundException | InstantiationException e) {
+ Log.d(QtTAG, "Could not instantiate class " + className + ", " + e);
+ }
+ }
+ }
+
+ /**
+ * Initialize the class loader instance and sets it via QtNative.
+ * This would also be used by QJniObject API.
+ **/
+ private void initClassLoader()
+ {
+ // directory where optimized DEX files should be written.
+ String outDexPath = m_context.getDir("outdex", Context.MODE_PRIVATE).getAbsolutePath();
+ String sourceDir = m_context.getApplicationInfo().sourceDir;
+ m_classLoader = new DexClassLoader(sourceDir, outDexPath, null, m_context.getClassLoader());
+ QtNative.setClassLoader(m_classLoader);
+ }
+
+ /**
+ * Returns the context's main library absolute path,
+ * or null if the library hasn't been loaded yet.
+ **/
+ public String getMainLibraryPath() {
+ return m_mainLibPath;
+ }
+
+ /**
+ * Set the name of the main app library to libName, which is the name of the library,
+ * not including the path, target architecture or .so suffix. This matches the target name
+ * of the app target in CMakeLists.txt.
+ * This method can be used when the name is not provided by androiddeployqt, for example when
+ * embedding QML views to a native Android app.
+ **/
+ public void setMainLibraryName(String libName) {
+ m_mainLibName = libName;
+ }
+
+ /**
+ * Returns the context's parameters that are used when calling
+ * the main library's main() function. This is assembled from
+ * a combination of static values and also metadata dependent values.
+ **/
+ public String getApplicationParameters() {
+ return m_applicationParameters;
+ }
+
+ /**
+ * Adds a list of parameters to the internal array list of parameters.
+ * Either a whitespace or a tab is accepted as a separator between parameters.
+ **/
+ public void appendApplicationParameters(String params)
+ {
+ if (params == null || params.isEmpty())
+ return;
+
+ if (!m_applicationParameters.isEmpty())
+ m_applicationParameters += " ";
+ m_applicationParameters += params;
+ }
+
+ /**
+ * Sets a single key/value environment variable pair.
+ **/
+ public void setEnvironmentVariable(String key, String value)
+ {
+ try {
+ android.system.Os.setenv(key, value, true);
+ m_environmentVariables.put(key, value);
+ } catch (Exception e) {
+ Log.e(QtTAG, "Could not set environment variable:" + key + "=" + value);
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * Sets a list of keys/values string to as environment variables.
+ * This expects the key/value to be separated by '=', and parameters
+ * to be separated by tabs or space.
+ **/
+ public void setEnvironmentVariables(String environmentVariables)
+ {
+ if (environmentVariables == null || environmentVariables.isEmpty())
+ return;
+
+ environmentVariables = environmentVariables.replaceAll("\t", " ");
+
+ for (String variable : environmentVariables.split(" ")) {
+ String[] keyValue = variable.split("=", 2);
+ if (keyValue.length < 2 || keyValue[0].isEmpty())
+ continue;
+
+ setEnvironmentVariable(keyValue[0], keyValue[1]);
+ }
+ }
+
+ /**
+ * Parses the native libraries dir. If the libraries are part of the APK,
+ * the path is set to the APK extracted libs path.
+ * Otherwise, it looks for the system level dir, that's either set in the Manifest,
+ * the deployment libs.xml.
+ * If none of the above are valid, it falls back to predefined system path.
+ **/
+ private void parseNativeLibrariesDir() {
+ if (isBundleQtLibs()) {
+ String nativeLibraryPrefix = m_context.getApplicationInfo().nativeLibraryDir + "/";
+ File nativeLibraryDir = new File(nativeLibraryPrefix);
+ if (nativeLibraryDir.exists()) {
+ String[] list = nativeLibraryDir.list();
+ if (nativeLibraryDir.isDirectory() && list != null && list.length > 0) {
+ m_nativeLibrariesDir = nativeLibraryPrefix;
+ }
+ }
+ } else {
+ // First check if user has provided system libs prefix in AndroidManifest
+ String systemLibsPrefix = getApplicationMetaData("android.app.system_libs_prefix");
+
+ // If not, check if it's provided by androiddeployqt in libs.xml
+ if (systemLibsPrefix.isEmpty())
+ systemLibsPrefix = getSystemLibsPrefix();
+
+ if (systemLibsPrefix.isEmpty()) {
+ final String SYSTEM_LIB_PATH = "/system/lib/";
+ systemLibsPrefix = SYSTEM_LIB_PATH;
+ Log.e(QtTAG, "Using " + SYSTEM_LIB_PATH + " as default libraries path. "
+ + "It looks like the app is deployed using Unbundled "
+ + "deployment. It may be necessary to specify the path to "
+ + "the directory where Qt libraries are installed using either "
+ + "android.app.system_libs_prefix metadata variable in your "
+ + "AndroidManifest.xml or QT_ANDROID_SYSTEM_LIBS_PATH in your "
+ + "CMakeLists.txt");
+ }
+
+ File systemLibraryDir = new File(systemLibsPrefix);
+ String[] list = systemLibraryDir.list();
+ if (systemLibraryDir.exists()) {
+ if (systemLibraryDir.isDirectory() && list != null && list.length > 0)
+ m_nativeLibrariesDir = systemLibsPrefix;
+ else
+ Log.e(QtTAG, "System library directory " + systemLibsPrefix + " is empty.");
+ } else {
+ Log.e(QtTAG, "System library directory " + systemLibsPrefix + " does not exist.");
+ }
+ }
+
+ if (m_nativeLibrariesDir != null && !m_nativeLibrariesDir.endsWith("/"))
+ m_nativeLibrariesDir += "/";
+ }
+
+ /**
+ * Returns the application level metadata.
+ *
+ * @noinspection SameParameterValue*/
+ private String getApplicationMetaData(String key) {
+ if (m_contextInfo == null)
+ return "";
+
+ ApplicationInfo applicationInfo = m_contextInfo.applicationInfo;
+ if (applicationInfo == null)
+ return "";
+
+ Bundle metadata = applicationInfo.metaData;
+ if (metadata == null || !metadata.containsKey(key))
+ return "";
+
+ return metadata.getString(key);
+ }
+
+ /**
+ * Returns the context level metadata.
+ **/
+ protected String getMetaData(String key) {
+ if (m_contextInfo == null)
+ return "";
+
+ Bundle metadata = m_contextInfo.metaData;
+ if (metadata == null || !metadata.containsKey(key))
+ return "";
+
+ return String.valueOf(metadata.get(key));
+ }
+
+ @SuppressLint("DiscouragedApi")
+ private ArrayList<String> getQtLibrariesList() {
+ int id = m_resources.getIdentifier("qt_libs", "array", m_packageName);
+ return preferredAbiLibs(m_resources.getStringArray(id));
+ }
+
+ @SuppressLint("DiscouragedApi")
+ private boolean useLocalQtLibs() {
+ int id = m_resources.getIdentifier("use_local_qt_libs", "string", m_packageName);
+ return Integer.parseInt(m_resources.getString(id)) == 1;
+ }
+
+ @SuppressLint("DiscouragedApi")
+ private boolean isBundleQtLibs() {
+ int id = m_resources.getIdentifier("bundle_local_qt_libs", "string", m_packageName);
+ return Integer.parseInt(m_resources.getString(id)) == 1;
+ }
+
+ @SuppressLint("DiscouragedApi")
+ private String getSystemLibsPrefix() {
+ int id = m_resources.getIdentifier("system_libs_prefix", "string", m_packageName);
+ return m_resources.getString(id);
+ }
+
+ @SuppressLint("DiscouragedApi")
+ private ArrayList<String> getLocalLibrariesList() {
+ int id = m_resources.getIdentifier("load_local_libs", "array", m_packageName);
+ ArrayList<String> localLibs = new ArrayList<>();
+ for (String arrayItem : preferredAbiLibs(m_resources.getStringArray(id))) {
+ Collections.addAll(localLibs, arrayItem.split(":"));
+ }
+ return localLibs;
+ }
+
+ @SuppressLint("DiscouragedApi")
+ private ArrayList<String> getStaticInitClasses() {
+ int id = m_resources.getIdentifier("static_init_classes", "string", m_packageName);
+ String[] classes = m_resources.getString(id).split(":");
+ ArrayList<String> finalClasses = new ArrayList<>();
+ for (String element : classes) {
+ if (!element.isEmpty()) {
+ finalClasses.add(element);
+ }
+ }
+ return finalClasses;
+ }
+
+ @SuppressLint("DiscouragedApi")
+ private String[] getBundledLibs() {
+ int id = m_resources.getIdentifier("bundled_libs", "array", m_packageName);
+ return m_resources.getStringArray(id);
+ }
+
+ /**
+ * Loads all Qt native bundled libraries and main library.
+ **/
+ public void loadQtLibraries() {
+ if (!useLocalQtLibs()) {
+ Log.w(QtTAG, "Use local Qt libs is false");
+ finish();
+ return;
+ }
+
+ if (m_nativeLibrariesDir == null)
+ parseNativeLibrariesDir();
+
+ if (m_nativeLibrariesDir == null || m_nativeLibrariesDir.isEmpty()) {
+ Log.e(QtTAG, "The native libraries directory is null or empty");
+ finish();
+ return;
+ }
+
+ setEnvironmentVariable("QT_PLUGIN_PATH", m_nativeLibrariesDir);
+ setEnvironmentVariable("QML_PLUGIN_PATH", m_nativeLibrariesDir);
+
+ // Load native Qt APK libraries
+ ArrayList<String> nativeLibraries = getQtLibrariesList();
+ nativeLibraries.addAll(getLocalLibrariesList());
+
+ if (m_debuggerSleepMs > 0) {
+ Log.i(QtTAG, "Sleeping for " + m_debuggerSleepMs +
+ "ms, helping the native debugger to settle. " +
+ "Use the env QT_ANDROID_DEBUGGER_MAIN_THREAD_SLEEP_MS variable to change this value.");
+ QtNative.getQtThread().sleep(m_debuggerSleepMs);
+ }
+
+ if (!loadLibraries(nativeLibraries)) {
+ Log.e(QtTAG, "Loading Qt native libraries failed");
+ finish();
+ return;
+ }
+
+ // add all bundled Qt libs to loader params
+ ArrayList<String> bundledLibraries = new ArrayList<>(preferredAbiLibs(getBundledLibs()));
+ if (!loadLibraries(bundledLibraries)) {
+ Log.e(QtTAG, "Loading Qt bundled libraries failed");
+ finish();
+ return;
+ }
+
+ if (m_mainLibName == null)
+ m_mainLibName = getMetaData("android.app.lib_name");
+ // Load main lib
+ if (!loadMainLibrary(m_mainLibName + "_" + m_preferredAbi)) {
+ Log.e(QtTAG, "Loading main library failed");
+ finish();
+ }
+ }
+
+ // Loading libraries using System.load() uses full lib paths
+ @SuppressLint("UnsafeDynamicallyLoadedCode")
+ private String loadLibraryHelper(String library)
+ {
+ String loadedLib = null;
+ try {
+ File libFile = new File(library);
+ if (libFile.exists()) {
+ System.load(library);
+ loadedLib = library;
+ } else {
+ Log.e(QtTAG, "Can't find '" + library + "'");
+ }
+ } catch (Exception e) {
+ Log.e(QtTAG, "Can't load '" + library + "'", e);
+ }
+
+ return loadedLib;
+ }
+
+ /**
+ * Returns an array with absolute library paths from a list of file names only.
+ **/
+ private ArrayList<String> getLibrariesFullPaths(final ArrayList<String> libraries)
+ {
+ if (libraries == null)
+ return null;
+
+ ArrayList<String> absolutePathLibraries = new ArrayList<>();
+ for (String libName : libraries) {
+ // Add lib and .so to the lib name only if it doesn't already end with .so,
+ // this means some names don't necessarily need to have the lib prefix
+ if (!libName.endsWith(".so")) {
+ libName = libName + ".so";
+ libName = "lib" + libName;
+ }
+
+ File file = new File(m_nativeLibrariesDir + libName);
+ absolutePathLibraries.add(file.getAbsolutePath());
+ }
+
+ return absolutePathLibraries;
+ }
+
+ /**
+ * Loads the main library.
+ * Returns true if loading was successful, and sets the absolute
+ * path to the main library. Otherwise, returns false and the path
+ * to the main library is null.
+ **/
+ private boolean loadMainLibrary(String mainLibName)
+ {
+ ArrayList<String> oneEntryArray = new ArrayList<>(Collections.singletonList(mainLibName));
+ String mainLibPath = getLibrariesFullPaths(oneEntryArray).get(0);
+ final boolean[] success = {true};
+ QtNative.getQtThread().run(() -> {
+ m_mainLibPath = loadLibraryHelper(mainLibPath);
+ if (m_mainLibPath == null)
+ success[0] = false;
+ });
+
+ return success[0];
+ }
+
+ /**
+ * Loads a list of libraries.
+ * Returns true if all libraries were loaded successfully,
+ * and false if any library failed. Stops loading at the first failure.
+ **/
+ @SuppressWarnings("BooleanMethodIsAlwaysInverted")
+ private boolean loadLibraries(final ArrayList<String> libraries)
+ {
+ if (libraries == null)
+ return false;
+
+ ArrayList<String> fullPathLibs = getLibrariesFullPaths(libraries);
+
+ final boolean[] success = {true};
+ QtNative.getQtThread().run(() -> {
+ for (int i = 0; i < fullPathLibs.size(); ++i) {
+ String libName = fullPathLibs.get(i);
+ if (loadLibraryHelper(libName) == null) {
+ success[0] = false;
+ break;
+ }
+ }
+ });
+
+ return success[0];
+ }
+}
diff --git a/src/android/jar/src/org/qtproject/qt/android/QtMessageDialogHelper.java b/src/android/jar/src/org/qtproject/qt/android/QtMessageDialogHelper.java
index c1fd998764..e13abbbadd 100644
--- a/src/android/jar/src/org/qtproject/qt/android/QtMessageDialogHelper.java
+++ b/src/android/jar/src/org/qtproject/qt/android/QtMessageDialogHelper.java
@@ -1,65 +1,28 @@
-/****************************************************************************
- **
- ** Copyright (C) 2016 BogDan Vatra <bogdan@kde.org>
- ** Contact: https://www.qt.io/licensing/
- **
- ** This file is part of the Android port of the Qt Toolkit.
- **
- ** $QT_BEGIN_LICENSE:LGPL$
- ** Commercial License Usage
- ** Licensees holding valid commercial Qt licenses may use this file in
- ** accordance with the commercial license agreement provided with the
- ** Software or, alternatively, in accordance with the terms contained in
- ** a written agreement between you and The Qt Company. For licensing terms
- ** and conditions see https://www.qt.io/terms-conditions. For further
- ** information use the contact form at https://www.qt.io/contact-us.
- **
- ** GNU Lesser General Public License Usage
- ** Alternatively, this file may be used under the terms of the GNU Lesser
- ** General Public License version 3 as published by the Free Software
- ** Foundation and appearing in the file LICENSE.LGPL3 included in the
- ** packaging of this file. Please review the following information to
- ** ensure the GNU Lesser General Public License version 3 requirements
- ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
- **
- ** GNU General Public License Usage
- ** Alternatively, this file may be used under the terms of the GNU
- ** General Public License version 2.0 or (at your option) the GNU General
- ** Public license version 3 or any later version approved by the KDE Free
- ** Qt Foundation. The licenses are as published by the Free Software
- ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
- ** included in the packaging of this file. Please review the following
- ** information to ensure the GNU General Public License requirements will
- ** be met: https://www.gnu.org/licenses/gpl-2.0.html and
- ** https://www.gnu.org/licenses/gpl-3.0.html.
- **
- ** $QT_END_LICENSE$
- **
- ****************************************************************************/
+// Copyright (C) 2016 BogDan Vatra <bogdan@kde.org>
+// 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.AlertDialog;
+import android.content.ClipData;
import android.content.Context;
-import android.content.DialogInterface;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
-import android.os.Build;
-import android.text.ClipboardManager;
+import android.content.ClipboardManager;
import android.text.Html;
import android.text.Spanned;
+import android.util.Log;
import android.util.TypedValue;
import android.view.View;
+import android.view.Window;
import android.widget.Button;
-import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.ScrollView;
import android.widget.TextView;
-import android.widget.Toast;
import java.util.ArrayList;
@@ -77,7 +40,7 @@ class ButtonStruct implements View.OnClickListener
m_text = Html.fromHtml(text);
}
QtMessageDialogHelper m_dialog;
- private int m_id;
+ private final int m_id;
Spanned m_text;
@Override
@@ -86,7 +49,7 @@ class ButtonStruct implements View.OnClickListener
}
}
-public class QtMessageDialogHelper
+class QtMessageDialogHelper
{
public QtMessageDialogHelper(Activity activity)
@@ -94,270 +57,254 @@ public class QtMessageDialogHelper
m_activity = activity;
}
-
- public void setIcon(int icon)
+ @UsedFromNativeCode
+ public void setStandardIcon(int icon)
{
- m_icon = icon;
+ m_standardIcon = icon;
}
private Drawable getIconDrawable()
{
- if (m_icon == 0)
+ if (m_standardIcon == 0)
return null;
- try {
- TypedValue typedValue = new TypedValue();
- m_theme.resolveAttribute(android.R.attr.alertDialogIcon, typedValue, true);
- return m_activity.getResources().getDrawable(typedValue.resourceId);
- } catch (Exception e) {
- e.printStackTrace();
- }
-
// Information, Warning, Critical, Question
- switch (m_icon)
+ switch (m_standardIcon)
{
case 1: // Information
- try {
- return m_activity.getResources().getDrawable(android.R.drawable.ic_dialog_info);
- } catch (Exception e) {
- e.printStackTrace();
- }
- break;
+ return m_activity.getResources().getDrawable(android.R.drawable.ic_dialog_info,
+ m_activity.getTheme());
case 2: // Warning
-// try {
-// return Class.forName("android.R$drawable").getDeclaredField("stat_sys_warning").getInt(null);
-// } catch (Exception e) {
-// e.printStackTrace();
-// }
-// break;
+ return m_activity.getResources().getDrawable(android.R.drawable.stat_sys_warning,
+ m_activity.getTheme());
case 3: // Critical
- try {
- return m_activity.getResources().getDrawable(android.R.drawable.ic_dialog_alert);
- } catch (Exception e) {
- e.printStackTrace();
- }
- break;
+ return m_activity.getResources().getDrawable(android.R.drawable.ic_dialog_alert,
+ m_activity.getTheme());
case 4: // Question
- try {
- return m_activity.getResources().getDrawable(android.R.drawable.ic_menu_help);
- } catch (Exception e) {
- e.printStackTrace();
- }
- break;
+ return m_activity.getResources().getDrawable(android.R.drawable.ic_menu_help,
+ m_activity.getTheme());
}
return null;
}
+ @UsedFromNativeCode
public void setTile(String title)
{
m_title = Html.fromHtml(title);
}
+ @UsedFromNativeCode
public void setText(String text)
{
m_text = Html.fromHtml(text);
}
+ @UsedFromNativeCode
public void setInformativeText(String informativeText)
{
m_informativeText = Html.fromHtml(informativeText);
}
+ @UsedFromNativeCode
public void setDetailedText(String text)
{
m_detailedText = Html.fromHtml(text);
}
+ @UsedFromNativeCode
public void addButton(int id, String text)
{
if (m_buttonsList == null)
- m_buttonsList = new ArrayList<ButtonStruct>();
+ m_buttonsList = new ArrayList<>();
m_buttonsList.add(new ButtonStruct(this, id, text));
}
- private Drawable getStyledDrawable(String drawable) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException
+ private Drawable getStyledDrawable(int id)
{
- int[] attrs = {Class.forName("android.R$attr").getDeclaredField(drawable).getInt(null)};
+ int[] attrs = { id };
final TypedArray a = m_theme.obtainStyledAttributes(attrs);
Drawable d = a.getDrawable(0);
a.recycle();
return d;
}
-
+ @UsedFromNativeCode
public void show(long handler)
{
m_handler = handler;
- m_activity.runOnUiThread( new Runnable() {
- @Override
- public void run() {
- if (m_dialog != null && m_dialog.isShowing())
- m_dialog.dismiss();
-
- m_dialog = new AlertDialog.Builder(m_activity).create();
- m_theme = m_dialog.getWindow().getContext().getTheme();
-
- if (m_title != null)
- m_dialog.setTitle(m_title);
- m_dialog.setOnCancelListener( new DialogInterface.OnCancelListener() {
- @Override
- public void onCancel(DialogInterface dialogInterface) {
- QtNativeDialogHelper.dialogResult(handler(), -1);
- }
- });
- m_dialog.setCancelable(m_buttonsList == null);
- m_dialog.setCanceledOnTouchOutside(m_buttonsList == null);
- m_dialog.setIcon(getIconDrawable());
- ScrollView scrollView = new ScrollView(m_activity);
- RelativeLayout dialogLayout = new RelativeLayout(m_activity);
- int id = 1;
- View lastView = null;
- View.OnLongClickListener copyText = new View.OnLongClickListener() {
- @Override
- public boolean onLongClick(View view) {
- TextView tv = (TextView)view;
- if (tv != null) {
- ClipboardManager cm = (android.text.ClipboardManager) m_activity.getSystemService(Context.CLIPBOARD_SERVICE);
- cm.setText(tv.getText());
- }
- return true;
- }
- };
- if (m_text != null)
- {
- TextView view = new TextView(m_activity);
- view.setId(id++);
- view.setOnLongClickListener(copyText);
- view.setLongClickable(true);
-
- view.setText(m_text);
- view.setTextAppearance(m_activity, android.R.style.TextAppearance_Medium);
+ m_activity.runOnUiThread(() -> {
+ if (m_dialog != null && m_dialog.isShowing())
+ m_dialog.dismiss();
+
+ m_dialog = new AlertDialog.Builder(m_activity).create();
+ Window window = m_dialog.getWindow();
+ if (window != null)
+ m_theme = window.getContext().getTheme();
+ else
+ Log.w(QtTAG, "show(): cannot set theme from null window!");
+
+ if (m_title != null)
+ m_dialog.setTitle(m_title);
+ m_dialog.setOnCancelListener(dialogInterface -> QtNativeDialogHelper.dialogResult(handler(), -1));
+ m_dialog.setCancelable(m_buttonsList == null);
+ m_dialog.setCanceledOnTouchOutside(m_buttonsList == null);
+ m_dialog.setIcon(getIconDrawable());
+ ScrollView scrollView = new ScrollView(m_activity);
+ RelativeLayout dialogLayout = new RelativeLayout(m_activity);
+ int id = 1;
+ View lastView = null;
+ View.OnLongClickListener copyText = view -> {
+ TextView tv = (TextView)view;
+ if (tv != null) {
+ ClipboardManager cm = (ClipboardManager) m_activity.getSystemService(
+ Context.CLIPBOARD_SERVICE);
+ cm.setPrimaryClip(ClipData.newPlainText(tv.getText(), tv.getText()));
+ }
+ return true;
+ };
+ if (m_text != null)
+ {
+ TextView view = new TextView(m_activity);
+ view.setId(id++);
+ view.setOnLongClickListener(copyText);
+ view.setLongClickable(true);
+
+ view.setText(m_text);
+ view.setTextAppearance(android.R.style.TextAppearance_Medium);
+
+ RelativeLayout.LayoutParams layout = new RelativeLayout.LayoutParams(
+ RelativeLayout.LayoutParams.MATCH_PARENT,
+ RelativeLayout.LayoutParams.WRAP_CONTENT);
+ layout.setMargins(16, 8, 16, 8);
+ layout.addRule(RelativeLayout.ALIGN_PARENT_TOP);
+ dialogLayout.addView(view, layout);
+ lastView = view;
+ }
- RelativeLayout.LayoutParams layout = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
- layout.setMargins(16, 8, 16, 8);
+ if (m_informativeText != null)
+ {
+ TextView view= new TextView(m_activity);
+ view.setId(id++);
+ view.setOnLongClickListener(copyText);
+ view.setLongClickable(true);
+
+ view.setText(m_informativeText);
+ view.setTextAppearance(android.R.style.TextAppearance_Medium);
+
+ RelativeLayout.LayoutParams layout = new RelativeLayout.LayoutParams(
+ RelativeLayout.LayoutParams.MATCH_PARENT,
+ RelativeLayout.LayoutParams.WRAP_CONTENT);
+ layout.setMargins(16, 8, 16, 8);
+ if (lastView != null)
+ layout.addRule(RelativeLayout.BELOW, lastView.getId());
+ else
layout.addRule(RelativeLayout.ALIGN_PARENT_TOP);
- dialogLayout.addView(view, layout);
- lastView = view;
- }
+ dialogLayout.addView(view, layout);
+ lastView = view;
+ }
- if (m_informativeText != null)
- {
- TextView view= new TextView(m_activity);
- view.setId(id++);
- view.setOnLongClickListener(copyText);
- view.setLongClickable(true);
-
- view.setText(m_informativeText);
- view.setTextAppearance(m_activity, android.R.style.TextAppearance_Medium);
-
- RelativeLayout.LayoutParams layout = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
- layout.setMargins(16, 8, 16, 8);
- if (lastView != null)
- layout.addRule(RelativeLayout.BELOW, lastView.getId());
- else
- layout.addRule(RelativeLayout.ALIGN_PARENT_TOP);
- dialogLayout.addView(view, layout);
- lastView = view;
- }
+ if (m_detailedText != null)
+ {
+ TextView view= new TextView(m_activity);
+ view.setId(id++);
+ view.setOnLongClickListener(copyText);
+ view.setLongClickable(true);
+
+ view.setText(m_detailedText);
+ view.setTextAppearance(android.R.style.TextAppearance_Small);
+
+ RelativeLayout.LayoutParams layout = new RelativeLayout.LayoutParams(
+ RelativeLayout.LayoutParams.MATCH_PARENT,
+ RelativeLayout.LayoutParams.WRAP_CONTENT);
+ layout.setMargins(16, 8, 16, 8);
+ if (lastView != null)
+ layout.addRule(RelativeLayout.BELOW, lastView.getId());
+ else
+ layout.addRule(RelativeLayout.ALIGN_PARENT_TOP);
+ dialogLayout.addView(view, layout);
+ lastView = view;
+ }
- if (m_detailedText != null)
+ if (m_buttonsList != null)
+ {
+ LinearLayout buttonsLayout = new LinearLayout(m_activity);
+ buttonsLayout.setOrientation(LinearLayout.HORIZONTAL);
+ buttonsLayout.setId(id++);
+ boolean firstButton = true;
+ for (ButtonStruct button: m_buttonsList)
{
- TextView view= new TextView(m_activity);
- view.setId(id++);
- view.setOnLongClickListener(copyText);
- view.setLongClickable(true);
-
- view.setText(m_detailedText);
- view.setTextAppearance(m_activity, android.R.style.TextAppearance_Small);
-
- RelativeLayout.LayoutParams layout = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
- layout.setMargins(16, 8, 16, 8);
- if (lastView != null)
- layout.addRule(RelativeLayout.BELOW, lastView.getId());
- else
- layout.addRule(RelativeLayout.ALIGN_PARENT_TOP);
- dialogLayout.addView(view, layout);
- lastView = view;
- }
+ Button bv;
+ try {
+ bv = new Button(m_activity, null, android.R.attr.borderlessButtonStyle);
+ } catch (Exception e) {
+ bv = new Button(m_activity);
+ e.printStackTrace();
+ }
- if (m_buttonsList != null)
- {
- LinearLayout buttonsLayout = new LinearLayout(m_activity);
- buttonsLayout.setOrientation(LinearLayout.HORIZONTAL);
- buttonsLayout.setId(id++);
- boolean firstButton = true;
- for (ButtonStruct button: m_buttonsList)
+ bv.setText(button.m_text);
+ bv.setOnClickListener(button);
+ if (!firstButton) // first button
{
- Button bv;
+ View spacer = new View(m_activity);
try {
- bv = new Button(m_activity, null, Class.forName("android.R$attr").getDeclaredField("borderlessButtonStyle").getInt(null));
+ LinearLayout.LayoutParams layout = new LinearLayout.LayoutParams(1,
+ RelativeLayout.LayoutParams.MATCH_PARENT);
+ spacer.setBackground(getStyledDrawable(android.R.attr.dividerVertical));
+ buttonsLayout.addView(spacer, layout);
} catch (Exception e) {
- bv = new Button(m_activity);
e.printStackTrace();
}
-
- bv.setText(button.m_text);
- bv.setOnClickListener(button);
- if (!firstButton) // first button
- {
- LinearLayout.LayoutParams layout = null;
- View spacer = new View(m_activity);
- try {
- layout = new LinearLayout.LayoutParams(1, RelativeLayout.LayoutParams.MATCH_PARENT);
- spacer.setBackgroundDrawable(getStyledDrawable("dividerVertical"));
- buttonsLayout.addView(spacer, layout);
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- LinearLayout.LayoutParams layout = null;
- layout = new LinearLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.WRAP_CONTENT, 1.0f);
- buttonsLayout.addView(bv, layout);
- firstButton = false;
}
+ LinearLayout.LayoutParams layout = new LinearLayout.LayoutParams(
+ RelativeLayout.LayoutParams.MATCH_PARENT,
+ RelativeLayout.LayoutParams.WRAP_CONTENT, 1.0f);
+ buttonsLayout.addView(bv, layout);
+ firstButton = false;
+ }
- try {
- View horizontalDevider = new View(m_activity);
- horizontalDevider.setId(id++);
- horizontalDevider.setBackgroundDrawable(getStyledDrawable("dividerHorizontal"));
- RelativeLayout.LayoutParams relativeParams = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, 1);
- relativeParams.setMargins(0, 10, 0, 0);
- if (lastView != null) {
- relativeParams.addRule(RelativeLayout.BELOW, lastView.getId());
- }
- else
- relativeParams.addRule(RelativeLayout.ALIGN_PARENT_TOP);
- dialogLayout.addView(horizontalDevider, relativeParams);
- lastView = horizontalDevider;
- } catch (Exception e) {
- e.printStackTrace();
- }
- RelativeLayout.LayoutParams relativeParams = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
+ try {
+ View horizontalDivider = new View(m_activity);
+ horizontalDivider.setId(id);
+ horizontalDivider.setBackground(getStyledDrawable(
+ android.R.attr.dividerHorizontal));
+ RelativeLayout.LayoutParams relativeParams = new RelativeLayout.LayoutParams(
+ RelativeLayout.LayoutParams.MATCH_PARENT, 1);
+ relativeParams.setMargins(0, 10, 0, 0);
if (lastView != null) {
relativeParams.addRule(RelativeLayout.BELOW, lastView.getId());
}
else
relativeParams.addRule(RelativeLayout.ALIGN_PARENT_TOP);
- relativeParams.setMargins(2, 0, 2, 0);
- dialogLayout.addView(buttonsLayout, relativeParams);
+ dialogLayout.addView(horizontalDivider, relativeParams);
+ lastView = horizontalDivider;
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ RelativeLayout.LayoutParams relativeParams = new RelativeLayout.LayoutParams(
+ RelativeLayout.LayoutParams.MATCH_PARENT,
+ RelativeLayout.LayoutParams.WRAP_CONTENT);
+ if (lastView != null) {
+ relativeParams.addRule(RelativeLayout.BELOW, lastView.getId());
}
- scrollView.addView(dialogLayout);
- m_dialog.setView(scrollView);
- m_dialog.show();
+ else
+ relativeParams.addRule(RelativeLayout.ALIGN_PARENT_TOP);
+ relativeParams.setMargins(2, 0, 2, 0);
+ dialogLayout.addView(buttonsLayout, relativeParams);
}
+ scrollView.addView(dialogLayout);
+ m_dialog.setView(scrollView);
+ m_dialog.show();
});
}
+ @UsedFromNativeCode
public void hide()
{
- m_activity.runOnUiThread( new Runnable() {
- @Override
- public void run() {
- if (m_dialog != null && m_dialog.isShowing())
- m_dialog.dismiss();
- reset();
- }
+ m_activity.runOnUiThread(() -> {
+ if (m_dialog != null && m_dialog.isShowing())
+ m_dialog.dismiss();
+ reset();
});
}
@@ -368,7 +315,7 @@ public class QtMessageDialogHelper
public void reset()
{
- m_icon = 0;
+ m_standardIcon = 0;
m_title = null;
m_text = null;
m_informativeText = null;
@@ -378,8 +325,9 @@ public class QtMessageDialogHelper
m_handler = 0;
}
- private Activity m_activity;
- private int m_icon = 0;
+ private static final String QtTAG = "QtMessageDialogHelper";
+ private final Activity m_activity;
+ private int m_standardIcon = 0;
private Spanned m_title, m_text, m_informativeText, m_detailedText;
private ArrayList<ButtonStruct> m_buttonsList;
private AlertDialog m_dialog;
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 11b27f96c0..97a45ef8fa 100644
--- a/src/android/jar/src/org/qtproject/qt/android/QtNative.java
+++ b/src/android/jar/src/org/qtproject/qt/android/QtNative.java
@@ -1,134 +1,56 @@
-/****************************************************************************
-**
-** Copyright (C) 2016 BogDan Vatra <bogdan@kde.org>
-** Copyright (C) 2016 The Qt Company Ltd.
-** Contact: https://www.qt.io/licensing/
-**
-** This file is part of the Android port of the Qt Toolkit.
-**
-** $QT_BEGIN_LICENSE:LGPL$
-** Commercial License Usage
-** Licensees holding valid commercial Qt licenses may use this file in
-** accordance with the commercial license agreement provided with the
-** Software or, alternatively, in accordance with the terms contained in
-** a written agreement between you and The Qt Company. For licensing terms
-** and conditions see https://www.qt.io/terms-conditions. For further
-** information use the contact form at https://www.qt.io/contact-us.
-**
-** GNU Lesser General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU Lesser
-** General Public License version 3 as published by the Free Software
-** Foundation and appearing in the file LICENSE.LGPL3 included in the
-** packaging of this file. Please review the following information to
-** ensure the GNU Lesser General Public License version 3 requirements
-** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
-**
-** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 2.0 or (at your option) the GNU General
-** Public license version 3 or any later version approved by the KDE Free
-** Qt Foundation. The licenses are as published by the Free Software
-** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
-** included in the packaging of this file. Please review the following
-** information to ensure the GNU General Public License requirements will
-** be met: https://www.gnu.org/licenses/gpl-2.0.html and
-** https://www.gnu.org/licenses/gpl-3.0.html.
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
+// Copyright (C) 2016 BogDan Vatra <bogdan@kde.org>
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
package org.qtproject.qt.android;
-import java.io.File;
-import java.io.FileDescriptor;
-import java.io.FileNotFoundException;
-import java.util.ArrayList;
-import java.util.Objects;
-import java.util.concurrent.Semaphore;
-import java.util.HashMap;
-
import android.app.Activity;
import android.app.Service;
import android.content.Context;
-import android.content.ContentResolver;
import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.ApplicationInfo;
import android.content.UriPermission;
+import android.content.pm.PackageManager;
import android.net.Uri;
-import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
-import android.content.ClipboardManager;
-import android.content.ClipData;
-import android.content.ClipDescription;
-import android.os.ParcelFileDescriptor;
import android.util.Log;
import android.view.ContextMenu;
-import android.view.KeyEvent;
import android.view.Menu;
-import android.view.MotionEvent;
import android.view.View;
-import android.view.InputDevice;
-import android.database.Cursor;
-import android.provider.DocumentsContract;
-import java.lang.reflect.Method;
+import java.lang.ref.WeakReference;
import java.security.KeyStore;
import java.security.cert.X509Certificate;
-import java.util.Iterator;
+import java.util.ArrayList;
import java.util.List;
-import javax.net.ssl.TrustManagerFactory;
+import java.util.Objects;
+
import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
-public class QtNative
+class QtNative
{
- private static Activity m_activity = null;
- private static boolean m_activityPaused = false;
- private static Service m_service = null;
- private static QtActivityDelegate m_activityDelegate = null;
- private static QtServiceDelegate m_serviceDelegate = null;
- public static Object m_mainActivityMutex = new Object(); // mutex used to synchronize runnable operations
-
- public static final String QtTAG = "Qt JAVA"; // string used for Log.x
- private static ArrayList<Runnable> m_lostActions = new ArrayList<Runnable>(); // a list containing all actions which could not be performed (e.g. the main activity is destroyed, etc.)
- private static boolean m_started = false;
- private static boolean m_isKeyboardHiding = false;
- private static int m_displayMetricsScreenWidthPixels = 0;
- private static int m_displayMetricsScreenHeightPixels = 0;
- private static int m_displayMetricsAvailableLeftPixels = 0;
- private static int m_displayMetricsAvailableTopPixels = 0;
- private static int m_displayMetricsAvailableWidthPixels = 0;
- private static int m_displayMetricsAvailableHeightPixels = 0;
- private static float m_displayMetricsRefreshRate = 60;
- private static double m_displayMetricsXDpi = .0;
- private static double m_displayMetricsYDpi = .0;
- private static double m_displayMetricsScaledDensity = 1.0;
- private static double m_displayMetricsDensity = 1.0;
- private static int m_oldx, m_oldy;
- private static final int m_moveThreshold = 0;
- private static ClipboardManager m_clipboardManager = null;
- private static Method m_checkSelfPermissionMethod = null;
- private static Boolean m_tabletEventSupported = null;
- private static boolean m_usePrimaryClip = false;
- public static QtThread m_qtThread = new QtThread();
- private static HashMap<String, Uri> m_cachedUris = new HashMap<String, Uri>();
- private static ArrayList<String> m_knownDirs = new ArrayList<String>();
- private static final int KEYBOARD_HEIGHT_THRESHOLD = 100;
-
- private static final String INVALID_OR_NULL_URI_ERROR_MESSAGE = "Received invalid/null Uri";
-
- private static final Runnable runPendingCppRunnablesRunnable = new Runnable() {
- @Override
- public void run() {
- runPendingCppRunnables();
- }
- };
+ 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 ApplicationStateDetails m_stateDetails = new ApplicationStateDetails();
+
+ public 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<>();
+
+ private static final QtThread m_qtThread = new QtThread();
private static ClassLoader m_classLoader = null;
+
+ private static final Runnable runPendingCppRunnablesRunnable = QtNative::runPendingCppRunnables;
+ private static final ArrayList<AppStateDetailsListener> m_appStateListeners = new ArrayList<>();
+ private static final Object m_appStateListenersLock = new Object();
+
+ @UsedFromNativeCode
public static ClassLoader classLoader()
{
return m_classLoader;
@@ -139,35 +61,54 @@ public class QtNative
m_classLoader = classLoader;
}
- public static Activity activity()
+ public static void setActivity(Activity qtMainActivity)
{
synchronized (m_mainActivityMutex) {
- return m_activity;
+ m_activity = new WeakReference<>(qtMainActivity);
}
}
- public static Service service()
+ public static void setService(Service qtMainService)
{
synchronized (m_mainActivityMutex) {
- return m_service;
+ m_service = new WeakReference<>(qtMainService);
}
}
-
- public static QtActivityDelegate activityDelegate()
+ @UsedFromNativeCode
+ public static Activity activity()
{
synchronized (m_mainActivityMutex) {
- return m_activityDelegate;
+ return m_activity != null ? m_activity.get() : null;
}
}
- public static QtServiceDelegate serviceDelegate()
+ public static boolean isActivityValid()
+ {
+ return m_activity != null && m_activity.get() != null;
+ }
+
+ @UsedFromNativeCode
+ public static Service service()
{
synchronized (m_mainActivityMutex) {
- return m_serviceDelegate;
+ return m_service != null ? m_service.get() : null;
}
}
+ public static boolean isServiceValid()
+ {
+ return m_service != null && m_service.get() != null;
+ }
+
+ @UsedFromNativeCode
+ public static Context getContext() {
+ if (isActivityValid())
+ return m_activity.get();
+ return service();
+ }
+
+ @UsedFromNativeCode
public static String[] getStringArray(String joinedString)
{
return joinedString.split(",");
@@ -178,6 +119,7 @@ public class QtNative
return new Exception().getStackTrace()[1].getMethodName() + ": ";
}
+ /** @noinspection SameParameterValue*/
private static Uri getUriWithValidPermission(Context context, String uri, String openMode)
{
Uri parsedUri;
@@ -192,7 +134,7 @@ public class QtNative
String scheme = parsedUri.getScheme();
// We only want to check permissions for content Uris
- if (scheme.compareTo("content") != 0)
+ if (scheme != null && scheme.compareTo("content") != 0)
return parsedUri;
List<UriPermission> permissions = context.getContentResolver().getPersistedUriPermissions();
@@ -205,7 +147,7 @@ public class QtNative
if (!openMode.equals("r"))
isRequestPermission = permissions.get(i).isWritePermission();
- if (iterUri.getPath().equals(uriStr) && isRequestPermission)
+ if (Objects.equals(iterUri.getPath(), uriStr) && isRequestPermission)
return iterUri;
}
@@ -214,16 +156,17 @@ public class QtNative
// and check for SecurityExceptions later
return parsedUri;
} catch (SecurityException e) {
- Log.e(QtTAG, getCurrentMethodNameLog() + e.toString());
+ Log.e(QtTAG, getCurrentMethodNameLog() + e);
return parsedUri;
}
}
+ @UsedFromNativeCode
public static boolean openURL(Context context, String url, String mime)
{
final Uri uri = getUriWithValidPermission(context, url, "r");
if (uri == null) {
- Log.e(QtTAG, getCurrentMethodNameLog() + INVALID_OR_NULL_URI_ERROR_MESSAGE);
+ Log.e(QtTAG, getCurrentMethodNameLog() + "received invalid/null Uri");
return false;
}
@@ -233,368 +176,128 @@ public class QtNative
if (!mime.isEmpty())
intent.setDataAndType(uri, mime);
- activity().startActivity(intent);
+ Activity activity = activity();
+ if (activity == null) {
+ Log.w(QtTAG, "openURL(): The activity reference is null");
+ return false;
+ }
+
+ activity.startActivity(intent);
return true;
} catch (Exception e) {
- Log.e(QtTAG, getCurrentMethodNameLog() + e.toString());
+ Log.e(QtTAG, getCurrentMethodNameLog() + e);
return false;
}
}
- public static ParcelFileDescriptor openParcelFdForContentUrl(Context context, String contentUrl,
- String openMode)
- {
- Uri uri = m_cachedUris.get(contentUrl);
- if (uri == null)
- uri = getUriWithValidPermission(context, contentUrl, openMode);
-
- if (uri == null) {
- Log.e(QtTAG, getCurrentMethodNameLog() + INVALID_OR_NULL_URI_ERROR_MESSAGE);
- return null;
- }
-
- try {
- final ContentResolver resolver = context.getContentResolver();
- return resolver.openFileDescriptor(uri, openMode);
- } catch (FileNotFoundException | IllegalArgumentException | SecurityException e) {
- Log.e(QtTAG, getCurrentMethodNameLog() + e.toString());
- }
-
- return null;
+ static QtThread getQtThread() {
+ return m_qtThread;
}
- public static FileDescriptor openFdObjectForContentUrl(Context context, String contentUrl,
- String openMode)
- {
- final ParcelFileDescriptor pfd = openParcelFdForContentUrl(context, contentUrl, openMode);
- if (pfd != null)
- return pfd.getFileDescriptor();
- return null;
+ interface AppStateDetailsListener {
+ void onAppStateDetailsChanged(ApplicationStateDetails details);
}
- public static int openFdForContentUrl(Context context, String contentUrl, String openMode)
- {
- Uri uri = m_cachedUris.get(contentUrl);
- if (uri == null)
- uri = getUriWithValidPermission(context, contentUrl, openMode);
-
- int fileDescriptor = -1;
- if (uri == null) {
- Log.e(QtTAG, getCurrentMethodNameLog() + INVALID_OR_NULL_URI_ERROR_MESSAGE);
- return fileDescriptor;
- }
-
- try {
- final ContentResolver resolver = context.getContentResolver();
- fileDescriptor = resolver.openFileDescriptor(uri, openMode).detachFd();
- } catch (IllegalArgumentException | SecurityException | FileNotFoundException e) {
- Log.e(QtTAG, getCurrentMethodNameLog() + e.toString());
- }
-
- return fileDescriptor;
+ // Keep in sync with src/corelib/global/qnamespace.h
+ public 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 long getSize(Context context, String contentUrl)
- {
- long size = -1;
- Uri uri = m_cachedUris.get(contentUrl);
- if (uri == null)
- uri = getUriWithValidPermission(context, contentUrl, "r");
-
- if (uri == null) {
- Log.e(QtTAG, getCurrentMethodNameLog() + INVALID_OR_NULL_URI_ERROR_MESSAGE);
- return size;
- } else if (!m_cachedUris.containsKey(contentUrl)) {
- m_cachedUris.put(contentUrl, uri);
- }
-
- try {
- ContentResolver resolver = context.getContentResolver();
- Cursor cur = resolver.query(uri, new String[] {
- DocumentsContract.Document.COLUMN_SIZE },
- null, null, null);
- if (cur != null) {
- if (cur.moveToFirst())
- size = cur.getLong(0);
- cur.close();
- }
- return size;
- } catch (IllegalArgumentException | SecurityException | UnsupportedOperationException e) {
- Log.e(QtTAG, getCurrentMethodNameLog() + e.toString());
- }
- return size;
+ public static class ApplicationStateDetails {
+ int state = ApplicationState.ApplicationSuspended;
+ boolean nativePluginIntegrationReady = false;
+ boolean isStarted = false;
}
- public static boolean checkFileExists(Context context, String contentUrl)
+ public static ApplicationStateDetails getStateDetails()
{
- boolean exists = false;
- Uri uri = m_cachedUris.get(contentUrl);
- if (uri == null)
- uri = getUriWithValidPermission(context, contentUrl, "r");
- if (uri == null) {
- Log.e(QtTAG, getCurrentMethodNameLog() + INVALID_OR_NULL_URI_ERROR_MESSAGE);
- return exists;
- } else {
- if (!m_cachedUris.containsKey(contentUrl))
- m_cachedUris.put(contentUrl, uri);
- }
-
- try {
- ContentResolver resolver = context.getContentResolver();
- Cursor cur = resolver.query(uri, null, null, null, null);
- if (cur != null) {
- exists = true;
- cur.close();
- }
- return exists;
- } catch (IllegalArgumentException | SecurityException | UnsupportedOperationException e) {
- Log.e(QtTAG, getCurrentMethodNameLog() + e.toString());
- }
- return exists;
+ return m_stateDetails;
}
- public static boolean checkIfWritable(Context context, String contentUrl)
+ public static void setStarted(boolean started)
{
- return getUriWithValidPermission(context, contentUrl, "w") != null;
+ m_stateDetails.isStarted = started;
+ notifyAppStateDetailsChanged(m_stateDetails);
}
- public static boolean checkIfDir(Context context, String contentUrl)
+ @UsedFromNativeCode
+ public static void notifyNativePluginIntegrationReady(boolean ready)
{
- boolean isDir = false;
- Uri uri = m_cachedUris.get(contentUrl);
- if (m_knownDirs.contains(contentUrl))
- return true;
- if (uri == null)
- uri = getUriWithValidPermission(context, contentUrl, "r");
-
- if (uri == null) {
- Log.e(QtTAG, getCurrentMethodNameLog() + INVALID_OR_NULL_URI_ERROR_MESSAGE);
- return isDir;
- } else {
- if (!m_cachedUris.containsKey(contentUrl))
- m_cachedUris.put(contentUrl, uri);
- }
-
- try {
- final List<String> paths = uri.getPathSegments();
- // getTreeDocumentId will throw an exception if it is not a directory so check manually
- if (!paths.get(0).equals("tree"))
- return false;
- ContentResolver resolver = context.getContentResolver();
- Uri docUri = DocumentsContract.buildDocumentUriUsingTree(uri,
- DocumentsContract.getTreeDocumentId(uri));
- if (!docUri.toString().startsWith(uri.toString()))
- return false;
- Cursor cur = resolver.query(docUri, new String[] {
- DocumentsContract.Document.COLUMN_MIME_TYPE },
- null, null, null);
- if (cur != null) {
- if (cur.moveToFirst()) {
- final String dirStr = new String(DocumentsContract.Document.MIME_TYPE_DIR);
- isDir = cur.getString(0).equals(dirStr);
- if (isDir)
- m_knownDirs.add(contentUrl);
- }
- cur.close();
- }
- return isDir;
- } catch (IllegalArgumentException | SecurityException | UnsupportedOperationException e) {
- Log.e(QtTAG, getCurrentMethodNameLog() + e.toString());
- }
- return false;
+ m_stateDetails.nativePluginIntegrationReady = ready;
+ notifyAppStateDetailsChanged(m_stateDetails);
}
- public static String[] listContentsFromTreeUri(Context context, String contentUrl)
+ public static void setApplicationState(int state)
{
- Uri treeUri = Uri.parse(contentUrl);
- final ArrayList<String> results = new ArrayList<>();
- if (treeUri == null) {
- Log.e(QtTAG, getCurrentMethodNameLog() + INVALID_OR_NULL_URI_ERROR_MESSAGE);
- return results.toArray(new String[results.size()]);
- }
- final ContentResolver resolver = context.getContentResolver();
- final Uri docUri = DocumentsContract.buildDocumentUriUsingTree(treeUri,
- DocumentsContract.getTreeDocumentId(treeUri));
- final Uri childrenUri = DocumentsContract.buildChildDocumentsUriUsingTree(docUri,
- DocumentsContract.getDocumentId(docUri));
- Cursor c;
- final String dirStr = DocumentsContract.Document.MIME_TYPE_DIR;
- try {
- c = resolver.query(childrenUri, new String[] {
- DocumentsContract.Document.COLUMN_DOCUMENT_ID,
- DocumentsContract.Document.COLUMN_DISPLAY_NAME,
- DocumentsContract.Document.COLUMN_MIME_TYPE },
- null, null, null);
- while (c.moveToNext()) {
- final String fileString = c.getString(1);
- if (!m_cachedUris.containsKey(contentUrl + "/" + fileString)) {
- m_cachedUris.put(contentUrl + "/" + fileString,
- DocumentsContract.buildDocumentUriUsingTree(treeUri,
- c.getString(0)));
- }
- results.add(fileString);
- if (c.getString(2).equals(dirStr))
- m_knownDirs.add(contentUrl + "/" + fileString);
+ synchronized (m_mainActivityMutex) {
+ m_stateDetails.state = state;
+ if (state == ApplicationState.ApplicationActive) {
+ for (Runnable mLostAction : m_lostActions)
+ runAction(mLostAction);
+ m_lostActions.clear();
}
- c.close();
- } catch (Exception e) {
- Log.w(QtTAG, "Failed query: " + e);
- return results.toArray(new String[results.size()]);
}
- return results.toArray(new String[results.size()]);
- }
-
- // this method loads full path libs
- public static void loadQtLibraries(final ArrayList<String> libraries)
- {
- m_qtThread.run(new Runnable() {
- @Override
- public void run() {
- if (libraries == null)
- return;
- for (String libName : libraries) {
- try {
- File f = new File(libName);
- if (f.exists())
- System.load(libName);
- else
- Log.i(QtTAG, "Can't find '" + libName + "'");
- } catch (SecurityException e) {
- Log.i(QtTAG, "Can't load '" + libName + "'", e);
- } catch (Exception e) {
- Log.i(QtTAG, "Can't load '" + libName + "'", e);
- }
- }
- }
- });
- }
-
- // this method loads bundled libs by name.
- public static void loadBundledLibraries(final ArrayList<String> libraries, final String nativeLibraryDir)
- {
- m_qtThread.run(new Runnable() {
- @Override
- public void run() {
- if (libraries == null)
- return;
-
- for (String libName : libraries) {
- try {
- String libNameTemplate = "lib" + libName + ".so";
- File f = new File(nativeLibraryDir + libNameTemplate);
- if (!f.exists()) {
- Log.i(QtTAG, "Can't find '" + f.getAbsolutePath());
- try {
- ApplicationInfo info = getContext().getApplicationContext().getPackageManager()
- .getApplicationInfo(getContext().getPackageName(), PackageManager.GET_META_DATA);
- String systemLibraryDir = QtNativeLibrariesDir.systemLibrariesDir;
- if (info.metaData.containsKey("android.app.system_libs_prefix"))
- systemLibraryDir = info.metaData.getString("android.app.system_libs_prefix");
- f = new File(systemLibraryDir + libNameTemplate);
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- if (f.exists())
- System.load(f.getAbsolutePath());
- else
- Log.i(QtTAG, "Can't find '" + f.getAbsolutePath());
- } catch (Exception e) {
- Log.i(QtTAG, "Can't load '" + libName + "'", e);
- }
- }
- }
- });
+ updateApplicationState(state);
+ notifyAppStateDetailsChanged(m_stateDetails);
}
- public static String loadMainLibrary(final String mainLibrary, final String nativeLibraryDir)
- {
- final String[] res = new String[1];
- res[0] = null;
- m_qtThread.run(new Runnable() {
- @Override
- public void run() {
- try {
- String mainLibNameTemplate = "lib" + mainLibrary + ".so";
- File f = new File(nativeLibraryDir + mainLibNameTemplate);
- if (!f.exists()) {
- try {
- ApplicationInfo info = getContext().getApplicationContext().getPackageManager()
- .getApplicationInfo(getContext().getPackageName(), PackageManager.GET_META_DATA);
- String systemLibraryDir = QtNativeLibrariesDir.systemLibrariesDir;
- if (info.metaData.containsKey("android.app.system_libs_prefix"))
- systemLibraryDir = info.metaData.getString("android.app.system_libs_prefix");
- f = new File(systemLibraryDir + mainLibNameTemplate);
- } catch (Exception e) {
- e.printStackTrace();
- return;
- }
- }
- if (!f.exists())
- return;
- System.load(f.getAbsolutePath());
- res[0] = f.getAbsolutePath();
- } catch (Exception e) {
- Log.e(QtTAG, "Can't load '" + mainLibrary + "'", e);
- }
- }
- });
- return res[0];
+ static void registerAppStateListener(AppStateDetailsListener listener) {
+ synchronized (m_appStateListenersLock) {
+ if (!m_appStateListeners.contains(listener))
+ m_appStateListeners.add(listener);
+ }
}
- public static void setActivity(Activity qtMainActivity, QtActivityDelegate qtActivityDelegate)
- {
- synchronized (m_mainActivityMutex) {
- m_activity = qtMainActivity;
- m_activityDelegate = qtActivityDelegate;
+ static void unregisterAppStateListener(AppStateDetailsListener listener) {
+ synchronized (m_appStateListenersLock) {
+ m_appStateListeners.remove(listener);
}
}
- public static void setService(Service qtMainService, QtServiceDelegate qtServiceDelegate)
- {
- synchronized (m_mainActivityMutex) {
- m_service = qtMainService;
- m_serviceDelegate = qtServiceDelegate;
+ static void notifyAppStateDetailsChanged(ApplicationStateDetails details) {
+ synchronized (m_appStateListenersLock) {
+ for (AppStateDetailsListener listener : m_appStateListeners)
+ listener.onAppStateDetailsChanged(details);
}
}
- public static void setApplicationState(int state)
+ // 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)
{
- synchronized (m_mainActivityMutex) {
- switch (state) {
- case QtActivityDelegate.ApplicationActive:
- m_activityPaused = false;
- Iterator<Runnable> itr = m_lostActions.iterator();
- while (itr.hasNext())
- runAction(itr.next());
- m_lostActions.clear();
- break;
- default:
- m_activityPaused = true;
- break;
- }
- }
- updateApplicationState(state);
+ runAction(action, true);
}
- private static void runAction(Runnable action)
+ public static void runAction(Runnable action, boolean queueWhenInactive)
{
synchronized (m_mainActivityMutex) {
final Looper mainLooper = Looper.getMainLooper();
final Handler handler = new Handler(mainLooper);
- final boolean active = (m_activity != null && !m_activityPaused) || m_service != null;
- if (!active || mainLooper == null || !handler.post(action))
- m_lostActions.add(action);
+
+ if (queueWhenInactive) {
+ final boolean isStateVisible =
+ (m_stateDetails.state != ApplicationState.ApplicationSuspended)
+ && (m_stateDetails.state != ApplicationState.ApplicationHidden);
+ final boolean active = (isActivityValid() && isStateVisible) || isServiceValid();
+ if (!active || !handler.post(action))
+ m_lostActions.add(action);
+ } else {
+ handler.post(action);
+ }
}
}
+ @UsedFromNativeCode
private static void runPendingCppRunnablesOnAndroidThread()
{
synchronized (m_mainActivityMutex) {
- if (m_activity != null) {
- if (!m_activityPaused)
- m_activity.runOnUiThread(runPendingCppRunnablesRunnable);
+ if (isActivityValid()) {
+ if (m_stateDetails.state == ApplicationState.ApplicationActive)
+ m_activity.get().runOnUiThread(runPendingCppRunnablesRunnable);
else
runAction(runPendingCppRunnablesRunnable);
} else {
@@ -610,611 +313,54 @@ public class QtNative
}
}
+ @UsedFromNativeCode
private static void setViewVisibility(final View view, final boolean visible)
{
- runAction(new Runnable() {
- @Override
- public void run() {
- view.setVisibility(visible ? View.VISIBLE : View.GONE);
- }
- });
+ runAction(() -> view.setVisibility(visible ? View.VISIBLE : View.GONE));
}
- public static boolean startApplication(String params, final String environment, String mainLib) throws Exception
+ public static void startApplication(String params, String mainLib)
{
- if (params == null)
- params = "-platform\tandroid";
-
- final boolean[] res = new boolean[1];
- res[0] = false;
synchronized (m_mainActivityMutex) {
- if (params.length() > 0 && !params.startsWith("\t"))
- params = "\t" + params;
- final String qtParams = mainLib + params;
- m_qtThread.run(new Runnable() {
- @Override
- public void run() {
- res[0] = startQtAndroidPlugin(qtParams, environment);
- setDisplayMetrics(
- m_displayMetricsScreenWidthPixels, m_displayMetricsScreenHeightPixels,
- m_displayMetricsAvailableLeftPixels, m_displayMetricsAvailableTopPixels,
- m_displayMetricsAvailableWidthPixels,
- m_displayMetricsAvailableHeightPixels, m_displayMetricsXDpi,
- m_displayMetricsYDpi, m_displayMetricsScaledDensity,
- m_displayMetricsDensity, m_displayMetricsRefreshRate);
- }
- });
- m_qtThread.post(new Runnable() {
- @Override
- public void run() {
- startQtApplication();
- }
+ m_qtThread.run(() -> {
+ final String qtParams = mainLib + " " + params;
+ if (!startQtAndroidPlugin(qtParams))
+ Log.e(QtTAG, "An error occurred while starting the Qt Android plugin");
});
+ m_qtThread.post(QtNative::startQtApplication);
waitForServiceSetup();
- m_started = true;
- }
- return res[0];
- }
-
- public static void setApplicationDisplayMetrics(int screenWidthPixels, int screenHeightPixels,
- int availableLeftPixels, int availableTopPixels,
- int availableWidthPixels,
- int availableHeightPixels, double XDpi,
- double YDpi, double scaledDensity,
- double density, float refreshRate)
- {
- /* Fix buggy dpi report */
- if (XDpi < android.util.DisplayMetrics.DENSITY_LOW)
- XDpi = android.util.DisplayMetrics.DENSITY_LOW;
- if (YDpi < android.util.DisplayMetrics.DENSITY_LOW)
- YDpi = android.util.DisplayMetrics.DENSITY_LOW;
-
- synchronized (m_mainActivityMutex) {
- if (m_started) {
- setDisplayMetrics(screenWidthPixels, screenHeightPixels, availableLeftPixels,
- availableTopPixels, availableWidthPixels, availableHeightPixels,
- XDpi, YDpi, scaledDensity, density, refreshRate);
- } else {
- m_displayMetricsScreenWidthPixels = screenWidthPixels;
- m_displayMetricsScreenHeightPixels = screenHeightPixels;
- m_displayMetricsAvailableLeftPixels = availableLeftPixels;
- m_displayMetricsAvailableTopPixels = availableTopPixels;
- m_displayMetricsAvailableWidthPixels = availableWidthPixels;
- m_displayMetricsAvailableHeightPixels = availableHeightPixels;
- m_displayMetricsXDpi = XDpi;
- m_displayMetricsYDpi = YDpi;
- m_displayMetricsScaledDensity = scaledDensity;
- m_displayMetricsDensity = density;
- m_displayMetricsRefreshRate = refreshRate;
- }
+ m_stateDetails.isStarted = true;
+ notifyAppStateDetailsChanged(m_stateDetails);
}
}
-
-
- // application methods
- public static native boolean startQtAndroidPlugin(String params, String env);
- 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();
- // application methods
-
- private static void quitApp()
+ public static void quitApp()
{
- runAction(new Runnable() {
- @Override
- public void run() {
- quitQtAndroidPlugin();
- if (m_activity != null)
- m_activity.finish();
- if (m_service != null)
- m_service.stopSelf();
- }
+ runAction(() -> {
+ quitQtAndroidPlugin();
+ if (isActivityValid())
+ m_activity.get().finish();
+ if (isServiceValid())
+ m_service.get().stopSelf();
+ m_stateDetails.isStarted = false;
+ // Likely no use to call notifyAppStateDetailsChanged at this point since we are exiting
});
}
- //@ANDROID-9
- static private int getAction(int index, MotionEvent event)
- {
- int action = event.getActionMasked();
- if (action == MotionEvent.ACTION_MOVE) {
- int hsz = event.getHistorySize();
- if (hsz > 0) {
- float x = event.getX(index);
- float y = event.getY(index);
- for (int h = 0; h < hsz; ++h) {
- if ( event.getHistoricalX(index, h) != x ||
- event.getHistoricalY(index, h) != y )
- return 1;
- }
- return 2;
- }
- return 1;
- }
- if (action == MotionEvent.ACTION_DOWN
- || action == MotionEvent.ACTION_POINTER_DOWN && index == event.getActionIndex()) {
- return 0;
- } else if (action == MotionEvent.ACTION_UP
- || action == MotionEvent.ACTION_POINTER_UP && index == event.getActionIndex()) {
- return 3;
- }
- return 2;
- }
- //@ANDROID-9
-
- static public void sendTouchEvent(MotionEvent event, int id)
- {
- int pointerType = 0;
-
- if (m_tabletEventSupported == null)
- m_tabletEventSupported = isTabletEventSupported();
-
- switch (event.getToolType(0)) {
- case MotionEvent.TOOL_TYPE_STYLUS:
- pointerType = 1; // QTabletEvent::Pen
- break;
- case MotionEvent.TOOL_TYPE_ERASER:
- pointerType = 3; // QTabletEvent::Eraser
- break;
- }
-
- if (event.getToolType(0) == MotionEvent.TOOL_TYPE_MOUSE) {
- sendMouseEvent(event, id);
- } else if (m_tabletEventSupported && pointerType != 0) {
- tabletEvent(id, event.getDeviceId(), event.getEventTime(), event.getAction(), pointerType,
- event.getButtonState(), event.getX(), event.getY(), event.getPressure());
- } else {
- touchBegin(id);
- for (int i = 0; i < event.getPointerCount(); ++i) {
- touchAdd(id,
- event.getPointerId(i),
- getAction(i, event),
- i == 0,
- (int)event.getX(i),
- (int)event.getY(i),
- event.getTouchMajor(i),
- event.getTouchMinor(i),
- event.getOrientation(i),
- event.getPressure(i));
- }
-
- switch (event.getAction()) {
- case MotionEvent.ACTION_DOWN:
- touchEnd(id, 0);
- break;
-
- case MotionEvent.ACTION_UP:
- touchEnd(id, 2);
- break;
-
- case MotionEvent.ACTION_CANCEL:
- touchCancel(id);
- break;
-
- default:
- touchEnd(id, 1);
- }
- }
- }
-
- static public void sendTrackballEvent(MotionEvent event, int id)
- {
- sendMouseEvent(event,id);
- }
-
- static public boolean sendGenericMotionEvent(MotionEvent event, int id)
- {
- if (((event.getAction() & (MotionEvent.ACTION_SCROLL | MotionEvent.ACTION_HOVER_MOVE)) == 0)
- || (event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != InputDevice.SOURCE_CLASS_POINTER) {
- return false;
- }
-
- return sendMouseEvent(event, id);
- }
-
- static public boolean sendMouseEvent(MotionEvent event, int id)
- {
- switch (event.getActionMasked()) {
- case MotionEvent.ACTION_UP:
- mouseUp(id, (int) event.getX(), (int) event.getY());
- break;
-
- case MotionEvent.ACTION_DOWN:
- mouseDown(id, (int) event.getX(), (int) event.getY());
- m_oldx = (int) event.getX();
- m_oldy = (int) event.getY();
- break;
- case MotionEvent.ACTION_HOVER_MOVE:
- case MotionEvent.ACTION_MOVE:
- if (event.getToolType(0) == MotionEvent.TOOL_TYPE_MOUSE) {
- mouseMove(id, (int) event.getX(), (int) event.getY());
- } else {
- int dx = (int) (event.getX() - m_oldx);
- int dy = (int) (event.getY() - m_oldy);
- if (Math.abs(dx) > 5 || Math.abs(dy) > 5) {
- mouseMove(id, (int) event.getX(), (int) event.getY());
- m_oldx = (int) event.getX();
- m_oldy = (int) event.getY();
- }
- }
- break;
- case MotionEvent.ACTION_SCROLL:
- mouseWheel(id, (int) event.getX(), (int) event.getY(),
- event.getAxisValue(MotionEvent.AXIS_HSCROLL), event.getAxisValue(MotionEvent.AXIS_VSCROLL));
- break;
- default:
- return false;
- }
- return true;
- }
-
- public static Context getContext() {
- if (m_activity != null)
- return m_activity;
- return m_service;
- }
-
+ @UsedFromNativeCode
public static int checkSelfPermission(String permission)
{
- int perm = PackageManager.PERMISSION_DENIED;
synchronized (m_mainActivityMutex) {
Context context = getContext();
PackageManager pm = context.getPackageManager();
- perm = pm.checkPermission(permission, context.getPackageName());
- }
-
- return perm;
- }
-
- private static void updateSelection(final int selStart,
- final int selEnd,
- final int candidatesStart,
- final int candidatesEnd)
- {
- runAction(new Runnable() {
- @Override
- public void run() {
- if (m_activityDelegate != null)
- m_activityDelegate.updateSelection(selStart, selEnd, candidatesStart, candidatesEnd);
- }
- });
- }
-
- private static int getSelectHandleWidth()
- {
- return m_activityDelegate.getSelectHandleWidth();
- }
-
- private static void updateHandles(final int mode,
- final int editX,
- final int editY,
- final int editButtons,
- final int x1,
- final int y1,
- final int x2,
- final int y2,
- final boolean rtl)
- {
- runAction(new Runnable() {
- @Override
- public void run() {
- m_activityDelegate.updateHandles(mode, editX, editY, editButtons, x1, y1, x2, y2, rtl);
- }
- });
- }
-
- private static void showSoftwareKeyboard(final int x,
- final int y,
- final int width,
- final int height,
- final int inputHints,
- final int enterKeyType)
- {
- runAction(new Runnable() {
- @Override
- public void run() {
- if (m_activityDelegate != null)
- m_activityDelegate.showSoftwareKeyboard(x, y, width, height, inputHints, enterKeyType);
- }
- });
- }
-
- private static void resetSoftwareKeyboard()
- {
- runAction(new Runnable() {
- @Override
- public void run() {
- if (m_activityDelegate != null)
- m_activityDelegate.resetSoftwareKeyboard();
- }
- });
- }
-
- private static void hideSoftwareKeyboard()
- {
- m_isKeyboardHiding = true;
- runAction(new Runnable() {
- @Override
- public void run() {
- if (m_activityDelegate != null)
- m_activityDelegate.hideSoftwareKeyboard();
- }
- });
- }
-
- private static void setSystemUiVisibility(final int systemUiVisibility)
- {
- runAction(new Runnable() {
- @Override
- public void run() {
- if (m_activityDelegate != null) {
- m_activityDelegate.setSystemUiVisibility(systemUiVisibility);
- }
- updateWindow();
- }
- });
- }
-
- public static boolean isSoftwareKeyboardVisible()
- {
- return m_activityDelegate.isKeyboardVisible() && !m_isKeyboardHiding;
- }
-
- private static void notifyAccessibilityLocationChange()
- {
- runAction(new Runnable() {
- @Override
- public void run() {
- if (m_activityDelegate != null) {
- m_activityDelegate.notifyAccessibilityLocationChange();
- }
- }
- });
- }
-
- private static void notifyObjectHide(final int viewId)
- {
- runAction(new Runnable() {
- @Override
- public void run() {
- if (m_activityDelegate != null) {
- m_activityDelegate.notifyObjectHide(viewId);
- }
- }
- });
- }
-
- private static void notifyObjectFocus(final int viewId)
- {
- runAction(new Runnable() {
- @Override
- public void run() {
- if (m_activityDelegate != null) {
- m_activityDelegate.notifyObjectFocus(viewId);
- }
- }
- });
- }
-
- public static void notifyQtAndroidPluginRunning(final boolean running)
- {
- m_activityDelegate.notifyQtAndroidPluginRunning(running);
- }
-
- private static void registerClipboardManager()
- {
- if (m_service == null || m_activity != null) { // Avoid freezing if only service
- final Semaphore semaphore = new Semaphore(0);
- runAction(new Runnable() {
- @Override
- public void run() {
- if (m_activity != null)
- m_clipboardManager = (android.content.ClipboardManager) m_activity.getSystemService(Context.CLIPBOARD_SERVICE);
- if (m_clipboardManager != null) {
- m_clipboardManager.addPrimaryClipChangedListener(new ClipboardManager.OnPrimaryClipChangedListener() {
- public void onPrimaryClipChanged() {
- onClipboardDataChanged();
- }
- });
- }
- semaphore.release();
- }
- });
- try {
- semaphore.acquire();
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- }
-
- private static void clearClipData()
- {
- if (Build.VERSION.SDK_INT >= 28 && m_clipboardManager != null)
- m_clipboardManager.clearPrimaryClip();
- m_usePrimaryClip = false;
- }
- private static void setClipboardText(String text)
- {
- if (m_clipboardManager != null) {
- ClipData clipData = ClipData.newPlainText("text/plain", text);
- updatePrimaryClip(clipData);
- }
- }
-
- public static boolean hasClipboardText()
- {
- try {
- if (m_clipboardManager != null && m_clipboardManager.hasPrimaryClip()) {
- ClipDescription primaryClipDescription = m_clipboardManager.getPrimaryClipDescription();
- return primaryClipDescription.hasMimeType("text/*");
- }
- } catch (Exception e) {
- Log.e(QtTAG, "Failed to get clipboard data", e);
- }
- return false;
- }
-
- private static String getClipboardText()
- {
- try {
- if (m_clipboardManager != null && m_clipboardManager.hasPrimaryClip()) {
- ClipData primaryClip = m_clipboardManager.getPrimaryClip();
- for (int i = 0; i < primaryClip.getItemCount(); ++i)
- if (primaryClip.getItemAt(i).getText() != null)
- return primaryClip.getItemAt(i).getText().toString();
- }
- } catch (Exception e) {
- Log.e(QtTAG, "Failed to get clipboard data", e);
- }
- return "";
- }
-
- private static void updatePrimaryClip(ClipData clipData)
- {
- try {
- if (m_usePrimaryClip) {
- ClipData clip = m_clipboardManager.getPrimaryClip();
- if (Build.VERSION.SDK_INT >= 26) {
- Objects.requireNonNull(clip).addItem(m_activity.getContentResolver(), clipData.getItemAt(0));
- } else {
- Objects.requireNonNull(clip).addItem(clipData.getItemAt(0));
- }
- m_clipboardManager.setPrimaryClip(clip);
- } else {
- m_clipboardManager.setPrimaryClip(clipData);
- m_usePrimaryClip = true;
- }
- } catch (Exception e) {
- Log.e(QtTAG, "Failed to set clipboard data", e);
- }
- }
-
- private static void setClipboardHtml(String text, String html)
- {
- if (m_clipboardManager != null) {
- ClipData clipData = ClipData.newHtmlText("text/html", text, html);
- updatePrimaryClip(clipData);
- }
- }
-
- public static boolean hasClipboardHtml()
- {
- try {
- if (m_clipboardManager != null && m_clipboardManager.hasPrimaryClip()) {
- ClipDescription primaryClipDescription = m_clipboardManager.getPrimaryClipDescription();
- return primaryClipDescription.hasMimeType("text/html");
- }
- } catch (Exception e) {
- Log.e(QtTAG, "Failed to get clipboard data", e);
+ return pm.checkPermission(permission, context.getPackageName());
}
- return false;
- }
-
- private static String getClipboardHtml()
- {
- try {
- if (m_clipboardManager != null && m_clipboardManager.hasPrimaryClip()) {
- ClipData primaryClip = m_clipboardManager.getPrimaryClip();
- for (int i = 0; i < primaryClip.getItemCount(); ++i)
- if (primaryClip.getItemAt(i).getHtmlText() != null)
- return primaryClip.getItemAt(i).getHtmlText().toString();
- }
- } catch (Exception e) {
- Log.e(QtTAG, "Failed to get clipboard data", e);
- }
- return "";
- }
-
- private static void setClipboardUri(String uriString)
- {
- if (m_clipboardManager != null) {
- ClipData clipData = ClipData.newUri(m_activity.getContentResolver(), "text/uri-list",
- Uri.parse(uriString));
- updatePrimaryClip(clipData);
- }
- }
-
- public static boolean hasClipboardUri()
- {
- try {
- if (m_clipboardManager != null && m_clipboardManager.hasPrimaryClip()) {
- ClipDescription primaryClipDescription = m_clipboardManager.getPrimaryClipDescription();
- return primaryClipDescription.hasMimeType("text/uri-list");
- }
- } catch (Exception e) {
- Log.e(QtTAG, "Failed to get clipboard data", e);
- }
- return false;
- }
-
- private static String[] getClipboardUris()
- {
- ArrayList<String> uris = new ArrayList<String>();
- try {
- if (m_clipboardManager != null && m_clipboardManager.hasPrimaryClip()) {
- ClipData primaryClip = m_clipboardManager.getPrimaryClip();
- for (int i = 0; i < primaryClip.getItemCount(); ++i)
- if (primaryClip.getItemAt(i).getUri() != null)
- uris.add(primaryClip.getItemAt(i).getUri().toString());
- }
- } catch (Exception e) {
- Log.e(QtTAG, "Failed to get clipboard data", e);
- }
- String[] strings = new String[uris.size()];
- strings = uris.toArray(strings);
- return strings;
- }
-
- private static void openContextMenu(final int x, final int y, final int w, final int h)
- {
- runAction(new Runnable() {
- @Override
- public void run() {
- if (m_activityDelegate != null)
- m_activityDelegate.openContextMenu(x, y, w, h);
- }
- });
- }
-
- private static void closeContextMenu()
- {
- runAction(new Runnable() {
- @Override
- public void run() {
- if (m_activityDelegate != null)
- m_activityDelegate.closeContextMenu();
- }
- });
- }
-
- private static void resetOptionsMenu()
- {
- runAction(new Runnable() {
- @Override
- public void run() {
- if (m_activityDelegate != null)
- m_activityDelegate.resetOptionsMenu();
- }
- });
- }
-
- private static void openOptionsMenu()
- {
- runAction(new Runnable() {
- @Override
- public void run() {
- if (m_activity != null)
- m_activity.openOptionsMenu();
- }
- });
}
+ @UsedFromNativeCode
private static byte[][] getSSLCertificates()
{
- ArrayList<byte[]> certificateList = new ArrayList<byte[]>();
+ ArrayList<byte[]> certificateList = new ArrayList<>();
try {
TrustManagerFactory factory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
@@ -1225,7 +371,7 @@ public class QtNative
X509TrustManager trustManager = (X509TrustManager) manager;
for (X509Certificate certificate : trustManager.getAcceptedIssuers()) {
- byte buffer[] = certificate.getEncoded();
+ byte[] buffer = certificate.getEncoded();
certificateList.add(buffer);
}
}
@@ -1239,105 +385,13 @@ public class QtNative
return certificateArray;
}
- private static void createSurface(final int id, final boolean onTop, final int x, final int y, final int w, final int h, final int imageDepth)
- {
- runAction(new Runnable() {
- @Override
- public void run() {
- if (m_activityDelegate != null)
- m_activityDelegate.createSurface(id, onTop, x, y, w, h, imageDepth);
- }
- });
- }
-
- private static void insertNativeView(final int id, final View view, final int x, final int y, final int w, final int h)
- {
- runAction(new Runnable() {
- @Override
- public void run() {
- if (m_activityDelegate != null)
- m_activityDelegate.insertNativeView(id, view, x, y, w, h);
- }
- });
- }
-
- private static void setSurfaceGeometry(final int id, final int x, final int y, final int w, final int h)
- {
- runAction(new Runnable() {
- @Override
- public void run() {
- if (m_activityDelegate != null)
- m_activityDelegate.setSurfaceGeometry(id, x, y, w, h);
- }
- });
- }
-
- private static void bringChildToFront(final int id)
- {
- runAction(new Runnable() {
- @Override
- public void run() {
- if (m_activityDelegate != null)
- m_activityDelegate.bringChildToFront(id);
- }
- });
- }
-
- private static void bringChildToBack(final int id)
- {
- runAction(new Runnable() {
- @Override
- public void run() {
- if (m_activityDelegate != null)
- m_activityDelegate.bringChildToBack(id);
- }
- });
- }
-
- private static void destroySurface(final int id)
- {
- runAction(new Runnable() {
- @Override
- public void run() {
- if (m_activityDelegate != null)
- m_activityDelegate.destroySurface(id);
- }
- });
- }
-
- private static void initializeAccessibility()
- {
- runAction(new Runnable() {
- @Override
- public void run() {
- m_activityDelegate.initializeAccessibility();
- }
- });
- }
-
- private static void hideSplashScreen(final int duration)
- {
- runAction(new Runnable() {
- @Override
- public void run() {
- if (m_activityDelegate != null)
- m_activityDelegate.hideSplashScreen(duration);
- }
- });
- }
-
- public static void keyboardVisibilityUpdated(boolean visibility)
- {
- m_isKeyboardHiding = false;
- keyboardVisibilityChanged(visibility);
- }
-
+ @UsedFromNativeCode
private static String[] listAssetContent(android.content.res.AssetManager asset, String path) {
String [] list;
- ArrayList<String> res = new ArrayList<String>();
+ ArrayList<String> res = new ArrayList<>();
try {
list = asset.list(path);
- if (list.length > 0) {
+ if (list != null) {
for (String file : list) {
try {
String[] isDir = asset.list(path.length() > 0 ? path + "/" + file : file);
@@ -1352,57 +406,21 @@ public class QtNative
} catch (Exception e) {
e.printStackTrace();
}
- return res.toArray(new String[res.size()]);
+ return res.toArray(new String[0]);
}
- // screen methods
- public static native void setDisplayMetrics(int screenWidthPixels, int screenHeightPixels,
- int availableLeftPixels, int availableTopPixels,
- int availableWidthPixels, int availableHeightPixels,
- double XDpi, double YDpi, double scaledDensity,
- double density, float refreshRate);
- public static native void handleOrientationChanged(int newRotation, int nativeOrientation);
- public static native void handleRefreshRateChanged(float refreshRate);
- // screen methods
-
- // pointer methods
- public static native void mouseDown(int winId, int x, int y);
- public static native void mouseUp(int winId, int x, int y);
- public static native void mouseMove(int winId, int x, int y);
- public static native void mouseWheel(int winId, int x, int y, float hdelta, float vdelta);
- public static native void touchBegin(int winId);
- public static native void touchAdd(int winId, int pointerId, int action, boolean primary, int x, int y, float major, float minor, float rotation, float pressure);
- public static native void touchEnd(int winId, int action);
- public static native void touchCancel(int winId);
- public static native void longPress(int winId, int x, int y);
- // pointer methods
-
- // tablet methods
- public static native boolean isTabletEventSupported();
- public static native void tabletEvent(int winId, int deviceId, long time, int action, int pointerType, int buttonState, float x, float y, float pressure);
- // tablet methods
-
- // keyboard methods
- public static native void keyDown(int key, int unicode, int modifier, boolean autoRepeat);
- public static native void keyUp(int key, int unicode, int modifier, boolean autoRepeat);
- public static native void keyboardVisibilityChanged(boolean visibility);
- public static native void keyboardGeometryChanged(int x, int y, int width, int height);
- // keyboard methods
-
- // handle methods
- public static final int IdCursorHandle = 1;
- public static final int IdLeftHandle = 2;
- public static final int IdRightHandle = 3;
- public static native void handleLocationChanged(int id, int x, int y);
- // handle methods
-
- // dispatch events methods
- public static native boolean dispatchGenericMotionEvent(MotionEvent ev);
- public static native boolean dispatchKeyEvent(KeyEvent event);
- // dispatch events methods
+ // 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();
+ // application methods
// surface methods
- public static native void setSurface(int id, Object surface, int w, int h);
+ public static native void setSurface(int id, Object surface);
// surface methods
// window methods
@@ -1423,10 +441,6 @@ public class QtNative
public static native void onContextMenuClosed(Menu menu);
// menu methods
- // clipboard methods
- public static native void onClipboardDataChanged();
- // clipboard methods
-
// activity methods
public static native void onActivityResult(int requestCode, int resultCode, Intent data);
public static native void onNewIntent(Intent data);
diff --git a/src/android/jar/src/org/qtproject/qt/android/QtNativeAccessibility.java b/src/android/jar/src/org/qtproject/qt/android/QtNativeAccessibility.java
new file mode 100644
index 0000000000..dd2cead8cd
--- /dev/null
+++ b/src/android/jar/src/org/qtproject/qt/android/QtNativeAccessibility.java
@@ -0,0 +1,22 @@
+// Copyright (C) 2016 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.graphics.Rect;
+import android.view.accessibility.AccessibilityNodeInfo;
+
+class QtNativeAccessibility
+{
+ static native void setActive(boolean enable);
+ static native int[] childIdListForAccessibleObject(int objectId);
+ static native int parentId(int objectId);
+ static native String descriptionForAccessibleObject(int objectId);
+ static native Rect screenRect(int objectId);
+ static native int hitTest(float x, float y);
+ static native boolean clickAction(int objectId);
+ static native boolean scrollForward(int objectId);
+ static native boolean scrollBackward(int objectId);
+
+ static native boolean populateNode(int objectId, AccessibilityNodeInfo node);
+}
diff --git a/src/android/jar/src/org/qtproject/qt/android/QtNativeLibrariesDir.java b/src/android/jar/src/org/qtproject/qt/android/QtNativeLibrariesDir.java
deleted file mode 100644
index 7283570609..0000000000
--- a/src/android/jar/src/org/qtproject/qt/android/QtNativeLibrariesDir.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/****************************************************************************
-**
-** Copyright (C) 2016 The Qt Company Ltd.
-** Copyright (C) 2012 BogDan Vatra <bogdan@kde.org>
-** Contact: https://www.qt.io/licensing/
-**
-** This file is part of the Android port of the Qt Toolkit.
-**
-** $QT_BEGIN_LICENSE:LGPL$
-** Commercial License Usage
-** Licensees holding valid commercial Qt licenses may use this file in
-** accordance with the commercial license agreement provided with the
-** Software or, alternatively, in accordance with the terms contained in
-** a written agreement between you and The Qt Company. For licensing terms
-** and conditions see https://www.qt.io/terms-conditions. For further
-** information use the contact form at https://www.qt.io/contact-us.
-**
-** GNU Lesser General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU Lesser
-** General Public License version 3 as published by the Free Software
-** Foundation and appearing in the file LICENSE.LGPL3 included in the
-** packaging of this file. Please review the following information to
-** ensure the GNU Lesser General Public License version 3 requirements
-** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
-**
-** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 2.0 or (at your option) the GNU General
-** Public license version 3 or any later version approved by the KDE Free
-** Qt Foundation. The licenses are as published by the Free Software
-** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
-** included in the packaging of this file. Please review the following
-** information to ensure the GNU General Public License requirements will
-** be met: https://www.gnu.org/licenses/gpl-2.0.html and
-** https://www.gnu.org/licenses/gpl-3.0.html.
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
-
-package org.qtproject.qt.android;
-
-import android.content.Context;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager.NameNotFoundException;
-
-public class QtNativeLibrariesDir {
- public static final String systemLibrariesDir = "/system/lib/";
- public static String nativeLibrariesDir(Context context)
- {
- String m_nativeLibraryDir = null;
- try {
- ApplicationInfo ai = context.getPackageManager().getApplicationInfo(context.getPackageName(), 0);
- m_nativeLibraryDir = ai.nativeLibraryDir + "/";
- } catch (NameNotFoundException e) {
- e.printStackTrace();
- }
- return m_nativeLibraryDir;
- }
-}
diff --git a/src/android/jar/src/org/qtproject/qt/android/QtRootLayout.java b/src/android/jar/src/org/qtproject/qt/android/QtRootLayout.java
new file mode 100644
index 0000000000..3dae587a71
--- /dev/null
+++ b/src/android/jar/src/org/qtproject/qt/android/QtRootLayout.java
@@ -0,0 +1,98 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// Copyright (C) 2012 BogDan Vatra <bogdan@kde.org>
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+package org.qtproject.qt.android;
+
+import android.app.Activity;
+import android.content.Context;
+import android.os.Build;
+import android.util.DisplayMetrics;
+import android.view.Display;
+import android.content.res.Configuration;
+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
+{
+ private int m_activityDisplayRotation = -1;
+ private int m_ownDisplayRotation = -1;
+ private int m_nativeOrientation = -1;
+ private int m_previousRotation = -1;
+
+ public QtRootLayout(Context context)
+ {
+ 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)
+ {
+ Activity activity = (Activity)getContext();
+ 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);
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration configuration)
+ {
+ Context context = getContext();
+ if (context instanceof Activity) {
+ Activity activity = (Activity)context;
+ //if orientation change is betwen invertedPortrait and portrait or
+ //invertedLandscape and landscape, we do not get sizeChanged callback.
+ int rotation = QtDisplayManager.getDisplayRotation(activity);
+ if (isSameSizeForOrientations(rotation, m_previousRotation))
+ QtDisplayManager.handleOrientationChanges(activity);
+ m_previousRotation = rotation;
+ }
+ }
+
+ public boolean isSameSizeForOrientations(int r1, int r2) {
+ return (r1 == r2) ||
+ (r1 == Surface.ROTATION_0 && r2 == Surface.ROTATION_180)
+ || (r1 == Surface.ROTATION_180 && r2 == Surface.ROTATION_0)
+ || (r1 == Surface.ROTATION_90 && r2 == Surface.ROTATION_270)
+ || (r1 == Surface.ROTATION_270 && r2 == Surface.ROTATION_90);
+ }
+}
diff --git a/src/android/jar/src/org/qtproject/qt/android/QtServiceBase.java b/src/android/jar/src/org/qtproject/qt/android/QtServiceBase.java
new file mode 100644
index 0000000000..b69dea4416
--- /dev/null
+++ b/src/android/jar/src/org/qtproject/qt/android/QtServiceBase.java
@@ -0,0 +1,51 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+package org.qtproject.qt.android;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+import android.util.Log;
+
+public class QtServiceBase extends Service {
+ @Override
+ public void onCreate()
+ {
+ super.onCreate();
+
+ // the application has already started, do not reload everything again
+ if (QtNative.getStateDetails().isStarted) {
+ Log.w(QtNative.QtTAG,
+ "A QtService tried to start in the same process as an initiated " +
+ "QtActivity. That is not supported. This results in the service " +
+ "functioning as an Android Service detached from Qt.");
+ return;
+ }
+
+ QtNative.setService(this);
+
+ QtServiceLoader loader = new QtServiceLoader(this);
+ loader.loadQtLibraries();
+ QtNative.startApplication(loader.getApplicationParameters(), loader.getMainLibraryPath());
+ QtNative.setApplicationState(QtNative.ApplicationState.ApplicationHidden);
+ }
+
+ @Override
+ public void onDestroy()
+ {
+ super.onDestroy();
+ QtNative.quitQtCoreApplication();
+ QtNative.terminateQt();
+ QtNative.setService(null);
+ QtNative.getQtThread().exit();
+ System.exit(0);
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ synchronized (this) {
+ return QtNative.onBind(intent);
+ }
+ }
+}
diff --git a/src/android/jar/src/org/qtproject/qt/android/QtServiceDelegate.java b/src/android/jar/src/org/qtproject/qt/android/QtServiceDelegate.java
deleted file mode 100644
index e0195b2bd2..0000000000
--- a/src/android/jar/src/org/qtproject/qt/android/QtServiceDelegate.java
+++ /dev/null
@@ -1,206 +0,0 @@
-/****************************************************************************
-**
-** Copyright (C) 2016 BogDan Vatra <bogdan@kde.org>
-** Copyright (C) 2016 The Qt Company Ltd.
-** Contact: https://www.qt.io/licensing/
-**
-** This file is part of the Android port of the Qt Toolkit.
-**
-** $QT_BEGIN_LICENSE:LGPL$
-** Commercial License Usage
-** Licensees holding valid commercial Qt licenses may use this file in
-** accordance with the commercial license agreement provided with the
-** Software or, alternatively, in accordance with the terms contained in
-** a written agreement between you and The Qt Company. For licensing terms
-** and conditions see https://www.qt.io/terms-conditions. For further
-** information use the contact form at https://www.qt.io/contact-us.
-**
-** GNU Lesser General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU Lesser
-** General Public License version 3 as published by the Free Software
-** Foundation and appearing in the file LICENSE.LGPL3 included in the
-** packaging of this file. Please review the following information to
-** ensure the GNU Lesser General Public License version 3 requirements
-** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
-**
-** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 2.0 or (at your option) the GNU General
-** Public license version 3 or any later version approved by the KDE Free
-** Qt Foundation. The licenses are as published by the Free Software
-** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
-** included in the packaging of this file. Please review the following
-** information to ensure the GNU General Public License requirements will
-** be met: https://www.gnu.org/licenses/gpl-2.0.html and
-** https://www.gnu.org/licenses/gpl-3.0.html.
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
-
-package org.qtproject.qt.android;
-
-import android.app.Service;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ActivityInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.res.Configuration;
-import android.graphics.drawable.ColorDrawable;
-import android.net.LocalServerSocket;
-import android.net.LocalSocket;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.ResultReceiver;
-import android.text.method.MetaKeyKeyListener;
-import android.util.Base64;
-import android.util.DisplayMetrics;
-import android.util.Log;
-import android.util.TypedValue;
-import android.view.ContextMenu;
-import android.view.ContextMenu.ContextMenuInfo;
-import android.view.KeyCharacterMap;
-import android.view.KeyEvent;
-import android.view.Menu;
-import android.view.MenuItem;
-import android.view.Surface;
-import android.view.View;
-import android.view.ViewConfiguration;
-import android.view.ViewGroup;
-import android.view.WindowManager;
-import android.view.inputmethod.InputMethodManager;
-
-import java.io.BufferedReader;
-import java.io.DataOutputStream;
-import java.io.File;
-import java.io.FileWriter;
-import java.io.InputStreamReader;
-import java.io.IOException;
-import java.lang.reflect.Constructor;
-import java.lang.reflect.Method;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.Objects;
-
-public class QtServiceDelegate
-{
- private static final String NATIVE_LIBRARIES_KEY = "native.libraries";
- private static final String BUNDLED_LIBRARIES_KEY = "bundled.libraries";
- private static final String MAIN_LIBRARY_KEY = "main.library";
- private static final String ENVIRONMENT_VARIABLES_KEY = "environment.variables";
- private static final String APPLICATION_PARAMETERS_KEY = "application.parameters";
- private static final String STATIC_INIT_CLASSES_KEY = "static.init.classes";
- private static final String APP_DISPLAY_METRIC_SCREEN_DESKTOP_KEY = "display.screen.desktop";
- private static final String APP_DISPLAY_METRIC_SCREEN_XDPI_KEY = "display.screen.dpi.x";
- private static final String APP_DISPLAY_METRIC_SCREEN_YDPI_KEY = "display.screen.dpi.y";
- private static final String APP_DISPLAY_METRIC_SCREEN_DENSITY_KEY = "display.screen.density";
-
- private String m_mainLib = null;
- private Service m_service = null;
- private static String m_environmentVariables = null;
- private static String m_applicationParameters = null;
-
- public boolean loadApplication(Service service, ClassLoader classLoader, Bundle loaderParams)
- {
- /// check parameters integrity
- if (!loaderParams.containsKey(NATIVE_LIBRARIES_KEY)
- || !loaderParams.containsKey(BUNDLED_LIBRARIES_KEY)) {
- return false;
- }
-
- m_service = service;
- QtNative.setService(m_service, this);
- QtNative.setClassLoader(classLoader);
-
- QtNative.setApplicationDisplayMetrics(10, 10, 0, 0, 10, 10, 120, 120, 1.0, 1.0, 60.0f);
-
- if (loaderParams.containsKey(STATIC_INIT_CLASSES_KEY)) {
- for (String className :
- Objects.requireNonNull(loaderParams.getStringArray(STATIC_INIT_CLASSES_KEY))) {
- if (className.length() == 0)
- continue;
- try {
- Class<?> initClass = classLoader.loadClass(className);
- Object staticInitDataObject = initClass.newInstance(); // create an instance
- try {
- Method m = initClass.getMethod("setService", Service.class, Object.class);
- m.invoke(staticInitDataObject, m_service, this);
- } catch (Exception e) {
- Log.d(QtNative.QtTAG,
- "Class " + className + " does not implement setService method");
- }
-
- // For modules that don't need/have setService
- try {
- Method m = initClass.getMethod("setContext", Context.class);
- m.invoke(staticInitDataObject, (Context)m_service);
- } catch (Exception e) {
- e.printStackTrace();
- }
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- }
- QtNative.loadQtLibraries(loaderParams.getStringArrayList(NATIVE_LIBRARIES_KEY));
- ArrayList<String> libraries = loaderParams.getStringArrayList(BUNDLED_LIBRARIES_KEY);
- String nativeLibsDir = QtNativeLibrariesDir.nativeLibrariesDir(m_service);
- QtNative.loadBundledLibraries(libraries, nativeLibsDir);
- m_mainLib = loaderParams.getString(MAIN_LIBRARY_KEY);
- m_environmentVariables = loaderParams.getString(ENVIRONMENT_VARIABLES_KEY);
- String additionalEnvironmentVariables = "QT_ANDROID_FONTS_MONOSPACE=Droid Sans Mono;Droid Sans;Droid Sans Fallback"
- + "\tQT_ANDROID_FONTS_SERIF=Droid Serif"
- + "\tHOME=" + m_service.getFilesDir().getAbsolutePath()
- + "\tTMPDIR=" + m_service.getFilesDir().getAbsolutePath();
- if (Build.VERSION.SDK_INT < 14)
- additionalEnvironmentVariables += "\tQT_ANDROID_FONTS=Droid Sans;Droid Sans Fallback";
- else
- additionalEnvironmentVariables += "\tQT_ANDROID_FONTS=Roboto;Droid Sans;Droid Sans Fallback";
-
- if (m_environmentVariables != null && m_environmentVariables.length() > 0)
- m_environmentVariables = additionalEnvironmentVariables + "\t" + m_environmentVariables;
- else
- m_environmentVariables = additionalEnvironmentVariables;
-
- if (loaderParams.containsKey(APPLICATION_PARAMETERS_KEY))
- m_applicationParameters = loaderParams.getString(APPLICATION_PARAMETERS_KEY);
- else
- m_applicationParameters = "";
-
- m_mainLib = QtNative.loadMainLibrary(m_mainLib, nativeLibsDir);
- return m_mainLib != null;
- }
-
- public boolean startApplication()
- {
- // start application
- try {
- String nativeLibraryDir = QtNativeLibrariesDir.nativeLibrariesDir(m_service);
- QtNative.startApplication(m_applicationParameters, m_environmentVariables, m_mainLib);
- return true;
- } catch (Exception e) {
- e.printStackTrace();
- return false;
- }
- }
-
- public void onDestroy()
- {
- QtNative.quitQtCoreApplication();
- QtNative.terminateQt();
- QtNative.setService(null, null);
- QtNative.m_qtThread.exit();
- System.exit(0);
- }
-
- public IBinder onBind(Intent intent)
- {
- synchronized (this) {
- return QtNative.onBind(intent);
- }
- }
-}
diff --git a/src/android/jar/src/org/qtproject/qt/android/QtServiceLoader.java b/src/android/jar/src/org/qtproject/qt/android/QtServiceLoader.java
new file mode 100644
index 0000000000..ce74ec21e7
--- /dev/null
+++ b/src/android/jar/src/org/qtproject/qt/android/QtServiceLoader.java
@@ -0,0 +1,28 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// Copyright (c) 2016, BogDan Vatra <bogdan@kde.org>
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+package org.qtproject.qt.android;
+
+import android.app.Service;
+import android.content.ContextWrapper;
+import android.util.Log;
+
+class QtServiceLoader extends QtLoader {
+ private final Service m_service;
+
+ public QtServiceLoader(Service service) {
+ super(new ContextWrapper(service));
+ m_service = service;
+
+ extractContextMetaData();
+ }
+
+ @Override
+ protected void finish() {
+ if (m_service != null)
+ m_service.stopSelf();
+ else
+ Log.w(QtTAG, "finish() called when service object is null");
+ }
+}
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 bd52f34abd..3165de4811 100644
--- a/src/android/jar/src/org/qtproject/qt/android/QtSurface.java
+++ b/src/android/jar/src/org/qtproject/qt/android/QtSurface.java
@@ -1,81 +1,33 @@
-/****************************************************************************
-**
-** Copyright (C) 2014 BogDan Vatra <bogdan@kde.org>
-** Copyright (C) 2016 The Qt Company Ltd.
-** Contact: https://www.qt.io/licensing/
-**
-** This file is part of the Android port of the Qt Toolkit.
-**
-** $QT_BEGIN_LICENSE:LGPL$
-** Commercial License Usage
-** Licensees holding valid commercial Qt licenses may use this file in
-** accordance with the commercial license agreement provided with the
-** Software or, alternatively, in accordance with the terms contained in
-** a written agreement between you and The Qt Company. For licensing terms
-** and conditions see https://www.qt.io/terms-conditions. For further
-** information use the contact form at https://www.qt.io/contact-us.
-**
-** GNU Lesser General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU Lesser
-** General Public License version 3 as published by the Free Software
-** Foundation and appearing in the file LICENSE.LGPL3 included in the
-** packaging of this file. Please review the following information to
-** ensure the GNU Lesser General Public License version 3 requirements
-** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
-**
-** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 2.0 or (at your option) the GNU General
-** Public license version 3 or any later version approved by the KDE Free
-** Qt Foundation. The licenses are as published by the Free Software
-** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
-** included in the packaging of this file. Please review the following
-** information to ensure the GNU General Public License requirements will
-** be met: https://www.gnu.org/licenses/gpl-2.0.html and
-** https://www.gnu.org/licenses/gpl-3.0.html.
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
+// Copyright (C) 2014 BogDan Vatra <bogdan@kde.org>
+// Copyright (C) 2016 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.annotation.SuppressLint;
import android.content.Context;
import android.graphics.PixelFormat;
-import android.view.GestureDetector;
-import android.view.MotionEvent;
+import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
-import java.lang.reflect.Constructor;
-import java.lang.reflect.Method;
-
-public class QtSurface extends SurfaceView implements SurfaceHolder.Callback
+@SuppressLint("ViewConstructor")
+class QtSurface extends SurfaceView implements SurfaceHolder.Callback
{
- private GestureDetector m_gestureDetector;
- private Object m_accessibilityDelegate = null;
+ private QtSurfaceInterface m_surfaceCallback;
- public QtSurface(Context context, int id, boolean onTop, int imageDepth)
+ public QtSurface(Context context, QtSurfaceInterface surfaceCallback, boolean onTop, int imageDepth)
{
super(context);
setFocusable(false);
setFocusableInTouchMode(false);
setZOrderMediaOverlay(onTop);
+ m_surfaceCallback = surfaceCallback;
getHolder().addCallback(this);
if (imageDepth == 16)
getHolder().setFormat(PixelFormat.RGB_565);
else
getHolder().setFormat(PixelFormat.RGBA_8888);
-
- setId(id);
- m_gestureDetector =
- new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() {
- public void onLongPress(MotionEvent event) {
- QtNative.longPress(getId(), (int) event.getX(), (int) event.getY());
- }
- });
- m_gestureDetector.setIsLongpressEnabled(true);
}
@Override
@@ -88,39 +40,14 @@ public class QtSurface extends SurfaceView implements SurfaceHolder.Callback
{
if (width < 1 || height < 1)
return;
-
- QtNative.setSurface(getId(), holder.getSurface(), width, height);
+ if (m_surfaceCallback != null)
+ m_surfaceCallback.onSurfaceChanged(holder.getSurface());
}
@Override
public void surfaceDestroyed(SurfaceHolder holder)
{
- QtNative.setSurface(getId(), null, 0, 0);
- }
-
- @Override
- public boolean onTouchEvent(MotionEvent event)
- {
- // QTBUG-65927
- // Fix event positions depending on Surface position.
- // In case when Surface is moved, we should also add this move to event position
- event.setLocation(event.getX() + getX(), event.getY() + getY());
-
- QtNative.sendTouchEvent(event, getId());
- m_gestureDetector.onTouchEvent(event);
- return true;
- }
-
- @Override
- public boolean onTrackballEvent(MotionEvent event)
- {
- QtNative.sendTrackballEvent(event, getId());
- return true;
- }
-
- @Override
- public boolean onGenericMotionEvent(MotionEvent event)
- {
- return QtNative.sendGenericMotionEvent(event, getId());
+ if (m_surfaceCallback != null)
+ m_surfaceCallback.onSurfaceChanged(null);
}
}
diff --git a/src/android/jar/src/org/qtproject/qt/android/QtSurfaceInterface.java b/src/android/jar/src/org/qtproject/qt/android/QtSurfaceInterface.java
new file mode 100644
index 0000000000..8df442f730
--- /dev/null
+++ b/src/android/jar/src/org/qtproject/qt/android/QtSurfaceInterface.java
@@ -0,0 +1,13 @@
+// Copyright (C) 2014 BogDan Vatra <bogdan@kde.org>
+// Copyright (C) 2016 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.view.Surface;
+
+
+public 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
new file mode 100644
index 0000000000..828838a9f0
--- /dev/null
+++ b/src/android/jar/src/org/qtproject/qt/android/QtTextureView.java
@@ -0,0 +1,52 @@
+// Copyright (C) 2014 BogDan Vatra <bogdan@kde.org>
+// Copyright (C) 2016 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.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
+{
+ private QtSurfaceInterface m_surfaceCallback;
+ private boolean m_staysOnTop;
+ private Surface m_surface;
+
+ public QtTextureView(Context context, QtSurfaceInterface surfaceCallback, boolean isOpaque)
+ {
+ super(context);
+ setFocusable(false);
+ setFocusableInTouchMode(false);
+ m_surfaceCallback = surfaceCallback;
+ setSurfaceTextureListener(this);
+ setOpaque(isOpaque);
+ setSurfaceTexture(new SurfaceTexture(false));
+ }
+
+ @Override
+ public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width, int height) {
+ m_surface = new Surface(surfaceTexture);
+ m_surfaceCallback.onSurfaceChanged(m_surface);
+ }
+
+ @Override
+ public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int width, int height) {
+ m_surface = new Surface(surfaceTexture);
+ m_surfaceCallback.onSurfaceChanged(m_surface);
+ }
+
+ @Override
+ public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) {
+ m_surfaceCallback.onSurfaceChanged(null);
+ return true;
+ }
+
+ @Override
+ public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) {
+ }
+}
diff --git a/src/android/jar/src/org/qtproject/qt/android/QtThread.java b/src/android/jar/src/org/qtproject/qt/android/QtThread.java
index 1689174e86..0943ad3265 100644
--- a/src/android/jar/src/org/qtproject/qt/android/QtThread.java
+++ b/src/android/jar/src/org/qtproject/qt/android/QtThread.java
@@ -1,51 +1,15 @@
-/****************************************************************************
-**
-** Copyright (C) 2018 BogDan Vatra <bogdan@kdab.com>
-** Contact: https://www.qt.io/licensing/
-**
-** This file is part of the Android port of the Qt Toolkit.
-**
-** $QT_BEGIN_LICENSE:LGPL$
-** Commercial License Usage
-** Licensees holding valid commercial Qt licenses may use this file in
-** accordance with the commercial license agreement provided with the
-** Software or, alternatively, in accordance with the terms contained in
-** a written agreement between you and The Qt Company. For licensing terms
-** and conditions see https://www.qt.io/terms-conditions. For further
-** information use the contact form at https://www.qt.io/contact-us.
-**
-** GNU Lesser General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU Lesser
-** General Public License version 3 as published by the Free Software
-** Foundation and appearing in the file LICENSE.LGPL3 included in the
-** packaging of this file. Please review the following information to
-** ensure the GNU Lesser General Public License version 3 requirements
-** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
-**
-** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 2.0 or (at your option) the GNU General
-** Public license version 3 or any later version approved by the KDE Free
-** Qt Foundation. The licenses are as published by the Free Software
-** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
-** included in the packaging of this file. Please review the following
-** information to ensure the GNU General Public License requirements will
-** be met: https://www.gnu.org/licenses/gpl-2.0.html and
-** https://www.gnu.org/licenses/gpl-3.0.html.
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
+// Copyright (C) 2018 BogDan Vatra <bogdan@kdab.com>
+// 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 java.util.ArrayList;
import java.util.concurrent.Semaphore;
-public class QtThread {
- private ArrayList<Runnable> m_pendingRunnables = new ArrayList<Runnable>();
+class QtThread {
+ private final ArrayList<Runnable> m_pendingRunnables = new ArrayList<>();
private boolean m_exit = false;
- private Thread m_qtThread = new Thread(new Runnable() {
+ private final Thread m_qtThread = new Thread(new Runnable() {
@Override
public void run() {
while (!m_exit) {
@@ -54,7 +18,7 @@ public class QtThread {
synchronized (m_qtThread) {
if (m_pendingRunnables.size() == 0)
m_qtThread.wait();
- pendingRunnables = new ArrayList<Runnable>(m_pendingRunnables);
+ pendingRunnables = new ArrayList<>(m_pendingRunnables);
m_pendingRunnables.clear();
}
for (Runnable runnable : pendingRunnables)
@@ -78,15 +42,20 @@ public class QtThread {
}
}
+ public void sleep(int milliseconds) {
+ try {
+ m_qtThread.sleep(milliseconds);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+
public void run(final Runnable runnable) {
final Semaphore sem = new Semaphore(0);
synchronized (m_qtThread) {
- m_pendingRunnables.add(new Runnable() {
- @Override
- public void run() {
- runnable.run();
- sem.release();
- }
+ m_pendingRunnables.add(() -> {
+ runnable.run();
+ sem.release();
});
m_qtThread.notify();
}
diff --git a/src/android/jar/src/org/qtproject/qt/android/QtView.java b/src/android/jar/src/org/qtproject/qt/android/QtView.java
new file mode 100644
index 0000000000..6836171187
--- /dev/null
+++ b/src/android/jar/src/org/qtproject/qt/android/QtView.java
@@ -0,0 +1,187 @@
+// 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.content.Context;
+import android.content.ContextWrapper;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.res.Resources;
+import android.os.Handler;
+import android.os.Looper;
+import android.view.View;
+import android.view.ViewGroup;
+
+import java.security.InvalidParameterException;
+import java.util.ArrayList;
+
+// Base class for embedding QWindow into native Android view hierarchy. Extend to implement
+// the creation of appropriate window to embed.
+abstract class QtView extends ViewGroup {
+ private final static String TAG = "QtView";
+
+ public interface QtWindowListener {
+ // Called when the QWindow has been created and it's Java counterpart embedded into
+ // QtView
+ void onQtWindowLoaded();
+ }
+
+ protected QtWindow m_window;
+ protected long m_windowReference;
+ protected QtWindowListener m_windowListener;
+ protected QtEmbeddedDelegate m_delegate;
+ // 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);
+
+ 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 {
+ 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);
+ addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
+ @Override
+ public void onLayoutChange(View v, int left, int top, int right, int bottom,
+ int oldLeft, int oldTop, int oldRight, int oldBottom) {
+ if (m_windowReference != 0L) {
+ final int oldWidth = oldRight - oldLeft;
+ final int oldHeight = oldBottom - oldTop;
+ final int newWidth = right - left;
+ final int newHeight = bottom - top;
+ if (oldWidth != newWidth || oldHeight != newHeight || left != oldLeft ||
+ top != oldTop) {
+ resizeWindow(m_windowReference, left, top, newWidth, newHeight);
+ }
+ }
+ }
+ });
+ loader.loadQtLibraries();
+ // Start Native Qt application
+ m_delegate.startNativeApplication(loader.getApplicationParameters(),
+ loader.getMainLibraryPath());
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ m_delegate.setView(this);
+ m_delegate.queueLoadWindow();
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ destroyWindow();
+ m_delegate.setView(null);
+ }
+
+ @Override
+ public void onLayout(boolean changed, int l, int t, int r, int b) {
+ if (m_window != null)
+ m_window.layout(0 /* left */, 0 /* top */, r - l /* right */, b - t /* bottom */);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
+ {
+ measureChildren(widthMeasureSpec, heightMeasureSpec);
+
+ final int count = getChildCount();
+
+ int maxHeight = 0;
+ int maxWidth = 0;
+
+ // Find out how big everyone wants to be
+ measureChildren(widthMeasureSpec, heightMeasureSpec);
+
+ // Find rightmost and bottom-most child
+ for (int i = 0; i < count; i++) {
+ View child = getChildAt(i);
+ if (child.getVisibility() != GONE) {
+ maxWidth = Math.max(maxWidth, child.getMeasuredWidth());
+ maxHeight = Math.max(maxHeight, child.getMeasuredHeight());
+ }
+ }
+
+ // Check against minimum height and width
+ maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
+ maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
+
+ setMeasuredDimension(resolveSize(maxWidth, widthMeasureSpec),
+ resolveSize(maxHeight, heightMeasureSpec));
+ }
+
+
+ public void setQtWindowListener(QtWindowListener listener) {
+ m_windowListener = listener;
+ }
+
+ void setWindowReference(long windowReference) {
+ m_windowReference = windowReference;
+ }
+
+ long windowReference() {
+ return m_windowReference;
+ }
+
+ // Set the visibility of the underlying QWindow. If visible is true, showNormal() is called.
+ // If false, the window is hidden.
+ void setWindowVisible(boolean visible) {
+ if (m_windowReference != 0L)
+ setWindowVisible(m_windowReference, true);
+ }
+
+ // Called from Qt when the QWindow has been created.
+ // window - the Java QtWindow of the created QAndroidPlatformWindow, to embed into the QtView
+ // viewReference - the reference to the created QQuickView
+ void addQtWindow(QtWindow window, long viewReference, long parentWindowRef) {
+ setWindowReference(viewReference);
+ m_delegate.setRootWindowRef(parentWindowRef);
+ final Handler handler = new Handler(Looper.getMainLooper());
+ handler.post(new Runnable() {
+ @Override
+ public void run() {
+ m_window = window;
+ m_window.setLayoutParams(new ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT));
+ addView(m_window, 0);
+ // Call show window + parent
+ setWindowVisible(true);
+ if (m_windowListener != null)
+ m_windowListener.onQtWindowLoaded();
+ }
+ });
+ }
+
+ // Destroy the underlying QWindow
+ void destroyWindow() {
+ if (m_windowReference != 0L)
+ QtEmbeddedDelegate.deleteWindow(m_windowReference);
+ m_windowReference = 0L;
+ }
+
+ QtWindow getQtWindow() {
+ return m_window;
+ }
+}
diff --git a/src/android/jar/src/org/qtproject/qt/android/QtWindow.java b/src/android/jar/src/org/qtproject/qt/android/QtWindow.java
new file mode 100644
index 0000000000..d72e69d32a
--- /dev/null
+++ b/src/android/jar/src/org/qtproject/qt/android/QtWindow.java
@@ -0,0 +1,215 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+package org.qtproject.qt.android;
+
+import android.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;
+
+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 QtWindow m_parentWindow;
+ private GestureDetector m_gestureDetector;
+ private final QtEditText m_editText;
+
+ 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)
+ {
+ super(context);
+ setId(View.generateViewId());
+ m_editText = new QtEditText(context, delegate);
+ setParent(parentWindow);
+ setFocusableInTouchMode(true);
+ addView(m_editText, new QtLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT));
+
+ QtNative.runAction(() -> {
+ m_gestureDetector =
+ new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() {
+ public void onLongPress(MotionEvent event) {
+ QtInputDelegate.longPress(getId(), (int) event.getX(), (int) event.getY());
+ }
+ });
+ m_gestureDetector.setIsLongpressEnabled(true);
+ });
+ }
+
+ void setVisible(boolean visible) {
+ QtNative.runAction(() -> {
+ if (visible)
+ setVisibility(View.VISIBLE);
+ else
+ setVisibility(View.INVISIBLE);
+ });
+ }
+
+ @Override
+ public void onSurfaceChanged(Surface surface)
+ {
+ setSurface(getId(), surface);
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event)
+ {
+ m_editText.requestFocus();
+ event.setLocation(event.getX() + getX(), event.getY() + getY());
+ QtInputDelegate.sendTouchEvent(event, getId());
+ m_gestureDetector.onTouchEvent(event);
+ return true;
+ }
+
+ @Override
+ public boolean onTrackballEvent(MotionEvent event)
+ {
+ QtInputDelegate.sendTrackballEvent(event, getId());
+ return true;
+ }
+
+ @Override
+ public boolean onGenericMotionEvent(MotionEvent event)
+ {
+ return QtInputDelegate.sendGenericMotionEvent(event, getId());
+ }
+
+ public void removeWindow()
+ {
+ if (m_parentWindow != null)
+ m_parentWindow.removeChildWindow(getId());
+ }
+
+ public void createSurface(final boolean onTop,
+ final int x, final int y, final int w, final int h,
+ final int imageDepth, final boolean isOpaque,
+ final int surfaceContainerType) // TODO constant for type
+ {
+ QtNative.runAction(()-> {
+ if (m_surfaceContainer != null)
+ removeView(m_surfaceContainer);
+
+ setLayoutParams(new QtLayout.LayoutParams(w, h, x, y));
+ if (surfaceContainerType == 0) {
+ m_surfaceContainer = new QtSurface(getContext(), QtWindow.this,
+ onTop, imageDepth);
+ } else {
+ m_surfaceContainer = new QtTextureView(getContext(), QtWindow.this, isOpaque);
+ }
+ m_surfaceContainer.setLayoutParams(new QtLayout.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT));
+ // The surface container of this window will be added as the first of the stack.
+ // All other views are stacked based on the order they are created.
+ addView(m_surfaceContainer, 0);
+ });
+ }
+
+ public void destroySurface()
+ {
+ QtNative.runAction(()-> {
+ if (m_surfaceContainer != null) {
+ removeView(m_surfaceContainer);
+ m_surfaceContainer = null;
+ }
+ }, false);
+ }
+
+ public void setGeometry(final int x, final int y, final int w, final int h)
+ {
+ QtNative.runAction(()-> {
+ if (getContext() instanceof QtActivityBase)
+ setLayoutParams(new QtLayout.LayoutParams(w, h, x, y));
+ });
+ }
+
+ public void addChildWindow(QtWindow window)
+ {
+ QtNative.runAction(()-> {
+ m_childWindows.put(window.getId(), window);
+ addView(window, getChildCount());
+ });
+ }
+
+ public void removeChildWindow(int id)
+ {
+ QtNative.runAction(()-> {
+ if (m_childWindows.containsKey(id))
+ removeView(m_childWindows.remove(id));
+ });
+ }
+
+ public void setNativeView(final View view,
+ final int x, final int y, final int w, final int h)
+ {
+ QtNative.runAction(()-> {
+ if (m_nativeView != null)
+ removeView(m_nativeView);
+
+ m_nativeView = view;
+ QtWindow.this.setLayoutParams(new QtLayout.LayoutParams(w, h, x, y));
+ m_nativeView.setLayoutParams(new QtLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT));
+ addView(m_nativeView);
+ });
+ }
+
+ public void bringChildToFront(int id)
+ {
+ QtNative.runAction(()-> {
+ View view = m_childWindows.get(id);
+ if (view != null) {
+ if (getChildCount() > 0)
+ moveChild(view, getChildCount() - 1);
+ }
+ });
+ }
+
+ public void bringChildToBack(int id) {
+ QtNative.runAction(()-> {
+ View view = m_childWindows.get(id);
+ if (view != null) {
+ moveChild(view, 0);
+ }
+ });
+ }
+
+ public void removeNativeView()
+ {
+ QtNative.runAction(()-> {
+ if (m_nativeView != null) {
+ removeView(m_nativeView);
+ m_nativeView = null;
+ }
+ });
+ }
+
+ void setParent(QtWindow parentWindow)
+ {
+ if (m_parentWindow == parentWindow)
+ return;
+
+ if (m_parentWindow != null)
+ m_parentWindow.removeChildWindow(getId());
+
+ m_parentWindow = parentWindow;
+ if (m_parentWindow != null)
+ m_parentWindow.addChildWindow(this);
+ }
+
+ QtWindow parent()
+ {
+ return m_parentWindow;
+ }
+}
diff --git a/src/android/jar/src/org/qtproject/qt/android/UsedFromNativeCode.java b/src/android/jar/src/org/qtproject/qt/android/UsedFromNativeCode.java
new file mode 100644
index 0000000000..e0223c083f
--- /dev/null
+++ b/src/android/jar/src/org/qtproject/qt/android/UsedFromNativeCode.java
@@ -0,0 +1,10 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+package org.qtproject.qt.android;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+@Retention(RetentionPolicy.CLASS)
+public @interface UsedFromNativeCode { }
diff --git a/src/android/jar/src/org/qtproject/qt/android/accessibility/QtNativeAccessibility.java b/src/android/jar/src/org/qtproject/qt/android/accessibility/QtNativeAccessibility.java
deleted file mode 100644
index aa6f7c5a67..0000000000
--- a/src/android/jar/src/org/qtproject/qt/android/accessibility/QtNativeAccessibility.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/****************************************************************************
-**
-** Copyright (C) 2016 The Qt Company Ltd.
-** Contact: https://www.qt.io/licensing/
-**
-** This file is part of the Android port of the Qt Toolkit.
-**
-** $QT_BEGIN_LICENSE:LGPL$
-** Commercial License Usage
-** Licensees holding valid commercial Qt licenses may use this file in
-** accordance with the commercial license agreement provided with the
-** Software or, alternatively, in accordance with the terms contained in
-** a written agreement between you and The Qt Company. For licensing terms
-** and conditions see https://www.qt.io/terms-conditions. For further
-** information use the contact form at https://www.qt.io/contact-us.
-**
-** GNU Lesser General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU Lesser
-** General Public License version 3 as published by the Free Software
-** Foundation and appearing in the file LICENSE.LGPL3 included in the
-** packaging of this file. Please review the following information to
-** ensure the GNU Lesser General Public License version 3 requirements
-** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
-**
-** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 2.0 or (at your option) the GNU General
-** Public license version 3 or any later version approved by the KDE Free
-** Qt Foundation. The licenses are as published by the Free Software
-** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
-** included in the packaging of this file. Please review the following
-** information to ensure the GNU General Public License requirements will
-** be met: https://www.gnu.org/licenses/gpl-2.0.html and
-** https://www.gnu.org/licenses/gpl-3.0.html.
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
-
-package org.qtproject.qt.android.accessibility;
-
-import android.graphics.Rect;
-import android.view.accessibility.AccessibilityNodeInfo;
-
-class QtNativeAccessibility
-{
- static native void setActive(boolean enable);
- static native int[] childIdListForAccessibleObject(int objectId);
- static native int parentId(int objectId);
- static native String descriptionForAccessibleObject(int objectId);
- static native Rect screenRect(int objectId);
- static native int hitTest(float x, float y);
- static native boolean clickAction(int objectId);
- static native boolean scrollForward(int objectId);
- static native boolean scrollBackward(int objectId);
-
- static native boolean populateNode(int objectId, AccessibilityNodeInfo node);
-}
diff --git a/src/android/jar/src/org/qtproject/qt/android/extras/QtAndroidBinder.java b/src/android/jar/src/org/qtproject/qt/android/extras/QtAndroidBinder.java
index 4e349552fe..bd837570fe 100644
--- a/src/android/jar/src/org/qtproject/qt/android/extras/QtAndroidBinder.java
+++ b/src/android/jar/src/org/qtproject/qt/android/extras/QtAndroidBinder.java
@@ -1,49 +1,16 @@
-/****************************************************************************
-**
-** Copyright (C) 2017 BogDan Vatra <bogdan@kde.org>
-** Contact: https://www.qt.io/licensing/
-**
-** This file is part of the Android port of the Qt Toolkit.
-**
-** $QT_BEGIN_LICENSE:LGPL$
-** Commercial License Usage
-** Licensees holding valid commercial Qt licenses may use this file in
-** accordance with the commercial license agreement provided with the
-** Software or, alternatively, in accordance with the terms contained in
-** a written agreement between you and The Qt Company. For licensing terms
-** and conditions see https://www.qt.io/terms-conditions. For further
-** information use the contact form at https://www.qt.io/contact-us.
-**
-** GNU Lesser General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU Lesser
-** General Public License version 3 as published by the Free Software
-** Foundation and appearing in the file LICENSE.LGPL3 included in the
-** packaging of this file. Please review the following information to
-** ensure the GNU Lesser General Public License version 3 requirements
-** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
-**
-** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 2.0 or (at your option) the GNU General
-** Public license version 3 or any later version approved by the KDE Free
-** Qt Foundation. The licenses are as published by the Free Software
-** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
-** included in the packaging of this file. Please review the following
-** information to ensure the GNU General Public License requirements will
-** be met: https://www.gnu.org/licenses/gpl-2.0.html and
-** https://www.gnu.org/licenses/gpl-3.0.html.
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
+// Copyright (C) 2017 BogDan Vatra <bogdan@kde.org>
+// 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.extras;
import android.os.Binder;
import android.os.Parcel;
-public class QtAndroidBinder extends Binder
+import org.qtproject.qt.android.UsedFromNativeCode;
+
+class QtAndroidBinder extends Binder
{
+ @UsedFromNativeCode
public QtAndroidBinder(long id)
{
m_id = id;
diff --git a/src/android/jar/src/org/qtproject/qt/android/extras/QtAndroidServiceConnection.java b/src/android/jar/src/org/qtproject/qt/android/extras/QtAndroidServiceConnection.java
index bab4d3f4ce..b70b64e3ac 100644
--- a/src/android/jar/src/org/qtproject/qt/android/extras/QtAndroidServiceConnection.java
+++ b/src/android/jar/src/org/qtproject/qt/android/extras/QtAndroidServiceConnection.java
@@ -1,41 +1,5 @@
-/****************************************************************************
-**
-** Copyright (C) 2017 BogDan Vatra <bogdan@kde.org>
-** Contact: https://www.qt.io/licensing/
-**
-** This file is part of the Android port of the Qt Toolkit.
-**
-** $QT_BEGIN_LICENSE:LGPL$
-** Commercial License Usage
-** Licensees holding valid commercial Qt licenses may use this file in
-** accordance with the commercial license agreement provided with the
-** Software or, alternatively, in accordance with the terms contained in
-** a written agreement between you and The Qt Company. For licensing terms
-** and conditions see https://www.qt.io/terms-conditions. For further
-** information use the contact form at https://www.qt.io/contact-us.
-**
-** GNU Lesser General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU Lesser
-** General Public License version 3 as published by the Free Software
-** Foundation and appearing in the file LICENSE.LGPL3 included in the
-** packaging of this file. Please review the following information to
-** ensure the GNU Lesser General Public License version 3 requirements
-** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
-**
-** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 2.0 or (at your option) the GNU General
-** Public license version 3 or any later version approved by the KDE Free
-** Qt Foundation. The licenses are as published by the Free Software
-** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
-** included in the packaging of this file. Please review the following
-** information to ensure the GNU General Public License requirements will
-** be met: https://www.gnu.org/licenses/gpl-2.0.html and
-** https://www.gnu.org/licenses/gpl-3.0.html.
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
+// Copyright (C) 2017 BogDan Vatra <bogdan@kde.org>
+// 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.extras;
@@ -43,8 +7,11 @@ import android.content.ComponentName;
import android.content.ServiceConnection;
import android.os.IBinder;
-public class QtAndroidServiceConnection implements ServiceConnection
+import org.qtproject.qt.android.UsedFromNativeCode;
+
+class QtAndroidServiceConnection implements ServiceConnection
{
+ @UsedFromNativeCode
public QtAndroidServiceConnection(long id)
{
m_id = id;
diff --git a/src/android/jar/src/org/qtproject/qt/android/extras/QtNative.java b/src/android/jar/src/org/qtproject/qt/android/extras/QtNative.java
index 86a9151500..f7ba8dd9b4 100644
--- a/src/android/jar/src/org/qtproject/qt/android/extras/QtNative.java
+++ b/src/android/jar/src/org/qtproject/qt/android/extras/QtNative.java
@@ -1,50 +1,12 @@
-/****************************************************************************
-**
-** Copyright (C) 2017 BogDan Vatra <bogdan@kde.org>
-** Contact: https://www.qt.io/licensing/
-**
-** This file is part of the Android port of the Qt Toolkit.
-**
-** $QT_BEGIN_LICENSE:LGPL$
-** Commercial License Usage
-** Licensees holding valid commercial Qt licenses may use this file in
-** accordance with the commercial license agreement provided with the
-** Software or, alternatively, in accordance with the terms contained in
-** a written agreement between you and The Qt Company. For licensing terms
-** and conditions see https://www.qt.io/terms-conditions. For further
-** information use the contact form at https://www.qt.io/contact-us.
-**
-** GNU Lesser General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU Lesser
-** General Public License version 3 as published by the Free Software
-** Foundation and appearing in the file LICENSE.LGPL3 included in the
-** packaging of this file. Please review the following information to
-** ensure the GNU Lesser General Public License version 3 requirements
-** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
-**
-** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 2.0 or (at your option) the GNU General
-** Public license version 3 or any later version approved by the KDE Free
-** Qt Foundation. The licenses are as published by the Free Software
-** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
-** included in the packaging of this file. Please review the following
-** information to ensure the GNU General Public License requirements will
-** be met: https://www.gnu.org/licenses/gpl-2.0.html and
-** https://www.gnu.org/licenses/gpl-3.0.html.
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
+// Copyright (C) 2017 BogDan Vatra <bogdan@kde.org>
+// 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.extras;
-import android.content.ComponentName;
-import android.content.ServiceConnection;
import android.os.IBinder;
import android.os.Parcel;
-public class QtNative {
+class QtNative {
// Binder
public static native boolean onTransact(long id, int code, Parcel data, Parcel reply, int flags);