// (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 #include #include #include #include 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("org/ekkescorner/utils/QShareUtils", "checkMimeTypeView", "(Ljava/lang/String;)Z", jsMime.object()); qDebug() << "View VERIFIED: " << mimeType << " - " << verified; return verified; } bool AndroidShareUtils::checkMimeTypeEdit(const QString &mimeType) { QAndroidJniObject jsMime = QAndroidJniObject::fromString(mimeType); jboolean verified = QAndroidJniObject::callStaticMethod("org/ekkescorner/utils/QShareUtils", "checkMimeTypeEdit", "(Ljava/lang/String;)Z", jsMime.object()); qDebug() << "Edit VERIFIED: " << mimeType << " - " << verified; return verified; } QString AndroidShareUtils::getTemporaryFileLocationPath() { return QStandardPaths::standardLocations(QStandardPaths::AppDataLocation).value(0) + "/temporaryFiles"; } void AndroidShareUtils::shareText(const QString &text) { QAndroidJniObject jsText = QAndroidJniObject::fromString(text); jboolean ok = QAndroidJniObject::callStaticMethod("org/ekkescorner/utils/QShareUtils", "shareText", "(Ljava/lang/String;)Z", jsText.object()); 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("org/ekkescorner/utils/QShareUtils", "sendFile", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;I)Z", jsPath.object(), jsTitle.object(), jsMimeType.object(), 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("org/ekkescorner/utils/QShareUtils", "viewFile", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;I)Z", jsPath.object(), jsTitle.object(), jsMimeType.object(), 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("org/ekkescorner/utils/QShareUtils", "editFile", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;I)Z", jsPath.object(), jsTitle.object(), jsMimeType.object(), 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("checkPendingIntents","(Ljava/lang/String;)V", jniWorkingDir.object()); 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)); } } 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 Java_de_itsblue_blueROCK_MainActivity_setFileUrlReceived(JNIEnv *env, 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 Java_de_itsblue_blueROCK_MainActivity_setFileReceivedAndSaved(JNIEnv *env, 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 Java_de_itsblue_blueROCK_MainActivity_checkFileExits(JNIEnv *env, 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 Java_de_itsblue_blueROCK_MainActivity_fireActivityResult(JNIEnv *env, jobject obj, jint requestCode, jint resultCode) { Q_UNUSED (obj) Q_UNUSED (env) AndroidShareUtils::getInstance()->onActivityResult(requestCode, resultCode); return; } #ifdef __cplusplus } #endif