├── .gitignore
├── AndroidFileDialog.qml
├── Demo
├── .gitignore
├── ContentTab.qml
├── Demo.pro
├── PreferenceTab.qml
├── UtilsTab.qml
├── android
│ ├── AndroidManifest.xml
│ ├── build.gradle
│ └── res
│ │ └── values
│ │ └── libs.xml
├── main.cpp
├── main.qml
├── opener.cpp
├── opener.h
├── prefmodel.cpp
├── prefmodel.h
├── qml.qrc
└── qtquickcontrols2.conf
├── FileDialog.qml
├── LICENSE
├── README.md
├── android
├── androidutils.gradle
└── src
│ └── de
│ └── skycoder42
│ └── androidutils
│ ├── AlarmReceiver.java
│ ├── AndroidUtils.java
│ ├── FileChooser.java
│ └── PrefHelper.java
├── androidutils.cpp
├── androidutils.h
├── androidutils_de.ts
├── androidutils_jni.cpp
├── androidutils_template.ts
├── contentdevice.cpp
├── contentdevice.h
├── de_skycoder42_androidutils.prc
├── de_skycoder42_androidutils.pri
├── de_skycoder42_androidutils.qrc
├── filechooser.cpp
├── filechooser.h
├── qmldir
├── qpm.json
├── qpmx.json
├── sharedpreferences.cpp
└── sharedpreferences.h
/.gitignore:
--------------------------------------------------------------------------------
1 | # C++ objects and libs
2 |
3 | *.slo
4 | *.lo
5 | *.o
6 | *.a
7 | *.la
8 | *.lai
9 | *.so
10 | *.dll
11 | *.dylib
12 |
13 | # Qt-es
14 |
15 | /.qmake.cache
16 | /.qmake.stash
17 | *.pro.user
18 | *.pro.user.*
19 | *.qbs.user
20 | *.qbs.user.*
21 | *.moc
22 | moc_*.cpp
23 | qrc_*.cpp
24 | ui_*.h
25 | Makefile*
26 | *build-*
27 |
28 | # QtCreator
29 |
30 | *.autosave
31 |
32 | # QtCtreator Qml
33 | *.qmlproject.user
34 | *.qmlproject.user.*
35 |
36 | # QtCtreator CMake
37 | CMakeLists.txt.user
38 |
39 | # qpm
40 | vendor
--------------------------------------------------------------------------------
/AndroidFileDialog.qml:
--------------------------------------------------------------------------------
1 | import QtQuick 2.8
2 | import QtQuick.Window 2.2
3 | import Qt.labs.platform 1.0
4 | import de.skycoder42.androidutils 1.1
5 |
6 | FileChooser {
7 | id: fileDialog
8 |
9 | property var labsDialog: null
10 | property alias fileChooser: fileDialog
11 |
12 | property bool saveFile: false
13 | property int dialogFlags: 0
14 | property string defaultSuffix
15 | property string folder
16 | property alias file: fileDialog.contentUrl
17 | property var nameFilters
18 |
19 | type: saveFile ? FileChooser.CreateDocument : FileChooser.OpenDocument
20 | contentUrl: folder + "file" + defaultSuffix
21 | }
22 |
--------------------------------------------------------------------------------
/Demo/.gitignore:
--------------------------------------------------------------------------------
1 | # This file is used to ignore files which are generated
2 | # ----------------------------------------------------------------------------
3 |
4 | *~
5 | *.autosave
6 | *.a
7 | *.core
8 | *.moc
9 | *.o
10 | *.obj
11 | *.orig
12 | *.rej
13 | *.so
14 | *.so.*
15 | *_pch.h.cpp
16 | *_resource.rc
17 | *.qm
18 | .#*
19 | *.*#
20 | core
21 | !core/
22 | tags
23 | .DS_Store
24 | .directory
25 | *.debug
26 | Makefile*
27 | *.prl
28 | *.app
29 | moc_*.cpp
30 | ui_*.h
31 | qrc_*.cpp
32 | Thumbs.db
33 | *.res
34 | *.rc
35 | /.qmake.cache
36 | /.qmake.stash
37 |
38 | # qtcreator generated files
39 | *.pro.user*
40 |
41 | # xemacs temporary files
42 | *.flc
43 |
44 | # Vim temporary files
45 | .*.swp
46 |
47 | # Visual Studio generated files
48 | *.ib_pdb_index
49 | *.idb
50 | *.ilk
51 | *.pdb
52 | *.sln
53 | *.suo
54 | *.vcproj
55 | *vcproj.*.*.user
56 | *.ncb
57 | *.sdf
58 | *.opensdf
59 | *.vcxproj
60 | *vcxproj.*
61 |
62 | # MinGW generated files
63 | *.Debug
64 | *.Release
65 |
66 | # Python byte code
67 | *.pyc
68 |
69 | # Binaries
70 | # --------
71 | *.dll
72 | *.exe
73 |
74 |
--------------------------------------------------------------------------------
/Demo/ContentTab.qml:
--------------------------------------------------------------------------------
1 | import QtQuick 2.8
2 | import QtQuick.Controls 2.1
3 | import QtQuick.Layouts 1.3
4 | import de.skycoder42.androidutils 1.1
5 | import de.skycoder42.androidutils.demo 1.0
6 |
7 | Pane {
8 | FileDialog {
9 | id: fileDialog
10 |
11 | saveFile: openBox.checked
12 |
13 | onAccepted: AndroidUtils.showToast("File choosen: " + fileDialog.file, true)
14 | onRejected: AndroidUtils.showToast("file choosing aborted!")
15 | }
16 |
17 | Opener {
18 | id: opener
19 |
20 | onDataLoaded: editField.text = data
21 | }
22 |
23 | GridLayout {
24 | anchors.fill: parent
25 | columns: 3
26 |
27 | TextField {
28 | id: contentField
29 | text: fileDialog.file
30 |
31 | Layout.fillWidth: true
32 | Layout.columnSpan: 2
33 |
34 | onEditingFinished: chooser.contentUrl = contentField.text
35 | }
36 |
37 | CheckBox {
38 | id: openBox
39 | text: "Get save file"
40 | }
41 |
42 | Button {
43 | id: chooserButton
44 | Layout.fillWidth: true
45 | text: "Open file chooser"
46 | onClicked: fileDialog.open()
47 | }
48 |
49 | Button {
50 | id: editButton
51 | Layout.fillWidth: true
52 | text: "Display/Edit File"
53 |
54 | onClicked: opener.openFile(fileDialog.file)
55 | }
56 |
57 | Button {
58 | id: saveButton
59 | Layout.fillWidth: true
60 | text: "Save File"
61 |
62 | onClicked: opener.saveFile(fileDialog.file, editField.text)
63 | }
64 |
65 | TextArea {
66 | id: editField
67 | Layout.fillHeight: true
68 | Layout.fillWidth: true
69 | Layout.columnSpan: 3
70 | }
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/Demo/Demo.pro:
--------------------------------------------------------------------------------
1 | QT += qml quick widgets
2 |
3 | CONFIG += c++11
4 |
5 | TARGET = AndroidUtilsDemo
6 |
7 | include(../de_skycoder42_androidutils.pri)
8 |
9 | SOURCES += main.cpp \
10 | opener.cpp \
11 | prefmodel.cpp
12 |
13 | RESOURCES += qml.qrc
14 |
15 | # Additional import path used to resolve QML modules in Qt Creator's code model
16 | QML_IMPORT_PATH =
17 |
18 | # Additional import path used to resolve QML modules just for Qt Quick Designer
19 | QML_DESIGNER_IMPORT_PATH =
20 |
21 | # The following define makes your compiler emit warnings if you use
22 | # any feature of Qt which as been marked deprecated (the exact warnings
23 | # depend on your compiler). Please consult the documentation of the
24 | # deprecated API in order to know how to port your code away from it.
25 | DEFINES += QT_DEPRECATED_WARNINGS
26 |
27 | # You can also make your code fail to compile if you use deprecated APIs.
28 | # In order to do so, uncomment the following line.
29 | # You can also select to disable deprecated APIs only up to a certain version of Qt.
30 | #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
31 |
32 | # Default rules for deployment.
33 | qnx: target.path = /tmp/$${TARGET}/bin
34 | else: unix:!android: target.path = /opt/$${TARGET}/bin
35 | !isEmpty(target.path): INSTALLS += target
36 |
37 | DISTFILES += \
38 | android/AndroidManifest.xml \
39 | android/res/values/libs.xml \
40 | android/build.gradle
41 |
42 | ANDROID_PACKAGE_SOURCE_DIR = $$PWD/android
43 |
44 | HEADERS += \
45 | opener.h \
46 | prefmodel.h
47 |
48 | !ReleaseBuild:!DebugBuild:!system(qpmx -d $$shell_quote($$_PRO_FILE_PWD_/..) --qmake-run init $$QPMX_EXTRA_OPTIONS $$shell_quote($$QMAKE_QMAKE) $$shell_quote($$OUT_PWD)): error(qpmx initialization failed. Check the compilation log for details.)
49 | else: include($$OUT_PWD/qpmx_generated.pri)
50 |
--------------------------------------------------------------------------------
/Demo/PreferenceTab.qml:
--------------------------------------------------------------------------------
1 | import QtQuick 2.8
2 | import QtQuick.Controls 2.1
3 | import QtQuick.Layouts 1.3
4 | import de.skycoder42.androidutils.demo 1.0
5 |
6 | Pane {
7 |
8 | PrefModel {
9 | id: pModel
10 | }
11 |
12 | GridLayout {
13 | anchors.fill: parent
14 | columns: 3
15 |
16 | TextField {
17 | id: keyField
18 | placeholderText: "key"
19 | }
20 |
21 | TextField {
22 | id: valueField
23 | placeholderText: "value"
24 | Layout.fillWidth: true
25 | }
26 |
27 | ToolButton {
28 | text: "+"
29 | onClicked: pModel.save(keyField.text, valueField.text)
30 | }
31 |
32 | ListView {
33 | model: pModel.model
34 |
35 | Layout.columnSpan: 3
36 | Layout.fillWidth: true
37 | Layout.fillHeight: true
38 |
39 | delegate: ItemDelegate {
40 | text: modelData
41 | width: parent.width
42 |
43 | onPressAndHold: pModel.remove(index)
44 | }
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/Demo/UtilsTab.qml:
--------------------------------------------------------------------------------
1 | import QtQuick 2.8
2 | import QtQuick.Controls 2.1
3 | import QtQuick.Layouts 1.3
4 | import de.skycoder42.androidutils 1.1
5 |
6 | Pane {
7 | ColumnLayout {
8 | anchors.centerIn: parent
9 |
10 | TextField {
11 | id: color
12 | placeholderText: "Enter a hex color value"
13 | inputMask: "\\#HHHHHH"
14 | text: "#ABCDEF"
15 | }
16 | Button {
17 | id: changeButton
18 | text: "Change color"
19 | onClicked: AndroidUtils.setStatusBarColor(color.text)
20 | }
21 |
22 | CheckBox {
23 | id: longBox
24 | text: "Long toast"
25 | }
26 | Button {
27 | id: toastButton
28 | text: "Show Toast"
29 | onClicked: AndroidUtils.showToast("This is a toast", longBox.checked)
30 | }
31 |
32 | Button {
33 | id: longButton
34 | text: "Press me long!"
35 | onPressAndHold: AndroidUtils.hapticFeedback(AndroidUtils.LongPress)
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/Demo/android/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
71 |
72 |
73 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
--------------------------------------------------------------------------------
/Demo/android/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | repositories {
3 | jcenter()
4 | }
5 |
6 | dependencies {
7 | classpath 'com.android.tools.build:gradle:1.1.0'
8 | }
9 | }
10 |
11 | allprojects {
12 | repositories {
13 | jcenter()
14 | }
15 | }
16 |
17 | apply plugin: 'com.android.application'
18 |
19 | dependencies {
20 | compile fileTree(dir: 'libs', include: ['*.jar'])
21 | }
22 |
23 | android {
24 | /*******************************************************
25 | * The following variables:
26 | * - androidBuildToolsVersion,
27 | * - androidCompileSdkVersion
28 | * - qt5AndroidDir - holds the path to qt android files
29 | * needed to build any Qt application
30 | * on Android.
31 | *
32 | * are defined in gradle.properties file. This file is
33 | * updated by QtCreator and androiddeployqt tools.
34 | * Changing them manually might break the compilation!
35 | *******************************************************/
36 |
37 | compileSdkVersion androidCompileSdkVersion.toInteger()
38 |
39 | buildToolsVersion androidBuildToolsVersion
40 |
41 | sourceSets {
42 | main {
43 | manifest.srcFile 'AndroidManifest.xml'
44 | java.srcDirs = [qt5AndroidDir + '/src', 'src', 'java']
45 | aidl.srcDirs = [qt5AndroidDir + '/src', 'src', 'aidl']
46 | res.srcDirs = [qt5AndroidDir + '/res', 'res']
47 | resources.srcDirs = ['src']
48 | renderscript.srcDirs = ['src']
49 | assets.srcDirs = ['assets']
50 | jniLibs.srcDirs = ['libs']
51 | }
52 | }
53 |
54 | lintOptions {
55 | abortOnError false
56 | }
57 | }
58 |
59 | apply from: "androidutils.gradle"
60 |
--------------------------------------------------------------------------------
/Demo/android/res/values/libs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | - https://download.qt.io/ministro/android/qt5/qt-5.8
5 |
6 |
7 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/Demo/main.cpp:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include
5 | #include "opener.h"
6 | #include "prefmodel.h"
7 | #ifdef Q_OS_ANDROID
8 | #include
9 | #include
10 | #endif
11 |
12 | int main(int argc, char *argv[])
13 | {
14 | QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
15 | QApplication app(argc, argv);
16 |
17 | qmlRegisterType("de.skycoder42.androidutils.demo", 1, 0, "Opener");
18 | qmlRegisterType("de.skycoder42.androidutils.demo", 1, 0, "PrefModel");
19 |
20 | QQmlApplicationEngine engine;
21 | engine.load(QUrl(QLatin1String("qrc:/main.qml")));
22 |
23 | return app.exec();
24 | }
25 |
--------------------------------------------------------------------------------
/Demo/main.qml:
--------------------------------------------------------------------------------
1 | import QtQuick 2.8
2 | import QtQuick.Controls 2.1
3 | import QtQuick.Layouts 1.3
4 | import de.skycoder42.androidutils 1.1
5 | import Qt.labs.platform 1.0 as Labs//fix because of file tree
6 |
7 | ApplicationWindow {
8 | visible: true
9 | width: 640
10 | height: 480
11 | title: qsTr("Hello World")
12 |
13 | footer: TabBar {
14 | id: tabBar
15 | currentIndex: swipeView.currentIndex
16 |
17 | TabButton {
18 | id: btn0
19 | text: "Utils"
20 | }
21 |
22 | TabButton {
23 | id: btn1
24 | text: "File Chooser"
25 | }
26 |
27 | TabButton {
28 | id: btn2
29 | text: "Preferences"
30 | }
31 | }
32 |
33 | Page {
34 | anchors.fill: parent
35 |
36 | SwipeView {
37 | id: swipeView
38 | anchors.fill: parent
39 | currentIndex: tabBar.currentIndex
40 |
41 | UtilsTab {}
42 | ContentTab {}
43 | PreferenceTab {}
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/Demo/opener.cpp:
--------------------------------------------------------------------------------
1 | #include "opener.h"
2 | #include
3 | #ifdef Q_OS_ANDROID
4 | #include
5 | #include
6 | #endif
7 |
8 | Opener::Opener(QObject *parent) :
9 | QObject(parent)
10 | {}
11 |
12 | void Opener::openFile(const QUrl &file)
13 | {
14 | #ifdef Q_OS_ANDROID
15 | ContentDevice device(QtAndroid::androidActivity(), file);
16 | #else
17 | QFile device(file.toLocalFile());
18 | #endif
19 | device.open(QIODevice::ReadOnly);
20 | auto data = QString::fromUtf8(device.readAll());
21 | device.close();
22 | emit dataLoaded(data);
23 | }
24 |
25 | void Opener::saveFile(const QUrl &file, const QString &data)
26 | {
27 | #ifdef Q_OS_ANDROID
28 | ContentDevice device(QtAndroid::androidActivity(), file);
29 | #else
30 | QFile device(file.toLocalFile());
31 | #endif
32 | device.open(QIODevice::WriteOnly);
33 | device.write(data.toUtf8());
34 | device.close();
35 | }
36 |
--------------------------------------------------------------------------------
/Demo/opener.h:
--------------------------------------------------------------------------------
1 | #ifndef OPENER_H
2 | #define OPENER_H
3 |
4 | #include
5 | #include
6 |
7 | class Opener : public QObject
8 | {
9 | Q_OBJECT
10 | public:
11 | explicit Opener(QObject *parent = nullptr);
12 |
13 | public slots:
14 | void openFile(const QUrl &file);
15 | void saveFile(const QUrl &file, const QString &data);
16 |
17 | signals:
18 | void dataLoaded(const QString &data);
19 | };
20 |
21 | #endif // OPENER_H
22 |
--------------------------------------------------------------------------------
/Demo/prefmodel.cpp:
--------------------------------------------------------------------------------
1 | #include "prefmodel.h"
2 | #include
3 |
4 | PrefModel::PrefModel(QObject *parent) :
5 | QObject(parent),
6 | prefs(SharedPreferences::getPreferences(this))
7 | {
8 | connect(prefs, &SharedPreferences::loaded,
9 | this, &PrefModel::modelChanged);
10 | connect(prefs, &SharedPreferences::changed,
11 | this, &PrefModel::modelChanged);
12 | }
13 |
14 | QStringList PrefModel::model() const
15 | {
16 | auto data = prefs->data();
17 | QStringList model;
18 | for(auto it = data.constBegin(); it != data.constEnd(); it++)
19 | model.append(it.key() + ": " + it.value().toString());
20 | return model;
21 | }
22 |
23 | void PrefModel::save(const QString &key, const QVariant &value)
24 | {
25 | prefs->setValue(key, value);
26 | }
27 |
28 | void PrefModel::remove(int index)
29 | {
30 | prefs->remove(prefs->keys().at(index));
31 | }
32 |
--------------------------------------------------------------------------------
/Demo/prefmodel.h:
--------------------------------------------------------------------------------
1 | #ifndef PREFMODEL_H
2 | #define PREFMODEL_H
3 |
4 | #include
5 | #include
6 |
7 | class PrefModel : public QObject
8 | {
9 | Q_OBJECT
10 |
11 | Q_PROPERTY(QStringList model READ model NOTIFY modelChanged)
12 |
13 | public:
14 | explicit PrefModel(QObject *parent = nullptr);
15 |
16 | QStringList model() const;
17 |
18 | public slots:
19 | void save(const QString &key, const QVariant &value);
20 | void remove(int index);
21 |
22 | signals:
23 | void modelChanged();
24 |
25 | private:
26 | SharedPreferences *prefs;
27 | };
28 |
29 | #endif // PREFMODEL_H
30 |
--------------------------------------------------------------------------------
/Demo/qml.qrc:
--------------------------------------------------------------------------------
1 |
2 |
3 | main.qml
4 | qtquickcontrols2.conf
5 | UtilsTab.qml
6 | ContentTab.qml
7 | PreferenceTab.qml
8 |
9 |
10 |
--------------------------------------------------------------------------------
/Demo/qtquickcontrols2.conf:
--------------------------------------------------------------------------------
1 | ; This file can be edited to change the style of the application
2 | ; See Styling Qt Quick Controls 2 in the documentation for details:
3 | ; http://doc.qt.io/qt-5/qtquickcontrols2-styles.html
4 |
5 | [Controls]
6 | Style=Material
7 |
8 | [Universal]
9 | Theme=Light
10 | ;Accent=Steel
11 |
12 | [Material]
13 | Theme=Light
14 | ;Accent=BlueGrey
15 | ;Primary=BlueGray
16 |
--------------------------------------------------------------------------------
/FileDialog.qml:
--------------------------------------------------------------------------------
1 | import Qt.labs.platform 1.0 as Labs
2 | import de.skycoder42.androidutils 1.1
3 |
4 | Labs.FileDialog {
5 | id: fileDialog
6 |
7 | property alias labsDialog: fileDialog
8 | property var fileChooser: null
9 |
10 | property bool saveFile: false
11 | property alias dialogFlags: fileDialog.options
12 | property int chooserFlags: FileChooser.OpenableFlag
13 | property string mimeType: "*/*"
14 |
15 | fileMode: saveFile ? Labs.FileDialog.SaveFile : Labs.FileDialog.OpenFile
16 | modality: Qt.WindowModal
17 | }
18 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | BSD 3-Clause License
2 |
3 | Copyright (c) 2017, Felix Barz
4 | All rights reserved.
5 |
6 | Redistribution and use in source and binary forms, with or without
7 | modification, are permitted provided that the following conditions are met:
8 |
9 | * Redistributions of source code must retain the above copyright notice, this
10 | list of conditions and the following disclaimer.
11 |
12 | * Redistributions in binary form must reproduce the above copyright notice,
13 | this list of conditions and the following disclaimer in the documentation
14 | and/or other materials provided with the distribution.
15 |
16 | * Neither the name of the copyright holder nor the names of its
17 | contributors may be used to endorse or promote products derived from
18 | this software without specific prior written permission.
19 |
20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # AndroidUtils
2 | Utils for easy c++ and qml integration of common android features
3 |
4 | ## Features
5 | - Utilizes [androidnative.pri](https://github.com/benlau/androidnative.pri)
6 | - Adds qmake wrappers and additional code to make integration easier
7 | - Automatically registers androidnative when registering AndroidUtils
8 | - Adds a singleton for c++/qml
9 | - Show a toast
10 | - Trigger haptic feedback
11 | - Change the statusbar color
12 | - Adds the `ContentDevice` class (android only)
13 | - QIODevice class for android contents
14 | - Allows you to open genric `content` uris
15 | - This includes local files, as well as data from services like Dropbox, Google Drive, etc.
16 | - Adds the `FileChooser` class
17 | - The Android equivalent of a file dialog
18 | - Shows an android file chooser activity to get/create a content uri
19 | - Provides `FileDialog` qml type that provides a file dialog for all platforms
20 | - On Android, uses the FileChooser
21 | - On other Platforms, the [Qt labs FileDialog](https://doc.qt.io/qt-5/qml-qt-labs-platform-filedialog.html) is used
22 | - Adds the `SharedPreferences` class
23 | - Allows easy C++ access to Android shared preferences
24 | - Notifies of changes in the preferences
25 |
26 | ## Installation
27 | The package is providet as qpm package, [`de.skycoder42.androidutils`](https://www.qpm.io/packages/de.skycoder42.androidutils/index.html). To install:
28 |
29 | 1. Install qpm (See [GitHub - Installing](https://github.com/Cutehacks/qpm/blob/master/README.md#installing), for **windows** see below)
30 | 2. In your projects root directory, run `qpm install de.skycoder42.androidutils`
31 | 3. Include qpm to your project by adding `include(vendor/vendor.pri)` to your `.pro` file
32 | 4. Add the line `apply from: "androidutils.gradle"` at the very bottom of your `build.gradle` file
33 | - That file is located in your android source directory, the directory with your manifest (See [`Demo/android`](Demo/android))
34 | - If you don't have the folder, you can let QtCreator create one for you. Go to `Projects > [Your Kit] Build > Create Android APK` and create the templates by pressing `Create Templates`
35 |
36 | Check their [GitHub - Usage for App Developers](https://github.com/Cutehacks/qpm/blob/master/README.md#usage-for-app-developers) to learn more about qpm.
37 |
38 | **Important for Windows users:** QPM Version *0.10.0* (the one you can download on the website) is currently broken on windows! It's already fixed in master, but not released yet. Until a newer versions gets released, you can download the latest dev build from here:
39 | - https://storage.googleapis.com/www.qpm.io/download/latest/windows_amd64/qpm.exe
40 | - https://storage.googleapis.com/www.qpm.io/download/latest/windows_386/qpm.exe
41 |
42 | ## Usage
43 | Just include/import (`import de.skycoder42.androidutils 1.0`) the class and use the methods. All the setup and registrations are done automatically.
44 |
45 | ### JNI_OnLoad
46 | By default, AndroidUtils defines the `JNI_OnLoad` for you. However, if you need to define this method yourself, this will create a conflict! To prevent AndroidUtils from creating the method, add the following line **before** including the vendor.pri
47 |
48 | ```pro
49 | CONFIG += noJniOnLoad
50 | include(vendor/vendor.pri)
51 | # ...
52 | ```
53 |
54 | In that case, make shure you initialize androidnative.pri by adding `AndroidNative::SystemDispatcher::registerNatives();` to your `JNI_onLoad` implementation!
55 |
56 | ### QPM_ROOT
57 | This variable should contain the root directory of qpm, i.e. the `vendor` directory. By default (if you don't set the variable), it is set to `$$_PRO_FILE_PWD_/vendor`. If your qpm vendor folder is not located in the same directory as your pro file, set this variable to the **absolute** path of your vendor folder. If you use the `_PRO_FILE_PWD_`, you can simply make the path relative to this directory, e.g. `QPM_ROOT = $$_PRO_FILE_PWD_/../vendor`.
58 |
59 | ## Android URI permissions
60 | To open generic content uris, you need to get permissions for those. For local files, this are the normal read/write external storage permissions. For other content, i.e. from Dropbox, those permissions are granted for every uri. Those permissions will stay as long as your application is running, but if you want to access them later, e.g. after a restart, you need to persist the permissions. The FileChooser can do this for you, by setting the required `chooserFlags`. If you get Security exceptions when trying to read/write a file, this is what you need to adjust.
61 |
--------------------------------------------------------------------------------
/android/androidutils.gradle:
--------------------------------------------------------------------------------
1 | // Obtain androidPackageSourceDir
2 | // androidPackageSourceDir is the absolute path of the folder containing build.gradle and AndroidManifests.xml
3 | // This code also works with androiddeployqt.
4 |
5 | dependencies {
6 | compile "com.android.support:support-core-utils:+"
7 | }
8 |
9 | String addAndroidUtilsPath(String path) {
10 | LinkedHashSet hash = android.sourceSets.main.java.srcDirs;
11 | hash.add(path);
12 | android.sourceSets.main.java.srcDirs = hash;
13 | }
14 |
15 | ext {
16 | addAndroidUtilsPath = this.&addAndroidUtilsPath;
17 | }
18 |
--------------------------------------------------------------------------------
/android/src/de/skycoder42/androidutils/AlarmReceiver.java:
--------------------------------------------------------------------------------
1 | package de.skycoder42.androidutils;
2 |
3 | import android.content.Context;
4 | import android.content.Intent;
5 | import android.app.PendingIntent;
6 | import android.app.AlarmManager;
7 | import android.util.Log;
8 | import android.support.v4.content.WakefulBroadcastReceiver;
9 |
10 | public class AlarmReceiver extends WakefulBroadcastReceiver {
11 | private static final String TAG = "de.skycoder42.androidutils.AlarmReceiver";
12 | private static final int ALARM_INTENT_ID = 20;
13 | private static final String CLASS_NAME_EXTRA = "de.skycoder42.androidutils.classname";
14 |
15 | @Override
16 | public void onReceive(Context context, Intent intent) {
17 | try {
18 | Class classType = Class.forName(intent.getStringExtra(CLASS_NAME_EXTRA));
19 | Intent serviceIntent = new Intent(context, classType);
20 | startWakefulService(context, serviceIntent);
21 | } catch(ClassNotFoundException e) {
22 | Log.e(TAG, e.getMessage());
23 | e.printStackTrace();
24 | }
25 | }
26 |
27 | static public void scheduleAutoCheck(Context context, boolean autoCheck, String serviceClass) {
28 | Intent intent = new Intent(context, AlarmReceiver.class);
29 | intent.putExtra(CLASS_NAME_EXTRA, serviceClass);
30 |
31 | PendingIntent pending = PendingIntent.getBroadcast(context,
32 | ALARM_INTENT_ID,
33 | intent,
34 | PendingIntent.FLAG_UPDATE_CURRENT);
35 |
36 | AlarmManager alarm = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
37 | if(autoCheck) {
38 | alarm.setRepeating(AlarmManager.RTC_WAKEUP,
39 | System.currentTimeMillis() + AlarmManager.INTERVAL_DAY,
40 | AlarmManager.INTERVAL_DAY,
41 | pending);
42 | } else {
43 | alarm.cancel(pending);
44 | pending.cancel();
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/android/src/de/skycoder42/androidutils/AndroidUtils.java:
--------------------------------------------------------------------------------
1 | package de.skycoder42.androidutils;
2 |
3 | import java.util.Map;
4 | import android.os.Build;
5 | import android.os.Handler;
6 | import android.content.Context;
7 | import android.app.Activity;
8 | import android.graphics.Color;
9 | import android.view.View;
10 | import android.view.Window;
11 | import android.view.WindowManager;
12 | import org.qtproject.qt5.android.QtNative;
13 | import androidnative.SystemDispatcher;
14 |
15 | public class AndroidUtils {
16 |
17 | static {
18 | SystemDispatcher.addListener(new SystemDispatcher.Listener() {
19 |
20 | public void onDispatched(String name , Map data) {
21 |
22 | if (name.equals("AndroidUtils.hapticFeedback"))
23 | AndroidUtils.hapticFeedback((Integer)data.get("feedbackConstant"));
24 | else if (name.equals("AndroidUtils.setStatusBarColor"))
25 | AndroidUtils.setStatusBarColor((String)data.get("color"));
26 | }
27 | });
28 | }
29 |
30 | static void hapticFeedback(final int feedbackConstant) {
31 | final Activity activity = QtNative.activity();
32 |
33 | Runnable runnable = new Runnable () {
34 | public void run() {
35 | View rootView = activity.getWindow().getDecorView().getRootView();
36 | rootView.performHapticFeedback(feedbackConstant);
37 | };
38 | };
39 | activity.runOnUiThread(runnable);
40 | }
41 |
42 | static void setStatusBarColor(String colorName) {
43 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
44 | final int color = Color.parseColor(colorName);
45 | final Activity activity = QtNative.activity();
46 |
47 | Runnable runnable = new Runnable () {
48 | public void run() {
49 | Window window = activity.getWindow();
50 | window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
51 | window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
52 | window.setStatusBarColor(color);
53 | };
54 | };
55 | activity.runOnUiThread(runnable);
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/android/src/de/skycoder42/androidutils/FileChooser.java:
--------------------------------------------------------------------------------
1 | package de.skycoder42.androidutils;
2 |
3 | import java.util.Map;
4 | import java.util.HashMap;
5 | import android.net.Uri;
6 | import android.content.Context;
7 | import android.content.Intent;
8 | import android.content.ContentResolver;
9 | import android.app.Activity;
10 | import org.qtproject.qt5.android.QtNative;
11 | import androidnative.SystemDispatcher;
12 |
13 | public class FileChooser {
14 | private static final int CHOOSE_CONTENT_ACTION = 0x1091c657;
15 | private static final int CHOOSE_PERSISTENT_ACTION = 0x1091c658;
16 |
17 | private static final String GET_CONTENT_MESSAGE = "AndroidUtils.FileChooser.getContent";
18 | private static final String OPEN_DOCUMENT_MESSAGE = "AndroidUtils.FileChooser.openDocument";
19 | private static final String CREATE_DOCUMENT_MESSAGE = "AndroidUtils.FileChooser.createDocument";
20 | private static final String CONTENT_CHOOSEN_MESSAGE = "AndroidUtils.FileChooser.contentChoosen";
21 |
22 | static {
23 | SystemDispatcher.addListener(new SystemDispatcher.Listener() {
24 | public void onDispatched(String type , Map message) {
25 | if (type.equals(GET_CONTENT_MESSAGE)) {
26 | getContent((String)message.get("title"),
27 | (String)message.get("mime"),
28 | (Boolean)message.get("openable"),
29 | (Boolean)message.get("localOnly"),
30 | (Boolean)message.get("grantWrite"));
31 | } else if (type.equals(OPEN_DOCUMENT_MESSAGE)) {
32 | openDocument((String)message.get("title"),
33 | (String)message.get("mime"),
34 | (String)message.get("url"),
35 | (Boolean)message.get("openable"),
36 | (Boolean)message.get("grantWrite"),
37 | (Boolean)message.get("persistPermissions"));
38 | } else if (type.equals(CREATE_DOCUMENT_MESSAGE)) {
39 | createDocument((String)message.get("title"),
40 | (String)message.get("mime"),
41 | (String)message.get("url"),
42 | (String)message.get("name"),
43 | (Boolean)message.get("openable"),
44 | (Boolean)message.get("persistPermissions"));
45 | } else if (type.equals(SystemDispatcher.ACTIVITY_RESULT_MESSAGE)) {
46 | onActivityResult((Integer)message.get("requestCode"),
47 | (Integer)message.get("resultCode"),
48 | (Intent)message.get("data"));
49 | }
50 | }
51 | });
52 | }
53 |
54 | static private void getContent(final String title, String mime, boolean openable, boolean localOnly, boolean grantWrite) {
55 | final Activity activity = QtNative.activity();
56 |
57 | final Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
58 | intent.setType(mime);
59 | intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
60 | if(grantWrite)
61 | intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
62 | if(openable)
63 | intent.addCategory(Intent.CATEGORY_OPENABLE);
64 | if(localOnly)
65 | intent.putExtra(Intent.EXTRA_LOCAL_ONLY, true);
66 |
67 | Runnable runnable = new Runnable () {
68 | public void run() {
69 | activity.startActivityForResult(
70 | Intent.createChooser(intent, title),
71 | CHOOSE_CONTENT_ACTION);
72 | };
73 | };
74 | activity.runOnUiThread(runnable);
75 | }
76 |
77 | static private void openDocument(final String title, String mime, String url, boolean openable, boolean grantWrite, final boolean persistPermissions) {
78 | final Activity activity = QtNative.activity();
79 |
80 | final Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
81 | intent.setType(mime);
82 | intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
83 | intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
84 | if(grantWrite)
85 | intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
86 | if(openable)
87 | intent.addCategory(Intent.CATEGORY_OPENABLE);
88 | //TODO add in Android O
89 | // if(!url.isEmpty())
90 | // intent.putExtra(Intent.EXTRA_INITIAL_URI, Uri.parse(url));
91 |
92 | Runnable runnable = new Runnable () {
93 | public void run() {
94 | activity.startActivityForResult(
95 | Intent.createChooser(intent, title),
96 | persistPermissions ? CHOOSE_PERSISTENT_ACTION : CHOOSE_CONTENT_ACTION);
97 | };
98 | };
99 | activity.runOnUiThread(runnable);
100 | }
101 |
102 | static private void createDocument(final String title, String mime, String url, String name, boolean openable, final boolean persistPermissions) {
103 | final Activity activity = QtNative.activity();
104 |
105 | final Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
106 | intent.setType(mime);
107 | intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
108 | intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
109 | intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
110 | if(openable)
111 | intent.addCategory(Intent.CATEGORY_OPENABLE);
112 | //TODO add in Android O
113 | // if(!url.isEmpty())
114 | // intent.putExtra(Intent.EXTRA_INITIAL_URI, Uri.parse(url));
115 | if(!name.isEmpty())
116 | intent.putExtra(Intent.EXTRA_TITLE, name);
117 |
118 | Runnable runnable = new Runnable () {
119 | public void run() {
120 | activity.startActivityForResult(
121 | Intent.createChooser(intent, title),
122 | persistPermissions ? CHOOSE_PERSISTENT_ACTION : CHOOSE_CONTENT_ACTION);
123 | };
124 | };
125 | activity.runOnUiThread(runnable);
126 | }
127 |
128 | static private void onActivityResult(int requestCode, int resultCode, Intent data) {
129 | if(requestCode == CHOOSE_CONTENT_ACTION ||
130 | requestCode == CHOOSE_PERSISTENT_ACTION) {
131 | Map reply = new HashMap();
132 | if (resultCode == Activity.RESULT_OK) {
133 | Uri uri = data.getData();
134 |
135 | if(requestCode == CHOOSE_PERSISTENT_ACTION) {
136 | int takeFlags = data.getFlags() & (Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
137 | QtNative.activity()
138 | .getContentResolver()
139 | .takePersistableUriPermission(uri, takeFlags);
140 | }
141 |
142 | reply.put("uri", uri.toString());
143 | reply.put("success", true);
144 | } else
145 | reply.put("success", false);
146 | SystemDispatcher.dispatch(CONTENT_CHOOSEN_MESSAGE, reply);
147 | }
148 | }
149 | }
150 |
--------------------------------------------------------------------------------
/android/src/de/skycoder42/androidutils/PrefHelper.java:
--------------------------------------------------------------------------------
1 | package de.skycoder42.androidutils;
2 |
3 | import java.util.Map;
4 | import java.util.AbstractMap;
5 | import java.util.HashMap;
6 | import android.content.Context;
7 | import android.content.SharedPreferences;
8 | import org.qtproject.qt5.android.QtNative;
9 | import androidnative.SystemDispatcher;
10 |
11 | public class PrefHelper {
12 | private static final String DATA_CHANGED_MESSAGE = "AndroidUtils.PrefHelper.changed.";
13 | private static final String DATA_LOADED_MESSAGE = "AndroidUtils.PrefHelper.loaded.";
14 |
15 | static {
16 | SystemDispatcher.addListener(new SystemDispatcher.Listener() {
17 |
18 | public void onDispatched(String name , Map data) {
19 | if (name.equals("AndroidUtils.PrefHelper.getPrefs"))
20 | getPrefs((String)data.get("id"));
21 | else if (name.equals("AndroidUtils.PrefHelper.getSharedPrefs"))
22 | getSharedPrefs((String)data.get("id"), (String)data.get("name"));
23 | else if (name.equals("AndroidUtils.PrefHelper.remPrefs"))
24 | remPrefs((String)data.get("id"));
25 | else if (name.equals("AndroidUtils.PrefHelper.save"))
26 | save((String)data.get("id"), (String)data.get("key"), data.get("value"));
27 | else if (name.equals("AndroidUtils.PrefHelper.remove"))
28 | remove((String)data.get("id"), (String)data.get("key"));
29 | }
30 | });
31 | }
32 |
33 | interface HelperListener extends SharedPreferences.OnSharedPreferenceChangeListener {
34 | SharedPreferences preferences();
35 | }
36 |
37 | static Map _activePrefs = new HashMap<>();
38 |
39 | static void getPrefs(String id) {
40 | registerPrefs(id, QtNative.activity().getPreferences(Context.MODE_PRIVATE));
41 | }
42 |
43 | static void getSharedPrefs(String id, String name) {
44 | Context c = QtNative.activity();
45 | if(c == null)
46 | c = QtNative.service();
47 | registerPrefs(id, c.getSharedPreferences(name, Context.MODE_PRIVATE));
48 | }
49 |
50 | static void remPrefs(final String id) {
51 | HelperListener listener = _activePrefs.remove(id);
52 | listener.preferences().unregisterOnSharedPreferenceChangeListener(listener);
53 | }
54 |
55 | static void save(String id, String key, Object value) {
56 | SharedPreferences.Editor prefs = _activePrefs.get(id).preferences().edit();
57 | if (value.getClass() == Boolean.class) {
58 | prefs.putBoolean(key, (Boolean)value);
59 | } else if (value.getClass() == Float.class) {
60 | prefs.putFloat(key, (Float)value);
61 | } else if (value.getClass() == Integer.class) {
62 | prefs.putInt(key, (Integer)value);
63 | } else if (value.getClass() == Long.class) {
64 | prefs.putLong(key, (Long)value);
65 | } else if (value.getClass() == String.class) {
66 | prefs.putString(key, (String)value);
67 | }
68 | prefs.apply();
69 | }
70 |
71 | static void remove(String id, String key) {
72 | SharedPreferences.Editor prefs = _activePrefs.get(id).preferences().edit();
73 | prefs.remove(key);
74 | prefs.apply();
75 | }
76 |
77 | static void registerPrefs(final String id, final SharedPreferences preferences) {
78 | HelperListener listener = new HelperListener() {
79 |
80 | public SharedPreferences preferences() {
81 | return preferences;
82 | }
83 |
84 | public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
85 | if(prefs.contains(key)) {
86 | Map data = prefs.getAll();
87 | Map reply = new HashMap();
88 | reply.put("key", key);
89 | reply.put("value", data.get(key));
90 | SystemDispatcher.dispatch(DATA_CHANGED_MESSAGE + id, reply);
91 | } else {
92 | Map reply = new HashMap();
93 | reply.put("key", key);
94 | reply.put("removed", true);
95 | SystemDispatcher.dispatch(DATA_CHANGED_MESSAGE + id, reply);
96 | }
97 | }
98 | };
99 | _activePrefs.put(id, listener);
100 | preferences.registerOnSharedPreferenceChangeListener(listener);
101 |
102 | SystemDispatcher.dispatch(DATA_LOADED_MESSAGE + id, preferences.getAll());
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/androidutils.cpp:
--------------------------------------------------------------------------------
1 | #include "androidutils.h"
2 | #include
3 | #include
4 | #include
5 | #include
6 | #ifdef Q_OS_ANDROID
7 | #include
8 | #else
9 | #include
10 | #endif
11 | #include "filechooser.h"
12 |
13 | static void setupUtils();
14 | static QObject *createQmlSingleton(QQmlEngine *qmlEngine, QJSEngine *jsEngine);
15 |
16 | Q_COREAPP_STARTUP_FUNCTION(setupUtils)
17 |
18 | AndroidUtils *AndroidUtils::_instance = nullptr;
19 |
20 | void AndroidUtils::javaThrow()
21 | {
22 | #ifdef Q_OS_ANDROID
23 | QAndroidJniEnvironment env;
24 | if (env->ExceptionCheck()) {
25 | auto exception = QAndroidJniObject::fromLocalRef(env->ExceptionOccurred());
26 | JavaException exc;
27 | exc._what = exception.callObjectMethod("getLocalizedMessage", "()Ljava/lang/String;").toString().toUtf8();
28 |
29 | QAndroidJniObject stringWriter("java/io/StringWriter");
30 | QAndroidJniObject printWriter("java/io/PrintWriter", "(Ljava/lang/Writer;)V", stringWriter.object());
31 | exception.callMethod("printStackTrace", "(Ljava/lang/PrintWriter;)V", printWriter.object());
32 | exc._stackTrace = stringWriter.callObjectMethod("toString", "()Ljava/lang/String;").toString().toUtf8();
33 |
34 | env->ExceptionClear();
35 |
36 | throw exc;
37 | }
38 | #endif
39 | }
40 |
41 | AndroidUtils::AndroidUtils(QObject *parent) :
42 | QObject(parent)
43 | {}
44 |
45 | AndroidUtils *AndroidUtils::instance()
46 | {
47 | if(!_instance)
48 | _instance = new AndroidUtils(qApp);
49 | return _instance;
50 | }
51 |
52 | void AndroidUtils::setStatusBarColor(const QColor &color)
53 | {
54 | #ifdef Q_OS_ANDROID
55 | AndroidNative::SystemDispatcher::instance()->dispatch("AndroidUtils.setStatusBarColor", {
56 | {"color", color.name()}
57 | });
58 | #else
59 | Q_UNUSED(color);
60 | #endif
61 | }
62 |
63 | void AndroidUtils::showToast(const QString &message, bool showLong)
64 | {
65 | #ifdef Q_OS_ANDROID
66 | AndroidNative::SystemDispatcher::instance()->dispatch("androidnative.Toast.showToast", {
67 | {"text", message},
68 | {"longLength", showLong}
69 | });
70 | #else
71 | Q_UNUSED(showLong)
72 | qInfo() << message;
73 | #endif
74 | }
75 |
76 | void AndroidUtils::hapticFeedback(HapticFeedbackConstant constant)
77 | {
78 | #ifdef Q_OS_ANDROID
79 | AndroidNative::SystemDispatcher::instance()->dispatch("AndroidUtils.hapticFeedback", {
80 | {"feedbackConstant", (int)constant}
81 | });
82 | #else
83 | Q_UNUSED(constant);
84 | #endif
85 | }
86 |
87 | static void setupUtils()
88 | {
89 | AndroidNative::SystemDispatcher::instance()->loadClass("androidnative.Toast");
90 | AndroidNative::SystemDispatcher::instance()->loadClass("de.skycoder42.androidutils.AndroidUtils");
91 |
92 | qmlRegisterSingletonType("de.skycoder42.androidutils", 1, 1, "AndroidUtils", createQmlSingleton);
93 | qmlRegisterType("de.skycoder42.androidutils", 1, 1, "FileChooser");
94 | //qmlProtectModule("de.skycoder42.quickextras", 1);
95 | }
96 |
97 | static QObject *createQmlSingleton(QQmlEngine *qmlEngine, QJSEngine *jsEngine)
98 | {
99 | Q_UNUSED(qmlEngine)
100 | Q_UNUSED(jsEngine)
101 | return new AndroidUtils(qmlEngine);
102 | }
103 |
104 |
105 |
106 | JavaException::JavaException() :
107 | _what(),
108 | _stackTrace()
109 | {}
110 |
111 | const char *JavaException::what() const noexcept
112 | {
113 | return _what.constData();
114 | }
115 |
116 | const QByteArray JavaException::printStackTrace() const noexcept
117 | {
118 | return _stackTrace;
119 | }
120 |
121 | void JavaException::raise() const
122 | {
123 | throw *this;
124 | }
125 |
126 | QException *JavaException::clone() const
127 | {
128 | auto e = new JavaException();
129 | e->_what = _what;
130 | e->_stackTrace = _stackTrace;
131 | return e;
132 | }
133 |
--------------------------------------------------------------------------------
/androidutils.h:
--------------------------------------------------------------------------------
1 | #ifndef ANDROIDUTILS_H
2 | #define ANDROIDUTILS_H
3 |
4 | #include
5 | #include
6 | #include
7 | #include
8 |
9 | class JavaException : public QException
10 | {
11 | friend class AndroidUtils;
12 | public:
13 | JavaException();
14 |
15 | const char *what() const noexcept override;
16 | const QByteArray printStackTrace() const noexcept;
17 |
18 | void raise() const override;
19 | QException *clone() const override;
20 |
21 | private:
22 | QByteArray _what;
23 | QByteArray _stackTrace;
24 | };
25 |
26 | class AndroidUtils : public QObject
27 | {
28 | Q_OBJECT
29 |
30 | public:
31 | enum HapticFeedbackConstant {
32 | LongPress = 0,
33 | VirtualKey = 1,
34 | KeyboardTap = 3,
35 | ClockTick = 4,
36 | ContextClick = 6
37 | };
38 | Q_ENUM(HapticFeedbackConstant)
39 |
40 | static void javaThrow();
41 |
42 | AndroidUtils(QObject *parent = nullptr);
43 | static AndroidUtils *instance();
44 |
45 | public slots:
46 | void setStatusBarColor(const QColor &color);
47 | void showToast(const QString &message, bool showLong = false);
48 | void hapticFeedback(HapticFeedbackConstant constant= LongPress);
49 |
50 | private:
51 | static AndroidUtils *_instance;
52 | };
53 |
54 | #endif // ANDROIDUTILS_H
55 |
--------------------------------------------------------------------------------
/androidutils_de.ts:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | ContentDevice
6 |
7 |
8 | You can only open ContentDevice with QIODevice::ReadOnly OR QIODevice::WriteOnly. Other flags are not supported
9 | Ein ContentDevice can nur mit QIODevice::ReadOnly ODER QIODevice::WriteOnly geöffnet werden. Andere modi werden nicht unterstützt
10 |
11 |
12 |
13 | FileChooser
14 |
15 |
16 |
17 | Open File
18 | Datei öffnen
19 |
20 |
21 |
22 | Save File
23 | Datei speichern
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/androidutils_jni.cpp:
--------------------------------------------------------------------------------
1 | #if defined(Q_OS_ANDROID)
2 | #include
3 |
4 | JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void*) {
5 | Q_UNUSED(vm);
6 | // It must call this function within JNI_OnLoad to enable System Dispatcher
7 | AndroidNative::SystemDispatcher::registerNatives();
8 | return JNI_VERSION_1_6;
9 | }
10 | #endif
11 |
--------------------------------------------------------------------------------
/androidutils_template.ts:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | ContentDevice
6 |
7 |
8 | You can only open ContentDevice with QIODevice::ReadOnly OR QIODevice::WriteOnly. Other flags are not supported
9 |
10 |
11 |
12 |
13 | FileChooser
14 |
15 |
16 |
17 | Open File
18 |
19 |
20 |
21 |
22 | Save File
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/contentdevice.cpp:
--------------------------------------------------------------------------------
1 | #include "androidutils.h"
2 | #include "contentdevice.h"
3 | #include
4 | #include
5 |
6 | ContentDevice::ContentDevice(QObject *parent) :
7 | QIODevice(parent),
8 | _context(QtAndroid::androidContext()),
9 | _url()
10 | {
11 | Q_ASSERT(_context.isValid());
12 | }
13 |
14 | ContentDevice::ContentDevice(const QUrl &url, QObject *parent) :
15 | QIODevice(parent),
16 | _context(QtAndroid::androidContext()),
17 | _url(url)
18 | {
19 | Q_ASSERT(_context.isValid());
20 | }
21 |
22 | ContentDevice::ContentDevice(const QAndroidJniObject &context, const QUrl &url, QObject *parent) :
23 | QIODevice(parent),
24 | _context(context),
25 | _url(url)
26 | {
27 | Q_ASSERT(_context.isValid());
28 | }
29 |
30 | void ContentDevice::setContext(const QAndroidJniObject &context)
31 | {
32 | _context = context;
33 | Q_ASSERT(_context.isValid());
34 | }
35 |
36 | bool ContentDevice::isSequential() const
37 | {
38 | return true;
39 | }
40 |
41 | bool ContentDevice::open(QIODevice::OpenMode mode)
42 | {
43 | if(!QIODevice::open(mode))
44 | return false;
45 |
46 | QAndroidJniEnvironment env;
47 | try {
48 | auto uri = QAndroidJniObject::callStaticObjectMethod("android/net/Uri",
49 | "parse",
50 | "(Ljava/lang/String;)Landroid/net/Uri;",
51 | QAndroidJniObject::fromString(_url.toString(QUrl::FullyEncoded)).object());
52 | AndroidUtils::javaThrow();
53 | if(!uri.isValid())
54 | return false;
55 |
56 | auto contentResolver = _context.callObjectMethod("getContentResolver", "()Landroid/content/ContentResolver;");
57 | AndroidUtils::javaThrow();
58 | if(!contentResolver.isValid())
59 | return false;
60 |
61 | switch(mode) {
62 | case QIODevice::ReadOnly:
63 | _stream = contentResolver.callObjectMethod("openInputStream",
64 | "(Landroid/net/Uri;)Ljava/io/InputStream;",
65 | uri.object());
66 | break;
67 | case QIODevice::WriteOnly:
68 | _stream = contentResolver.callObjectMethod("openOutputStream",
69 | "(Landroid/net/Uri;)Ljava/io/OutputStream;",
70 | uri.object());
71 | break;
72 | default:
73 | setErrorString(tr("You can only open ContentDevice with QIODevice::ReadOnly OR QIODevice::WriteOnly. Other flags are not supported"));
74 | QIODevice::close();
75 | return false;
76 | }
77 |
78 | AndroidUtils::javaThrow();
79 | if(!_stream.isValid())
80 | return false;
81 |
82 | return true;
83 | } catch(QException &e) {
84 | QIODevice::close();
85 | setErrorString(e.what());
86 | return false;
87 | }
88 | }
89 |
90 | void ContentDevice::close()
91 | {
92 | if(_stream.isValid()) {
93 | _stream.callMethod("close");
94 | _stream = QAndroidJniObject();
95 | }
96 | QIODevice::close();
97 | }
98 |
99 | qint64 ContentDevice::bytesAvailable() const
100 | {
101 | if(openMode().testFlag(QIODevice::ReadOnly) && _stream.isValid())
102 | return (qint64)_stream.callMethod("available");
103 |
104 | return -1;
105 | }
106 |
107 | void ContentDevice::flush()
108 | {
109 | if(openMode().testFlag(QIODevice::WriteOnly) && _stream.isValid())
110 | _stream.callMethod("flush");
111 | }
112 |
113 | QUrl ContentDevice::url() const
114 | {
115 | return _url;
116 | }
117 |
118 | void ContentDevice::setUrl(QUrl url)
119 | {
120 | if (_url == url)
121 | return;
122 |
123 | _url = url;
124 | emit urlChanged(url);
125 | }
126 |
127 | qint64 ContentDevice::readData(char *data, qint64 maxlen)
128 | {
129 | QAndroidJniEnvironment env;
130 | auto array = env->NewByteArray((jsize)maxlen);
131 | try {
132 | auto cnt = _stream.callMethod("read", "([B)I", array);
133 | AndroidUtils::javaThrow();
134 | if(cnt > 0)
135 | env->GetByteArrayRegion(array, 0, cnt, reinterpret_cast(data));
136 | env->DeleteLocalRef(array);
137 | return (qint64)cnt;
138 | } catch(QException &e) {
139 | setErrorString(e.what());
140 | env->DeleteLocalRef(array);
141 | return -1;
142 | }
143 | }
144 |
145 | qint64 ContentDevice::writeData(const char *data, qint64 len)
146 | {
147 | QAndroidJniEnvironment env;
148 | auto array = env->NewByteArray((jsize)len);
149 | env->SetByteArrayRegion (array, 0, len, reinterpret_cast(data));
150 | try {
151 | _stream.callMethod("write", "([B)V", array);
152 | AndroidUtils::javaThrow();
153 | env->DeleteLocalRef(array);
154 | return len;
155 | } catch(QException &e) {
156 | setErrorString(e.what());
157 | env->DeleteLocalRef(array);
158 | return -1;
159 | }
160 | }
161 |
--------------------------------------------------------------------------------
/contentdevice.h:
--------------------------------------------------------------------------------
1 | #ifndef CONTENTDEVICE_H
2 | #define CONTENTDEVICE_H
3 |
4 | #include
5 | #include
6 | #include
7 |
8 | class ContentDevice : public QIODevice
9 | {
10 | Q_OBJECT
11 |
12 | Q_PROPERTY(QUrl url READ url WRITE setUrl NOTIFY urlChanged)
13 |
14 |
15 | public:
16 | explicit ContentDevice(QObject *parent = nullptr);
17 | explicit ContentDevice(const QUrl &url, QObject *parent = nullptr);
18 | explicit ContentDevice(const QAndroidJniObject &context, const QUrl &url, QObject *parent = nullptr);
19 |
20 | void setContext(const QAndroidJniObject &context);
21 |
22 | bool isSequential() const override;
23 | bool open(OpenMode mode) override;
24 | void close() override;
25 | qint64 bytesAvailable() const override;
26 | void flush();
27 |
28 | QUrl url() const;
29 |
30 | public slots:
31 | void setUrl(QUrl url);
32 |
33 | signals:
34 | void urlChanged(QUrl url);
35 |
36 | protected:
37 | qint64 readData(char *data, qint64 maxlen) override;
38 | qint64 writeData(const char *data, qint64 len) override;
39 |
40 | private:
41 | QAndroidJniObject _context;
42 | QUrl _url;
43 |
44 | QAndroidJniObject _stream;
45 | };
46 |
47 | #endif // CONTENTDEVICE_H
48 |
--------------------------------------------------------------------------------
/de_skycoder42_androidutils.prc:
--------------------------------------------------------------------------------
1 | DISTFILES += \
2 | $$PWD/android/androidutils.gradle \
3 | $$PWD/android/src/de/skycoder42/androidutils/AlarmReceiver.java \
4 | $$PWD/android/src/de/skycoder42/androidutils/AndroidUtils.java \
5 | $$PWD/android/src/de/skycoder42/androidutils/FileChooser.java \
6 | $$PWD/android/src/de/skycoder42/androidutils/PrefHelper.java
7 |
8 | android {
9 | QT *= androidextras
10 |
11 | !noJniOnLoad: SOURCES += androidutils_jni.cpp
12 |
13 | #isEmpty(ANDROID_NATIVE_PATH): ANDROID_NATIVE_PATH = $$_PRO_FILE_PWD_/vendor/android/native/pri
14 | isEmpty(ANDROID_NATIVE_PATH): ANDROID_NATIVE_PATH = $$PWD/../../android.native.pri/0.0.5/ #assume qpmx install, both as qpm package
15 |
16 | copygradle.path = /
17 | copygradle.files = $$PWD/android/androidutils.gradle
18 |
19 | setupnative.path = /
20 | setupnative.files =
21 | setupnative.commands = echo 'addAndroidUtilsPath\\(\\\"$$ANDROID_NATIVE_PATH/java/src/\\\"\\)' >> $(INSTALL_ROOT)/androidutils.gradle
22 | setupnative.depends = install_copygradle
23 |
24 | setupgradle.path = /
25 | setupgradle.files =
26 | setupgradle.commands = echo 'addAndroidUtilsPath\\(\\\"$$PWD/android/src\\\"\\)' >> $(INSTALL_ROOT)/androidutils.gradle
27 | setupgradle.depends = install_copygradle
28 |
29 | INSTALLS += copygradle setupnative setupgradle
30 | }
31 |
--------------------------------------------------------------------------------
/de_skycoder42_androidutils.pri:
--------------------------------------------------------------------------------
1 | QT += qml quick
2 |
3 | HEADERS += \
4 | $$PWD/androidutils.h \
5 | $$PWD/filechooser.h \
6 | $$PWD/sharedpreferences.h
7 |
8 | SOURCES += \
9 | $$PWD/androidutils.cpp \
10 | $$PWD/filechooser.cpp \
11 | $$PWD/sharedpreferences.cpp
12 |
13 | RESOURCES += \
14 | $$PWD/de_skycoder42_androidutils.qrc
15 |
16 | INCLUDEPATH += $$PWD
17 |
18 | TRANSLATIONS += $$PWD/androidutils_de.ts \
19 | $$PWD/androidutils_template.ts
20 |
21 | android {
22 | HEADERS += $$PWD/contentdevice.h
23 | SOURCES += $$PWD/contentdevice.cpp
24 | }
25 |
26 | !qpmx_static: include($$PWD/de_skycoder42_androidutils.prc)
27 | else:android: QT *= androidextras
28 |
--------------------------------------------------------------------------------
/de_skycoder42_androidutils.qrc:
--------------------------------------------------------------------------------
1 |
2 |
3 | qmldir
4 | FileDialog.qml
5 | AndroidFileDialog.qml
6 |
7 |
8 |
--------------------------------------------------------------------------------
/filechooser.cpp:
--------------------------------------------------------------------------------
1 | #include "filechooser.h"
2 | #include
3 |
4 | const QString FileChooser::ContentChoosenMessage = QStringLiteral("AndroidUtils.FileChooser.contentChoosen");
5 |
6 | FileChooser::FileChooser(QObject *parent) :
7 | QObject(parent),
8 | _title(),
9 | _contentUrl(),
10 | _type(OpenDocument),
11 | _mimeType(QStringLiteral("*/*")),
12 | _flags(OpenableFlag)
13 | {
14 | auto dispatcher = AndroidNative::SystemDispatcher::instance();
15 | dispatcher->loadClass(QStringLiteral("de.skycoder42.androidutils.FileChooser"));
16 | connect(dispatcher, &AndroidNative::SystemDispatcher::dispatched,
17 | this, &FileChooser::onDispatched,
18 | Qt::QueuedConnection);
19 | }
20 |
21 | QString FileChooser::title() const
22 | {
23 | return _title;
24 | }
25 |
26 | QUrl FileChooser::contentUrl() const
27 | {
28 | return _contentUrl;
29 | }
30 |
31 | FileChooser::ChooserType FileChooser::type() const
32 | {
33 | return _type;
34 | }
35 |
36 | QString FileChooser::mimeType() const
37 | {
38 | return _mimeType;
39 | }
40 |
41 | FileChooser::ChooserFlags FileChooser::chooserFlags() const
42 | {
43 | return _flags;
44 | }
45 |
46 | void FileChooser::setContentUrl(QUrl contentUrl)
47 | {
48 | if (_contentUrl == contentUrl)
49 | return;
50 |
51 | _contentUrl = contentUrl;
52 | emit contentUrlChanged(contentUrl);
53 | }
54 |
55 | void FileChooser::setType(FileChooser::ChooserType type)
56 | {
57 | if (_type == type)
58 | return;
59 |
60 | _type = type;
61 | emit typeChanged(type);
62 | }
63 |
64 | void FileChooser::setMimeType(QString mimeType)
65 | {
66 | if (_mimeType == mimeType)
67 | return;
68 |
69 | _mimeType = mimeType;
70 | emit mimeTypeChanged(mimeType);
71 | }
72 |
73 | void FileChooser::setChooserFlags(ChooserFlags chooserFlags)
74 | {
75 | if (_flags == chooserFlags)
76 | return;
77 |
78 | _flags = chooserFlags;
79 | emit chooserFlagsChanged(chooserFlags);
80 | }
81 |
82 | void FileChooser::onDispatched(const QString &message, const QVariantMap &data)
83 | {
84 | if(message == ContentChoosenMessage) {
85 | if(data.value(QStringLiteral("success"), false).toBool()) {
86 | _contentUrl = data.value(QStringLiteral("uri")).toString();
87 | emit contentUrlChanged(_contentUrl);
88 | emit accepted();
89 | } else
90 | emit rejected();
91 | }
92 | }
93 |
94 | void FileChooser::open()
95 | {
96 | QString message;
97 | QVariantMap data;
98 |
99 | switch (_type) {
100 | case FileChooser::GetContent:
101 | message = QStringLiteral("AndroidUtils.FileChooser.getContent");
102 | data = {
103 | {"title", _title.isEmpty() ? tr("Open File") : _title},
104 | {"mime", _mimeType},
105 | {"openable", _flags.testFlag(OpenableFlag)},
106 | {"localOnly", _flags.testFlag(LocalOnlyFlag)},
107 | {"grantWrite", _flags.testFlag(AlwaysGrantWriteFlag)}
108 | };
109 | break;
110 | case FileChooser::OpenDocument:
111 | message = QStringLiteral("AndroidUtils.FileChooser.openDocument");
112 | data = {
113 | {"title", _title.isEmpty() ? tr("Open File") : _title},
114 | {"mime", _mimeType},
115 | {"url", _contentUrl.toString()},
116 | {"openable", _flags.testFlag(OpenableFlag)},
117 | {"grantWrite", _flags.testFlag(AlwaysGrantWriteFlag)},
118 | {"persistPermissions", _flags.testFlag(PersistPermissionsFlag)}
119 | };
120 | break;
121 | case FileChooser::CreateDocument:
122 | message = QStringLiteral("AndroidUtils.FileChooser.createDocument");
123 | data = {
124 | {"title", _title.isEmpty() ? tr("Save File") : _title},
125 | {"mime", _mimeType},
126 | {"url", _contentUrl.toString()},
127 | {"name", _contentUrl.fileName()},
128 | {"openable", _flags.testFlag(OpenableFlag)},
129 | {"persistPermissions", _flags.testFlag(PersistPermissionsFlag)}
130 | };
131 | break;
132 | default:
133 | Q_UNREACHABLE();
134 | break;
135 | }
136 |
137 | AndroidNative::SystemDispatcher::instance()->dispatch(message, data);
138 | }
139 |
140 | void FileChooser::setTitle(QString title)
141 | {
142 | if (_title == title)
143 | return;
144 |
145 | _title = title;
146 | emit titleChanged(title);
147 | }
148 |
--------------------------------------------------------------------------------
/filechooser.h:
--------------------------------------------------------------------------------
1 | #ifndef FILECHOOSER_H
2 | #define FILECHOOSER_H
3 |
4 | #include
5 | #include
6 |
7 | class FileChooser : public QObject
8 | {
9 | Q_OBJECT
10 |
11 | Q_PROPERTY(QString title READ title WRITE setTitle NOTIFY titleChanged)
12 |
13 | Q_PROPERTY(QUrl contentUrl READ contentUrl WRITE setContentUrl NOTIFY contentUrlChanged)
14 | Q_PROPERTY(ChooserType type READ type WRITE setType NOTIFY typeChanged)
15 | Q_PROPERTY(QString mimeType READ mimeType WRITE setMimeType NOTIFY mimeTypeChanged)
16 | Q_PROPERTY(ChooserFlags chooserFlags READ chooserFlags WRITE setChooserFlags NOTIFY chooserFlagsChanged)
17 |
18 | public:
19 | enum ChooserType {
20 | GetContent = 0,
21 | OpenDocument = 1,
22 | CreateDocument = 2
23 | };
24 | Q_ENUM(ChooserType)
25 |
26 | enum ChooserFlag {
27 | OpenableFlag = 0x01,
28 | LocalOnlyFlag = 0x02,
29 | AlwaysGrantWriteFlag = 0x04,
30 | PersistPermissionsFlag = 0x08
31 | };
32 | Q_DECLARE_FLAGS(ChooserFlags, ChooserFlag)
33 | Q_FLAG(ChooserFlags)
34 |
35 | explicit FileChooser(QObject *parent = nullptr);
36 |
37 | QString title() const;
38 | QUrl contentUrl() const;
39 | ChooserType type() const;
40 | QString mimeType() const;
41 | ChooserFlags chooserFlags() const;
42 |
43 | public slots:
44 | void open();
45 |
46 | void setTitle(QString title);
47 | void setContentUrl(QUrl contentUrl);
48 | void setType(ChooserType type);
49 | void setMimeType(QString mimeType);
50 | void setChooserFlags(ChooserFlags chooserFlags);
51 |
52 | signals:
53 | void accepted();
54 | void rejected();
55 |
56 | void titleChanged(QString title);
57 | void contentUrlChanged(QUrl contentUrl);
58 | void typeChanged(ChooserType type);
59 | void mimeTypeChanged(QString mimeType);
60 | void chooserFlagsChanged(ChooserFlags chooserFlags);
61 |
62 | private slots:
63 | void onDispatched(const QString &message, const QVariantMap &data);
64 |
65 | private:
66 | static const QString ContentChoosenMessage;
67 |
68 | QString _title;
69 | QUrl _contentUrl;
70 | ChooserType _type;
71 | QString _mimeType;
72 | ChooserFlags _flags;
73 | };
74 |
75 | #endif // FILECHOOSER_H
76 |
--------------------------------------------------------------------------------
/qmldir:
--------------------------------------------------------------------------------
1 | module de.skycoder42.androidutils
2 |
3 | FileDialog 1.1 FileDialog.qml
4 |
--------------------------------------------------------------------------------
/qpm.json:
--------------------------------------------------------------------------------
1 | {
2 | "author": {
3 | "email": "skycoder42.de@gmx.de",
4 | "name": "Skycoder"
5 | },
6 | "dependencies": [
7 | "android.native.pri@0.0.5"
8 | ],
9 | "description": "Utils for easy c++ and qml integration of common android features",
10 | "license": "BSD_3_CLAUSE",
11 | "name": "de.skycoder42.androidutils",
12 | "pri_filename": "de_skycoder42_androidutils.pri",
13 | "repository": {
14 | "type": "GITHUB",
15 | "url": "https://github.com/Skycoder42/AndroidUtils.git"
16 | },
17 | "version": {
18 | "fingerprint": "",
19 | "label": "1.2.2",
20 | "revision": ""
21 | },
22 | "webpage": "https://github.com/Skycoder42/AndroidUtils"
23 | }
24 |
--------------------------------------------------------------------------------
/qpmx.json:
--------------------------------------------------------------------------------
1 | {
2 | "dependencies": [
3 | {
4 | "package": "android.native.pri",
5 | "provider": "qpm",
6 | "version": "0.0.5"
7 | }
8 | ],
9 | "license": {
10 | "file": "LICENSE",
11 | "name": "BSD_3_CLAUSE"
12 | },
13 | "prcFile": "de_skycoder42_androidutils.prc",
14 | "priFile": "de_skycoder42_androidutils.pri",
15 | "priIncludes": [
16 | ],
17 | "publishers": {
18 | "qpm": {
19 | "author": {
20 | "email": "skycoder42.de@gmx.de",
21 | "name": "Skycoder"
22 | },
23 | "description": "Utils for easy c++ and qml integration of common android features",
24 | "name": "de.skycoder42.androidutils",
25 | "repository": {
26 | "type": "GITHUB",
27 | "url": "https://github.com/Skycoder42/AndroidUtils.git"
28 | },
29 | "webpage": "https://github.com/Skycoder42/AndroidUtils"
30 | }
31 | },
32 | "source": false
33 | }
34 |
--------------------------------------------------------------------------------
/sharedpreferences.cpp:
--------------------------------------------------------------------------------
1 | #include "sharedpreferences.h"
2 |
3 | #include
4 | #include
5 |
6 | SharedPreferences::SharedPreferences(const QString &id, QObject *parent) :
7 | QObject(parent),
8 | _id(id),
9 | _data()
10 | {
11 | AndroidNative::SystemDispatcher::instance()->loadClass(QStringLiteral("de.skycoder42.androidutils.PrefHelper"));
12 |
13 | connect(AndroidNative::SystemDispatcher::instance(), &AndroidNative::SystemDispatcher::dispatched,
14 | this, &SharedPreferences::dispatched,
15 | Qt::QueuedConnection);
16 | }
17 |
18 | SharedPreferences::~SharedPreferences()
19 | {
20 | AndroidNative::SystemDispatcher::instance()->dispatch(QStringLiteral("AndroidUtils.PrefHelper.remPrefs"), {
21 | {QStringLiteral("id"), _id}
22 | });
23 | }
24 |
25 | SharedPreferences *SharedPreferences::getPreferences(QObject *parent)
26 | {
27 | auto id = QUuid::createUuid().toString();
28 | auto prefs = new SharedPreferences(id, parent);
29 | AndroidNative::SystemDispatcher::instance()->dispatch(QStringLiteral("AndroidUtils.PrefHelper.getPrefs"), {
30 | {QStringLiteral("id"), id}
31 | });
32 | return prefs;
33 | }
34 |
35 | SharedPreferences *SharedPreferences::getSharedPreferences(const QString &name, QObject *parent)
36 | {
37 | auto id = QUuid::createUuid().toString();
38 | auto prefs = new SharedPreferences(id, parent);
39 | AndroidNative::SystemDispatcher::instance()->dispatch(QStringLiteral("AndroidUtils.PrefHelper.getSharedPrefs"), {
40 | {QStringLiteral("id"), id},
41 | {QStringLiteral("name"), name}
42 | });
43 | return prefs;
44 | }
45 |
46 | QStringList SharedPreferences::keys() const
47 | {
48 | return _data.keys();
49 | }
50 |
51 | bool SharedPreferences::contains(const QString &key) const
52 | {
53 | return _data.contains(key);
54 | }
55 |
56 | QVariant SharedPreferences::value(const QString &key) const
57 | {
58 | return _data.value(key);
59 | }
60 |
61 | QVariantMap SharedPreferences::data() const
62 | {
63 | return _data;
64 | }
65 |
66 | void SharedPreferences::setValue(const QString &key, const QVariant &value)
67 | {
68 | _data.insert(key, value);
69 | AndroidNative::SystemDispatcher::instance()->dispatch(QStringLiteral("AndroidUtils.PrefHelper.save"), {
70 | {QStringLiteral("id"), _id},
71 | {QStringLiteral("key"), key},
72 | {QStringLiteral("value"), value}
73 | });
74 | emit changed(key, value);
75 | }
76 |
77 | void SharedPreferences::remove(const QString &key)
78 | {
79 | _data.remove(key);
80 | AndroidNative::SystemDispatcher::instance()->dispatch(QStringLiteral("AndroidUtils.PrefHelper.remove"), {
81 | {QStringLiteral("id"), _id},
82 | {QStringLiteral("key"), key}
83 | });
84 | emit changed(key, QVariant());
85 | }
86 |
87 | void SharedPreferences::dispatched(const QString &message, const QVariantMap &data)
88 | {
89 | if(message == QStringLiteral("AndroidUtils.PrefHelper.changed.") + _id) {
90 | auto key = data.value(QStringLiteral("key")).toString();
91 | auto removed = data.value(QStringLiteral("removed"), false).toBool();
92 | auto value = data.value(QStringLiteral("value"));
93 | if(removed) {
94 | if(_data.remove(key) > 0)
95 | emit changed(key, QVariant());
96 | } else {
97 | _data.insert(key, value);
98 | emit changed(key, value);
99 | }
100 | } else if(message == QStringLiteral("AndroidUtils.PrefHelper.loaded.") + _id) {
101 | _data = data;
102 | emit loaded();
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/sharedpreferences.h:
--------------------------------------------------------------------------------
1 | #ifndef SHAREDPREFERENCES_H
2 | #define SHAREDPREFERENCES_H
3 |
4 | #include
5 | #include
6 | #include
7 |
8 | class SharedPreferences : public QObject
9 | {
10 | Q_OBJECT
11 |
12 | public:
13 | ~SharedPreferences();
14 |
15 | static SharedPreferences *getPreferences(QObject *parent = nullptr);
16 | static SharedPreferences *getSharedPreferences(const QString &name, QObject *parent = nullptr);
17 |
18 | Q_INVOKABLE QStringList keys() const;
19 | Q_INVOKABLE bool contains(const QString &key) const;
20 | Q_INVOKABLE QVariant value(const QString &key) const;
21 |
22 | Q_INVOKABLE QVariantMap data() const;
23 |
24 | public slots:
25 | void setValue(const QString &key, const QVariant &value);
26 | void remove(const QString &key);
27 |
28 | signals:
29 | void loaded();
30 | void changed(const QString &key, const QVariant &value);
31 |
32 | private slots:
33 | void dispatched(const QString &message, const QVariantMap &data);
34 |
35 | private:
36 | const QString _id;
37 | QVariantMap _data;
38 |
39 | explicit SharedPreferences(const QString &id, QObject *parent = nullptr);
40 | };
41 |
42 | #endif // SHAREDPREFERENCES_H
43 |
--------------------------------------------------------------------------------