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 //}