From ee05af296f65e240f64db6470cdddafbddbf2fa3 Mon Sep 17 00:00:00 2001 From: Assam Boudjelthia Date: Tue, 4 May 2021 18:51:06 +0300 Subject: [PATCH] Move Android Extras as private APIs under qtbase To the option to users to use some needed APIs until we make them ready as proper cross-platform public APIs. Change-Id: I53006397463331ebae8314bf8a3a019474aec617 Reviewed-by: Ville Voutilainen --- .../qt/android/extras/QtAndroidBinder.java | 69 ++ .../extras/QtAndroidServiceConnection.java | 78 ++ .../qtproject/qt/android/extras/QtNative.java | 55 + src/corelib/CMakeLists.txt | 1 + .../platform/android/qandroidextras.cpp | 1030 +++++++++++++++++ .../platform/android/qandroidextras_p.h | 262 +++++ 6 files changed, 1495 insertions(+) create mode 100644 src/android/jar/src/org/qtproject/qt/android/extras/QtAndroidBinder.java create mode 100644 src/android/jar/src/org/qtproject/qt/android/extras/QtAndroidServiceConnection.java create mode 100644 src/android/jar/src/org/qtproject/qt/android/extras/QtNative.java create mode 100644 src/corelib/platform/android/qandroidextras.cpp create mode 100644 src/corelib/platform/android/qandroidextras_p.h 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 new file mode 100644 index 00000000000..4e349552feb --- /dev/null +++ b/src/android/jar/src/org/qtproject/qt/android/extras/QtAndroidBinder.java @@ -0,0 +1,69 @@ +/**************************************************************************** +** +** Copyright (C) 2017 BogDan Vatra +** 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.extras; + +import android.os.Binder; +import android.os.Parcel; + +public class QtAndroidBinder extends Binder +{ + public QtAndroidBinder(long id) + { + m_id = id; + } + + public void setId(long id) + { + synchronized(this) + { + m_id = id; + } + } + @Override + protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) + { + synchronized(this) + { + return QtNative.onTransact(m_id, code, data, reply, flags); + } + } + + private long m_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 new file mode 100644 index 00000000000..bab4d3f4ce9 --- /dev/null +++ b/src/android/jar/src/org/qtproject/qt/android/extras/QtAndroidServiceConnection.java @@ -0,0 +1,78 @@ +/**************************************************************************** +** +** Copyright (C) 2017 BogDan Vatra +** 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.extras; + +import android.content.ComponentName; +import android.content.ServiceConnection; +import android.os.IBinder; + +public class QtAndroidServiceConnection implements ServiceConnection +{ + public QtAndroidServiceConnection(long id) + { + m_id = id; + } + + public void setId(long id) + { + synchronized(this) + { + m_id = id; + } + } + + @Override + public void onServiceConnected(ComponentName name, IBinder service) + { + synchronized(this) { + QtNative.onServiceConnected(m_id, name.flattenToString(), service); + } + } + + @Override + public void onServiceDisconnected(ComponentName name) + { + synchronized(this) { + QtNative.onServiceDisconnected(m_id, name.flattenToString()); + } + } + + private long m_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 new file mode 100644 index 00000000000..86a91515001 --- /dev/null +++ b/src/android/jar/src/org/qtproject/qt/android/extras/QtNative.java @@ -0,0 +1,55 @@ +/**************************************************************************** +** +** Copyright (C) 2017 BogDan Vatra +** 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.extras; + +import android.content.ComponentName; +import android.content.ServiceConnection; +import android.os.IBinder; +import android.os.Parcel; + +public class QtNative { + // Binder + public static native boolean onTransact(long id, int code, Parcel data, Parcel reply, int flags); + + + // ServiceConnection + public static native void onServiceConnected(long id, String name, IBinder service); + public static native void onServiceDisconnected(long id, String name); +} diff --git a/src/corelib/CMakeLists.txt b/src/corelib/CMakeLists.txt index 06acea216b3..242b12b305a 100644 --- a/src/corelib/CMakeLists.txt +++ b/src/corelib/CMakeLists.txt @@ -983,6 +983,7 @@ qt_internal_extend_target(Core CONDITION ANDROID AND NOT ANDROID_EMBEDDED kernel/qjnienvironment.cpp kernel/qjnienvironment.h kernel/qjniobject.cpp kernel/qjniobject.h kernel/qjnihelpers.cpp kernel/qjnihelpers_p.h + platform/android/qandroidextras_p.h platform/android/qandroidextras.cpp platform/android/qandroidnativeinterface.cpp ) diff --git a/src/corelib/platform/android/qandroidextras.cpp b/src/corelib/platform/android/qandroidextras.cpp new file mode 100644 index 00000000000..8d1b41bf80d --- /dev/null +++ b/src/corelib/platform/android/qandroidextras.cpp @@ -0,0 +1,1030 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtCore module 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$ +** +****************************************************************************/ + +#include "qandroidextras_p.h" + +#include +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QAndroidParcelPrivate +{ +public: + QAndroidParcelPrivate(); + explicit QAndroidParcelPrivate(const QJniObject& parcel); + + void writeData(const QByteArray &data) const; + void writeBinder(const QAndroidBinder &binder) const; + void writeFileDescriptor(int fd) const; + + QByteArray readData() const; + int readFileDescriptor() const; + QAndroidBinder readBinder() const; + +private: + friend class QAndroidBinder; + friend class QAndroidParcel; + QJniObject handle; +}; + +struct FileDescriptor +{ + explicit FileDescriptor(int fd = -1) + : handle("java/io/FileDescriptor") + { + QJniEnvironment().checkAndClearExceptions(); + handle.setField("descriptor", fd); + } + + QJniObject handle; +}; + +QAndroidParcelPrivate::QAndroidParcelPrivate() + : handle(QJniObject::callStaticObjectMethod("android/os/Parcel","obtain", + "()Landroid/os/Parcel;").object()) +{} + +QAndroidParcelPrivate::QAndroidParcelPrivate(const QJniObject &parcel) + : handle(parcel) +{} + +void QAndroidParcelPrivate::writeData(const QByteArray &data) const +{ + if (data.isEmpty()) + return; + + QJniEnvironment().checkAndClearExceptions(); + QJniEnvironment env; + jbyteArray array = env->NewByteArray(data.size()); + env->SetByteArrayRegion(array, 0, data.length(), + reinterpret_cast(data.constData())); + handle.callMethod("writeByteArray", "([B)V", array); + env->DeleteLocalRef(array); +} + +void QAndroidParcelPrivate::writeBinder(const QAndroidBinder &binder) const +{ + QJniEnvironment().checkAndClearExceptions(); + handle.callMethod("writeStrongBinder", "(Landroid/os/IBinder;)V", + binder.handle().object()); +} + +void QAndroidParcelPrivate::writeFileDescriptor(int fd) const +{ + QJniEnvironment().checkAndClearExceptions(); + handle.callMethod("writeFileDescriptor", "(Ljava/io/FileDescriptor;)V", + FileDescriptor(fd).handle.object()); +} + +QByteArray QAndroidParcelPrivate::readData() const +{ + QJniEnvironment().checkAndClearExceptions(); + auto array = handle.callObjectMethod("createByteArray", "()[B"); + QJniEnvironment env; + auto sz = env->GetArrayLength(jbyteArray(array.object())); + QByteArray res(sz, Qt::Initialization::Uninitialized); + env->GetByteArrayRegion(jbyteArray(array.object()), 0, sz, + reinterpret_cast(res.data())); + return res; +} + +int QAndroidParcelPrivate::readFileDescriptor() const +{ + QJniEnvironment().checkAndClearExceptions(); + auto parcelFD = handle.callObjectMethod("readFileDescriptor", + "()Landroid/os/ParcelFileDescriptor;"); + if (parcelFD.isValid()) + return parcelFD.callMethod("getFd", "()I"); + return -1; +} + +QAndroidBinder QAndroidParcelPrivate::readBinder() const +{ + QJniEnvironment().checkAndClearExceptions(); + auto strongBinder = handle.callObjectMethod("readStrongBinder", "()Landroid/os/IBinder;"); + return QAndroidBinder(strongBinder.object()); +} + +/*! + \class QAndroidParcel + \inmodule QtCore + \brief Wraps the most important methods of Android Parcel class. + + The QAndroidParcel is a convenience class that wraps the most important + \l {https://developer.android.com/reference/android/os/Parcel.html}{Android Parcel} + methods. + + \since 6.2 +*/ + +/*! + Creates a new object. + */ +QAndroidParcel::QAndroidParcel() + : d(new QAndroidParcelPrivate()) +{ +} + +/*! + Wraps the \a parcel object. + */ +QAndroidParcel::QAndroidParcel(const QJniObject& parcel) + : d(new QAndroidParcelPrivate(parcel)) +{ + +} + +QAndroidParcel::~QAndroidParcel() +{ +} + +/*! + Writes the provided \a data as a byte array + */ +void QAndroidParcel::writeData(const QByteArray &data) const +{ + d->writeData(data); +} + +/*! + Writes the provided \a value. The value is converted into a + QByteArray before is written. + */ +void QAndroidParcel::writeVariant(const QVariant &value) const +{ + QByteArray buff; + QDataStream stream(&buff, QIODevice::WriteOnly); + stream << value; + d->writeData(buff); +} + +/*! + Writes a \a binder object. This is useful for a client to + send to a server a binder which can be used by the server callback the client. + */ +void QAndroidParcel::writeBinder(const QAndroidBinder &binder) const +{ + d->writeBinder(binder); +} + +/*! + Writes the provided \a fd. + */ +void QAndroidParcel::writeFileDescriptor(int fd) const +{ + d->writeFileDescriptor(fd); +} + +/*! + Returns the data as a QByteArray + */ +QByteArray QAndroidParcel::readData() const +{ + return d->readData(); +} + +/*! + Returns the data as a QVariant + */ +QVariant QAndroidParcel::readVariant() const +{ + QDataStream stream(d->readData()); + QVariant res; + stream >> res; + return res; +} + +/*! + Returns the binder as a QAndroidBinder + */ +QAndroidBinder QAndroidParcel::readBinder() const +{ + return d->readBinder(); +} + +/*! + Returns the file descriptor + */ +int QAndroidParcel::readFileDescriptor() const +{ + return d->readFileDescriptor(); +} + +/*! + The return value is useful to call other Java API which are not covered by this wrapper + */ +QJniObject QAndroidParcel::handle() const +{ + return d->handle; +} + + + +/*! + \class QAndroidBinder + \inmodule QtCore + \brief Wraps the most important methods of Android Binder class. + + The QAndroidBinder is a convenience class that wraps the most important + \l {https://developer.android.com/reference/android/os/Binder.html}{Android Binder} + methods. + + \since 6.2 +*/ + + +/*! + \enum QAndroidBinder::CallType + + This enum is used with \l QAndroidBinder::transact() to describe the mode in which the + IPC call is performed. + + \value Normal normal IPC, meaning that the caller waits the result from the callee + \value OneWay one-way IPC, meaning that the caller returns immediately, without waiting + for a result from the callee +*/ + + +class QAndroidBinderPrivate +{ +public: + explicit QAndroidBinderPrivate(QAndroidBinder *binder) + : handle("org/qtproject/qt/android/extras/QtAndroidBinder", "(J)V", jlong(binder)) + , m_isQtAndroidBinder(true) + { + QJniEnvironment().checkAndClearExceptions(); + } + + explicit QAndroidBinderPrivate(const QJniObject &binder) + : handle(binder), m_isQtAndroidBinder(false) {}; + void setDeleteListener(const std::function &func) { m_deleteListener = func; } + ~QAndroidBinderPrivate() + { + if (m_isQtAndroidBinder) { + QJniEnvironment().checkAndClearExceptions(); + handle.callMethod("setId", "(J)V", jlong(0)); + if (m_deleteListener) + m_deleteListener(); + } + } + +private: + QJniObject handle; + std::function m_deleteListener; + bool m_isQtAndroidBinder; + friend class QAndroidBinder; +}; + +/*! + Creates a new object which can be used to perform IPC. + + \sa onTransact, transact + */ +QAndroidBinder::QAndroidBinder() + : d(new QAndroidBinderPrivate(this)) +{ +} + +/*! + Creates a new object from the \a binder Java object. + + \sa transact + */ +QAndroidBinder::QAndroidBinder(const QJniObject &binder) + : d(new QAndroidBinderPrivate(binder)) +{ +} + +QAndroidBinder::~QAndroidBinder() +{ +} + +/*! + Default implementation is a stub that returns false. + The user should override this method to get the transact data from the caller. + + The \a code is the action to perform. + The \a data is the marshaled data sent by the caller.\br + The \a reply is the marshaled data to be sent to the caller.\br + The \a flags are the additional operation flags.\br + + \warning This method is called from Binder's thread which is different + from the thread that this object was created. + + \sa transact + */ +bool QAndroidBinder::onTransact(int /*code*/, const QAndroidParcel &/*data*/, + const QAndroidParcel &/*reply*/, CallType /*flags*/) +{ + return false; +} + +/*! + Performs an IPC call + + The \a code is the action to perform. Should be between + \l {https://developer.android.com/reference/android/os/IBinder.html#FIRST_CALL_TRANSACTION} + {FIRST_CALL_TRANSACTION} and + \l {https://developer.android.com/reference/android/os/IBinder.html#LAST_CALL_TRANSACTION} + {LAST_CALL_TRANSACTION}.\br + The \a data is the marshaled data to send to the target.\br + The \a reply (if specified) is the marshaled data to be received from the target. + May be \b nullptr if you are not interested in the return value.\br + The \a flags are the additional operation flags.\br + + \return true on success + */ +bool QAndroidBinder::transact(int code, const QAndroidParcel &data, + QAndroidParcel *reply, CallType flags) const +{ + QJniEnvironment().checkAndClearExceptions(); + return d->handle.callMethod("transact", + "(ILandroid/os/Parcel;Landroid/os/Parcel;I)Z", + jint(code), data.d->handle.object(), + reply ? reply->d->handle.object() : nullptr, + jint(flags)); +} + +/*! + The return value is useful to call other Java API which are not covered by this wrapper + */ +QJniObject QAndroidBinder::handle() const +{ + return d->handle; +} + + + + +/*! + \class QAndroidServiceConnection + \inmodule QtCore + \brief Wraps the most important methods of Android ServiceConnection class. + + The QAndroidServiceConnection is a convenience abstract class which wraps the + \l {https://developer.android.com/reference/android/content/ServiceConnection.html}{AndroidServiceConnection} + interface. + + It is useful when you perform a QtAndroidPrivate::bindService operation. + + \since 6.2 +*/ + +/*! + Creates a new object + */ +QAndroidServiceConnection::QAndroidServiceConnection() + : m_handle("org/qtproject/qt/android/extras/QtAndroidServiceConnection", "(J)V", jlong(this)) +{ +} + +/*! + Creates a new object from an existing \a serviceConnection. + + It's useful when you have your own Java implementation. + Of course onServiceConnected()/onServiceDisconnected() + will not be called anymore. + */ +QAndroidServiceConnection::QAndroidServiceConnection(const QJniObject &serviceConnection) + : m_handle(serviceConnection) +{ +} + +QAndroidServiceConnection::~QAndroidServiceConnection() +{ + m_handle.callMethod("setId", "(J)V", jlong(this)); +} + +/*! + returns the underline QJniObject + */ +QJniObject QAndroidServiceConnection::handle() const +{ + return m_handle; +} + +/*! + \fn void QAndroidServiceConnection::onServiceConnected(const QString &name, const QAndroidBinder &serviceBinder) + + This notification is called when the client managed to connect to the service. + The \a name contains the server name, the \a serviceBinder is the binder that the client + uses to perform IPC operations. + + \warning This method is called from Binder's thread which is different + from the thread that this object was created. + + returns the underline QJniObject + */ + +/*! + \fn void QAndroidServiceConnection::onServiceDisconnected(const QString &name) + + Called when a connection to the Service has been lost. + The \a name parameter specifies which connectioen was lost. + + \warning This method is called from Binder's thread which is different + from the thread that this object was created. + + returns the underline QJniObject + */ + + + +// Get a unique activity request code. +static int uniqueActivityRequestCode() +{ + static QMutex mutex; + static int requestCode = 0x1000; // Reserve all request codes under 0x1000 for Qt. + + QMutexLocker locker(&mutex); + if (requestCode == 0xf3ee) // Special case for MINISTRO_INSTALL_REQUEST_CODE + requestCode++; + + if (requestCode == INT_MAX) + qWarning("Unique activity request code has wrapped. Unexpected behavior may occur."); + + return requestCode++; +} + +class QAndroidActivityResultReceiverPrivate: public QtAndroidPrivate::ActivityResultListener +{ +public: + QAndroidActivityResultReceiver *q; + mutable QHash localToGlobalRequestCode; + mutable QHash globalToLocalRequestCode; + + int globalRequestCode(int localRequestCode) const + { + if (!localToGlobalRequestCode.contains(localRequestCode)) { + int globalRequestCode = uniqueActivityRequestCode(); + localToGlobalRequestCode[localRequestCode] = globalRequestCode; + globalToLocalRequestCode[globalRequestCode] = localRequestCode; + } + return localToGlobalRequestCode.value(localRequestCode); + } + + bool handleActivityResult(jint requestCode, jint resultCode, jobject data) + { + if (globalToLocalRequestCode.contains(requestCode)) { + q->handleActivityResult(globalToLocalRequestCode.value(requestCode), + resultCode, QJniObject(data)); + return true; + } + + return false; + } + + static QAndroidActivityResultReceiverPrivate *get(QAndroidActivityResultReceiver *publicObject) + { + return publicObject->d.data(); + } +}; + +/*! + \class QAndroidActivityResultReceiver + \inmodule QtCore + \since 6.2 + \brief Interface used for callbacks from onActivityResult() in the main Android activity. + + Create a subclass of this class to be notified of the results when using the + \c QtAndroidPrivate::startActivity() and \c QtAndroidPrivate::startIntentSender() APIs. + */ + +/*! + \internal +*/ +QAndroidActivityResultReceiver::QAndroidActivityResultReceiver() + : d(new QAndroidActivityResultReceiverPrivate) +{ + d->q = this; + QtAndroidPrivate::registerActivityResultListener(d.data()); +} + +/*! + \internal +*/ +QAndroidActivityResultReceiver::~QAndroidActivityResultReceiver() +{ + QtAndroidPrivate::unregisterActivityResultListener(d.data()); +} + +/*! + \fn void QAndroidActivityResultReceiver::handleActivityResult(int receiverRequestCode, int resultCode, const QJniObject &data) + + Reimplement this function to get activity results after starting an activity using + either QtAndroidPrivate::startActivity() or QtAndroidPrivate::startIntentSender(). + The \a receiverRequestCode is the request code unique to this receiver which was + originally passed to the startActivity() or startIntentSender() functions. The + \a resultCode is the result returned by the activity, and \a data is either null + or a Java object of the class android.content.Intent. Both the last to arguments + are identical to the arguments passed to onActivityResult(). +*/ + + + +class QAndroidServicePrivate : public QObject, public QtAndroidPrivate::OnBindListener +{ +public: + QAndroidServicePrivate(QAndroidService *service, + const std::function &binder ={}) + : m_service(service) + , m_binder(binder) + { + QTimer::singleShot(0,this, [this]{ QtAndroidPrivate::setOnBindListener(this);}); + } + + ~QAndroidServicePrivate() + { + QMutexLocker lock(&m_bindersMutex); + while (!m_binders.empty()) { + auto it = m_binders.begin(); + lock.unlock(); + delete (*it); + lock.relock(); + } + } + + // OnBindListener interface + jobject onBind(jobject intent) override + { + auto qai = QAndroidIntent(intent); + auto binder = m_binder ? m_binder(qai) : m_service->onBind(qai); + if (binder) { + { + QMutexLocker lock(&m_bindersMutex); + binder->d->setDeleteListener([this, binder]{binderDestroied(binder);}); + m_binders.insert(binder); + } + return binder->handle().object(); + } + return nullptr; + } + +private: + void binderDestroied(QAndroidBinder* obj) + { + QMutexLocker lock(&m_bindersMutex); + m_binders.remove(obj); + } + +public: + QAndroidService *m_service = nullptr; + std::function m_binder; + QMutex m_bindersMutex; + QSet m_binders; +}; + +/*! + \class QAndroidService + \inmodule QtCore + \brief Wraps the most important methods of Android Service class. + + The QAndroidService is a convenience class that wraps the most important + \l {https://developer.android.com/reference/android/app/Service.html}{Android Service} + methods. + + \since 6.2 +*/ + + +/*! + \fn QAndroidService::QAndroidService(int &argc, char **argv) + + Creates a new Android service, passing \a argc and \a argv as parameters. + + //! Parameter \a flags is omitted in the documentation. + + \sa QCoreApplication + */ +QAndroidService::QAndroidService(int &argc, char **argv, int flags) + : QCoreApplication (argc, argv, QtAndroidPrivate::acuqireServiceSetup(flags)) + , d(new QAndroidServicePrivate{this}) +{ +} + +/*! + \fn QAndroidService::QAndroidService(int &argc, char **argv, const std::function &binder) + + Creates a new Android service, passing \a argc and \a argv as parameters. + + \a binder is used to create a \l {QAndroidBinder}{binder} when needed. + + //! Parameter \a flags is omitted in the documentation. + + \sa QCoreApplication + */ +QAndroidService::QAndroidService(int &argc, char **argv, + const std::function &binder, + int flags) + : QCoreApplication (argc, argv, QtAndroidPrivate::acuqireServiceSetup(flags)) + , d(new QAndroidServicePrivate{this, binder}) +{ +} + +QAndroidService::~QAndroidService() +{} + +/*! + The user must override this method and to return a binder. + + The \a intent parameter contains all the caller information. + + The returned binder is used by the caller to perform IPC calls. + + \warning This method is called from Binder's thread which is different + from the thread that this object was created. + + \sa QAndroidBinder::onTransact, QAndroidBinder::transact + */ +QAndroidBinder* QAndroidService::onBind(const QAndroidIntent &/*intent*/) +{ + return nullptr; +} + + +/*! + \class QAndroidIntent + \inmodule QtCore + \brief Wraps the most important methods of Android Intent class. + + The QAndroidIntent is a convenience class that wraps the most important + \l {https://developer.android.com/reference/android/content/Intent.html}{Android Intent} + methods. + + \since 6.2 +*/ + +/*! + Create a new intent + */ +QAndroidIntent::QAndroidIntent() + : m_handle("android.content.Intent", "()V") +{ + +} + +QAndroidIntent::~QAndroidIntent() +{} + +/*! + Wraps the provided \a intent java object. + */ +QAndroidIntent::QAndroidIntent(const QJniObject &intent) + : m_handle(intent) +{ +} + +/*! + Creates a new intent and sets the provided \a action. + */ +QAndroidIntent::QAndroidIntent(const QString &action) + : m_handle("android.content.Intent", "(Ljava/lang/String;)V", + QJniObject::fromString(action).object()) +{ + QJniEnvironment().checkAndClearExceptions(); +} + +/*! + Creates a new intent and sets the provided \a packageContext and the service \a className. + Example: + \code + auto serviceIntent = QAndroidIntent(QtAndroidPrivate::androidActivity().object(), "com.example.MyService"); + \endcode + + \sa QtAndroidPrivate::androidActivity, QtAndroidPrivate::bindService + */ +QAndroidIntent::QAndroidIntent(const QJniObject &packageContext, const char *className) + : m_handle("android/content/Intent", "(Landroid/content/Context;Ljava/lang/Class;)V", + packageContext.object(), QJniEnvironment().findClass(className)) +{ + QJniEnvironment().checkAndClearExceptions(); +} + +/*! + Sets the \a key with the \a data in the Intent extras + */ +void QAndroidIntent::putExtra(const QString &key, const QByteArray &data) +{ + QJniEnvironment().checkAndClearExceptions(); + QJniEnvironment env; + jbyteArray array = env->NewByteArray(data.size()); + env->SetByteArrayRegion(array, 0, data.length(), + reinterpret_cast(data.constData())); + m_handle.callObjectMethod("putExtra", "(Ljava/lang/String;[B)Landroid/content/Intent;", + QJniObject::fromString(key).object(), array); + env->DeleteLocalRef(array); + QJniEnvironment().checkAndClearExceptions(); +} + +/*! + Returns the extra \a key data from the Intent extras + */ +QByteArray QAndroidIntent::extraBytes(const QString &key) +{ + QJniEnvironment().checkAndClearExceptions(); + auto array = m_handle.callObjectMethod("getByteArrayExtra", "(Ljava/lang/String;)[B", + QJniObject::fromString(key).object()); + if (!array.isValid() || !array.object()) + return QByteArray(); + QJniEnvironment env; + auto sz = env->GetArrayLength(jarray(array.object())); + QByteArray res(sz, Qt::Initialization::Uninitialized); + env->GetByteArrayRegion(jbyteArray(array.object()), 0, sz, + reinterpret_cast(res.data())); + QJniEnvironment().checkAndClearExceptions(); + + return res; +} + +/*! + Sets the \a key with the \a value in the Intent extras. + */ +void QAndroidIntent::putExtra(const QString &key, const QVariant &value) +{ + QByteArray buff; + QDataStream stream(&buff, QIODevice::WriteOnly); + stream << value; + putExtra(key, buff); +} + +/*! + Returns the extra \a key data from the Intent extras as a QVariant + */ +QVariant QAndroidIntent::extraVariant(const QString &key) +{ + QDataStream stream(extraBytes(key)); + QVariant res; + stream >> res; + return res; +} + +/*! + The return value is useful to call other Java API which are not covered by this wrapper + */ +QJniObject QAndroidIntent::handle() const +{ + return m_handle; +} + + + +/*! + \namespace QtAndroid + \inmodule QtCore + \since 6.2 + \brief The QtAndroid namespace provides miscellaneous functions to aid Android development. + \inheaderfile QtAndroid +*/ + +/*! + \since 6.2 + \enum QtAndroidPrivate::BindFlag + + This enum is used with QtAndroidPrivate::bindService to describe the mode in which the + binding is performed. + + \value None No options. + \value AutoCreate Automatically creates the service as long as the binding exist. + See \l {https://developer.android.com/reference/android/content/Context.html#BIND_AUTO_CREATE} + {BIND_AUTO_CREATE} documentation for more details. + \value DebugUnbind Include debugging help for mismatched calls to unbind. + See \l {https://developer.android.com/reference/android/content/Context.html#BIND_DEBUG_UNBIND} + {BIND_DEBUG_UNBIND} documentation for more details. + \value NotForeground Don't allow this binding to raise the target service's process to the foreground scheduling priority. + See \l {https://developer.android.com/reference/android/content/Context.html#BIND_NOT_FOREGROUND} + {BIND_NOT_FOREGROUND} documentation for more details. + \value AboveClient Indicates that the client application binding to this service considers the service to be more important than the app itself. + See \l {https://developer.android.com/reference/android/content/Context.html#BIND_ABOVE_CLIENT} + {BIND_ABOVE_CLIENT} documentation for more details. + \value AllowOomManagement Allow the process hosting the bound service to go through its normal memory management. + See \l {https://developer.android.com/reference/android/content/Context.html#BIND_ALLOW_OOM_MANAGEMENT} + {BIND_ALLOW_OOM_MANAGEMENT} documentation for more details. + \value WaivePriority Don't impact the scheduling or memory management priority of the target service's hosting process. + See \l {https://developer.android.com/reference/android/content/Context.html#BIND_WAIVE_PRIORITY} + {BIND_WAIVE_PRIORITY} documentation for more details. + \value Important This service is assigned a higher priority so that it is available to the client when needed. + See \l {https://developer.android.com/reference/android/content/Context.html#BIND_IMPORTANT} + {BIND_IMPORTANT} documentation for more details. + \value AdjustWithActivity If binding from an activity, allow the target service's process importance to be raised based on whether the activity is visible to the user. + See \l {https://developer.android.com/reference/android/content/Context.html#BIND_ADJUST_WITH_ACTIVITY} + {BIND_ADJUST_WITH_ACTIVITY} documentation for more details. + \value ExternalService The service being bound is an isolated, external service. + See \l {https://developer.android.com/reference/android/content/Context.html#BIND_EXTERNAL_SERVICE} + {BIND_EXTERNAL_SERVICE} documentation for more details. +*/ + +/*! + \since 6.2 + + Starts the activity given by \a intent and provides the result asynchronously through the + \a resultReceiver if this is non-null. + + If \a resultReceiver is null, then the \c startActivity() method in the \c androidActivity() + will be called. Otherwise \c startActivityForResult() will be called. + + The \a receiverRequestCode is a request code unique to the \a resultReceiver, and will be + returned along with the result, making it possible to use the same receiver for more than + one intent. + + */ +void QtAndroidPrivate::startActivity(const QJniObject &intent, + int receiverRequestCode, + QAndroidActivityResultReceiver *resultReceiver) +{ + QJniObject activity = QtAndroidPrivate::activity(); + if (resultReceiver != 0) { + QAndroidActivityResultReceiverPrivate *resultReceiverD = + QAndroidActivityResultReceiverPrivate::get(resultReceiver); + activity.callMethod("startActivityForResult", + "(Landroid/content/Intent;I)V", + intent.object(), + resultReceiverD->globalRequestCode(receiverRequestCode)); + } else { + activity.callMethod("startActivity", + "(Landroid/content/Intent;)V", + intent.object()); + } +} + +/*! + \since 6.2 + + Starts the activity given by \a intent and provides the result asynchronously through the + \a resultReceiver if this is non-null. + + If \a resultReceiver is null, then the \c startActivity() method in the \c androidActivity() + will be called. Otherwise \c startActivityForResult() will be called. + + The \a receiverRequestCode is a request code unique to the \a resultReceiver, and will be + returned along with the result, making it possible to use the same receiver for more than + one intent. + + */ +void QtAndroidPrivate::startActivity(const QAndroidIntent &intent, + int receiverRequestCode, + QAndroidActivityResultReceiver *resultReceiver) +{ + startActivity(intent.handle(), receiverRequestCode, resultReceiver); +} + +/*! + \since 6.2 + + Starts the activity given by \a intent, using the request code \a receiverRequestCode, + and provides the result by calling \a callbackFunc. +*/ +void QtAndroidPrivate::startActivity(const QJniObject &intent, + int receiverRequestCode, + std::function callbackFunc) +{ + QJniObject activity = QtAndroidPrivate::activity(); + QAndroidActivityCallbackResultReceiver::instance()->registerCallback(receiverRequestCode, + callbackFunc); + startActivity(intent, receiverRequestCode, QAndroidActivityCallbackResultReceiver::instance()); +} + +/*! + \since 6.2 + + Starts the activity given by \a intentSender and provides the result asynchronously through the + \a resultReceiver if this is non-null. + + If \a resultReceiver is null, then the \c startIntentSender() method in the \c androidActivity() + will be called. Otherwise \c startIntentSenderForResult() will be called. + + The \a receiverRequestCode is a request code unique to the \a resultReceiver, and will be + returned along with the result, making it possible to use the same receiver for more than + one intent. + +*/ +void QtAndroidPrivate::startIntentSender(const QJniObject &intentSender, + int receiverRequestCode, + QAndroidActivityResultReceiver *resultReceiver) +{ + QJniObject activity = QtAndroidPrivate::activity(); + if (resultReceiver != 0) { + QAndroidActivityResultReceiverPrivate *resultReceiverD = + QAndroidActivityResultReceiverPrivate::get(resultReceiver); + activity.callMethod("startIntentSenderForResult", + "(Landroid/content/IntentSender;ILandroid/content/Intent;III)V", + intentSender.object(), + resultReceiverD->globalRequestCode(receiverRequestCode), + 0, // fillInIntent + 0, // flagsMask + 0, // flagsValues + 0); // extraFlags + } else { + activity.callMethod("startIntentSender", + "(Landroid/content/IntentSender;Landroid/content/Intent;III)V", + intentSender.object(), + 0, // fillInIntent + 0, // flagsMask + 0, // flagsValues + 0); // extraFlags + + } + +} + +/*! + \since 6.2 + \fn bool QtAndroidPrivate::bindService(const QAndroidIntent &serviceIntent, const QAndroidServiceConnection &serviceConnection, BindFlags flags = BindFlag::None) + + Binds the service given by \a serviceIntent, \a serviceConnection and \a flags. + The \a serviceIntent object identifies the service to connect to. + The \a serviceConnection is a listener that receives the information as the service + is started and stopped. + + \return true on success + + See \l {https://developer.android.com/reference/android/content/Context.html#bindService%28android.content.Intent,%20android.content.ServiceConnection,%20int%29} + {Android documentation} documentation for more details. + + \sa QAndroidIntent, QAndroidServiceConnection, BindFlag +*/ +bool QtAndroidPrivate::bindService(const QAndroidIntent &serviceIntent, + const QAndroidServiceConnection &serviceConnection, BindFlags flags) +{ + QJniEnvironment().checkAndClearExceptions(); + QJniObject contextObj = QtAndroidPrivate::context(); + return contextObj.callMethod( + "bindService", + "(Landroid/content/Intent;Landroid/content/ServiceConnection;I)Z", + serviceIntent.handle().object(), + serviceConnection.handle().object(), + jint(flags)); +} + +QAndroidActivityCallbackResultReceiver * QAndroidActivityCallbackResultReceiver::s_instance = nullptr; + +QAndroidActivityCallbackResultReceiver::QAndroidActivityCallbackResultReceiver() + : QAndroidActivityResultReceiver() + , callbackMap() +{ +} + +void QAndroidActivityCallbackResultReceiver::handleActivityResult(int receiverRequestCode, + int resultCode, + const QJniObject &intent) +{ + callbackMap[receiverRequestCode](receiverRequestCode, resultCode, intent); + callbackMap.remove(receiverRequestCode); +} + +QAndroidActivityCallbackResultReceiver * QAndroidActivityCallbackResultReceiver::instance() { + if (!s_instance) { + s_instance = new QAndroidActivityCallbackResultReceiver(); + } + return s_instance; +} + +void QAndroidActivityCallbackResultReceiver::registerCallback( + int receiverRequestCode, + std::function callbackFunc) +{ + callbackMap.insert(receiverRequestCode, callbackFunc); +} + +QT_END_NAMESPACE diff --git a/src/corelib/platform/android/qandroidextras_p.h b/src/corelib/platform/android/qandroidextras_p.h new file mode 100644 index 00000000000..464c7f8aea6 --- /dev/null +++ b/src/corelib/platform/android/qandroidextras_p.h @@ -0,0 +1,262 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtCore module 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$ +** +****************************************************************************/ + +#ifndef QANDROIDEXTRAS_H +#define QANDROIDEXTRAS_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include + +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QAndroidParcel; +class QAndroidBinderPrivate; +class QAndroidBinder; + +class Q_CORE_EXPORT QAndroidBinder +{ +public: + enum class CallType { + Normal = 0, + OneWay = 1 + }; + +public: + explicit QAndroidBinder(); + QAndroidBinder(const QJniObject &binder); + + virtual ~QAndroidBinder(); + + virtual bool onTransact(int code, const QAndroidParcel &data, + const QAndroidParcel &reply, CallType flags); + bool transact(int code, const QAndroidParcel &data, + QAndroidParcel *reply = nullptr, CallType flags = CallType::Normal) const; + + QJniObject handle() const; + +private: + friend class QAndroidBinderPrivate; + friend class QAndroidParcelPrivate; + friend class QAndroidServicePrivate; + QSharedPointer d; +}; + +class QAndroidParcelPrivate; + +class Q_CORE_EXPORT QAndroidParcel +{ +public: + QAndroidParcel(); + explicit QAndroidParcel(const QJniObject& parcel); + virtual ~QAndroidParcel(); + + void writeData(const QByteArray &data) const; + void writeVariant(const QVariant &value) const; + void writeBinder(const QAndroidBinder &binder) const; + void writeFileDescriptor(int fd) const; + + QByteArray readData() const; + QVariant readVariant() const; + QAndroidBinder readBinder() const; + int readFileDescriptor() const; + + QJniObject handle() const; + +private: + friend class QAndroidParcelPrivate; + friend class QAndroidBinder; + QSharedPointer d; +}; + +class QAndroidActivityResultReceiverPrivate; + +class Q_CORE_EXPORT QAndroidActivityResultReceiver +{ +public: + QAndroidActivityResultReceiver(); + virtual ~QAndroidActivityResultReceiver(); + virtual void handleActivityResult(int receiverRequestCode, int resultCode, + const QJniObject &data) = 0; + +private: + friend class QAndroidActivityResultReceiverPrivate; + Q_DISABLE_COPY(QAndroidActivityResultReceiver) + + QScopedPointer d; +}; + +class Q_CORE_EXPORT QAndroidServiceConnection +{ +public: + QAndroidServiceConnection(); + explicit QAndroidServiceConnection(const QJniObject &serviceConnection); + virtual ~QAndroidServiceConnection(); + + virtual void onServiceConnected(const QString &name, + const QAndroidBinder &serviceBinder) = 0; + virtual void onServiceDisconnected(const QString &name) = 0; + + QJniObject handle() const; +private: + Q_DISABLE_COPY(QAndroidServiceConnection) + QJniObject m_handle; +}; + +class Q_CORE_EXPORT QAndroidIntent +{ +public: + QAndroidIntent(); + virtual ~QAndroidIntent(); + explicit QAndroidIntent(const QJniObject &intent); + explicit QAndroidIntent(const QString &action); + explicit QAndroidIntent(const QJniObject &packageContext, const char *className); + + void putExtra(const QString &key, const QByteArray &data); + QByteArray extraBytes(const QString &key); + + void putExtra(const QString &key, const QVariant &value); + QVariant extraVariant(const QString &key); + + QJniObject handle() const; + +private: + QJniObject m_handle; +}; + +class QAndroidServicePrivate; + +class Q_CORE_EXPORT QAndroidService : public QCoreApplication +{ + Q_OBJECT + +public: + QAndroidService(int &argc, char **argv +#ifndef Q_QDOC + , int flags = ApplicationFlags +#endif + ); + QAndroidService(int &argc, char **argv, + const std::function & binder +#ifndef Q_QDOC + , int flags = ApplicationFlags +#endif + ); + virtual ~QAndroidService(); + + virtual QAndroidBinder* onBind(const QAndroidIntent &intent); + +private: + friend class QAndroidServicePrivate; + Q_DISABLE_COPY(QAndroidService) + + QScopedPointer d; +}; + +class QAndroidActivityCallbackResultReceiver: public QAndroidActivityResultReceiver +{ +public: + QAndroidActivityCallbackResultReceiver(); + void handleActivityResult(int receiverRequestCode, int resultCode, + const QJniObject &intent) override; + void registerCallback(int receiverRequestCode, + std::function callbackFunc); + + static QAndroidActivityCallbackResultReceiver *instance(); +private: + QMap> callbackMap; + + static QAndroidActivityCallbackResultReceiver *s_instance; +}; + +namespace QtAndroidPrivate +{ + Q_CORE_EXPORT void startIntentSender(const QJniObject &intentSender, + int receiverRequestCode, + QAndroidActivityResultReceiver *resultReceiver = nullptr); + Q_CORE_EXPORT void startActivity(const QJniObject &intent, + int receiverRequestCode, + QAndroidActivityResultReceiver *resultReceiver = nullptr); + Q_CORE_EXPORT void startActivity(const QAndroidIntent &intent, + int receiverRequestCode, + QAndroidActivityResultReceiver *resultReceiver = nullptr); + Q_CORE_EXPORT void startActivity(const QJniObject &intent, + int receiverRequestCode, + std::function + callbackFunc); + + enum class BindFlag { + None = 0x00000000, + AutoCreate = 0x00000001, + DebugUnbind = 0x00000002, + NotForeground = 0x00000004, + AboveClient = 0x00000008, + AllowOomManagement = 0x00000010, + WaivePriority = 0x00000020, + Important = 0x00000040, + AdjustWithActivity = 0x00000080, + ExternalService = -2147483648 // 0x80000000 + + }; + Q_DECLARE_FLAGS(BindFlags, BindFlag) + + Q_CORE_EXPORT bool bindService(const QAndroidIntent &serviceIntent, + const QAndroidServiceConnection &serviceConnection, + BindFlags flags = BindFlag::None); +} + +QT_END_NAMESPACE + +#endif // QANDROIDEXTRAS_H