File indexing completed on 2024-05-26 04:49:54

0001 /*
0002  *   Copyright 2018 Camilo Higuita <milo.h@aol.com>
0003  *
0004  *   This program is free software; you can redistribute it and/or modify
0005  *   it under the terms of the GNU Library General Public License as
0006  *   published by the Free Software Foundation; either version 2, or
0007  *   (at your option) any later version.
0008  *
0009  *   This program is distributed in the hope that it will be useful,
0010  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
0011  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0012  *   GNU General Public License for more details
0013  *
0014  *   You should have received a copy of the GNU Library General Public
0015  *   License along with this program; if not, write to the
0016  *   Free Software Foundation, Inc.,
0017  *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
0018  */
0019 
0020 #include "mauiandroid.h"
0021 #include <QColor>
0022 #include <QCoreApplication>
0023 #include <QDebug>
0024 #include <QException>
0025 #include <QFile>
0026 #include <QFileInfo>
0027 #include <QImage>
0028 #include <QMimeData>
0029 #include <QMimeDatabase>
0030 #include <QProcess>
0031 #include <QUrl>
0032 
0033 #include <android/bitmap.h>
0034 // WindowManager.LayoutParams
0035 #define FLAG_TRANSLUCENT_STATUS 0x04000000
0036 #define FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS 0x80000000
0037 // View
0038 #define SYSTEM_UI_FLAG_LIGHT_STATUS_BAR 0x00002000
0039 #define SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR 0x00000010
0040 
0041 class InterfaceConnFailedException : public QException
0042 {
0043 public:
0044     void raise() const
0045     {
0046         throw *this;
0047     }
0048     InterfaceConnFailedException *clone() const
0049     {
0050         return new InterfaceConnFailedException(*this);
0051     }
0052 };
0053 
0054 static MAUIAndroid *m_instance = nullptr;
0055 
0056 MAUIAndroid::MAUIAndroid(QObject *parent)
0057     : AbstractPlatform(parent)
0058 {
0059     m_instance = this;
0060 //    connect(qApp, &QCoreApplication::aboutToQuit, []()
0061 //    {
0062 //        qDebug() << "Lets remove MauiApp singleton instance";
0063 //        delete m_instance;
0064 //        m_instance = nullptr;
0065 //    });
0066 
0067 }
0068 
0069 static QAndroidJniObject getAndroidWindow()
0070 {
0071     QAndroidJniObject window = QtAndroid::androidActivity().callObjectMethod("getWindow", "()Landroid/view/Window;");
0072     window.callMethod<void>("addFlags", "(I)V", FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
0073     window.callMethod<void>("clearFlags", "(I)V", FLAG_TRANSLUCENT_STATUS);
0074     return window;
0075 }
0076 
0077 void MAUIAndroid::statusbarColor(const QString &bg, const bool &light)
0078 {
0079     if (QtAndroid::androidSdkVersion() <= 23)
0080         return;
0081 
0082     QtAndroid::runOnAndroidThread([=]() {
0083         QAndroidJniObject window = getAndroidWindow();
0084         QAndroidJniObject view = window.callObjectMethod("getDecorView", "()Landroid/view/View;");
0085         int visibility = view.callMethod<int>("getSystemUiVisibility", "()I");
0086         if (light)
0087             visibility |= SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
0088         else
0089             visibility &= ~SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
0090         view.callMethod<void>("setSystemUiVisibility", "(I)V", visibility);
0091         window.callMethod<void>("setStatusBarColor", "(I)V", QColor(bg).rgba());
0092     });
0093 }
0094 
0095 void MAUIAndroid::navBarColor(const QString &bg, const bool &light)
0096 {
0097     if (QtAndroid::androidSdkVersion() <= 23)
0098         return;
0099 
0100     QtAndroid::runOnAndroidThread([=]() {
0101         QAndroidJniObject window = getAndroidWindow();
0102         QAndroidJniObject view = window.callObjectMethod("getDecorView", "()Landroid/view/View;");
0103         int visibility = view.callMethod<int>("getSystemUiVisibility", "()I");
0104         if (light)
0105             visibility |= SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
0106         else
0107             visibility &= ~SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
0108         view.callMethod<void>("setSystemUiVisibility", "(I)V", visibility);
0109         window.callMethod<void>("setNavigationBarColor", "(I)V", QColor(bg).rgba());
0110     });
0111 }
0112 
0113 void MAUIAndroid::shareFiles(const QList<QUrl> &urls)
0114 {
0115     QAndroidJniEnvironment _env;
0116     QAndroidJniObject activity = QAndroidJniObject::callStaticObjectMethod("org/qtproject/qt5/android/QtNative", "activity", "()Landroid/app/Activity;"); // activity is valid
0117     if (_env->ExceptionCheck()) {
0118         _env->ExceptionClear();
0119         throw InterfaceConnFailedException();
0120     }
0121 
0122     if(urls.isEmpty())
0123         return;
0124 
0125     if (activity.isValid())
0126     {
0127         qDebug() << "trying to share dialog << valid";
0128 
0129         QMimeDatabase mimedb;
0130         QString mimeType = mimedb.mimeTypeForFile(urls.first().toLocalFile()).name();
0131 
0132         jobjectArray stringArray = _env->NewObjectArray(urls.count(), _env->FindClass("java/lang/String"), NULL);
0133 
0134         int index = -1;
0135         for(auto url : urls)
0136         {
0137                _env->SetObjectArrayElement(stringArray, ++index, QAndroidJniObject::fromString(url.toLocalFile()).object<jstring>());
0138            }
0139 
0140         QAndroidJniObject::callStaticMethod<void>("com/kde/maui/tools/SendIntent",
0141                                                   "share",
0142                                                   "(Landroid/app/Activity;[Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V",
0143                                                   activity.object<jobject>(),
0144                                                    QAndroidJniObject::fromLocalRef(stringArray).object<jobjectArray>(),
0145                                                   QAndroidJniObject::fromString(mimeType).object<jstring>(),
0146                                                   QAndroidJniObject::fromString(QString("%1.fileprovider").arg(qApp->organizationDomain())).object<jstring>());
0147 
0148         if (_env->ExceptionCheck())
0149         {
0150             qDebug() << "trying to share dialog << exception";
0151 
0152             _env->ExceptionClear();
0153             throw InterfaceConnFailedException();
0154         }
0155     } else
0156         throw InterfaceConnFailedException();
0157 }
0158 
0159 
0160 void MAUIAndroid::shareText(const QString &text)
0161 {
0162     qDebug() << "trying to share text";
0163     QAndroidJniEnvironment _env;
0164     QAndroidJniObject activity = QAndroidJniObject::callStaticObjectMethod("org/qtproject/qt5/android/QtNative", "activity", "()Landroid/app/Activity;"); // activity is valid
0165     if (_env->ExceptionCheck()) {
0166         _env->ExceptionClear();
0167         throw InterfaceConnFailedException();
0168     }
0169     if (activity.isValid()) {
0170         QAndroidJniObject::callStaticMethod<void>("com/kde/maui/tools/SendIntent", "sendText", "(Landroid/app/Activity;Ljava/lang/String;)V", activity.object<jobject>(), QAndroidJniObject::fromString(text).object<jstring>());
0171 
0172         if (_env->ExceptionCheck()) {
0173             _env->ExceptionClear();
0174             throw InterfaceConnFailedException();
0175         }
0176     } else
0177         throw InterfaceConnFailedException();
0178 }
0179 
0180 void MAUIAndroid::shareLink(const QString &link)
0181 {
0182     qDebug() << "trying to share link";
0183     QAndroidJniEnvironment _env;
0184     QAndroidJniObject activity = QAndroidJniObject::callStaticObjectMethod("org/qtproject/qt5/android/QtNative", "activity", "()Landroid/app/Activity;"); // activity is valid
0185     if (_env->ExceptionCheck()) {
0186         _env->ExceptionClear();
0187         throw InterfaceConnFailedException();
0188     }
0189     if (activity.isValid()) {
0190         QAndroidJniObject::callStaticMethod<void>("com/kde/maui/tools/SendIntent", "sendUrl", "(Landroid/app/Activity;Ljava/lang/String;)V", activity.object<jobject>(), QAndroidJniObject::fromString(link).object<jstring>());
0191 
0192         if (_env->ExceptionCheck()) {
0193             _env->ExceptionClear();
0194             throw InterfaceConnFailedException();
0195         }
0196     } else
0197         throw InterfaceConnFailedException();
0198 }
0199 
0200 void MAUIAndroid::openUrl(const QUrl &url)
0201 {
0202     qDebug() << "trying to open file with";
0203     QAndroidJniEnvironment _env;
0204     QAndroidJniObject activity = QAndroidJniObject::callStaticObjectMethod("org/qtproject/qt5/android/QtNative", "activity", "()Landroid/app/Activity;"); // activity is valid
0205     if (_env->ExceptionCheck()) {
0206         _env->ExceptionClear();
0207         throw InterfaceConnFailedException();
0208     }
0209     if (activity.isValid()) {
0210         QAndroidJniObject::callStaticMethod<void>("com/kde/maui/tools/SendIntent",
0211                                                   "openUrl",
0212                                                   "(Landroid/app/Activity;Ljava/lang/String;Ljava/lang/String;)V",
0213                                                   activity.object<jobject>(),
0214                                                   QAndroidJniObject::fromString(url.toLocalFile()).object<jstring>(),
0215                                                   QAndroidJniObject::fromString(QString("%1.fileprovider").arg(qApp->organizationDomain())).object<jstring>());
0216 
0217         if (_env->ExceptionCheck()) {
0218             _env->ExceptionClear();
0219             throw InterfaceConnFailedException();
0220         }
0221     } else
0222         throw InterfaceConnFailedException();
0223 }
0224 
0225 QString MAUIAndroid::homePath()
0226 {
0227     QAndroidJniObject mediaDir = QAndroidJniObject::callStaticObjectMethod("android/os/Environment", "getExternalStorageDirectory", "()Ljava/io/File;");
0228     QAndroidJniObject mediaPath = mediaDir.callObjectMethod("getAbsolutePath", "()Ljava/lang/String;");
0229 
0230     return mediaPath.toString();
0231 }
0232 
0233 QStringList MAUIAndroid::sdDirs()
0234 {
0235     QStringList res;
0236 
0237 //    QAndroidJniObject mediaDir = QAndroidJniObject::callStaticObjectMethod("com/kde/maui/tools/SDCard", "findSdCardPath", "(Landroid/content/Context;)Ljava/io/File;", QtAndroid::androidActivity().object<jobject>());
0238 
0239 //    if (mediaDir == NULL)
0240 //        return res;
0241 
0242 //    QAndroidJniObject mediaPath = mediaDir.callObjectMethod("getAbsolutePath", "()Ljava/lang/String;");
0243 //    QString dataAbsPath = mediaPath.toString();
0244 
0245 //    res << QUrl::fromLocalFile(dataAbsPath).toString();
0246     return res;
0247 }
0248 
0249 // void MAUIAndroid::handleActivityResult(int receiverRequestCode, int resultCode, const QAndroidJniObject &data)
0250 //{
0251 //    qDebug() << "ACTIVITY RESULTS";
0252 //    jint RESULT_OK = QAndroidJniObject::getStaticField<jint>("android/app/Activity", "RESULT_OK");
0253 
0254 //    if (receiverRequestCode == 42 && resultCode == RESULT_OK) {
0255 //        QString url = data.callObjectMethod("getData", "()Landroid/net/Uri;").callObjectMethod("getPath", "()Ljava/lang/String;").toString();
0256 //        emit folderPicked(url);
0257 //    }
0258 //}
0259 
0260 void MAUIAndroid::fileChooser()
0261 {
0262     QAndroidJniEnvironment _env;
0263     QAndroidJniObject activity = QAndroidJniObject::callStaticObjectMethod("org/qtproject/qt5/android/QtNative", "activity", "()Landroid/app/Activity;"); // activity is valid
0264     if (_env->ExceptionCheck()) {
0265         _env->ExceptionClear();
0266         throw InterfaceConnFailedException();
0267     }
0268     if (activity.isValid()) {
0269         QAndroidJniObject::callStaticMethod<void>("com/example/android/tools/SendIntent", "fileChooser", "(Landroid/app/Activity;)V", activity.object<jobject>());
0270         if (_env->ExceptionCheck()) {
0271             _env->ExceptionClear();
0272             throw InterfaceConnFailedException();
0273         }
0274     } else
0275         throw InterfaceConnFailedException();
0276 }
0277 
0278 QVariantList MAUIAndroid::transform(const QAndroidJniObject &obj)
0279 {
0280     QVariantList res;
0281     const auto size = obj.callMethod<jint>("size", "()I");
0282 
0283     for (auto i = 0; i < size; i++) {
0284         QAndroidJniObject hashObj = obj.callObjectMethod("get", "(I)Ljava/lang/Object;", i);
0285         res << createVariantMap(hashObj.object<jobject>());
0286     }
0287 
0288     return res;
0289 }
0290 
0291 QVariantMap MAUIAndroid::createVariantMap(jobject data)
0292 {
0293     QVariantMap res;
0294 
0295     QAndroidJniEnvironment env;
0296     /* Reference : https://community.oracle.com/thread/1549999 */
0297 
0298     // Get the HashMap Class
0299     jclass jclass_of_hashmap = (env)->GetObjectClass(data);
0300 
0301     // Get link to Method "entrySet"
0302     jmethodID entrySetMethod = (env)->GetMethodID(jclass_of_hashmap, "entrySet", "()Ljava/util/Set;");
0303 
0304     // Invoke the "entrySet" method on the HashMap object
0305     jobject jobject_of_entryset = env->CallObjectMethod(data, entrySetMethod);
0306 
0307     // Get the Set Class
0308     jclass jclass_of_set = (env)->FindClass("java/util/Set"); // Problem during compilation !!!!!
0309 
0310     if (jclass_of_set == 0) {
0311         qWarning() << "java/util/Set lookup failed\n";
0312         return res;
0313     }
0314 
0315     // Get link to Method "iterator"
0316     jmethodID iteratorMethod = env->GetMethodID(jclass_of_set, "iterator", "()Ljava/util/Iterator;");
0317 
0318     // Invoke the "iterator" method on the jobject_of_entryset variable of type Set
0319     jobject jobject_of_iterator = env->CallObjectMethod(jobject_of_entryset, iteratorMethod);
0320 
0321     // Get the "Iterator" class
0322     jclass jclass_of_iterator = (env)->FindClass("java/util/Iterator");
0323 
0324     // Get link to Method "hasNext"
0325     jmethodID hasNextMethod = env->GetMethodID(jclass_of_iterator, "hasNext", "()Z");
0326 
0327     jmethodID nextMethod = env->GetMethodID(jclass_of_iterator, "next", "()Ljava/lang/Object;");
0328 
0329     while (env->CallBooleanMethod(jobject_of_iterator, hasNextMethod)) {
0330         jobject jEntry = env->CallObjectMethod(jobject_of_iterator, nextMethod);
0331         QAndroidJniObject entry = QAndroidJniObject(jEntry);
0332         QAndroidJniObject key = entry.callObjectMethod("getKey", "()Ljava/lang/Object;");
0333         QAndroidJniObject value = entry.callObjectMethod("getValue", "()Ljava/lang/Object;");
0334         QString k = key.toString();
0335 
0336         QVariant v = value.toString();
0337 
0338         env->DeleteLocalRef(jEntry);
0339 
0340         if (v.isNull()) {
0341             continue;
0342         }
0343 
0344         res[k] = v;
0345     }
0346 
0347     if (env->ExceptionOccurred()) {
0348         env->ExceptionDescribe();
0349         env->ExceptionClear();
0350     }
0351 
0352     env->DeleteLocalRef(jclass_of_hashmap);
0353     env->DeleteLocalRef(jobject_of_entryset);
0354     env->DeleteLocalRef(jclass_of_set);
0355     env->DeleteLocalRef(jobject_of_iterator);
0356     env->DeleteLocalRef(jclass_of_iterator);
0357 
0358     return res;
0359 }
0360 
0361 bool MAUIAndroid::checkRunTimePermissions(const QStringList &permissions)
0362 {
0363     qDebug() << "CHECKIGN PERMISSSIONS";
0364 
0365     for (const auto &permission : permissions) {
0366         QtAndroid::PermissionResult r = QtAndroid::checkPermission(permission);
0367         if (r == QtAndroid::PermissionResult::Denied) {
0368             QtAndroid::requestPermissionsSync({permission});
0369             r = QtAndroid::checkPermission(permission);
0370             if (r == QtAndroid::PermissionResult::Denied) {
0371                 qWarning() << "Permission denied";
0372                 return false;
0373             }
0374         }
0375     }
0376 
0377     qDebug() << "Permissions granted!";
0378     return true;
0379 }
0380 
0381 bool MAUIAndroid::hasKeyboard()
0382 {
0383     QAndroidJniObject context = QtAndroid::androidContext().object<jobject>();
0384 
0385     if (context.isValid()) {
0386         QAndroidJniObject resources = context.callObjectMethod("getResources", "()Landroid/content/res/Resources;");
0387         QAndroidJniObject config = resources.callObjectMethod("getConfiguration", "()Landroid/content/res/Configuration;");
0388         int value = config.getField<jint>("keyboard");
0389         //        QVariant v = value.toString();
0390         qDebug() << "KEYBOARD" << value;
0391 
0392         return value == 2 || value == 3; // KEYBOARD_12KEY || KEYBOARD_QWERTY
0393 
0394     } else
0395         throw InterfaceConnFailedException();
0396 }
0397 
0398 bool MAUIAndroid::hasMouse()
0399 {
0400     return false;
0401 }
0402 
0403 void MAUIAndroid::handleActivityResult(int receiverRequestCode, int resultCode, const QAndroidJniObject &data)
0404 {
0405     qDebug() << "ACTIVITY RESULTS" << receiverRequestCode;
0406     emit this->hasKeyboardChanged();
0407     jint RESULT_OK = QAndroidJniObject::getStaticField<jint>("android/app/Activity", "RESULT_OK");
0408 
0409     if (receiverRequestCode == 42 && resultCode == RESULT_OK) {
0410         QString url = data.callObjectMethod("getData", "()Landroid/net/Uri;").callObjectMethod("getPath", "()Ljava/lang/String;").toString();
0411         emit folderPicked(url);
0412     }
0413 }
0414 
0415 
0416 bool MAUIAndroid::darkModeEnabled()
0417 {
0418     jint res = QAndroidJniObject::callStaticMethod<jint>(
0419         "com/kde/maui/tools/ConfigActivity",
0420         "systemStyle",
0421         "(Landroid/content/Context;)I",
0422         QtAndroid::androidActivity().object());
0423 
0424     return res == 1;
0425 }
0426 
0427 //JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* /*reserved*/)
0428 //{
0429 //    JNIEnv* env;
0430 //    if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
0431 //        return JNI_ERR;
0432 //    }
0433 
0434 //    jclass javaClass = env->FindClass("com/kde/maui/tools/ConfigActivity");
0435 //    if (!javaClass)
0436 //        return JNI_ERR;
0437 
0438 //    if (env->RegisterNatives(javaClass, methods, sizeof(methods) / sizeof(methods[0])) < 0) {
0439 //        return JNI_ERR;
0440 //    }
0441 //    return JNI_VERSION_1_6;
0442 //}