/**************************************************************************** ** ** 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 #if QT_CONFIG(future) #include #endif 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 \preliminary \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 \preliminary \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 \preliminary \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 */ static QBasicAtomicInteger nextUniqueActivityRequestCode = Q_BASIC_ATOMIC_INITIALIZER(0); // Get a unique activity request code. static int uniqueActivityRequestCode() { constexpr uint ReservedForQtOffset = 0x1000; // Reserve all request codes under 0x1000 for Qt const uint requestCodeBase = nextUniqueActivityRequestCode.fetchAndAddRelaxed(1); if (requestCodeBase == uint(INT_MAX) - ReservedForQtOffset) qWarning("Unique activity request code has wrapped. Unexpected behavior may occur."); const int requestCode = static_cast(requestCodeBase + ReservedForQtOffset); return requestCode; } class QAndroidActivityResultReceiverPrivate: public QtAndroidPrivate::ActivityResultListener { public: QAndroidActivityResultReceiver *q; mutable QHash localToGlobalRequestCode; mutable QHash globalToLocalRequestCode; int globalRequestCode(int localRequestCode) const { const auto oldSize = localToGlobalRequestCode.size(); auto &e = localToGlobalRequestCode[localRequestCode]; if (localToGlobalRequestCode.size() != oldSize) { // new entry, populate: int globalRequestCode = uniqueActivityRequestCode(); e = globalRequestCode; globalToLocalRequestCode[globalRequestCode] = localRequestCode; } return e; } bool handleActivityResult(jint requestCode, jint resultCode, jobject data) { const auto it = std::as_const(globalToLocalRequestCode).find(requestCode); if (it != globalToLocalRequestCode.cend()) { q->handleActivityResult(*it, resultCode, QJniObject(data)); return true; } return false; } static QAndroidActivityResultReceiverPrivate *get(QAndroidActivityResultReceiver *publicObject) { return publicObject->d.data(); } }; /*! \class QAndroidActivityResultReceiver \preliminary \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 \preliminary \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 \preliminary \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::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 QtAndroidPrivate \preliminary \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); } // Permissions API static const char qtNativeClassName[] = "org/qtproject/qt/android/QtNative"; QtAndroidPrivate::PermissionResult resultFromAndroid(jint value) { return value == 0 ? QtAndroidPrivate::Authorized : QtAndroidPrivate::Denied; } using PendingPermissionRequestsHash = QHash>>; Q_GLOBAL_STATIC(PendingPermissionRequestsHash, g_pendingPermissionRequests); static QBasicMutex g_pendingPermissionRequestsMutex; static int nextRequestCode() { static QBasicAtomicInt counter = Q_BASIC_ATOMIC_INITIALIZER(0); return counter.fetchAndAddRelaxed(1); } static QStringList nativeStringsFromPermission(QtAndroidPrivate::PermissionType permission) { static const auto precisePerm = QStringLiteral("android.permission.ACCESS_FINE_LOCATION"); static const auto coarsePerm = QStringLiteral("android.permission.ACCESS_COARSE_LOCATION"); static const auto backgroundPerm = QStringLiteral("android.permission.ACCESS_BACKGROUND_LOCATION"); switch (permission) { case QtAndroidPrivate::Location: return {coarsePerm}; case QtAndroidPrivate::PreciseLocation: return {precisePerm}; case QtAndroidPrivate::BackgroundLocation: // Keep the background permission first to be able to use .first() // in checkPermission because it takes single permission if (QtAndroidPrivate::androidSdkVersion() >= 29) return {backgroundPerm, coarsePerm}; return {coarsePerm}; case QtAndroidPrivate::PreciseBackgroundLocation: // Keep the background permission first to be able to use .first() // in checkPermission because it takes single permission if (QtAndroidPrivate::androidSdkVersion() >= 29) return {backgroundPerm, precisePerm}; return {precisePerm}; case QtAndroidPrivate::Camera: return {QStringLiteral("android.permission.CAMERA")}; case QtAndroidPrivate::Microphone: return {QStringLiteral("android.permission.RECORD_AUDIO")}; case QtAndroidPrivate::Bluetooth: return { QStringLiteral("android.permission.BLUETOOTH") }; case QtAndroidPrivate::BodySensors: return {QStringLiteral("android.permission.BODY_SENSORS")}; case QtAndroidPrivate::PhysicalActivity: return {QStringLiteral("android.permission.ACTIVITY_RECOGNITION")}; case QtAndroidPrivate::Contacts: return {QStringLiteral("android.permission.READ_CONTACTS"), QStringLiteral("android.permission.WRITE_CONTACTS")}; case QtAndroidPrivate::Storage: return {QStringLiteral("android.permission.READ_EXTERNAL_STORAGE"), QStringLiteral("android.permission.WRITE_EXTERNAL_STORAGE")}; case QtAndroidPrivate::Calendar: return {QStringLiteral("android.permission.READ_CALENDAR"), QStringLiteral("android.permission.WRITE_CALENDAR")}; } return {}; } /*! \internal This function is called when the result of the permission request is available. Once a permission is requested, the result is braodcast by the OS and listened to by QtActivity which passes it to C++ through a native JNI method call. */ static void sendRequestPermissionsResult(JNIEnv *env, jobject *obj, jint requestCode, jobjectArray permissions, jintArray grantResults) { Q_UNUSED(obj); QMutexLocker locker(&g_pendingPermissionRequestsMutex); auto it = g_pendingPermissionRequests->constFind(requestCode); if (it == g_pendingPermissionRequests->constEnd()) { qWarning() << "Found no valid pending permission request for request code" << requestCode; return; } auto request = *it; g_pendingPermissionRequests->erase(it); locker.unlock(); const int size = env->GetArrayLength(permissions); std::unique_ptr results(new jint[size]); env->GetIntArrayRegion(grantResults, 0, size, results.get()); for (int i = 0 ; i < size; ++i) { QtAndroidPrivate::PermissionResult result = resultFromAndroid(results[i]); request->addResult(result, i); } request->finish(); } QFuture requestPermissionsInternal(const QStringList &permissions) { QSharedPointer> promise; promise.reset(new QPromise()); QFuture future = promise->future(); promise->start(); // No mechanism to request permission for SDK version below 23, because // permissions defined in the manifest are granted at install time. if (QtAndroidPrivate::androidSdkVersion() < 23) { for (int i = 0; i < permissions.size(); ++i) promise->addResult(QtAndroidPrivate::checkPermission(permissions.at(i)).result(), i); promise->finish(); return future; } const int requestCode = nextRequestCode(); QMutexLocker locker(&g_pendingPermissionRequestsMutex); g_pendingPermissionRequests->insert(requestCode, promise); QNativeInterface::QAndroidApplication::runOnAndroidMainThread([permissions, requestCode] { QJniEnvironment env; jclass clazz = env.findClass("java/lang/String"); auto array = env->NewObjectArray(permissions.size(), clazz, nullptr); int index = 0; for (auto &perm : permissions) env->SetObjectArrayElement(array, index++, QJniObject::fromString(perm).object()); QJniObject(QtAndroidPrivate::activity()).callMethod("requestPermissions", "([Ljava/lang/String;I)V", array, requestCode); env->DeleteLocalRef(array); }); return future; } /*! \preliminary Requests the \a permission and returns a QFuture representing the result of the request. \since 6.2 \sa checkPermission() */ QFuture QtAndroidPrivate::requestPermission(const QString &permission) { // avoid the uneccessary call and response to an empty permission string if (permission.size() > 0) return requestPermissionsInternal({permission}); QPromise promise; QFuture future = promise.future(); promise.start(); promise.addResult(QtAndroidPrivate::Denied); promise.finish(); return future; } static bool isBackgroundLocationApi29(QtAndroidPrivate::PermissionType permission) { return QNativeInterface::QAndroidApplication::sdkVersion() >= 29 && (permission == QtAndroidPrivate::BackgroundLocation || permission == QtAndroidPrivate::PreciseBackgroundLocation); } /*! \preliminary Requests the \a permission and returns a QFuture representing the result of the request. \since 6.2 \sa checkPermission() */ QFuture QtAndroidPrivate::requestPermission(QtAndroidPrivate::PermissionType permission) { QSharedPointer> promise; promise.reset(new QPromise()); QFuture future = promise->future(); promise->start(); const auto nativePermissions = nativeStringsFromPermission(permission); if (nativePermissions.size() > 0) { requestPermissionsInternal(nativePermissions).then( [promise, permission](QFuture future) { auto AuthorizedCount = future.results().count(QtAndroidPrivate::Authorized); if (AuthorizedCount > 0) { if (isBackgroundLocationApi29(permission)) promise->addResult(future.resultAt(0), 0); else promise->addResult(QtAndroidPrivate::Authorized, 0); } else { promise->addResult(QtAndroidPrivate::Denied, 0); } promise->finish(); }); return future; } promise->addResult(QtAndroidPrivate::Denied); promise->finish(); return future; } /*! \preliminary Checks whether this process has the named \a permission and returns a QFuture representing the result of the check. \since 6.2 \sa requestPermission() */ QFuture QtAndroidPrivate::checkPermission(const QString &permission) { QPromise promise; QFuture future = promise.future(); promise.start(); if (permission.size() > 0) { auto res = QJniObject::callStaticMethod(qtNativeClassName, "checkSelfPermission", "(Ljava/lang/String;)I", QJniObject::fromString(permission).object()); promise.addResult(resultFromAndroid(res)); } else { promise.addResult(QtAndroidPrivate::Denied); } promise.finish(); return future; } /*! \preliminary Checks whether this process has the named \a permission and returns a QFuture representing the result of the check. \since 6.2 \sa requestPermission() */ QFuture QtAndroidPrivate::checkPermission(QtAndroidPrivate::PermissionType permission) { const auto nativePermissions = nativeStringsFromPermission(permission); if (nativePermissions.size() > 0) return checkPermission(nativePermissions.first()); QPromise promise; QFuture future = promise.future(); promise.start(); promise.addResult(QtAndroidPrivate::Denied); promise.finish(); return future; } bool QtAndroidPrivate::registerPermissionNatives() { if (QtAndroidPrivate::androidSdkVersion() < 23) return true; JNINativeMethod methods[] = { {"sendRequestPermissionsResult", "(I[Ljava/lang/String;[I)V", reinterpret_cast(sendRequestPermissionsResult) }}; QJniEnvironment env; return env.registerNativeMethods(qtNativeClassName, methods, 1); } QT_END_NAMESPACE