2021-06-19 09:06:52 +02:00
|
|
|
// (c) 2017 Ekkehard Gentz (ekke) @ekkescorner
|
|
|
|
// my blog about Qt for mobile: http://j.mp/qt-x
|
|
|
|
// see also /COPYRIGHT and /LICENSE
|
|
|
|
|
|
|
|
#include "shareUtils/androidshareutils.h"
|
|
|
|
|
|
|
|
#include <QUrl>
|
|
|
|
#include <QFileInfo>
|
|
|
|
#include <QDateTime>
|
|
|
|
|
|
|
|
#include <QtAndroidExtras/QAndroidJniObject>
|
|
|
|
#include <jni.h>
|
|
|
|
|
|
|
|
const static int RESULT_OK = -1;
|
|
|
|
const static int RESULT_CANCELED = 0;
|
|
|
|
|
|
|
|
AndroidShareUtils* AndroidShareUtils::mInstance = NULL;
|
|
|
|
|
|
|
|
AndroidShareUtils::AndroidShareUtils(QObject* parent) : PlatformShareUtils(parent)
|
|
|
|
{
|
|
|
|
// we need the instance for JNI Call
|
|
|
|
mInstance = this;
|
|
|
|
}
|
|
|
|
|
|
|
|
AndroidShareUtils* AndroidShareUtils::getInstance()
|
|
|
|
{
|
|
|
|
if (!mInstance) {
|
|
|
|
mInstance = new AndroidShareUtils;
|
|
|
|
qWarning() << "AndroidShareUtils should be instantiated !";
|
|
|
|
}
|
|
|
|
|
|
|
|
return mInstance;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool AndroidShareUtils::checkMimeTypeView(const QString &mimeType)
|
|
|
|
{
|
|
|
|
QAndroidJniObject jsMime = QAndroidJniObject::fromString(mimeType);
|
|
|
|
jboolean verified = QAndroidJniObject::callStaticMethod<jboolean>("org/ekkescorner/utils/QShareUtils",
|
|
|
|
"checkMimeTypeView",
|
|
|
|
"(Ljava/lang/String;)Z",
|
|
|
|
jsMime.object<jstring>());
|
|
|
|
qDebug() << "View VERIFIED: " << mimeType << " - " << verified;
|
|
|
|
return verified;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool AndroidShareUtils::checkMimeTypeEdit(const QString &mimeType)
|
|
|
|
{
|
|
|
|
QAndroidJniObject jsMime = QAndroidJniObject::fromString(mimeType);
|
|
|
|
jboolean verified = QAndroidJniObject::callStaticMethod<jboolean>("org/ekkescorner/utils/QShareUtils",
|
|
|
|
"checkMimeTypeEdit",
|
|
|
|
"(Ljava/lang/String;)Z",
|
|
|
|
jsMime.object<jstring>());
|
|
|
|
qDebug() << "Edit VERIFIED: " << mimeType << " - " << verified;
|
|
|
|
return verified;
|
|
|
|
}
|
|
|
|
|
2021-06-20 16:51:31 +02:00
|
|
|
QString AndroidShareUtils::getTemporaryFileLocationPath() {
|
|
|
|
return QStandardPaths::standardLocations(QStandardPaths::AppDataLocation).value(0) + "/temporaryFiles";
|
|
|
|
}
|
|
|
|
|
2021-06-19 09:06:52 +02:00
|
|
|
void AndroidShareUtils::shareText(const QString &text)
|
|
|
|
{
|
|
|
|
QAndroidJniObject jsText = QAndroidJniObject::fromString(text);
|
|
|
|
jboolean ok = QAndroidJniObject::callStaticMethod<jboolean>("org/ekkescorner/utils/QShareUtils",
|
|
|
|
"shareText",
|
|
|
|
"(Ljava/lang/String;)Z",
|
|
|
|
jsText.object<jstring>());
|
|
|
|
|
|
|
|
if(!ok) {
|
|
|
|
qWarning() << "Unable to resolve activity from Java";
|
|
|
|
emit shareNoAppAvailable(0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* As default we're going the Java - way with one simple JNI call (recommended)
|
|
|
|
* if altImpl is true we're going the pure JNI way
|
|
|
|
*
|
|
|
|
* If a requestId was set we want to get the Activity Result back (recommended)
|
|
|
|
* We need the Request Id and Result Id to control our workflow
|
|
|
|
*/
|
|
|
|
void AndroidShareUtils::sendFile(const QString &filePath, const QString &title, const QString &mimeType, const int &requestId)
|
|
|
|
{
|
|
|
|
mIsEditMode = false;
|
|
|
|
|
|
|
|
QAndroidJniObject jsPath = QAndroidJniObject::fromString(filePath);
|
|
|
|
QAndroidJniObject jsTitle = QAndroidJniObject::fromString(title);
|
|
|
|
QAndroidJniObject jsMimeType = QAndroidJniObject::fromString(mimeType);
|
|
|
|
jboolean ok = QAndroidJniObject::callStaticMethod<jboolean>("org/ekkescorner/utils/QShareUtils",
|
|
|
|
"sendFile",
|
|
|
|
"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;I)Z",
|
|
|
|
jsPath.object<jstring>(), jsTitle.object<jstring>(), jsMimeType.object<jstring>(), requestId);
|
|
|
|
if(!ok) {
|
|
|
|
qWarning() << "Unable to resolve activity from Java";
|
|
|
|
emit shareNoAppAvailable(requestId);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* As default we're going the Java - way with one simple JNI call (recommended)
|
|
|
|
* if altImpl is true we're going the pure JNI way
|
|
|
|
*
|
|
|
|
* If a requestId was set we want to get the Activity Result back (recommended)
|
|
|
|
* We need the Request Id and Result Id to control our workflow
|
|
|
|
*/
|
|
|
|
void AndroidShareUtils::viewFile(const QString &filePath, const QString &title, const QString &mimeType, const int &requestId)
|
|
|
|
{
|
|
|
|
mIsEditMode = false;
|
|
|
|
|
|
|
|
QAndroidJniObject jsPath = QAndroidJniObject::fromString(filePath);
|
|
|
|
QAndroidJniObject jsTitle = QAndroidJniObject::fromString(title);
|
|
|
|
QAndroidJniObject jsMimeType = QAndroidJniObject::fromString(mimeType);
|
|
|
|
jboolean ok = QAndroidJniObject::callStaticMethod<jboolean>("org/ekkescorner/utils/QShareUtils",
|
|
|
|
"viewFile",
|
|
|
|
"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;I)Z",
|
|
|
|
jsPath.object<jstring>(), jsTitle.object<jstring>(), jsMimeType.object<jstring>(), requestId);
|
|
|
|
if(!ok) {
|
|
|
|
qWarning() << "Unable to resolve activity from Java";
|
|
|
|
emit shareNoAppAvailable(requestId);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* As default we're going the Java - way with one simple JNI call (recommended)
|
|
|
|
* if altImpl is true we're going the pure JNI way
|
|
|
|
*
|
|
|
|
* If a requestId was set we want to get the Activity Result back (recommended)
|
|
|
|
* We need the Request Id and Result Id to control our workflow
|
|
|
|
*/
|
|
|
|
void AndroidShareUtils::editFile(const QString &filePath, const QString &title, const QString &mimeType, const int &requestId)
|
|
|
|
{
|
|
|
|
mIsEditMode = true;
|
|
|
|
mCurrentFilePath = filePath;
|
|
|
|
QFileInfo fileInfo = QFileInfo(mCurrentFilePath);
|
|
|
|
mLastModified = fileInfo.lastModified().toSecsSinceEpoch();
|
|
|
|
qDebug() << "LAST MODIFIED: " << mLastModified;
|
|
|
|
|
|
|
|
QAndroidJniObject jsPath = QAndroidJniObject::fromString(filePath);
|
|
|
|
QAndroidJniObject jsTitle = QAndroidJniObject::fromString(title);
|
|
|
|
QAndroidJniObject jsMimeType = QAndroidJniObject::fromString(mimeType);
|
|
|
|
|
|
|
|
jboolean ok = QAndroidJniObject::callStaticMethod<jboolean>("org/ekkescorner/utils/QShareUtils",
|
|
|
|
"editFile",
|
|
|
|
"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;I)Z",
|
|
|
|
jsPath.object<jstring>(), jsTitle.object<jstring>(), jsMimeType.object<jstring>(), requestId);
|
|
|
|
|
|
|
|
if(!ok) {
|
|
|
|
qWarning() << "Unable to resolve activity from Java";
|
|
|
|
emit shareNoAppAvailable(requestId);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// used from QAndroidActivityResultReceiver
|
|
|
|
void AndroidShareUtils::handleActivityResult(int receiverRequestCode, int resultCode, const QAndroidJniObject &data)
|
|
|
|
{
|
|
|
|
Q_UNUSED(data);
|
|
|
|
qDebug() << "From JNI QAndroidActivityResultReceiver: " << receiverRequestCode << "ResultCode:" << resultCode;
|
|
|
|
processActivityResult(receiverRequestCode, resultCode);
|
|
|
|
}
|
|
|
|
|
|
|
|
// used from Activity.java onActivityResult()
|
|
|
|
void AndroidShareUtils::onActivityResult(int requestCode, int resultCode)
|
|
|
|
{
|
|
|
|
qDebug() << "From Java Activity onActivityResult: " << requestCode << "ResultCode:" << resultCode;
|
|
|
|
processActivityResult(requestCode, resultCode);
|
|
|
|
}
|
|
|
|
|
|
|
|
void AndroidShareUtils::processActivityResult(int requestCode, int resultCode)
|
|
|
|
{
|
|
|
|
// we're getting RESULT_OK only if edit is done
|
|
|
|
if(resultCode == RESULT_OK) {
|
|
|
|
emit shareEditDone(requestCode);
|
|
|
|
} else if(resultCode == RESULT_CANCELED) {
|
|
|
|
if(mIsEditMode) {
|
|
|
|
// Attention: not all Apps will give you the correct ResultCode:
|
|
|
|
// Google Fotos will send OK if saved and CANCELED if canceled
|
|
|
|
// Some Apps always sends CANCELED even if you modified and Saved the File
|
|
|
|
// so you should check the modified Timestamp of the File to know if
|
|
|
|
// you should emit shareEditDone() or shareFinished() !!!
|
|
|
|
QFileInfo fileInfo = QFileInfo(mCurrentFilePath);
|
|
|
|
qint64 currentModified = fileInfo.lastModified().toSecsSinceEpoch();
|
|
|
|
qDebug() << "CURRENT MODIFIED: " << currentModified;
|
|
|
|
if(currentModified > mLastModified) {
|
|
|
|
emit shareEditDone(requestCode);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
emit shareFinished(requestCode);
|
|
|
|
} else {
|
|
|
|
qDebug() << "wrong result code: " << resultCode << " from request: " << requestCode;
|
|
|
|
emit shareError(requestCode, tr("Share: an Error occured"));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void AndroidShareUtils::checkPendingIntents(const QString workingDirPath)
|
|
|
|
{
|
|
|
|
QAndroidJniObject activity = QtAndroid::androidActivity();
|
|
|
|
if(activity.isValid()) {
|
|
|
|
// create a Java String for the Working Dir Path
|
|
|
|
QAndroidJniObject jniWorkingDir = QAndroidJniObject::fromString(workingDirPath);
|
|
|
|
if(!jniWorkingDir.isValid()) {
|
|
|
|
qWarning() << "QAndroidJniObject jniWorkingDir not valid.";
|
|
|
|
emit shareError(0, tr("Share: an Error occured\nWorkingDir not valid"));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
activity.callMethod<void>("checkPendingIntents","(Ljava/lang/String;)V", jniWorkingDir.object<jstring>());
|
|
|
|
qDebug() << "checkPendingIntents: " << workingDirPath;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
qDebug() << "checkPendingIntents: Activity not valid";
|
|
|
|
}
|
|
|
|
|
|
|
|
void AndroidShareUtils::setFileUrlReceived(const QString &url)
|
|
|
|
{
|
|
|
|
if(url.isEmpty()) {
|
|
|
|
qWarning() << "setFileUrlReceived: we got an empty URL";
|
|
|
|
emit shareError(0, tr("Empty URL received"));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
qDebug() << "AndroidShareUtils setFileUrlReceived: we got the File URL from JAVA: " << url;
|
|
|
|
QString myUrl;
|
|
|
|
if(url.startsWith("file://")) {
|
|
|
|
myUrl= url.right(url.length()-7);
|
|
|
|
qDebug() << "QFile needs this URL: " << myUrl;
|
|
|
|
} else {
|
|
|
|
myUrl= url;
|
|
|
|
}
|
|
|
|
|
|
|
|
// check if File exists
|
|
|
|
QFileInfo fileInfo = QFileInfo(myUrl);
|
|
|
|
if(fileInfo.exists()) {
|
|
|
|
emit fileUrlReceived(myUrl);
|
|
|
|
} else {
|
|
|
|
qDebug() << "setFileUrlReceived: FILE does NOT exist ";
|
|
|
|
emit shareError(0, tr("File does not exist: %1").arg(myUrl));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-20 19:04:36 +02:00
|
|
|
void AndroidShareUtils::setOtherUrlReceived(const QString &url, const QString &scheme)
|
|
|
|
{
|
|
|
|
if(url.isEmpty()) {
|
|
|
|
qWarning() << "setFileUrlReceived: we got an empty URL";
|
|
|
|
emit shareError(0, tr("Empty URL received"));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
qDebug() << "AndroidShareUtils setOtherUrlReceived: we got the Other URL from JAVA: " << url;
|
|
|
|
|
|
|
|
emit otherUrlReceived(url, scheme);
|
|
|
|
}
|
|
|
|
|
2021-06-19 09:06:52 +02:00
|
|
|
void AndroidShareUtils::setFileReceivedAndSaved(const QString &url)
|
|
|
|
{
|
|
|
|
if(url.isEmpty()) {
|
|
|
|
qWarning() << "setFileReceivedAndSaved: we got an empty URL";
|
|
|
|
emit shareError(0, tr("Empty URL received"));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
qDebug() << "AndroidShareUtils setFileReceivedAndSaved: we got the File URL from JAVA: " << url;
|
|
|
|
QString myUrl;
|
|
|
|
if(url.startsWith("file://")) {
|
|
|
|
myUrl= url.right(url.length()-7);
|
|
|
|
qDebug() << "QFile needs this URL: " << myUrl;
|
|
|
|
} else {
|
|
|
|
myUrl= url;
|
|
|
|
}
|
|
|
|
|
|
|
|
// check if File exists
|
|
|
|
QFileInfo fileInfo = QFileInfo(myUrl);
|
|
|
|
if(fileInfo.exists()) {
|
|
|
|
emit fileReceivedAndSaved(myUrl);
|
|
|
|
} else {
|
|
|
|
qDebug() << "setFileReceivedAndSaved: FILE does NOT exist ";
|
|
|
|
emit shareError(0, tr("File does not exist: %1").arg(myUrl));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// to be safe we check if a File Url from java really exists for Qt
|
|
|
|
// if not on the Java side we'll try to read the content as Stream
|
|
|
|
bool AndroidShareUtils::checkFileExits(const QString &url)
|
|
|
|
{
|
|
|
|
if(url.isEmpty()) {
|
|
|
|
qWarning() << "checkFileExits: we got an empty URL";
|
|
|
|
emit shareError(0, tr("Empty URL received"));
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
qDebug() << "AndroidShareUtils checkFileExits: we got the File URL from JAVA: " << url;
|
|
|
|
QString myUrl;
|
|
|
|
if(url.startsWith("file://")) {
|
|
|
|
myUrl= url.right(url.length()-7);
|
|
|
|
qDebug() << "QFile needs this URL: " << myUrl;
|
|
|
|
} else {
|
|
|
|
myUrl= url;
|
|
|
|
}
|
|
|
|
|
|
|
|
// check if File exists
|
|
|
|
QFileInfo fileInfo = QFileInfo(myUrl);
|
|
|
|
if(fileInfo.exists()) {
|
|
|
|
qDebug() << "Yep: the File exists for Qt";
|
|
|
|
return true;
|
|
|
|
} else {
|
|
|
|
qDebug() << "Uuups: FILE does NOT exist ";
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// instead of defining all JNICALL as demonstrated below
|
|
|
|
// there's another way, making it easier to manage all the methods
|
|
|
|
// see https://www.kdab.com/qt-android-episode-5/
|
|
|
|
|
|
|
|
#ifdef __cplusplus
|
|
|
|
extern "C" {
|
|
|
|
#endif
|
|
|
|
|
|
|
|
JNIEXPORT void JNICALL
|
2021-06-20 16:51:31 +02:00
|
|
|
Java_de_itsblue_blueROCK_MainActivity_setFileUrlReceived(JNIEnv *env,
|
2021-06-19 09:06:52 +02:00
|
|
|
jobject obj,
|
|
|
|
jstring url)
|
|
|
|
{
|
|
|
|
const char *urlStr = env->GetStringUTFChars(url, NULL);
|
|
|
|
Q_UNUSED (obj)
|
|
|
|
AndroidShareUtils::getInstance()->setFileUrlReceived(urlStr);
|
|
|
|
env->ReleaseStringUTFChars(url, urlStr);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
JNIEXPORT void JNICALL
|
2021-06-20 19:04:36 +02:00
|
|
|
Java_de_itsblue_blueROCK_MainActivity_setOtherUrlReceived(JNIEnv *env,
|
|
|
|
jobject obj,
|
|
|
|
jstring url,
|
|
|
|
jstring scheme)
|
|
|
|
{
|
|
|
|
const char *urlStr = env->GetStringUTFChars(url, NULL);
|
|
|
|
const char *schemeStr = env->GetStringUTFChars(scheme, NULL);
|
|
|
|
Q_UNUSED (obj)
|
|
|
|
AndroidShareUtils::getInstance()->setOtherUrlReceived(urlStr, schemeStr);
|
|
|
|
env->ReleaseStringUTFChars(url, urlStr);
|
|
|
|
env->ReleaseStringUTFChars(scheme, schemeStr);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
JNIEXPORT void JNICALL
|
2021-06-20 16:51:31 +02:00
|
|
|
Java_de_itsblue_blueROCK_MainActivity_setFileReceivedAndSaved(JNIEnv *env,
|
2021-06-19 09:06:52 +02:00
|
|
|
jobject obj,
|
|
|
|
jstring url)
|
|
|
|
{
|
|
|
|
const char *urlStr = env->GetStringUTFChars(url, NULL);
|
|
|
|
Q_UNUSED (obj)
|
|
|
|
AndroidShareUtils::getInstance()->setFileReceivedAndSaved(urlStr);
|
|
|
|
env->ReleaseStringUTFChars(url, urlStr);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
JNIEXPORT bool JNICALL
|
2021-06-20 16:51:31 +02:00
|
|
|
Java_de_itsblue_blueROCK_MainActivity_checkFileExits(JNIEnv *env,
|
2021-06-19 09:06:52 +02:00
|
|
|
jobject obj,
|
|
|
|
jstring url)
|
|
|
|
{
|
|
|
|
const char *urlStr = env->GetStringUTFChars(url, NULL);
|
|
|
|
Q_UNUSED (obj)
|
|
|
|
bool exists = AndroidShareUtils::getInstance()->checkFileExits(urlStr);
|
|
|
|
env->ReleaseStringUTFChars(url, urlStr);
|
|
|
|
return exists;
|
|
|
|
}
|
|
|
|
|
|
|
|
JNIEXPORT void JNICALL
|
2021-06-20 16:51:31 +02:00
|
|
|
Java_de_itsblue_blueROCK_MainActivity_fireActivityResult(JNIEnv *env,
|
2021-06-19 09:06:52 +02:00
|
|
|
jobject obj,
|
|
|
|
jint requestCode,
|
|
|
|
jint resultCode)
|
|
|
|
{
|
|
|
|
Q_UNUSED (obj)
|
|
|
|
Q_UNUSED (env)
|
|
|
|
AndroidShareUtils::getInstance()->onActivityResult(requestCode, resultCode);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef __cplusplus
|
|
|
|
}
|
|
|
|
#endif
|