MauiKit Controls

mauiandroid.cpp
1/*
2 * Copyright 2018 Camilo Higuita <milo.h@aol.com>
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU Library General Public License as
6 * published by the Free Software Foundation; either version 2, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details
13 *
14 * You should have received a copy of the GNU Library General Public
15 * License along with this program; if not, write to the
16 * Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 */
19
20#include "mauiandroid.h"
21#include <QColor>
22#include <QCoreApplication>
23#include <QDebug>
24#include <QException>
25#include <QFile>
26#include <QFileInfo>
27#include <QImage>
28#include <QMimeData>
29#include <QMimeDatabase>
30#include <QProcess>
31#include <QUrl>
32#include <QOperatingSystemVersion>
33
34#include <android/bitmap.h>
35
36#include <QtCore/private/qandroidextras_p.h>
37// WindowManager.LayoutParams
38#define FLAG_TRANSLUCENT_STATUS 0x04000000
39#define FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS 0x80000000
40// View
41#define SYSTEM_UI_FLAG_LIGHT_STATUS_BAR 0x00002000
42#define SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR 0x00000010
43
44class InterfaceConnFailedException : public QException
45{
46public:
47 void raise() const
48 {
49 throw *this;
50 }
51 InterfaceConnFailedException *clone() const
52 {
53 return new InterfaceConnFailedException(*this);
54 }
55};
56
57static MAUIAndroid *m_instance = nullptr;
58
59MAUIAndroid::MAUIAndroid(QObject *parent)
60 : AbstractPlatform(parent)
61{
62 m_instance = this;
63 // connect(qApp, &QCoreApplication::aboutToQuit, []()
64 // {
65 // qDebug() << "Lets remove MauiApp singleton instance";
66 // delete m_instance;
67 // m_instance = nullptr;
68 // });
69
70}
71
72static QJniObject getAndroidWindow()
73{
75 auto window = activity.callObjectMethod("getWindow", "()Landroid/view/Window;");
76 window.callMethod<void>("addFlags", "(I)V", FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
77 window.callMethod<void>("clearFlags", "(I)V", FLAG_TRANSLUCENT_STATUS);
78 return window;
79}
80
81void MAUIAndroid::statusbarColor(const QString &bg, const bool &light)
82{
84 return;
85 qDebug() << "Set the status bar color" << light;
87 QJniObject window = getAndroidWindow();
88 QJniObject view = window.callObjectMethod("getDecorView", "()Landroid/view/View;");
89 int visibility = view.callMethod<int>("getSystemUiVisibility", "()I");
90 if (light)
91 visibility |= SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
92 else
93 visibility &= ~SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
94 view.callMethod<void>("setSystemUiVisibility", "(I)V", visibility);
95 window.callMethod<void>("setStatusBarColor", "(I)V", QColor(bg).rgba());
96 });
97}
98
99void MAUIAndroid::navBarColor(const QString &bg, const bool &light)
100{
102 return;
103 qDebug() << "Set the nav bar color" << light;
104
106 QJniObject window = getAndroidWindow();
107 QJniObject view = window.callObjectMethod("getDecorView", "()Landroid/view/View;");
108 int visibility = view.callMethod<int>("getSystemUiVisibility", "()I");
109 if (light)
110 visibility |= SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
111 else
112 visibility &= ~SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
113 view.callMethod<void>("setSystemUiVisibility", "(I)V", visibility);
114 window.callMethod<void>("setNavigationBarColor", "(I)V", QColor(bg).rgba());
115 });
116}
117
119{
120 QJniEnvironment _env;
121 QJniObject activity = QJniObject::callStaticObjectMethod("org/qtproject/qt/android/QtNative", "activity", "()Landroid/app/Activity;"); // activity is valid
122 if (_env->ExceptionCheck()) {
123 _env->ExceptionClear();
124 throw InterfaceConnFailedException();
125 }
126
127 if(urls.isEmpty())
128 return;
129
130 if (activity.isValid())
131 {
132 qDebug() << "trying to share dialog << valid" << QString("%1.provider").arg(qApp->organizationDomain()) ;
133
134 QMimeDatabase mimedb;
135 QString mimeType = mimedb.mimeTypeForFile(urls.first().toLocalFile()).name();
136
137 jobjectArray stringArray = _env->NewObjectArray(urls.count(), _env->FindClass("java/lang/String"), NULL);
138
139 int index = -1;
140 for(auto url : urls)
141 {
142 _env->SetObjectArrayElement(stringArray, ++index, QJniObject::fromString(url.toLocalFile()).object<jstring>());
143 }
144
145 QJniObject::callStaticMethod<void>("com/kde/maui/tools/SendIntent",
146 "share",
147 "(Landroid/app/Activity;[Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V",
148 activity.object<jobject>(),
149 QJniObject::fromLocalRef(stringArray).object<jobjectArray>(),
150 QJniObject::fromString(mimeType).object<jstring>(),
151 QJniObject::fromString(QString("%1.provider").arg(qApp->organizationDomain())).object<jstring>());
152
153 if (_env->ExceptionCheck())
154 {
155 qDebug() << "trying to share dialog << exception";
156
157 _env->ExceptionClear();
158 throw InterfaceConnFailedException();
159 }
160 } else
161 throw InterfaceConnFailedException();
162}
163
164
166{
167 qDebug() << "trying to share text";
168 QJniEnvironment _env;
169 QJniObject activity = QJniObject::callStaticObjectMethod("org/qtproject/qt/android/QtNative", "activity", "()Landroid/app/Activity;"); // activity is valid
170 if (_env->ExceptionCheck()) {
171 _env->ExceptionClear();
172 throw InterfaceConnFailedException();
173 }
174 if (activity.isValid()) {
175 QJniObject::callStaticMethod<void>("com/kde/maui/tools/SendIntent", "sendText", "(Landroid/app/Activity;Ljava/lang/String;)V", activity.object<jobject>(), QJniObject::fromString(text).object<jstring>());
176
177 if (_env->ExceptionCheck()) {
178 _env->ExceptionClear();
179 throw InterfaceConnFailedException();
180 }
181 } else
182 throw InterfaceConnFailedException();
183}
184
186{
187 qDebug() << "trying to share link";
188 QJniEnvironment _env;
189 QJniObject activity = QJniObject::callStaticObjectMethod("org/qtproject/qt/android/QtNative", "activity", "()Landroid/app/Activity;"); // activity is valid
190 if (_env->ExceptionCheck()) {
191 _env->ExceptionClear();
192 throw InterfaceConnFailedException();
193 }
194 if (activity.isValid()) {
195 QJniObject::callStaticMethod<void>("com/kde/maui/tools/SendIntent", "sendUrl", "(Landroid/app/Activity;Ljava/lang/String;)V", activity.object<jobject>(), QJniObject::fromString(link).object<jstring>());
196
197 if (_env->ExceptionCheck()) {
198 _env->ExceptionClear();
199 throw InterfaceConnFailedException();
200 }
201 } else
202 throw InterfaceConnFailedException();
203}
204
206{
207 qDebug() << "trying to open file with";
208 QJniEnvironment _env;
209 QJniObject activity = QJniObject::callStaticObjectMethod("org/qtproject/qt/android/QtNative", "activity", "()Landroid/app/Activity;"); // activity is valid
210 if (_env->ExceptionCheck()) {
211 _env->ExceptionClear();
212 throw InterfaceConnFailedException();
213 }
214 if (activity.isValid())
215 {
216 QMimeDatabase mimedb;
217 QString mimeType = mimedb.mimeTypeForFile(url.toLocalFile()).name();
218
219 QJniObject::callStaticMethod<void>("com/kde/maui/tools/SendIntent",
220 "openUrl",
221 "(Landroid/app/Activity;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V",
222 activity.object<jobject>(),
223 QJniObject::fromString(url.toLocalFile()).object<jstring>(),
224 QJniObject::fromString(mimeType).object<jstring>(),
225 QJniObject::fromString(QString("%1.provider").arg(qApp->organizationDomain())).object<jstring>());
226
227 if (_env->ExceptionCheck()) {
228 _env->ExceptionClear();
229 throw InterfaceConnFailedException();
230 }
231 } else
232 throw InterfaceConnFailedException();
233}
234
236{
237 QJniObject mediaDir = QJniObject::callStaticObjectMethod("android/os/Environment", "getExternalStorageDirectory", "()Ljava/io/File;");
238 QJniObject mediaPath = mediaDir.callObjectMethod("getAbsolutePath", "()Ljava/lang/String;");
239
240 return mediaPath.toString();
241}
242
243QString MAUIAndroid::getStandardPath(QStandardPaths::StandardLocation path)
244{
245 QString type;
246 switch(path)
247 {
248 case QStandardPaths::PicturesLocation: type = "Pictures"; break;
249 case QStandardPaths::MusicLocation: type = "Music"; break;
250 case QStandardPaths::DocumentsLocation: type = "Documents"; break;
251 case QStandardPaths::DownloadLocation: type = "Download"; break;
252 case QStandardPaths::MoviesLocation: type = "Movies"; break;
253 default: type = "";
254 }
255
256 if(type.isEmpty())
257 return "";
258
259 QJniObject mediaDir = QJniObject::callStaticObjectMethod("android/os/Environment",
260 "getExternalStoragePublicDirectory",
261 "(Ljava/lang/String;)Ljava/io/File;",
262 QJniObject::fromString(type).object<jstring>());
263 QJniObject mediaPath = mediaDir.callObjectMethod("getAbsolutePath", "()Ljava/lang/String;");
264
265 return mediaPath.toString();
266}
267
269{
270 QStringList res;
271
272 // QJniObject mediaDir = QJniObject::callStaticObjectMethod("com/kde/maui/tools/SDCard", "findSdCardPath", "(Landroid/content/Context;)Ljava/io/File;", QtAndroid::androidActivity().object<jobject>());
273
274 // if (mediaDir == NULL)
275 // return res;
276
277 // QJniObject mediaPath = mediaDir.callObjectMethod("getAbsolutePath", "()Ljava/lang/String;");
278 // QString dataAbsPath = mediaPath.toString();
279
280 // res << QUrl::fromLocalFile(dataAbsPath).toString();
281 return res;
282}
283
284// void MAUIAndroid::handleActivityResult(int receiverRequestCode, int resultCode, const QJniObject &data)
285//{
286// qDebug() << "ACTIVITY RESULTS";
287// jint RESULT_OK = QJniObject::getStaticField<jint>("android/app/Activity", "RESULT_OK");
288
289// if (receiverRequestCode == 42 && resultCode == RESULT_OK) {
290// QString url = data.callObjectMethod("getData", "()Landroid/net/Uri;").callObjectMethod("getPath", "()Ljava/lang/String;").toString();
291// emit folderPicked(url);
292// }
293//}
294
296{
297 QJniEnvironment _env;
298 QJniObject activity = QJniObject::callStaticObjectMethod("org/qtproject/qt/android/QtNative", "activity", "()Landroid/app/Activity;"); // activity is valid
299 if (_env->ExceptionCheck()) {
300 _env->ExceptionClear();
301 throw InterfaceConnFailedException();
302 }
303 if (activity.isValid()) {
304 QJniObject::callStaticMethod<void>("com/example/android/tools/SendIntent", "fileChooser", "(Landroid/app/Activity;)V", activity.object<jobject>());
305 if (_env->ExceptionCheck()) {
306 _env->ExceptionClear();
307 throw InterfaceConnFailedException();
308 }
309 } else
310 throw InterfaceConnFailedException();
311}
312
313QVariantList MAUIAndroid::transform(const QJniObject &obj)
314{
315 QVariantList res;
316 const auto size = obj.callMethod<jint>("size", "()I");
317
318 for (auto i = 0; i < size; i++) {
319 QJniObject hashObj = obj.callObjectMethod("get", "(I)Ljava/lang/Object;", i);
320 res << createVariantMap(hashObj.object<jobject>());
321 }
322
323 return res;
324}
325
326QVariantMap MAUIAndroid::createVariantMap(jobject data)
327{
328 QVariantMap res;
329
330 QJniEnvironment env;
331 /* Reference : https://community.oracle.com/thread/1549999 */
332
333 // Get the HashMap Class
334 jclass jclass_of_hashmap = (env)->GetObjectClass(data);
335
336 // Get link to Method "entrySet"
337 jmethodID entrySetMethod = (env)->GetMethodID(jclass_of_hashmap, "entrySet", "()Ljava/util/Set;");
338
339 // Invoke the "entrySet" method on the HashMap object
340 jobject jobject_of_entryset = env->CallObjectMethod(data, entrySetMethod);
341
342 // Get the Set Class
343 jclass jclass_of_set = (env)->FindClass("java/util/Set"); // Problem during compilation !!!!!
344
345 if (jclass_of_set == 0) {
346 qWarning() << "java/util/Set lookup failed\n";
347 return res;
348 }
349
350 // Get link to Method "iterator"
351 jmethodID iteratorMethod = env->GetMethodID(jclass_of_set, "iterator", "()Ljava/util/Iterator;");
352
353 // Invoke the "iterator" method on the jobject_of_entryset variable of type Set
354 jobject jobject_of_iterator = env->CallObjectMethod(jobject_of_entryset, iteratorMethod);
355
356 // Get the "Iterator" class
357 jclass jclass_of_iterator = (env)->FindClass("java/util/Iterator");
358
359 // Get link to Method "hasNext"
360 jmethodID hasNextMethod = env->GetMethodID(jclass_of_iterator, "hasNext", "()Z");
361
362 jmethodID nextMethod = env->GetMethodID(jclass_of_iterator, "next", "()Ljava/lang/Object;");
363
364 while (env->CallBooleanMethod(jobject_of_iterator, hasNextMethod)) {
365 jobject jEntry = env->CallObjectMethod(jobject_of_iterator, nextMethod);
366 QJniObject entry = QJniObject(jEntry);
367 QJniObject key = entry.callObjectMethod("getKey", "()Ljava/lang/Object;");
368 QJniObject value = entry.callObjectMethod("getValue", "()Ljava/lang/Object;");
369 QString k = key.toString();
370
371 QVariant v = value.toString();
372
373 env->DeleteLocalRef(jEntry);
374
375 if (v.isNull()) {
376 continue;
377 }
378
379 res[k] = v;
380 }
381
382 if (env->ExceptionOccurred()) {
383 env->ExceptionDescribe();
384 env->ExceptionClear();
385 }
386
387 env->DeleteLocalRef(jclass_of_hashmap);
388 env->DeleteLocalRef(jobject_of_entryset);
389 env->DeleteLocalRef(jclass_of_set);
390 env->DeleteLocalRef(jobject_of_iterator);
391 env->DeleteLocalRef(jclass_of_iterator);
392
393 return res;
394}
395
396bool MAUIAndroid::hasKeyboard()
397{
398 QJniObject context = QNativeInterface::QAndroidApplication::context().object<jobject>();
399
400 if (context.isValid()) {
401 QJniObject resources = context.callObjectMethod("getResources", "()Landroid/content/res/Resources;");
402 QJniObject config = resources.callObjectMethod("getConfiguration", "()Landroid/content/res/Configuration;");
403 int value = config.getField<jint>("keyboard");
404 // QVariant v = value.toString();
405 qDebug() << "KEYBOARD" << value;
406
407 return value == 2 || value == 3; // KEYBOARD_12KEY || KEYBOARD_QWERTY
408
409 } else
410 throw InterfaceConnFailedException();
411}
412
413bool MAUIAndroid::hasMouse()
414{
415 return false;
416}
417
418static void accessAllFiles()
419{
421 qDebug() << "it is less then Android 11 - ALL FILES permission isn't possible!";
422 return;
423 }
424 qDebug() << "requesting ACCESS TO ALL FILES" << qApp->organizationDomain();
425
426 jboolean value = QJniObject::callStaticMethod<jboolean>("android/os/Environment", "isExternalStorageManager");
427 if(value == false)
428 {
429 qDebug() << "requesting ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION";
430 QJniObject ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION = QJniObject::getStaticObjectField( "android/provider/Settings", "ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION","Ljava/lang/String;" );
431 QJniObject intent("android/content/Intent", "(Ljava/lang/String;)V", ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION.object());
432 QJniObject jniPath = QJniObject::fromString("package:"+qApp->organizationDomain());
433 QJniObject jniUri = QJniObject::callStaticObjectMethod("android/net/Uri", "parse", "(Ljava/lang/String;)Landroid/net/Uri;", jniPath.object<jstring>());
434 QJniObject jniResult = intent.callObjectMethod("setData", "(Landroid/net/Uri;)Landroid/content/Intent;", jniUri.object<jobject>() );
436 } else {
437 qDebug() << "SUCCESS ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION";
438 }
439}
440
441bool MAUIAndroid::checkRunTimePermissions(const QStringList &permissions)
442{
443 for (const auto &permission : permissions)
444 {
445 if(permission == "android.permission.MANAGE_EXTERNAL_STORAGE")
446 {
447 accessAllFiles();
448 continue;
449 }
450
451 auto r = QtAndroidPrivate::checkPermission(permission).result();
452 if (r == QtAndroidPrivate::Denied)
453 {
454 r = QtAndroidPrivate::requestPermission(permission).result();
455 if (r == QtAndroidPrivate::Denied)
456 return false;
457 }
458 }
459 return true;
460}
461
462void MAUIAndroid::handleActivityResult(int receiverRequestCode, int resultCode, const QJniObject &data)
463{
464 qDebug() << "ACTIVITY RESULTS" << receiverRequestCode;
465 Q_EMIT this->hasKeyboardChanged();
466 jint RESULT_OK = QJniObject::getStaticField<jint>("android/app/Activity", "RESULT_OK");
467
468 if (receiverRequestCode == 42 && resultCode == RESULT_OK) {
469 QString url = data.callObjectMethod("getData", "()Landroid/net/Uri;").callObjectMethod("getPath", "()Ljava/lang/String;").toString();
470 emit folderPicked(url);
471 }
472}
473
474
475bool MAUIAndroid::darkModeEnabled()
476{
478 "com/kde/maui/tools/ConfigActivity",
479 "systemStyle",
480 "(Landroid/content/Context;)I",
482
483 return res == 1;
484}
485
486//JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* /*reserved*/)
487//{
488// JNIEnv* env;
489// if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
490// return JNI_ERR;
491// }
492
493// jclass javaClass = env->FindClass("com/kde/maui/tools/ConfigActivity");
494// if (!javaClass)
495// return JNI_ERR;
496
497// if (env->RegisterNatives(javaClass, methods, sizeof(methods) / sizeof(methods[0])) < 0) {
498// return JNI_ERR;
499// }
500// return JNI_VERSION_1_6;
501//}
The AbstractPlatform class Defines abstract methods and properties that are common to be implemeted b...
The MAUIAndroid class.
Definition mauiandroid.h:41
void folderPicked(QString path)
folderPicked
void shareText(const QString &text) override final
shareText
static void shareLink(const QString &link)
shareLink
static QString homePath()
homePath
static void openUrl(const QUrl &url)
openUrl
static QStringList sdDirs()
sdDirs
static void navBarColor(const QString &bg, const bool &light)
navBarColor
static void fileChooser()
fileChooser
static void statusbarColor(const QString &bg, const bool &light)
statusbarColor
void shareFiles(const QList< QUrl > &urls) override final
shareFiles
Type type(const QSqlDatabase &db)
KGUIADDONS_EXPORT QWindow * window(QObject *job)
QRgb rgba() const const
auto callMethod(const char *methodName, Args &&... args) const const
QJniObject callObjectMethod(const char *methodName, Args &&... args) const const
auto callStaticMethod(const char *className, const char *methodName, Args &&... args)
QJniObject callStaticObjectMethod(const char *className, const char *methodName, Args &&... args)
QJniObject fromLocalRef(jobject localRef)
QJniObject fromString(const QString &string)
auto getField(const char *fieldName) const const
auto getStaticField(const char *className, const char *fieldName)
QJniObject getStaticObjectField(const char *className, const char *fieldName)
bool isValid() const const
jobject object() const const
QString toString() const const
qsizetype count() const const
T & first()
bool isEmpty() const const
QMimeType mimeTypeForFile(const QFileInfo &fileInfo, MatchMode mode) const const
QFuture< QVariant > runOnAndroidMainThread(const std::function< QVariant()> &runnable, const QDeadlineTimer timeout)
Q_EMITQ_EMIT
QOperatingSystemVersion current()
QString arg(Args &&... args) const const
QFuture< QtAndroidPrivate::PermissionResult > checkPermission(const QString &permission)
QFuture< QtAndroidPrivate::PermissionResult > requestPermission(const QString &permission)
void startActivity(const QAndroidIntent &intent, int receiverRequestCode, QAndroidActivityResultReceiver *resultReceiver)
QString toLocalFile() const const
bool isNull() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2025 The KDE developers.
Generated on Fri Jan 31 2025 12:11:16 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.