Compare commits

...

18 Commits

Author SHA1 Message Date
d3f1c40071 fix documentation
replace the com.google.android.gms:play-services-ads:16.+ to com.google.android.gms:play-services-ads:19.+
2021-05-10 13:09:09 +03:00
FalsinSoft
fb3474dd18 Fixed a crash of the tool ApkExpansionFiles in the new android versions 2021-04-21 10:18:38 +02:00
FalsinSoft
e052f667b0 Fixed incorrect assignment in ApkExpansionFiles tools translations strings 2021-04-16 23:22:59 +02:00
FalsinSoft
5619caadf2 Changed the way ApkExpansionFiles tools manage the translations strings 2021-04-15 22:49:48 +02:00
FalsinSoft
8bf4450d04 Added different units to pixels conversion functions 2021-04-07 23:11:25 +02:00
FalsinSoft
a1fdf22e23 Added AbMob adaptive banner 2021-04-07 17:10:49 +02:00
FalsinSoft
42488003b9 Updated documentation 2021-03-29 11:31:59 +02:00
FalsinSoft
19c82f4f08 Add Audio tool 2021-03-16 10:03:33 +01:00
FalsinSoft
a188695de3 Add CMake project files 2021-03-15 23:45:49 +01:00
Fabio Falsini
3f60dbb778
Merge pull request #27 from Skrywerbeer/cmake
Add library CMakeLists.txt
2021-03-15 23:05:03 +01:00
FalsinSoft
2212f58646 Updated documentation 2021-02-24 23:08:09 +01:00
FalsinSoft
9cd1ed916a Add xhdpi, xxhdpi and xxxxhdpi logo and icon 2021-02-24 22:57:33 +01:00
Skrywerbeer
ba79270834 Added CMakeLists.txt. 2021-01-07 12:53:58 +02:00
FalsinSoft
9441f17c9b Improved tool for manage consent form 2020-12-07 15:17:00 +01:00
FalsinSoft
cb38e39286 Add tool for manage consent form 2020-10-20 23:10:41 +02:00
FalsinSoft
10e553aca9 Imported changes from forked project https://github.com/auxility/better-apk-expansion 2020-09-29 22:47:30 +02:00
FalsinSoft
70a8fbf7be Updated project to androidx package support 2020-09-24 23:27:22 +02:00
FalsinSoft
783e2be3ea Updated configuration files to Qt 5.15.1 params 2020-09-22 21:59:03 +02:00
66 changed files with 1578 additions and 418 deletions

View File

@ -55,6 +55,8 @@
<li><a href="#GoogleAccount">GoogleAccount</a></li>
<li><a href="#GoogleDrive">GoogleDrive</a></li>
<li><a href="#Sharing">Sharing</a></li>
<li><a href="#UserMessagingPlatform">UserMessagingPlatform</a></li>
<li><a href="#Audio">Audio</a></li>
<li><a href="#System">System</a></li>
</ul>
</nav>
@ -87,7 +89,9 @@
QTAT_PLAY_STORE \
QTAT_GOOGLE_ACCOUNT \
QTAT_GOOGLE_DRIVE \
QTAT_SHARING</pre>
QTAT_SHARING \
QTAT_USER_MESSAGING_PLATFORM \
QTAT_AUDIO</pre>
</li>
<li>In the <i>main()</i> body insert the call for initialize the library<br />
<pre class="prettyprint">QtAndroidTools::initializeQmlTools();</pre>
@ -122,6 +126,10 @@
&lt;!-- Required to read and write the expansion files on shared storage --&gt;
&lt;uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE" /&gt;
&lt;!-- Required to start download service in foreground --&gt;
&lt;uses-permission android:name="android.permission.FOREGROUND_SERVICE"/&gt;
...
&lt;/manifest&gt;</pre>
<p>Also note, in case your Android app is target for version 23 or above you have to explicitly ask for the <i>WRITE_EXTERNAL_STORAGE</i> permission cause this is one of the "dangerous" permission. You can use the other <a href="#AppPermissions">AppPermissions</a> tool available in this library. Also you have to correctly the various part of the library by addicting these lines inside your <i>AndroidManifest.xml</i> file:</p>
@ -259,7 +267,7 @@ android.permission.WRITE_EXTERNAL_STORAGE</pre>
<p>Than you have to add the following dependencies into your app gradle file:</p>
<pre class="prettyprint">dependencies {
....
implementation 'com.google.android.gms:play-services-ads:16.+'
implementation 'com.google.android.gms:play-services-ads:19.+'
}</pre>
<p>Banner is showed inside the following QML item:</p>
<pre class="prettyprint">import QtAndroidTools 1.0
@ -312,7 +320,7 @@ android.permission.WRITE_EXTERNAL_STORAGE</pre>
<p>Than you have to add the following dependencies into your app gradle file:</p>
<pre class="prettyprint">dependencies {
....
implementation 'com.google.android.gms:play-services-ads:16.+'
implementation 'com.google.android.gms:play-services-ads:19.+'
}</pre>
<p>Interstitial ad show on full screen but you have to define the AdMob <i>unitId</i> using the item as follow:</p>
<pre class="prettyprint">QtAndroidAdMobInterstitial {
@ -357,7 +365,7 @@ android.permission.WRITE_EXTERNAL_STORAGE</pre>
<p>Than you have to add the following dependencies into your app gradle file:</p>
<pre class="prettyprint">dependencies {
....
implementation 'com.google.android.gms:play-services-ads:16.+'
implementation 'com.google.android.gms:play-services-ads:19.+'
}</pre>
<p>Rewarded Video ad show on full screen but you have to define the AdMob <i>unitId</i> using the item as follow:</p>
<pre class="prettyprint">QtAndroidAdMobRewardedVideo {
@ -537,7 +545,7 @@ QtAndroidGoogleAccount.signedInAccount.familyName
QtAndroidGoogleAccount.signedInAccount.givenName
QtAndroidGoogleAccount.signedInAccount.photo</pre>
<p>The <i>photo</i> field export the binary image of the account. For show in a standard Image control you can use the generic image provider as follow:</p>
<pre>QtAndroidTools.insertImage("AccountPhoto", QtAndroidGoogleAccount.signedInAccount.photo);
<pre class="prettyprint">QtAndroidTools.insertImage("AccountPhoto", QtAndroidGoogleAccount.signedInAccount.photo);
accountPhoto.source = "image://QtAndroidTools/AccountPhoto";</pre>
<p>For know which type of scopes use for signin you have to check the documentation cause it changes based to the Google resource you want to access in.</p>
<img src="images/googleaccount1.png">
@ -547,7 +555,7 @@ accountPhoto.source = "image://QtAndroidTools/AccountPhoto";</pre>
<p>This tool export a basic set of methods for work with Google Drive.</p>
<p><b>PLEASE NOTE:</b> be very careful in using these methods cause you risk deleting some important files from user's Google drive. It's important to <b>TEST VERY WELL</b> your app before release. I take no responsibility for any damage that can be done using these methods.</p>
<p>For know how to use these methods and how to configure the Google drive access you have to read the official documentation <a href="https://developers.google.com/drive/api/v3/about-sdk" target="_blank">here</a>. As first operation you have to authenticate your app by using the corresponding methods:</p>
<pre>QtAndroidGoogleDrive.authenticate(appName, scopeName)</pre>
<pre class="prettyprint">QtAndroidGoogleDrive.authenticate(appName, scopeName)</pre>
<p>The method return a bool value informing the result of the operation. Scope is used to declare the type of access you want to get for Google Drive. The user must authorize the type of access requested before being able to operate. The list of possible scopes is the following, for use of each scope read the documentation <a href="https://developers.google.com/drive/api/v3/about-auth" target="_blank">here</a>:</p>
<ul>
<li>SCOPE_DRIVE</li>
@ -560,7 +568,7 @@ accountPhoto.source = "image://QtAndroidTools/AccountPhoto";</pre>
<li>SCOPE_DRIVE_SCRIPTS</li>
</ul>
<p>Once got the authorization to access the following methods are available:</p>
<pre>QtAndroidGoogleDrive.getFilesList(query)
<pre class="prettyprint">QtAndroidGoogleDrive.getFilesList(query)
QtAndroidGoogleDrive.getRootId()
QtAndroidGoogleDrive.downloadFile(fileId, localFilePath)
QtAndroidGoogleDrive.uploadFile(localFilePath, mimeType, parentFolderId)
@ -569,7 +577,7 @@ QtAndroidGoogleDrive.isFolder(fileId)
QtAndroidGoogleDrive.moveFile(fileId, folderId)
QtAndroidGoogleDrive.deleteFile(fileId)</pre>
<p>The first method return an array of structs listing the drive files as showed in the example:</p>
<pre>var filesList = QtAndroidGoogleDrive.getFilesList();
<pre class="prettyprint">var filesList = QtAndroidGoogleDrive.getFilesList();
var rootId = QtAndroidGoogleDrive.getRootId();
filesListModel.clear();
@ -593,7 +601,7 @@ for(var i = 0; i < filesList.length; i++)
});
}</pre>
<p>The param <i>query</i> is optional. If you don't pass it the method return the list of all files inside the drive. In case you need a more specific search the documentation about how to use the query is <a href="https://developers.google.com/drive/api/v3/search-files" target="_blank">here</a>. All the files and folder have a string id, the method <i>getRootId()</i> return the id of the root folder. Is possible to download a drive file or upload inside drive. The <i>localFilePath</i> is a full path of the file to upload/download including the file name (also for download). The remaining methods allow to manage the drive files. Remember for download a file you must to have granted the WRITE_EXTERNAL_STORAGE permission. Two signals are used to update info regarding the download/upload operations as follow:</p>
<pre>Connections {
<pre class="prettyprint">Connections {
target: QtAndroidGoogleDrive
function onDownloadProgressChanged(state, progress)
{
@ -643,7 +651,7 @@ for(var i = 0; i < filesList.length; i++)
&lt;data android:mimeType=&quot;image/*&quot;/&gt;
&lt;/intent-filter&gt;</pre>
<p>Regarding the file sharing provider the code is the following:</p>
<pre>&lt;provider android:name=&quot;android.support.v4.content.FileProvider&quot;
<pre class="prettyprint">&lt;provider android:name=&quot;androidx.core.content.FileProvider&quot;
android:authorities=&quot;${applicationId}.qtandroidtoolsfileprovider&quot;
android:grantUriPermissions=&quot;true&quot;
android:exported=&quot;false&quot;&gt;
@ -651,17 +659,17 @@ for(var i = 0; i < filesList.length; i++)
&lt;/provider&gt;</pre>
<p>You can name the <i>resource</i> as you prefer but don't change the <i>authorities</i> name cause the tool refer to this label (qtandroidtoolsfileprovider) for configure the correct resource.</p>
<p>For share simple data from your app to other apps the following function are available:</p>
<pre>QtAndroidSharing.shareText(text)
<pre class="prettyprint">QtAndroidSharing.shareText(text)
QtAndroidSharing.shareBinaryData(mimeType, dataFilePath)</pre>
<p>A system window will be showed with all the apps able to receive the data type you want to share as follow:</p>
<img src="images/sharing1.png">
<p>Once the user will select the preferred one the data will be transfered automatically.</p>
<p>In case you want to ask for a file shared by other apps the procedure is a bit different. At first you have to use the following function with the param the file mime type you need:</p>
<pre>QtAndroidSharing.requestSharedFile(mimeType)</pre>
<pre class="prettyprint">QtAndroidSharing.requestSharedFile(mimeType)</pre>
<p>Anothe different systme window will show the apps able to share the file type you need:</p>
<img src="images/sharing2.png">
<p>The selection will open the app sharing the file and the user have to choose the file or cancel the operation. Both actions will return the two events as follow:</p>
<pre>Connections {
<pre class="prettyprint">Connections {
target: QtAndroidSharing
function onRequestedSharedFileReadyToSave(mimeType, name, size)
{
@ -671,11 +679,11 @@ QtAndroidSharing.shareBinaryData(mimeType, dataFilePath)</pre>
}
}</pre>
<p>The second event will inform thet the requested file is not available for various reasons (probably the user cancelled the operation). The first event will export three params named <i>name</i>, <i>size</i> and <i>mimeType</i> with info regarding the user selected file. Please note in this phase the file has not been imported yet. The info will help you to "decide" if the selected file can be imported (for example if the selected file is too much big in size). If you decide the file is correct you have to call the function to import and save somewhere in your app space:</p>
<pre>QtAndroidSharing.saveRequestedSharedFile(filePath)</pre>
<pre class="prettyprint">QtAndroidSharing.saveRequestedSharedFile(filePath)</pre>
<p>In case you are not interested to have the file you have to cancel the operation by call:</p>
<pre>QtAndroidSharing.closeRequestedSharedFile()</pre>
<pre class="prettyprint">QtAndroidSharing.closeRequestedSharedFile()</pre>
<p>Now the opposite part, that's mean reply to shared requested from other apps. As explained in the first part of this section the activity receiving the request is always the main one. This mean the only way to know if our app has been lanuched from another app asking for share something is to check on startup phase as follow:</p>
<pre>Component.onCompleted: {
<pre class="prettyprint">Component.onCompleted: {
if(QtAndroidTools.activityAction === QtAndroidTools.ACTION_SEND)
{
if(QtAndroidTools.activityMimeType === "text/plain")
@ -690,20 +698,69 @@ QtAndroidSharing.shareBinaryData(mimeType, dataFilePath)</pre>
}
}</pre>
<p>Currently the supported actions are the following:</p>
<pre>QtAndroidTools.ACTION_NONE
<pre class="prettyprint">QtAndroidTools.ACTION_NONE
QtAndroidTools.ACTION_SEND
QtAndroidTools.ACTION_SEND_MULTIPLE
QtAndroidTools.ACTION_PICK</pre>
<p>For the first two requests (ACTION_SEND and ACTION_SEND_MULTIPLE) you can receive the shared data sent to you by using the following functions:</p>
<pre>QtAndroidSharing.getReceivedSharedText()
<pre class="prettyprint">QtAndroidSharing.getReceivedSharedText()
QtAndroidSharing.getReceivedSharedBinaryData()
QtAndroidSharing.getReceivedMultipleSharedBinaryData()</pre>
<p>For the action requesting to share a file (ACTION_PICK) you have to show a window for allow the user to select the file he want to import into the requesting app. In the demo app we have only one file to share that the choose is only if you want or not the file:</p>
<img src="images/sharing3.png">
<p>After the user made a selection you have to reply by using the following function:</p>
<pre>QtAndroidSharing.shareFile(fileAvailable, mimeType, filePath)</pre>
<pre class="prettyprint">QtAndroidSharing.shareFile(fileAvailable, mimeType, filePath)</pre>
<p>In case the user want the file you have to set the <i>fileAvailable</i> as true and provide the other params. In the opposite case (the user refused) you have to call this function by set the first param as false without provide the other params.</p>
</div>
<div class="section-txt" id="UserMessagingPlatform">
<h3>UserMessagingPlatform</h3>
<p>Under the Google <a href="https://www.google.com/about/company/user-consent-policy/" target="_blank">EU User Consent Policy</a>, you must make certain disclosures to your users in the European Economic Area (EEA) along with the UK and obtain their consent to use cookies or other local storage, where legally required, and to use personal data (such as AdID) to serve ads. This policy reflects the requirements of the EU ePrivacy Directive and the General Data Protection Regulation (GDPR).</p>
<p>This tool support the use of the UMP SDK but for a clear explanation about how to use this SDK and how to configure the consent form you have to read the official guide <a href="https://developers.google.com/admob/ump/android/quick-start" target="_blank">here</a>. At first you must to request the consent form that can be or not be available for the zone your current user is:</p>
<pre class="prettyprint">Connections {
target: QtAndroidUserMessagingPlatform
function onConsentFormRequestResult(eventId)
{
switch(eventId)
{
case QtAndroidUserMessagingPlatform.CONSENT_FORM_INFO_UPDATE_FAILURE:
....
break;
case QtAndroidUserMessagingPlatform.CONSENT_FORM_NOT_AVAILABLE:
....
break;
case QtAndroidUserMessagingPlatform.CONSENT_FORM_LOAD_SUCCESS:
....
break;
case QtAndroidUserMessagingPlatform.CONSENT_FORM_LOAD_FAILURE:
....
break;
}
}
QtAndroidUserMessagingPlatform.requestConsentForm()</pre>
<p>Once request process finish the signal <i>consentFormRequestResult()</i> is emitted with the results. Any event different from CONSENT_FORM_LOAD_SUCCESS means the form is not available for various reasons. Once obtained the form is necessary to check if it must be showed to the user. You can get such information as follow:</p>
<pre class="prettyprint">QtAndroidUserMessagingPlatform.consentStatus()</pre>
<p>Possible returned values are:</p>
<pre class="prettyprint">QtAndroidUserMessagingPlatform.CONSENT_FORM_STATUS_UNKNOWN
QtAndroidUserMessagingPlatform.CONSENT_FORM_STATUS_REQUIRED
QtAndroidUserMessagingPlatform.CONSENT_FORM_STATUS_NOT_REQUIRED
QtAndroidUserMessagingPlatform.CONSENT_FORM_STATUS_OBTAINED</pre>
<p>If the status is different from CONSENT_FORM_STATUS_REQUIRED you don't need to show the form. On the contrary, in case of form required or if the user want to change his preferences you can show the form using the call:</p>
<pre class="prettyprint">QtAndroidUserMessagingPlatform.showConsentForm()</pre>
<p>The event <i>consentFormClosed()</i> will inform you when the user closed the form and your app is ready to go. After the user accepted the form some data will be saved in the device and the form will not be required anymore. However if you want to reset these data for restart from scratch you can use the call:</p>
<pre class="prettyprint">QtAndroidUserMessagingPlatform.resetConsentInformation()</pre>
<p>This will clean all saved data.</p>
</div>
<div class="section-txt" id="Audio">
<h3>Audio</h3>
<p>This tool allow to get the audio focus and be notified when audio is requested by another application (typical when a call comes in).</p>
<p>You can request and abandon the audio focus using the following calls. If you don't get the audio focus you will not be advised if some other apps request the focus. This is important because, for example, if you are generating a sound and a call comes in, your sound will be generated in parallel with the voice of the call itself. Using this feature you must stop the sound when you lose focus and resume when the focus returns to your application.</p>
<pre class="prettyprint">QtAndroidAudio.requestFocus()
QtAndroidAudio.abandonFocus()</pre>
<p>Once the focus has been acquired, the following variable indicates whether the focus is present or lost.</p>
<pre class="prettyprint">QtAndroidAudio.focus</pre>
</div>
<div class="section-txt" id="System">
<h3>System</h3>
<p>Currently this tool export only the system paths.</p>

View File

@ -0,0 +1,205 @@
cmake_minimum_required(VERSION 3.14)
project(QtAndroidTools
VERSION 1.4
LANGUAGES CXX)
set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(QT NAMES Qt6 Qt5 COMPONENTS
Core
Quick
AndroidExtras
REQUIRED)
find_package(Qt${QT_VERSION_MAJOR} COMPONENTS
Core
Quick
AndroidExtras
REQUIRED)
set(QTAT_JAVA_DIR src/com/falsinsoft/qtandroidtools)
set(QTAT_SOURCE_FILES QtAndroidTools.cpp)
set(QTAT_HEADER_FILES QtAndroidTools.h)
set(QTAT_JAVA_FILES ${QTAT_JAVA_DIR}/AndroidTools.java)
option(QTAT_APP_PERMISSIONS "Enable QtAndroidTools App permissions.")
if(QTAT_APP_PERMISSIONS)
add_compile_definitions(QTAT_APP_PERMISSIONS)
list(APPEND QTAT_SOURCE_FILES QAndroidAppPermissions.cpp)
list(APPEND QTAT_HEADER_FILES QAndroidAppPermissions.h)
endif()
option(QTAT_APK_INFO "Enable QtAndroidTools Apk info.")
if(QTAT_APK_INFO)
add_compile_definitions(QTAT_APK_INFO)
list(APPEND QTAT_SOURCE_FILES QAndroidApkInfo.cpp)
list(APPEND QTAT_HEADER_FILES QAndroidApkInfo.h)
endif()
option(QTAT_SCREEN "Enable QtAndroidTools Screen")
if(QTAT_SCREEN)
add_compile_definitions(QTAT_SCREEN)
list(APPEND QTAT_SOURCE_FILES QAndroidScreen.cpp)
list(APPEND QTAT_HEADER_FILES QAndroidScreen.h)
endif()
option(QTAT_SYSTEM "Enable QtAndroidTools System")
if(QTAT_SYSTEM)
add_compile_definitions(QTAT_SYSTEM)
list(APPEND QTAT_SOURCE_FILES QAndroidSystem.cpp)
list(APPEND QTAT_HEADER_FILES QAndroidSystem.h)
list(APPEND QTAT_JAVA_FILES ${QTAT_JAVA_DIR}/AndroidSystem.java)
endif()
option(QTAT_APK_EXPANSION_FILES "Enable QtAndroidTools Apk Expansion.")
if(QTAT_APK_EXPANSION_FILES)
add_compile_definitions(QTAT_APK_EXPANSION_FILES)
list(APPEND QTAT_SOURCE_FILES QAndroidApkExpansionFiles.cpp)
list(APPEND QTAT_HEADER_FILES QAndroidApkExpansionFiles.h)
list(APPEND QTAT_JAVA_FILES ${QTAT_JAVA_DIR}/AndroidApkExpansionFiles.java)
file(COPY src/com/google DESTINATION ${ANDROID_PACKAGE_SOURCE_DIR}/src/com/)
file(COPY aidl DESTINATION ${ANDROID_PACKAGE_SOURCE_DIR}/)
endif()
option(QTAT_BATTERY_STATE "Enable QtAndroidTools Battery State.")
if(QTAT_BATTERY_STATE)
add_compile_definitions(QTAT_BATTERY_STATE)
list(APPEND QTAT_SOURCE_FILES QAndroidBatteryState.cpp)
list(APPEND QTAT_HEADER_FILES QAndroidBatteryState.h)
list(APPEND QTAT_JAVA_FILES ${QTAT_JAVA_DIR}/AndroidBatteryState.java)
endif()
option(QTAT_SIGNAL_STRENGTH "Enable QtAndroidTools Signal Strength.")
if(QTAT_SIGNAL_STRENGTH)
add_compile_definitions(QTAT_SIGNAL_STRENGTH)
list(APPEND QTAT_SOURCE_FILES QAndroidSignalStrength.cpp)
list(APPEND QTAT_HEADER_FILES QAndroidSignalStrength.h)
list(APPEND QTAT_JAVA_FILES ${QTAT_JAVA_DIR}/AndroidSignalStrength.java)
endif()
option(QTAT_ADMOB_BANNER "Enable QtAndroidTools AdMob banner.")
if(QTAT_ADMOB_BANNER)
add_compile_definitions(QTAT_ADMOB_BANNER)
list(APPEND QTAT_SOURCE_FILES QAndroidAdMobBanner.cpp)
list(APPEND QTAT_HEADER_FILES QAndroidAdMobBanner.h)
list(APPEND QTAT_JAVA_FILES
${QTAT_JAVA_DIR}/AndroidAdMob.java
${QTAT_JAVA_DIR}/AndroidAdMobBanner.java
${QTAT_JAVA_DIR}/SyncRunOnUiThread.java)
endif()
option(QTAT_ADMOB_INTERSTITIAL "Enable QtAndroidTools AdMob Interstitial.")
if(QTAT_ADMOB_INTERSTITIAL)
add_compile_definitions(QTAT_ADMOB_INTERSTITIAL)
list(APPEND QTAT_SOURCE_FILES QAndroidAdMobInterstitial.cpp)
list(APPEND QTAT_HEADER_FILES QAndroidAdMobInterstitial.h)
list(APPEND QTAT_JAVA_FILES
${QTAT_JAVA_DIR}/AndroidAdMob.java
${QTAT_JAVA_DIR}/AndroidAdMobInterstitial.java
${QTAT_JAVA_DIR}/SyncRunOnUiThread.java)
endif()
option(QTAT_ADMOB_REWAREDED_VIDEO "Enable QtAndroidTools AdMob Rewareded Video.")
if(QTAT_ADMOB_INTERSTITIAL)
add_compile_definitions(QTAT_ADMOB_REWAREDED_VIDEO)
list(APPEND QTAT_SOURCE_FILES QAndroidAdMobRewardedVideo.cpp)
list(APPEND QTAT_HEADER_FILES QAndroidAdMobRewardedVideo.h)
list(APPEND QTAT_JAVA_FILES
${QTAT_JAVA_DIR}/AndroidAdMob.java
${QTAT_JAVA_DIR}/AndroidAdMobRewardedVideo.java
${QTAT_JAVA_DIR}/SyncRunOnUiThread.java)
endif()
option(QTAT_IMAGES "Enable QtAndroidTools Images.")
if(QTAT_IMAGES)
add_compile_definitions(QTAT_IMAGES)
list(APPEND QTAT_SOURCE_FILES QAndroidImages.cpp)
list(APPEND QTAT_HEADER_FILES QAndroidImages.h)
list(APPEND QTAT_JAVA_FILES ${QTAT_JAVA_DIR}/AndroidImages.java)
endif()
option(QTAT_NOTIFICATION "Enable QtAndroidTools Notifications.")
if(QTAT_NOTIFICATION)
add_compile_definitions(QTAT_NOTIFICATION)
list(APPEND QTAT_SOURCE_FILES QAndroidNotification.cpp)
list(APPEND QTAT_HEADER_FILES QAndroidNotification.h)
list(APPEND QTAT_JAVA_FILES ${QTAT_JAVA_DIR}/AndroidNotification.java)
endif()
option(QTAT_PLAY_STORE "Enable QtAndroidTools Play Store.")
if(QTAT_PLAY_STORE)
add_compile_definitions(QTAT_PLAY_STORE)
list(APPEND QTAT_SOURCE_FILES QAndroidPlayStore.cpp)
list(APPEND QTAT_HEADER_FILES QAndroidPlayStore.h)
list(APPEND QTAT_JAVA_FILES ${QTAT_JAVA_DIR}/AndroidPlayStore.java)
endif()
option(QTAT_GOOGLE_ACCOUNT "Enable QtAndroidTools Google Account.")
if(QTAT_GOOGLE_ACCOUNT)
add_compile_definitions(QTAT_GOOGLE_ACCOUNT)
list(APPEND QTAT_SOURCE_FILES QAndroidGoogleAccount.cpp)
list(APPEND QTAT_HEADER_FILES QAndroidGoogleAccount.h)
list(APPEND QTAT_JAVA_FILES ${QTAT_JAVA_DIR}/AndroidGoogleAccount.java)
endif()
option(QTAT_GOOGLE_DRIVE "Enable QtAndroidTools Google Drive.")
if(QTAT_GOOGLE_DRIVE)
add_compile_definitions(QTAT_GOOGLE_DRIVE)
list(APPEND QTAT_SOURCE_FILES QAndroidGoogleDrive.cpp)
list(APPEND QTAT_HEADER_FILES QAndroidGoogleDrive.h)
list(APPEND QTAT_JAVA_FILES ${QTAT_JAVA_DIR}/AndroidGoogleDrive.java)
endif()
option(QTAT_SHARING "Enable QtAndroidTools Sharing.")
if(QTAT_SHARING)
add_compile_definitions(QTAT_SHARING)
list(APPEND QTAT_SOURCE_FILES QAndroidSharing.cpp)
list(APPEND QTAT_HEADER_FILES QAndroidSharing.h)
list(APPEND QTAT_JAVA_FILES ${QTAT_JAVA_DIR}/AndroidSharing.java)
endif()
option(QTAT_USER_MESSAGING_PLATFORM "Enable QtAndroidTools User Messaging Platform.")
if(QTAT_USER_MESSAGING_PLATFORM)
add_compile_definitions(QTAT_USER_MESSAGING_PLATFORM)
list(APPEND QTAT_SOURCE_FILES QAndroidUserMessagingPlatform.cpp)
list(APPEND QTAT_HEADER_FILES QAndroidUserMessagingPlatform.h)
list(APPEND QTAT_JAVA_FILES ${QTAT_JAVA_DIR}/AndroidUserMessagingPlatform.java)
endif()
option(QTAT_AUDIO "Enable QtAndroidTools Audio.")
if(QTAT_AUDIO)
add_compile_definitions(QTAT_AUDIO)
list(APPEND QTAT_SOURCE_FILES QAndroidAudio.cpp)
list(APPEND QTAT_HEADER_FILES QAndroidAudio.h)
list(APPEND QTAT_JAVA_FILES ${QTAT_JAVA_DIR}/AndroidAudio.java)
endif()
add_library(QtAndroidTools STATIC
${QTAT_SOURCE_FILES}
${QTAT_HEADER_FILES})
if(QTAT_NOTIFICATION)
target_link_libraries(QtAndroidTools jnigraphics)
endif()
target_include_directories(QtAndroidTools PRIVATE ${Qt5Core_INCLUDE_DIRS})
target_include_directories(QtAndroidTools PRIVATE ${Qt5Quick_INCLUDE_DIRS})
target_include_directories(QtAndroidTools PRIVATE ${Qt5AndroidExtras_INCLUDE_DIRS})
file(MAKE_DIRECTORY ${ANDROID_PACKAGE_SOURCE_DIR}/${QTAT_JAVA_DIR}/)
foreach(JAVA_FILE ${QTAT_JAVA_FILES})
configure_file(
${CMAKE_CURRENT_SOURCE_DIR}/${JAVA_FILE}
${ANDROID_PACKAGE_SOURCE_DIR}/${QTAT_JAVA_DIR}/
COPYONLY)
endforeach(JAVA_FILE)
source_group("Source" FILES ${QTAT_SOURCE_FILES})
source_group("Headers" FILES ${QTAT_HEADER_FILES})
source_group("QTAT_JAVA" FILES ${QTAT_JAVA_FILES})

View File

@ -55,7 +55,6 @@ QAndroidAdMobBanner::QAndroidAdMobBanner(QQuickItem *parent) : QQuickItem(parent
connect(qGuiApp, &QGuiApplication::applicationStateChanged, this, &QAndroidAdMobBanner::applicationStateChanged);
connect(qGuiApp->primaryScreen(), &QScreen::geometryChanged, this, &QAndroidAdMobBanner::screenGeometryChanged);
setNewAppState(APP_STATE_CREATE);
}
QAndroidAdMobBanner::~QAndroidAdMobBanner()

View File

@ -49,7 +49,8 @@ public:
TYPE_LARGE_BANNER,
TYPE_MEDIUM_RECTANGLE,
TYPE_SMART_BANNER,
TYPE_WIDE_SKYSCRAPER
TYPE_WIDE_SKYSCRAPER,
TYPE_ADAPTIVE_BANNER
};
enum ERROR_TYPE
{

View File

@ -35,7 +35,6 @@ QAndroidApkExpansionFiles::QAndroidApkExpansionFiles() : m_javaApkExpansionFiles
if(m_javaApkExpansionFiles.isValid())
{
const JNINativeMethod jniMethod[] = {
{"getString", "(I)Ljava/lang/String;", reinterpret_cast<void *>(&QAndroidApkExpansionFiles::downloaderGetString)},
{"downloadStateChanged", "(I)V", reinterpret_cast<void *>(&QAndroidApkExpansionFiles::downloaderStateChanged)},
{"downloadProgress", "(JJJF)V", reinterpret_cast<void *>(&QAndroidApkExpansionFiles::downloaderProgress)}
};
@ -46,6 +45,7 @@ QAndroidApkExpansionFiles::QAndroidApkExpansionFiles() : m_javaApkExpansionFiles
jniEnv->RegisterNatives(objectClass, jniMethod, sizeof(jniMethod)/sizeof(JNINativeMethod));
jniEnv->DeleteLocalRef(objectClass);
}
populateStringsList();
connect(qGuiApp, &QGuiApplication::applicationStateChanged, this, &QAndroidApkExpansionFiles::applicationStateChanged);
setNewAppState(APP_STATE_CREATE);
}
@ -241,18 +241,16 @@ void QAndroidApkExpansionFiles::setPatchExpansionFileInfo(const QAndroidApkExpan
m_expansionsFileInfo[1] = patchExpansionFileInfo;
}
jstring QAndroidApkExpansionFiles::downloaderGetString(JNIEnv *env, jobject thiz, jint stringID)
QString QAndroidApkExpansionFiles::getString(int stringID)
{
QString textString;
Q_UNUSED(thiz)
if(m_pInstance != nullptr)
QString text;
if(stringID >= 0 && stringID < m_stringsList.count())
{
textString = m_pInstance->getString(stringID);
text = m_stringsList[stringID];
}
return env->NewString(textString.utf16(), textString.length());
return text;
}
void QAndroidApkExpansionFiles::downloaderStateChanged(JNIEnv *env, jobject thiz, jint newState)
@ -277,77 +275,49 @@ void QAndroidApkExpansionFiles::downloaderProgress(JNIEnv *env, jobject thiz, jl
}
}
QString QAndroidApkExpansionFiles::getString(int stringID)
void QAndroidApkExpansionFiles::populateStringsList()
{
QString textString;
m_stringsList.clear();
switch(stringID)
m_stringsList << tr("Waiting for download to start"); // STRING_IDLE
m_stringsList << tr("Looking for resources to download"); // STRING_FETCHING_URL
m_stringsList << tr("Connecting to the download server"); // STRING_CONNECTING
m_stringsList << tr("Downloading resources"); // STRING_DOWNLOADING
m_stringsList << tr("Download finished"); // STRING_COMPLETED
m_stringsList << tr("Download paused because no network is available"); // STRING_PAUSED_NETWORK_UNAVAILABLE
m_stringsList << tr("Download paused"); // STRING_PAUSED_BY_REQUEST
m_stringsList << tr("Download paused because wifi is disabled"); // STRING_PAUSED_WIFI_DISABLED_NEED_CELLULAR_PERMISSION
m_stringsList << tr("Download paused because wifi is unavailable"); // STRING_PAUSED_NEED_CELLULAR_PERMISSION
m_stringsList << tr("Download paused because wifi is disabled"); // STRING_PAUSED_WIFI_DISABLED
m_stringsList << tr("Download paused because wifi is unavailable"); // STRING_PAUSED_NEED_WIFI
m_stringsList << tr("Download paused because you are roaming"); // STRING_PAUSED_ROAMING
m_stringsList << tr("Download paused because the network is inaccessible"); // STRING_PAUSED_NETWORK_SETUP_FAILURE
m_stringsList << tr("Download paused because the external storage is unavailable"); // STRING_PAUSED_SDCARD_UNAVAILABLE
m_stringsList << tr("Download failed because you may not have purchased this app"); // STRING_FAILED_UNLICENSED
m_stringsList << tr("Download failed because the resources could not be found"); // STRING_FAILED_FETCHING_URL
m_stringsList << tr("Download failed because the external storage is full"); // STRING_FAILED_SDCARD_FULL
m_stringsList << tr("Download cancelled"); // STRING_FAILED_CANCELED
m_stringsList << tr("Download failed"); // STRING_FAILED
m_stringsList << tr("Unknown status"); // STRING_UNKNOWN
m_stringsList << tr("Time left"); // STRING_TIME_LEFT
m_stringsList << tr("App data download"); // STRING_NOTIFICATION_CHANNEL_NAME
if(m_javaApkExpansionFiles.isValid())
{
case STRING_IDLE:
textString = tr("Waiting for download to start");
break;
case STRING_FETCHING_URL:
textString = tr("Looking for resources to download");
break;
case STRING_CONNECTING:
textString = tr("Connecting to the download server");
break;
case STRING_DOWNLOADING:
textString = tr("Downloading resources");
break;
case STRING_COMPLETED:
textString = tr("Download finished");
break;
case STRING_PAUSED_NETWORK_UNAVAILABLE:
textString = tr("Download paused because no network is available");
break;
case STRING_PAUSED_BY_REQUEST:
textString = tr("Download paused");
break;
case STRING_PAUSED_WIFI_DISABLED_NEED_CELLULAR_PERMISSION:
textString = tr("Download paused because wifi is disabled");
break;
case STRING_PAUSED_NEED_CELLULAR_PERMISSION:
case STRING_PAUSED_NEED_WIFI:
textString = tr("Download paused because wifi is unavailable");
break;
case STRING_PAUSED_WIFI_DISABLED:
textString = tr("Download paused because wifi is disabled");
break;
case STRING_PAUSED_ROAMING:
textString = tr("Download paused because you are roaming");
break;
case STRING_PAUSED_NETWORK_SETUP_FAILURE:
textString = tr("Download paused. Test a website in browser");
break;
case STRING_PAUSED_SDCARD_UNAVAILABLE:
textString = tr("Download paused because the external storage is unavailable");
break;
case STRING_FAILED_UNLICENSED:
textString = tr("Download failed because you may not have purchased this app");
break;
case STRING_FAILED_FETCHING_URL:
textString = tr("Download failed because the resources could not be found");
break;
case STRING_FAILED_SDCARD_FULL:
textString = tr("Download failed because the external storage is full");
break;
case STRING_FAILED_CANCELED:
textString = tr("Download cancelled");
break;
case STRING_FAILED:
textString = tr("Download failed");
break;
case STRING_UNKNOWN:
textString = tr("Unknown error");
break;
case STRING_TIME_LEFT:
textString = tr("Time left");
break;
case STRING_NOTIFICATION_CHANNEL_NAME:
textString = tr("App data download");
break;
}
const QAndroidJniObject stringObj("java/lang/String");
QAndroidJniObject stringArrayObj;
QAndroidJniEnvironment jniEnv;
return textString;
stringArrayObj = QAndroidJniObject::fromLocalRef(jniEnv->NewObjectArray(m_stringsList.count(), jniEnv->GetObjectClass(stringObj.object()), NULL));
for(int i = 0; i < m_stringsList.count(); i++)
{
jniEnv->SetObjectArrayElement(stringArrayObj.object<jobjectArray>(), i, QAndroidJniObject::fromString(m_stringsList[i]).object<jstring>());
}
m_javaApkExpansionFiles.callMethod<void>("setStringsList",
"([Ljava/lang/String;)V",
stringArrayObj.object<jobjectArray>()
);
}
}

View File

@ -145,10 +145,10 @@ private:
const QAndroidJniObject m_javaApkExpansionFiles;
static QAndroidApkExpansionFiles *m_pInstance;
QAndroidApkExpansionFileInfo m_expansionsFileInfo[2];
QStringList m_stringsList;
QString m_base64PublicKey;
QVector<int> m_SALT;
static jstring downloaderGetString(JNIEnv *env, jobject thiz, jint stringID);
static void downloaderStateChanged(JNIEnv *env, jobject thiz, jint newState);
static void downloaderProgress(JNIEnv *env, jobject thiz, jlong overallTotal, jlong overallProgress, jlong timeRemaining, jfloat currentSpeed);
@ -168,4 +168,5 @@ private:
REQUEST_DOWNLOAD_STATUS
};
void sendRequest(REQUEST_ID requestID);
void populateStringsList();
};

View File

@ -0,0 +1,108 @@
/*
* MIT License
*
* Copyright (c) 2018 Fabio Falsini <falsinsoft@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include "QAndroidAudio.h"
QAndroidAudio *QAndroidAudio::m_pInstance = nullptr;
QAndroidAudio::QAndroidAudio() : m_javaAudio("com/falsinsoft/qtandroidtools/AndroidAudio",
"(Landroid/app/Activity;)V",
QtAndroid::androidActivity().object<jobject>()),
m_focus(false)
{
m_pInstance = this;
if(m_javaAudio.isValid())
{
const JNINativeMethod jniMethod[] = {
{"focusChanged", "(Z)V", reinterpret_cast<void*>(&QAndroidAudio::deviceFocusChanged)},
};
QAndroidJniEnvironment jniEnv;
jclass objectClass;
objectClass = jniEnv->GetObjectClass(m_javaAudio.object<jobject>());
jniEnv->RegisterNatives(objectClass, jniMethod, sizeof(jniMethod)/sizeof(JNINativeMethod));
jniEnv->DeleteLocalRef(objectClass);
}
}
QAndroidAudio::~QAndroidAudio()
{
}
QObject* QAndroidAudio::qmlInstance(QQmlEngine *engine, QJSEngine *scriptEngine)
{
Q_UNUSED(engine);
Q_UNUSED(scriptEngine);
return new QAndroidAudio();
}
QAndroidAudio* QAndroidAudio::instance()
{
return m_pInstance;
}
void QAndroidAudio::deviceFocusChanged(JNIEnv *env, jobject thiz, jboolean focus)
{
Q_UNUSED(env)
Q_UNUSED(thiz)
if(m_pInstance != nullptr)
{
Q_EMIT m_pInstance->setFocus(focus);
}
}
bool QAndroidAudio::hasFocus()
{
return m_focus;
}
void QAndroidAudio::setFocus(bool focus)
{
m_focus = focus;
Q_EMIT focusChanged();
}
bool QAndroidAudio::requestFocus()
{
if(m_javaAudio.isValid())
{
if(m_javaAudio.callMethod<jboolean>("requestFocus"))
{
setFocus(true);
return true;
}
}
return false;
}
void QAndroidAudio::abandonFocus()
{
if(m_javaAudio.isValid())
{
m_javaAudio.callMethod<void>("abandonAudioFocus");
setFocus(false);
}
}

View File

@ -0,0 +1,59 @@
/*
* MIT License
*
* Copyright (c) 2018 Fabio Falsini <falsinsoft@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#pragma once
#include <QtAndroidExtras>
#include <QQmlEngine>
class QAndroidAudio : public QObject
{
Q_PROPERTY(int focus READ hasFocus NOTIFY focusChanged)
Q_DISABLE_COPY(QAndroidAudio)
Q_OBJECT
QAndroidAudio();
public:
~QAndroidAudio();
static QObject* qmlInstance(QQmlEngine *engine, QJSEngine *scriptEngine);
static QAndroidAudio* instance();
Q_INVOKABLE bool requestFocus();
Q_INVOKABLE void abandonFocus();
bool hasFocus();
Q_SIGNALS:
void focusChanged();
private:
const QAndroidJniObject m_javaAudio;
static QAndroidAudio *m_pInstance;
bool m_focus;
void setFocus(bool focus);
static void deviceFocusChanged(JNIEnv *env, jobject thiz, jboolean focus);
};

View File

@ -163,10 +163,9 @@ const QString& QAndroidNotification::getSmallIconName() const
void QAndroidNotification::setSmallIconName(const QString &smallIconName)
{
const QAndroidJniObject activity = QtAndroid::androidActivity();
QAndroidJniObject packageName, packageManager, resources;
QAndroidJniObject packageName, resources;
int smallIconResourceId;
packageManager = activity.callObjectMethod("getPackageManager", "()Landroid/content/pm/PackageManager;");
packageName = activity.callObjectMethod("getPackageName", "()Ljava/lang/String;");
resources = activity.callObjectMethod("getResources", "()Landroid/content/res/Resources;");

View File

@ -25,7 +25,9 @@
QAndroidSystem *QAndroidSystem::m_pInstance = nullptr;
QAndroidSystem::QAndroidSystem()
QAndroidSystem::QAndroidSystem() : m_javaSystem("com/falsinsoft/qtandroidtools/AndroidSystem",
"(Landroid/app/Activity;)V",
QtAndroid::androidActivity().object<jobject>())
{
m_pInstance = this;
loadStandardPaths();
@ -44,6 +46,33 @@ QAndroidSystem* QAndroidSystem::instance()
return m_pInstance;
}
int QAndroidSystem::spToPx(float sp)
{
if(m_javaSystem.isValid())
{
return m_javaSystem.callMethod<jint>("spToPx", "(F)I", sp);
}
return -1;
}
int QAndroidSystem::dipToPx(float dip)
{
if(m_javaSystem.isValid())
{
return m_javaSystem.callMethod<jint>("dipToPx", "(F)I", dip);
}
return -1;
}
int QAndroidSystem::ptToPx(float pt)
{
if(m_javaSystem.isValid())
{
return m_javaSystem.callMethod<jint>("ptToPx", "(F)I", pt);
}
return -1;
}
const QString& QAndroidSystem::getDataLocation() const
{
return m_standardPaths.dataLocation;

View File

@ -44,6 +44,10 @@ public:
static QObject* qmlInstance(QQmlEngine *engine, QJSEngine *scriptEngine);
static QAndroidSystem* instance();
Q_INVOKABLE int spToPx(float sp);
Q_INVOKABLE int dipToPx(float dip);
Q_INVOKABLE int ptToPx(float pt);
const QString& getDataLocation() const;
const QString& getConfigLocation() const;
const QString& getDownloadLocation() const;
@ -52,6 +56,7 @@ public:
const QString& getPicturesLocation() const;
private:
const QAndroidJniObject m_javaSystem;
static QAndroidSystem *m_pInstance;
struct {

View File

@ -0,0 +1,116 @@
/*
* MIT License
*
* Copyright (c) 2018 Fabio Falsini <falsinsoft@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include "QAndroidUserMessagingPlatform.h"
QAndroidUserMessagingPlatform *QAndroidUserMessagingPlatform::m_pInstance = nullptr;
QAndroidUserMessagingPlatform::QAndroidUserMessagingPlatform() : m_javaUserMessagingPlatform("com/falsinsoft/qtandroidtools/AndroidUserMessagingPlatform",
"(Landroid/app/Activity;)V",
QtAndroid::androidActivity().object<jobject>())
{
m_pInstance = this;
if(m_javaUserMessagingPlatform.isValid())
{
const JNINativeMethod jniMethod[] = {
{"consentFormRequestResult", "(I)V", reinterpret_cast<void*>(&QAndroidUserMessagingPlatform::deviceConsentFormRequestResult)},
{"consentFormClosed", "()V", reinterpret_cast<void*>(&QAndroidUserMessagingPlatform::deviceConsentFormClosed)},
};
QAndroidJniEnvironment jniEnv;
jclass objectClass;
objectClass = jniEnv->GetObjectClass(m_javaUserMessagingPlatform.object<jobject>());
jniEnv->RegisterNatives(objectClass, jniMethod, sizeof(jniMethod)/sizeof(JNINativeMethod));
jniEnv->DeleteLocalRef(objectClass);
}
}
QObject* QAndroidUserMessagingPlatform::qmlInstance(QQmlEngine *engine, QJSEngine *scriptEngine)
{
Q_UNUSED(engine);
Q_UNUSED(scriptEngine);
return new QAndroidUserMessagingPlatform();
}
QAndroidUserMessagingPlatform* QAndroidUserMessagingPlatform::instance()
{
return m_pInstance;
}
void QAndroidUserMessagingPlatform::deviceConsentFormRequestResult(JNIEnv *env, jobject thiz, int eventId)
{
Q_UNUSED(env)
Q_UNUSED(thiz)
if(m_pInstance != nullptr)
{
Q_EMIT m_pInstance->consentFormRequestResult(eventId);
}
}
void QAndroidUserMessagingPlatform::deviceConsentFormClosed(JNIEnv *env, jobject thiz)
{
Q_UNUSED(env)
Q_UNUSED(thiz)
if(m_pInstance != nullptr)
{
Q_EMIT m_pInstance->consentFormClosed();
}
}
void QAndroidUserMessagingPlatform::requestConsentForm()
{
if(m_javaUserMessagingPlatform.isValid())
{
m_javaUserMessagingPlatform.callMethod<void>("requestConsentForm");
}
}
int QAndroidUserMessagingPlatform::consentStatus()
{
if(m_javaUserMessagingPlatform.isValid())
{
return m_javaUserMessagingPlatform.callMethod<jint>("consentStatus");
}
return CONSENT_FORM_STATUS_UNKNOWN;
}
bool QAndroidUserMessagingPlatform::showConsentForm()
{
if(m_javaUserMessagingPlatform.isValid())
{
return m_javaUserMessagingPlatform.callMethod<jboolean>("showConsentForm");
}
return false;
}
void QAndroidUserMessagingPlatform::resetConsentInformation()
{
if(m_javaUserMessagingPlatform.isValid())
{
m_javaUserMessagingPlatform.callMethod<void>("resetConsentInformation");
}
}

View File

@ -0,0 +1,72 @@
/*
* MIT License
*
* Copyright (c) 2018 Fabio Falsini <falsinsoft@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#pragma once
#include <QtAndroidExtras>
#include <QQmlEngine>
class QAndroidUserMessagingPlatform : public QObject
{
Q_DISABLE_COPY(QAndroidUserMessagingPlatform)
Q_ENUMS(CONSENT_STATUS)
Q_ENUMS(REQUEST_RESULT)
Q_OBJECT
QAndroidUserMessagingPlatform();
public:
enum CONSENT_STATUS
{
CONSENT_FORM_STATUS_UNKNOWN = 0,
CONSENT_FORM_STATUS_REQUIRED = 1,
CONSENT_FORM_STATUS_NOT_REQUIRED = 2,
CONSENT_FORM_STATUS_OBTAINED = 3,
};
enum REQUEST_RESULT
{
CONSENT_FORM_INFO_UPDATE_FAILURE = 0,
CONSENT_FORM_NOT_AVAILABLE = 1,
CONSENT_FORM_LOAD_SUCCESS = 2,
CONSENT_FORM_LOAD_FAILURE = 3
};
static QObject* qmlInstance(QQmlEngine *engine, QJSEngine *scriptEngine);
static QAndroidUserMessagingPlatform* instance();
Q_INVOKABLE void requestConsentForm();
Q_INVOKABLE int consentStatus();
Q_INVOKABLE bool showConsentForm();
Q_INVOKABLE void resetConsentInformation();
Q_SIGNALS:
void consentFormRequestResult(int eventId);
void consentFormClosed();
private:
const QAndroidJniObject m_javaUserMessagingPlatform;
static QAndroidUserMessagingPlatform *m_pInstance;
static void deviceConsentFormRequestResult(JNIEnv *env, jobject thiz, int eventId);
static void deviceConsentFormClosed(JNIEnv *env, jobject thiz);
};

View File

@ -70,6 +70,12 @@
#ifdef QTAT_SHARING
#include "QAndroidSharing.h"
#endif
#ifdef QTAT_USER_MESSAGING_PLATFORM
#include "QAndroidUserMessagingPlatform.h"
#endif
#ifdef QTAT_AUDIO
#include "QAndroidAudio.h"
#endif
#include "QtAndroidTools.h"
QtAndroidTools *QtAndroidTools::m_pInstance = nullptr;
@ -221,4 +227,10 @@ void QtAndroidTools::initializeQmlTools()
#ifdef QTAT_SHARING
qmlRegisterSingletonType<QAndroidSharing>("QtAndroidTools", 1, 0, "QtAndroidSharing", &QAndroidSharing::qmlInstance);
#endif
#ifdef QTAT_USER_MESSAGING_PLATFORM
qmlRegisterSingletonType<QAndroidUserMessagingPlatform>("QtAndroidTools", 1, 0, "QtAndroidUserMessagingPlatform", &QAndroidUserMessagingPlatform::qmlInstance);
#endif
#ifdef QTAT_AUDIO
qmlRegisterSingletonType<QAndroidAudio>("QtAndroidTools", 1, 0, "QtAndroidAudio", &QAndroidAudio::qmlInstance);
#endif
}

View File

@ -47,6 +47,12 @@ contains(DEFINES, QTAT_SCREEN) {
contains(DEFINES, QTAT_SYSTEM) {
HEADERS += $$PWD/QAndroidSystem.h
SOURCES += $$PWD/QAndroidSystem.cpp
OTHER_FILES += $$PWD/src/com/falsinsoft/qtandroidtools/AndroidSystem.java
equals(COPY_JAVA_FILE, true) {
copy_system.commands = $(COPY_FILE) $$shell_path($$PWD/src/com/falsinsoft/qtandroidtools/AndroidSystem.java) $$shell_path($$ANDROID_PACKAGE_SOURCE_DIR/src/com/falsinsoft/qtandroidtools/)
PRE_TARGETDEPS += copy_system
QMAKE_EXTRA_TARGETS += copy_system
}
}
contains(DEFINES, QTAT_APK_EXPANSION_FILES) {
HEADERS += $$PWD/QAndroidApkExpansionFiles.h
@ -209,3 +215,23 @@ contains(DEFINES, QTAT_SHARING) {
QMAKE_EXTRA_TARGETS += copy_sharing
}
}
contains(DEFINES, QTAT_USER_MESSAGING_PLATFORM) {
HEADERS += $$PWD/QAndroidUserMessagingPlatform.h
SOURCES += $$PWD/QAndroidUserMessagingPlatform.cpp
OTHER_FILES += $$PWD/src/com/falsinsoft/qtandroidtools/AndroidUserMessagingPlatform.java
equals(COPY_JAVA_FILE, true) {
copy_user_messaging_platform.commands = $(COPY_FILE) $$shell_path($$PWD/src/com/falsinsoft/qtandroidtools/AndroidUserMessagingPlatform.java) $$shell_path($$ANDROID_PACKAGE_SOURCE_DIR/src/com/falsinsoft/qtandroidtools/)
PRE_TARGETDEPS += copy_user_messaging_platform
QMAKE_EXTRA_TARGETS += copy_user_messaging_platform
}
}
contains(DEFINES, QTAT_AUDIO) {
HEADERS += $$PWD/QAndroidAudio.h
SOURCES += $$PWD/QAndroidAudio.cpp
OTHER_FILES += $$PWD/src/com/falsinsoft/qtandroidtools/AndroidAudio.java
equals(COPY_JAVA_FILE, true) {
copy_audio.commands = $(COPY_FILE) $$shell_path($$PWD/src/com/falsinsoft/qtandroidtools/AndroidAudio.java) $$shell_path($$ANDROID_PACKAGE_SOURCE_DIR/src/com/falsinsoft/qtandroidtools/)
PRE_TARGETDEPS += copy_audio
QMAKE_EXTRA_TARGETS += copy_audio
}
}

View File

@ -24,14 +24,33 @@
package com.falsinsoft.qtandroidtools;
import com.google.ads.mediation.admob.AdMobAdapter;
import android.app.Activity;
import android.content.Context;
import com.google.android.gms.ads.MobileAds;
import com.google.android.gms.ads.AdRequest;
import com.google.android.gms.ads.initialization.InitializationStatus;
import com.google.android.gms.ads.initialization.OnInitializationCompleteListener;
import com.google.ads.mediation.admob.AdMobAdapter;
import android.os.Bundle;
public class AndroidAdMob
{
private static boolean mMobileAdsInitialized = false;
private boolean mNonPersonalizedAds = false;
public AndroidAdMob(Activity activityInstance)
{
if(mMobileAdsInitialized == false)
{
MobileAds.initialize(activityInstance, new MobileAdsInitializationListener());
}
}
protected boolean isMobileAdsInitialized()
{
return mMobileAdsInitialized;
}
public void setNonPersonalizedAds(boolean npa)
{
mNonPersonalizedAds = npa;
@ -46,4 +65,13 @@ public class AndroidAdMob
builder.addNetworkExtrasBundle(AdMobAdapter.class, extras);
}
}
private class MobileAdsInitializationListener implements OnInitializationCompleteListener
{
@Override
public void onInitializationComplete(InitializationStatus initializationStatus)
{
mMobileAdsInitialized = true;
}
}
}

View File

@ -38,6 +38,8 @@ import android.util.Log;
import android.graphics.Rect;
import android.widget.FrameLayout;
import android.graphics.Color;
import android.view.Display;
import android.util.DisplayMetrics;
public class AndroidAdMobBanner extends AndroidAdMob
{
@ -52,6 +54,7 @@ public class AndroidAdMobBanner extends AndroidAdMob
public AndroidAdMobBanner(Activity activityInstance)
{
super(activityInstance);
mViewGroup = (ViewGroup)activityInstance.getWindow().getDecorView().findViewById(android.R.id.content);
mBannerListener = new BannerListener();
mActivityInstance = activityInstance;
@ -100,6 +103,9 @@ public class AndroidAdMobBanner extends AndroidAdMob
case TYPE_WIDE_SKYSCRAPER:
bannerSize = AdSize.WIDE_SKYSCRAPER;
break;
case TYPE_ADAPTIVE_BANNER:
bannerSize = getAdaptiveBannerAdSize();
break;
}
mBannerView.setAdSize(bannerSize);
@ -284,6 +290,15 @@ public class AndroidAdMobBanner extends AndroidAdMob
uiThread.exec();
}
private AdSize getAdaptiveBannerAdSize()
{
Display display = mActivityInstance.getWindowManager().getDefaultDisplay();
DisplayMetrics outMetrics = new DisplayMetrics();
display.getMetrics(outMetrics);
return AdSize.getCurrentOrientationAnchoredAdaptiveBannerAdSize(mActivityInstance, (int)(((float)outMetrics.widthPixels) / outMetrics.density));
}
private class BannerListener extends AdListener
{
public void onAdLoaded()
@ -348,6 +363,7 @@ public class AndroidAdMobBanner extends AndroidAdMob
private static final int TYPE_MEDIUM_RECTANGLE = 3;
private static final int TYPE_SMART_BANNER = 4;
private static final int TYPE_WIDE_SKYSCRAPER = 5;
private static final int TYPE_ADAPTIVE_BANNER = 6;
private static final int APP_STATE_CREATE = 0;
private static final int APP_STATE_START = 1;

View File

@ -48,6 +48,7 @@ public class AndroidAdMobInterstitial extends AndroidAdMob
public AndroidAdMobInterstitial(Activity activityInstance)
{
super(activityInstance);
mInterstitialListener = new InterstitialListener();
mActivityInstance = activityInstance;
}

View File

@ -43,6 +43,7 @@ public class AndroidAdMobRewardedVideo extends AndroidAdMob
public AndroidAdMobRewardedVideo(Activity activityInstance)
{
super(activityInstance);
mRewardedVideoListener = new RewardedVideoListener();
mActivityInstance = activityInstance;
}

View File

@ -41,14 +41,15 @@ import android.os.Build;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import java.util.Arrays;
public class AndroidApkExpansionFiles
{
private static final String TAG = "AndroidApkExpansionFiles";
private final String NOTIFICATION_CHANNEL_ID;
private final DownloaderClient mDownloaderClient;
private final DownloaderProxy mDownloaderProxy;
private final Activity mActivityInstance;
private static String[] mStringsList = null;
public AndroidApkExpansionFiles(Activity activityInstance)
{
@ -58,6 +59,11 @@ public class AndroidApkExpansionFiles
mActivityInstance = activityInstance;
}
public void setStringsList(String[] stringsList)
{
mStringsList = stringsList;
}
public boolean isAPKFileDelivered(boolean isMain, int fileVersion, int fileSize)
{
final String fileName = Helpers.getExpansionAPKFileName(mActivityInstance, isMain, fileVersion);
@ -164,6 +170,18 @@ public class AndroidApkExpansionFiles
return downloadResult;
}
public static String getString(int stringID)
{
String text = "";
if(mStringsList != null && stringID >= 0 && stringID < mStringsList.length)
{
text = mStringsList[stringID];
}
return text;
}
private class DownloaderClient extends BroadcastDownloaderClient
{
@Override
@ -214,6 +232,5 @@ public class AndroidApkExpansionFiles
private static native void downloadStateChanged(int newState);
private static native void downloadProgress(long overallTotal, long overallProgress, long timeRemaining, float currentSpeed);
public static native String getString(int stringID);
}

View File

@ -0,0 +1,111 @@
/*
* MIT License
*
* Copyright (c) 2018 Fabio Falsini <falsinsoft@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.falsinsoft.qtandroidtools;
import android.content.Context;
import android.app.Activity;
import android.util.Log;
import android.media.AudioManager;
import android.media.AudioFocusRequest;
import android.media.AudioAttributes;
import android.media.AudioManager.OnAudioFocusChangeListener;
public class AndroidAudio
{
private static final String TAG = "AndroidAudio";
private final Activity mActivityInstance;
private final AudioManager mAudioManager;
private AudioFocusChangeListener mAudioFocusChangeListener = null;
public AndroidAudio(Activity activityInstance)
{
mAudioManager = (AudioManager) activityInstance.getSystemService(Context.AUDIO_SERVICE);
mActivityInstance = activityInstance;
}
public boolean requestFocus()
{
if(mAudioFocusChangeListener == null)
{
mAudioFocusChangeListener = new AudioFocusChangeListener();
if(mAudioManager.requestAudioFocus(createAudioFocusRequest(mAudioFocusChangeListener)) == AudioManager.AUDIOFOCUS_REQUEST_FAILED)
{
mAudioFocusChangeListener = null;
return false;
}
}
return true;
}
public void abandonFocus()
{
if(mAudioFocusChangeListener != null)
{
mAudioManager.abandonAudioFocusRequest(createAudioFocusRequest(mAudioFocusChangeListener));
mAudioFocusChangeListener = null;
}
}
private AudioFocusRequest createAudioFocusRequest(AudioFocusChangeListener listener)
{
AudioAttributes audioAttributes;
AudioFocusRequest focusRequest;
audioAttributes = new AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_MEDIA)
.setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
.build();
focusRequest = new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
.setAudioAttributes(audioAttributes)
.setAcceptsDelayedFocusGain(true)
.setWillPauseWhenDucked(true)
.setOnAudioFocusChangeListener(listener)
.build();
return focusRequest;
}
private class AudioFocusChangeListener implements OnAudioFocusChangeListener
{
@Override
public void onAudioFocusChange(int focusChange)
{
switch(focusChange)
{
case AudioManager.AUDIOFOCUS_GAIN:
focusChanged(true);
break;
case AudioManager.AUDIOFOCUS_LOSS:
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
focusChanged(false);
break;
}
}
}
private static native void focusChanged(boolean focus);
}

View File

@ -35,7 +35,7 @@ import android.os.AsyncTask;
import android.content.pm.ActivityInfo;
import android.content.pm.ResolveInfo;
import android.content.ComponentName;
import android.support.annotation.NonNull;
import androidx.annotation.NonNull;
import com.google.android.gms.auth.api.signin.GoogleSignIn;
import com.google.android.gms.auth.api.signin.GoogleSignInAccount;
import com.google.android.gms.auth.api.signin.GoogleSignInClient;

View File

@ -33,8 +33,8 @@ import android.content.Intent;
import android.app.PendingIntent;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.support.v4.app.NotificationManagerCompat;
import android.support.v4.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat;
import androidx.core.app.NotificationCompat;
public class AndroidNotification
{

View File

@ -33,7 +33,7 @@ import android.os.Bundle;
import android.content.pm.ActivityInfo;
import android.content.pm.ResolveInfo;
import android.content.ComponentName;
import android.support.v4.content.FileProvider;
import androidx.core.content.FileProvider;
import android.content.ContentResolver;
import android.database.Cursor;
import android.provider.OpenableColumns;

View File

@ -0,0 +1,58 @@
/*
* MIT License
*
* Copyright (c) 2018 Fabio Falsini <falsinsoft@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.falsinsoft.qtandroidtools;
import android.content.Context;
import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.util.Log;
import android.util.TypedValue;
public class AndroidSystem
{
private static final String TAG = "AndroidSystem";
private final Activity mActivityInstance;
public AndroidSystem(Activity activityInstance)
{
mActivityInstance = activityInstance;
}
public int spToPx(float sp)
{
return (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, sp, mActivityInstance.getResources().getDisplayMetrics());
}
public int dipToPx(float dip)
{
return (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dip, mActivityInstance.getResources().getDisplayMetrics());
}
public int ptToPx(float pt)
{
return (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PT, pt, mActivityInstance.getResources().getDisplayMetrics());
}
}

View File

@ -22,16 +22,16 @@
* SOFTWARE.
*/
package com.falsinsoft.qtandroidtools;
package com.falsinsoft.qtandroidtools;
import android.content.Context;
import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.util.Log;
import android.content.Context;
import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.util.Log;
public class AndroidTools
{
public class AndroidTools
{
private static final String TAG = "AndroidTools";
private final Activity mActivityInstance;

View File

@ -0,0 +1,172 @@
/*
* MIT License
*
* Copyright (c) 2018 Fabio Falsini <falsinsoft@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.falsinsoft.qtandroidtools;
import android.content.Context;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import androidx.annotation.Nullable;
import com.google.android.ump.ConsentForm;
import com.google.android.ump.ConsentInformation;
import com.google.android.ump.ConsentRequestParameters;
import com.google.android.ump.FormError;
import com.google.android.ump.UserMessagingPlatform;
public class AndroidUserMessagingPlatform
{
private final ConsentInformation mConsentInformation;
private final ConsentListener mConsentListener;
private final Activity mActivityInstance;
private int mConsentStatus = ConsentInformation.ConsentStatus.UNKNOWN;
private ConsentForm mConsentForm = null;
public AndroidUserMessagingPlatform(Activity activityInstance)
{
mConsentInformation = UserMessagingPlatform.getConsentInformation(activityInstance);
mConsentListener = new ConsentListener();
mActivityInstance = activityInstance;
}
public boolean showConsentForm()
{
if(mConsentForm != null)
{
mActivityInstance.runOnUiThread(new Runnable()
{
@Override
public void run()
{
mConsentForm.show(mActivityInstance, mConsentListener);
}
});
return true;
}
return false;
}
public void resetConsentInformation()
{
mConsentInformation.reset();
}
public void requestConsentForm()
{
final ConsentRequestParameters params = new ConsentRequestParameters.Builder().build();
mConsentInformation.requestConsentInfoUpdate(mActivityInstance, params, mConsentListener, mConsentListener);
}
public int consentStatus()
{
int status = CONSENT_FORM_STATUS_UNKNOWN;
switch(mConsentStatus)
{
case ConsentInformation.ConsentStatus.REQUIRED:
status = CONSENT_FORM_STATUS_REQUIRED;
break;
case ConsentInformation.ConsentStatus.NOT_REQUIRED:
status = CONSENT_FORM_STATUS_NOT_REQUIRED;
break;
case ConsentInformation.ConsentStatus.OBTAINED:
status = CONSENT_FORM_STATUS_OBTAINED;
break;
case ConsentInformation.ConsentStatus.UNKNOWN:
status = CONSENT_FORM_STATUS_UNKNOWN;
break;
}
return status;
}
private class ConsentListener implements ConsentInformation.OnConsentInfoUpdateSuccessListener,
ConsentInformation.OnConsentInfoUpdateFailureListener,
UserMessagingPlatform.OnConsentFormLoadSuccessListener,
UserMessagingPlatform.OnConsentFormLoadFailureListener,
ConsentForm.OnConsentFormDismissedListener
{
@Override
public void onConsentInfoUpdateSuccess()
{
if(mConsentInformation.isConsentFormAvailable())
{
mActivityInstance.runOnUiThread(new Runnable()
{
@Override
public void run()
{
UserMessagingPlatform.loadConsentForm(mActivityInstance, mConsentListener, mConsentListener);
}
});
}
else
{
consentFormRequestResult(CONSENT_FORM_NOT_AVAILABLE);
}
}
@Override
public void onConsentInfoUpdateFailure(FormError formError)
{
consentFormRequestResult(CONSENT_FORM_INFO_UPDATE_FAILURE);
}
@Override
public void onConsentFormLoadSuccess(ConsentForm consentForm)
{
mConsentStatus = mConsentInformation.getConsentStatus();
mConsentForm = consentForm;
consentFormRequestResult(CONSENT_FORM_LOAD_SUCCESS);
}
@Override
public void onConsentFormLoadFailure(FormError formError)
{
consentFormRequestResult(CONSENT_FORM_LOAD_FAILURE);
}
@Override
public void onConsentFormDismissed(@Nullable FormError formError)
{
consentFormClosed();
}
}
private static final int CONSENT_FORM_INFO_UPDATE_FAILURE = 0;
private static final int CONSENT_FORM_NOT_AVAILABLE = 1;
private static final int CONSENT_FORM_LOAD_SUCCESS = 2;
private static final int CONSENT_FORM_LOAD_FAILURE = 3;
private static final int CONSENT_FORM_STATUS_UNKNOWN = 0;
private static final int CONSENT_FORM_STATUS_REQUIRED = 1;
private static final int CONSENT_FORM_STATUS_NOT_REQUIRED = 2;
private static final int CONSENT_FORM_STATUS_OBTAINED = 3;
private static native void consentFormRequestResult(int eventId);
private static native void consentFormClosed();
}

View File

@ -22,10 +22,9 @@ import android.os.Build;
import android.os.Environment;
import android.os.StatFs;
import android.os.SystemClock;
import android.support.annotation.StringRes;
import androidx.annotation.StringRes;
import android.util.Log;
//import com.android.vending.expansion.downloader.R;
import com.falsinsoft.qtandroidtools.AndroidApkExpansionFiles;
import java.io.File;
@ -40,6 +39,7 @@ import java.util.regex.Pattern;
/**
* Some helper functions for the download manager
*/
@SuppressWarnings("unused")
public class Helpers {
public static Random sRandom = new Random(SystemClock.uptimeMillis());
@ -225,13 +225,18 @@ public class Helpers {
// This technically existed since Honeycomb, but it is critical
// on KitKat and greater versions since it will create the
// directory if needed
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
return c.getObbDir().toString();
} else {
File root = Environment.getExternalStorageDirectory();
String path = root.toString() + Constants.EXP_PATH + c.getPackageName();
return path;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT)
{
final File obbDir = c.getObbDir();
if (obbDir != null ) // It really can return null in some cases. So, if it's null - go to old fallback mechanism...
{
return obbDir.toString();
}
}
File root = Environment.getExternalStorageDirectory();
return root.toString() + Constants.EXP_PATH + c.getPackageName();
}
/**

View File

@ -4,7 +4,7 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.support.v4.content.LocalBroadcastManager;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import com.google.android.vending.expansion.downloader.DownloadProgressInfo;
import com.google.android.vending.expansion.downloader.IDownloaderClient;
import com.google.android.vending.expansion.downloader.IDownloaderService;

View File

@ -3,7 +3,7 @@ package com.google.android.vending.expansion.downloader.impl;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.content.LocalBroadcastManager;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import com.google.android.vending.expansion.downloader.DownloadProgressInfo;
import com.google.android.vending.expansion.downloader.IDownloaderClient;

View File

@ -16,13 +16,13 @@
package com.google.android.vending.expansion.downloader.impl;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.support.annotation.StringRes;
import android.support.v4.app.NotificationCompat;
import androidx.annotation.StringRes;
import androidx.core.app.NotificationCompat;
//import com.android.vending.expansion.downloader.R;
import com.falsinsoft.qtandroidtools.AndroidApkExpansionFiles;
import com.google.android.vending.expansion.downloader.DownloadProgressInfo;
@ -40,6 +40,7 @@ import com.google.android.vending.expansion.downloader.IDownloaderClient;
* The application interface for the downloader also needs to understand and
* handle these transient states.
*/
@SuppressWarnings("unused")
class DownloadNotification {
private int mState;
@ -71,8 +72,9 @@ class DownloadNotification {
mNotificationManager = (NotificationManager)
mContext.getSystemService(Context.NOTIFICATION_SERVICE);
mClientProxy = new ClientProxy(ctx);
mActiveDownloadBuilder = new NotificationCompat.Builder(ctx);
mBuilder = new NotificationCompat.Builder(ctx);
// Passing empty string as a channel ID is just a hack against deprecation, channel ID will be set later.
mActiveDownloadBuilder = new NotificationCompat.Builder(ctx, "");
mBuilder = new NotificationCompat.Builder(ctx, "");
// Set Notification category and priorities to something that makes sense for a long
// lived background task.
@ -89,6 +91,10 @@ class DownloadNotification {
return mContentIntent;
}
public int getNotificationId() { return NOTIFICATION_ID; }
public Notification buildCurrentNotification() { return mCurrentBuilder.build(); }
public void setClientIntent(PendingIntent clientIntent) {
this.mBuilder.setContentIntent(clientIntent);
this.mActiveDownloadBuilder.setContentIntent(clientIntent);

View File

@ -46,6 +46,7 @@ import java.io.File;
* Note that Android by default will kill off any process that has an open file
* handle on the shared (SD Card) partition if the partition is unmounted.
*/
@SuppressWarnings("unused")
public class DownloaderService extends CustomIntentService implements IDownloaderService {
public DownloaderService() {
@ -377,6 +378,13 @@ public class DownloaderService extends CustomIntentService implements IDownloade
*/
private static boolean sIsRunning;
/**
* Service parameters
*/
String mChannelId;
byte[] mSalt;
String mPublicKey;
@Override
public IBinder onBind(Intent paramIntent) {
Log.d(Constants.TAG, "Service Bound");
@ -588,10 +596,7 @@ public class DownloaderService extends CustomIntentService implements IDownloade
private static boolean isLVLCheckRequired(DownloadsDB db, PackageInfo pi) {
// we need to update the LVL check and get a successful status to
// proceed
if (db.mVersionCode != pi.versionCode) {
return true;
}
return false;
return db.mVersionCode != pi.versionCode;
}
/**
@ -672,7 +677,11 @@ public class DownloaderService extends CustomIntentService implements IDownloade
downloadIntent.putExtra(EXTRA_CHANNEL_ID, channelId);
downloadIntent.putExtra(EXTRA_SALT, salt);
downloadIntent.putExtra(EXTRA_PUBLIC_KEY, publicKey);
context.startService(downloadIntent);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
context.startForegroundService(downloadIntent);
} else {
context.startService(downloadIntent);
}
break;
}
return status;
@ -702,7 +711,15 @@ public class DownloaderService extends CustomIntentService implements IDownloade
}
Intent fileIntent = new Intent(this, this.getClass());
fileIntent.putExtra(EXTRA_PENDING_INTENT, mPendingIntent);
this.startService(fileIntent);
fileIntent.putExtra(EXTRA_CHANNEL_ID, mChannelId);
fileIntent.putExtra(EXTRA_SALT, mSalt);
fileIntent.putExtra(EXTRA_PUBLIC_KEY, mPublicKey);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
this.startForegroundService(fileIntent);
} else {
this.startService(fileIntent);
}
}
private class LVLRunnable implements Runnable {
@ -911,7 +928,7 @@ public class DownloaderService extends CustomIntentService implements IDownloade
return !Helpers.doesFileExist(this, filename, fileSize, true);
}
private void scheduleAlarm(long wakeUp, boolean repeated, Bundle callerExtras) {
private void scheduleAlarm(long wakeUp, boolean repeated, Intent originalIntent) {
AlarmManager alarms = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
if (alarms == null) {
Log.e(Constants.TAG, "couldn't get alarm manager");
@ -925,7 +942,7 @@ public class DownloaderService extends CustomIntentService implements IDownloade
// put original extras to the wake up intent
Intent intent = new Intent(this, AlarmReceiver.class);
intent.setAction(Constants.ACTION_RETRY);
intent.putExtras(callerExtras);
intent.putExtras(originalIntent);
mAlarmIntent = PendingIntent.getBroadcast(this, 0, intent,
PendingIntent.FLAG_CANCEL_CURRENT);
@ -966,13 +983,19 @@ public class DownloaderService extends CustomIntentService implements IDownloade
@Override
public void onReceive(Context context, Intent intent) {
pollNetworkState();
if (mStateChanged
&& !isServiceRunning()) {
if (mStateChanged && !isServiceRunning()) {
Log.d(Constants.TAG, "InnerBroadcastReceiver Called");
Intent fileIntent = new Intent(context, mService.getClass());
fileIntent.putExtra(EXTRA_PENDING_INTENT, mPendingIntent);
fileIntent.putExtra(EXTRA_CHANNEL_ID, mChannelId);
fileIntent.putExtra(EXTRA_SALT, mSalt);
fileIntent.putExtra(EXTRA_PUBLIC_KEY, mPublicKey);
// send a new intent to the service
context.startService(fileIntent);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
context.startForegroundService(fileIntent);
} else {
context.startService(fileIntent);
}
}
}
}
@ -984,7 +1007,7 @@ public class DownloaderService extends CustomIntentService implements IDownloade
@Override
public void onReceive(Context context, Intent intent) {
try {
final PendingIntent pendingIntent = (PendingIntent) intent
final PendingIntent pendingIntent = intent
.getParcelableExtra(EXTRA_PENDING_INTENT);
startDownloadServiceIfRequired(
@ -996,7 +1019,9 @@ public class DownloaderService extends CustomIntentService implements IDownloade
);
} catch (PackageManager.NameNotFoundException e) {
Log.e(getClass().getSimpleName(), "onReceive: ", e);
if (Constants.LOGVV) {
Log.e(getClass().getSimpleName(), "onReceive: ", e);
}
}
}
}
@ -1013,11 +1038,11 @@ public class DownloaderService extends CustomIntentService implements IDownloade
// and download status when the instance is created
DownloadsDB db = DownloadsDB.getDB(this);
final PendingIntent pendingIntent = intent.getParcelableExtra(EXTRA_PENDING_INTENT);
final String channelId = intent.getStringExtra(EXTRA_CHANNEL_ID);
final byte[] salt = intent.getByteArrayExtra(EXTRA_SALT);
final String publicKey = intent.getStringExtra(EXTRA_PUBLIC_KEY);
mChannelId = intent.getStringExtra(EXTRA_CHANNEL_ID);
mSalt = intent.getByteArrayExtra(EXTRA_SALT);
mPublicKey = intent.getStringExtra(EXTRA_PUBLIC_KEY);
mNotification.setChannelId(channelId);
mNotification.setChannelId(mChannelId);
if (null != pendingIntent) {
mNotification.setClientIntent(pendingIntent);
@ -1029,10 +1054,14 @@ public class DownloaderService extends CustomIntentService implements IDownloade
return;
}
// Critical to use on Android O (API 26+) or else service will be killed by ANR within 5 seconds!
// See here: https://stackoverflow.com/questions/44425584/context-startforegroundservice-did-not-then-call-service-startforeground
startForeground(mNotification.getNotificationId(), mNotification.buildCurrentNotification());
// when the LVL check completes, a successful response will update
// the service
if (isLVLCheckRequired(db, mPackageInfo)) {
updateLVL(this, channelId, salt, publicKey);
updateLVL(this, mChannelId, mSalt, mPublicKey);
return;
}
@ -1079,7 +1108,7 @@ public class DownloaderService extends CustomIntentService implements IDownloade
DownloadThread dt = new DownloadThread(info, this, mNotification);
cancelAlarms();
// schedule repeated alarm to check if process is alive
scheduleAlarm(Constants.ACTIVE_THREAD_WATCHDOG, true, intent.getExtras());
scheduleAlarm(Constants.ACTIVE_THREAD_WATCHDOG, true, intent);
dt.run();
cancelAlarms();
}
@ -1089,7 +1118,7 @@ public class DownloaderService extends CustomIntentService implements IDownloade
switch (info.mStatus) {
case STATUS_FORBIDDEN:
// the URL is out of date
updateLVL(this, channelId, salt, publicKey);
updateLVL(this, mChannelId, mSalt, mPublicKey);
return;
case STATUS_SUCCESS:
mBytesSoFar += info.mCurrentBytes - startingCount;
@ -1144,7 +1173,7 @@ public class DownloaderService extends CustomIntentService implements IDownloade
break;
}
if (setWakeWatchdog) {
scheduleAlarm(Constants.WATCHDOG_WAKE_TIMER, false, intent.getExtras());
scheduleAlarm(Constants.WATCHDOG_WAKE_TIMER, false, intent);
} else {
cancelAlarms();
}
@ -1325,10 +1354,7 @@ public class DownloaderService extends CustomIntentService implements IDownloade
// the database automatically reads the metadata for version code
// and download status when the instance is created
DownloadsDB db = DownloadsDB.getDB(this);
if (db.mStatus == 0) {
return true;
}
return false;
return db.mStatus == 0;
}
@Override

View File

@ -1,200 +0,0 @@
/*
* Copyright (C) 2012 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.vending.expansion.downloader.impl;
import android.text.format.Time;
import java.util.Calendar;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Helper for parsing an HTTP date.
*/
public final class HttpDateTime {
/*
* Regular expression for parsing HTTP-date. Wdy, DD Mon YYYY HH:MM:SS GMT
* RFC 822, updated by RFC 1123 Weekday, DD-Mon-YY HH:MM:SS GMT RFC 850,
* obsoleted by RFC 1036 Wdy Mon DD HH:MM:SS YYYY ANSI C's asctime() format
* with following variations Wdy, DD-Mon-YYYY HH:MM:SS GMT Wdy, (SP)D Mon
* YYYY HH:MM:SS GMT Wdy,DD Mon YYYY HH:MM:SS GMT Wdy, DD-Mon-YY HH:MM:SS
* GMT Wdy, DD Mon YYYY HH:MM:SS -HHMM Wdy, DD Mon YYYY HH:MM:SS Wdy Mon
* (SP)D HH:MM:SS YYYY Wdy Mon DD HH:MM:SS YYYY GMT HH can be H if the first
* digit is zero. Mon can be the full name of the month.
*/
private static final String HTTP_DATE_RFC_REGEXP =
"([0-9]{1,2})[- ]([A-Za-z]{3,9})[- ]([0-9]{2,4})[ ]"
+ "([0-9]{1,2}:[0-9][0-9]:[0-9][0-9])";
private static final String HTTP_DATE_ANSIC_REGEXP =
"[ ]([A-Za-z]{3,9})[ ]+([0-9]{1,2})[ ]"
+ "([0-9]{1,2}:[0-9][0-9]:[0-9][0-9])[ ]([0-9]{2,4})";
/**
* The compiled version of the HTTP-date regular expressions.
*/
private static final Pattern HTTP_DATE_RFC_PATTERN =
Pattern.compile(HTTP_DATE_RFC_REGEXP);
private static final Pattern HTTP_DATE_ANSIC_PATTERN =
Pattern.compile(HTTP_DATE_ANSIC_REGEXP);
private static class TimeOfDay {
TimeOfDay(int h, int m, int s) {
this.hour = h;
this.minute = m;
this.second = s;
}
int hour;
int minute;
int second;
}
public static long parse(String timeString)
throws IllegalArgumentException {
int date = 1;
int month = Calendar.JANUARY;
int year = 1970;
TimeOfDay timeOfDay;
Matcher rfcMatcher = HTTP_DATE_RFC_PATTERN.matcher(timeString);
if (rfcMatcher.find()) {
date = getDate(rfcMatcher.group(1));
month = getMonth(rfcMatcher.group(2));
year = getYear(rfcMatcher.group(3));
timeOfDay = getTime(rfcMatcher.group(4));
} else {
Matcher ansicMatcher = HTTP_DATE_ANSIC_PATTERN.matcher(timeString);
if (ansicMatcher.find()) {
month = getMonth(ansicMatcher.group(1));
date = getDate(ansicMatcher.group(2));
timeOfDay = getTime(ansicMatcher.group(3));
year = getYear(ansicMatcher.group(4));
} else {
throw new IllegalArgumentException();
}
}
// FIXME: Y2038 BUG!
if (year >= 2038) {
year = 2038;
month = Calendar.JANUARY;
date = 1;
}
Time time = new Time(Time.TIMEZONE_UTC);
time.set(timeOfDay.second, timeOfDay.minute, timeOfDay.hour, date,
month, year);
return time.toMillis(false /* use isDst */);
}
private static int getDate(String dateString) {
if (dateString.length() == 2) {
return (dateString.charAt(0) - '0') * 10
+ (dateString.charAt(1) - '0');
} else {
return (dateString.charAt(0) - '0');
}
}
/*
* jan = 9 + 0 + 13 = 22 feb = 5 + 4 + 1 = 10 mar = 12 + 0 + 17 = 29 apr = 0
* + 15 + 17 = 32 may = 12 + 0 + 24 = 36 jun = 9 + 20 + 13 = 42 jul = 9 + 20
* + 11 = 40 aug = 0 + 20 + 6 = 26 sep = 18 + 4 + 15 = 37 oct = 14 + 2 + 19
* = 35 nov = 13 + 14 + 21 = 48 dec = 3 + 4 + 2 = 9
*/
private static int getMonth(String monthString) {
int hash = Character.toLowerCase(monthString.charAt(0)) +
Character.toLowerCase(monthString.charAt(1)) +
Character.toLowerCase(monthString.charAt(2)) - 3 * 'a';
switch (hash) {
case 22:
return Calendar.JANUARY;
case 10:
return Calendar.FEBRUARY;
case 29:
return Calendar.MARCH;
case 32:
return Calendar.APRIL;
case 36:
return Calendar.MAY;
case 42:
return Calendar.JUNE;
case 40:
return Calendar.JULY;
case 26:
return Calendar.AUGUST;
case 37:
return Calendar.SEPTEMBER;
case 35:
return Calendar.OCTOBER;
case 48:
return Calendar.NOVEMBER;
case 9:
return Calendar.DECEMBER;
default:
throw new IllegalArgumentException();
}
}
private static int getYear(String yearString) {
if (yearString.length() == 2) {
int year = (yearString.charAt(0) - '0') * 10
+ (yearString.charAt(1) - '0');
if (year >= 70) {
return year + 1900;
} else {
return year + 2000;
}
} else if (yearString.length() == 3) {
// According to RFC 2822, three digit years should be added to 1900.
int year = (yearString.charAt(0) - '0') * 100
+ (yearString.charAt(1) - '0') * 10
+ (yearString.charAt(2) - '0');
return year + 1900;
} else if (yearString.length() == 4) {
return (yearString.charAt(0) - '0') * 1000
+ (yearString.charAt(1) - '0') * 100
+ (yearString.charAt(2) - '0') * 10
+ (yearString.charAt(3) - '0');
} else {
return 1970;
}
}
private static TimeOfDay getTime(String timeString) {
// HH might be H
int i = 0;
int hour = timeString.charAt(i++) - '0';
if (timeString.charAt(i) != ':')
hour = hour * 10 + (timeString.charAt(i++) - '0');
// Skip ':'
i++;
int minute = (timeString.charAt(i++) - '0') * 10
+ (timeString.charAt(i++) - '0');
// Skip ':'
i++;
int second = (timeString.charAt(i++) - '0') * 10
+ (timeString.charAt(i++) - '0');
return new TimeOfDay(hour, minute, second);
}
}

View File

@ -0,0 +1,80 @@
cmake_minimum_required(VERSION 3.14)
project(QtAndroidToolsDemo LANGUAGES CXX)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
if(ANDROID)
set(ANDROID_PACKAGE_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/android")
endif()
find_package(QT NAMES Qt6 Qt5 COMPONENTS Core Quick QuickControls2 Svg REQUIRED)
find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Core Quick QuickControls2 Svg REQUIRED)
if(ANDROID)
set(QTAT_APP_PERMISSIONS ON)
set(QTAT_APK_EXPANSION_FILES ON)
set(QTAT_APK_INFO ON)
set(QTAT_SCREEN ON)
set(QTAT_SYSTEM ON)
set(QTAT_BATTERY_STATE ON)
set(QTAT_SIGNAL_STRENGTH ON)
set(QTAT_IMAGES ON)
set(QTAT_NOTIFICATION ON)
set(QTAT_ADMOB_BANNER ON)
set(QTAT_ADMOB_INTERSTITIAL ON)
set(QTAT_ADMOB_REWARDED_VIDEO ON)
set(QTAT_PLAY_STORE ON)
set(QTAT_GOOGLE_ACCOUNT ON)
set(QTAT_GOOGLE_DRIVE ON)
set(QTAT_SHARING ON)
set(QTAT_USER_MESSAGING_PLATFORM ON)
set(QTAT_AUDIO ON)
find_package(Qt${QT_VERSION_MAJOR} COMPONENTS AndroidExtras REQUIRED)
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/../QtAndroidTools build)
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../QtAndroidTools)
endif()
set(PROJECT_SOURCES
Main.cpp
Sources.qrc
QtAndroidToolsDemo.qrc
android/AndroidManifest.xml
android/build.gradle
android/gradle.properties
)
if(${QT_VERSION_MAJOR} GREATER_EQUAL 6)
qt_add_executable(QtAndroidToolsDemo
${PROJECT_SOURCES}
)
else()
if(ANDROID)
add_library(QtAndroidToolsDemo SHARED
${PROJECT_SOURCES}
)
else()
add_executable(QtAndroidToolsDemo
${PROJECT_SOURCES}
)
endif()
endif()
target_compile_definitions(QtAndroidToolsDemo
PRIVATE $<$<OR:$<CONFIG:Debug>,$<CONFIG:RelWithDebInfo>>:QT_QML_DEBUG>)
target_link_libraries(QtAndroidToolsDemo
PRIVATE Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::Quick
Qt${QT_VERSION_MAJOR}::QuickControls2 Qt${QT_VERSION_MAJOR}::Svg)
if(ANDROID)
target_include_directories(QtAndroidToolsDemo
PRIVATE ${Qt5AndroidExtras_INCLUDE_DIRS})
target_link_libraries(QtAndroidToolsDemo
PRIVATE Qt${QT_VERSION_MAJOR}::AndroidExtras
QtAndroidTools)
endif()

View File

@ -2,6 +2,9 @@
#include <QQmlApplicationEngine>
#include <QQuickStyle>
#include <QIcon>
#include <QDir>
#include <QFile>
#include <QStandardPaths>
#include "QtAndroidTools.h"
void prepareSharedFiles(const QString &sharedFolderName)

View File

@ -106,6 +106,8 @@ ApplicationWindow {
ListElement { title: "GoogleAccount"; source: "qrc:/tools/AndroidGoogleAccount.qml" }
ListElement { title: "GoogleDrive"; source: "qrc:/tools/AndroidGoogleDrive.qml" }
ListElement { title: "Sharing"; source: "qrc:/tools/AndroidSharing.qml" }
ListElement { title: "UserMessagingPlatform"; source: "qrc:/tools/AndroidUserMessagingPlatform.qml" }
ListElement { title: "Audio"; source: "qrc:/tools/AndroidAudio.qml" }
ListElement { title: "System"; source: "qrc:/tools/AndroidSystem.qml" }
}
@ -130,7 +132,7 @@ ApplicationWindow {
}
Label {
text: "Small collections of tools for manage some Android features from Qt and QML app"
text: "Small collections of tools to manage some Android features from Qt and QML app"
anchors.margins: 20
anchors.top: logo.bottom
anchors.left: parent.left

View File

@ -8,16 +8,13 @@ SOURCES += \
Main.cpp
RESOURCES += \
Sources.qrc \
qtquickcontrols2.conf \
icons/tools/index.theme \
$$files(icons/*.png, true) \
$$files(images/*.jpg) \
$$files(images/*.svg)
QtAndroidToolsDemo.qrc \
Sources.qrc
OTHER_FILES += \
android/AndroidManifest.xml \
android/build.gradle
android/build.gradle \
android/gradle.properties
android {
ANDROID_PACKAGE_SOURCE_DIR = $$PWD/android
@ -37,7 +34,10 @@ DEFINES += \
QTAT_PLAY_STORE \
QTAT_GOOGLE_ACCOUNT \
QTAT_GOOGLE_DRIVE \
QTAT_SHARING
QTAT_SHARING \
QTAT_USER_MESSAGING_PLATFORM \
QTAT_AUDIO
include(../QtAndroidTools/QtAndroidTools.pri)
}

View File

@ -0,0 +1,22 @@
<RCC>
<qresource prefix="/">
<file>qtquickcontrols2.conf</file>
<file>icons/tools/index.theme</file>
<file>images/correct.svg</file>
<file>images/error.svg</file>
<file>images/logo_falsinsoft.jpg</file>
<file>images/unknown.svg</file>
<file>icons/tools/20x20/back.png</file>
<file>icons/tools/20x20/drawer.png</file>
<file>icons/tools/20x20/menu.png</file>
<file>icons/tools/20x20@2/back.png</file>
<file>icons/tools/20x20@2/drawer.png</file>
<file>icons/tools/20x20@2/menu.png</file>
<file>icons/tools/20x20@3/back.png</file>
<file>icons/tools/20x20@3/drawer.png</file>
<file>icons/tools/20x20@3/menu.png</file>
<file>icons/tools/20x20@4/back.png</file>
<file>icons/tools/20x20@4/drawer.png</file>
<file>icons/tools/20x20@4/menu.png</file>
</qresource>
</RCC>

View File

@ -18,5 +18,7 @@
<file>tools/AndroidGoogleDrive.qml</file>
<file>tools/AndroidSystem.qml</file>
<file>tools/AndroidSharing.qml</file>
<file>tools/AndroidUserMessagingPlatform.qml</file>
<file>tools/AndroidAudio.qml</file>
</qresource>
</RCC>

View File

@ -1,12 +1,11 @@
<?xml version="1.0"?>
<manifest package="com.falsinsoft.QtAndroidToolsDemo" xmlns:android="http://schemas.android.com/apk/res/android" android:versionName="1.0" android:versionCode="1" android:installLocation="auto">
<application android:hardwareAccelerated="true" android:name="org.qtproject.qt5.android.bindings.QtApplication" android:label="-- %%INSERT_APP_NAME%% --" android:extractNativeLibs="true" android:icon="@drawable/icon">
<activity android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|layoutDirection|locale|fontScale|keyboard|keyboardHidden|navigation|mcc|mnc|density" android:name="org.qtproject.qt5.android.bindings.QtActivity" android:label="-- %%INSERT_APP_NAME%% --" android:screenOrientation="unspecified" android:launchMode="singleTop" android:theme="@style/SplashScreenTheme">
<activity android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|layoutDirection|locale|fontScale|keyboard|keyboardHidden|navigation|mcc|mnc|density" android:name="org.qtproject.qt5.android.bindings.QtActivity" android:label="-- %%INSERT_APP_NAME%% --" android:screenOrientation="portrait" android:launchMode="singleTop" android:theme="@android:style/Theme.Translucent.NoTitleBar">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND"/>
<category android:name="android.intent.category.DEFAULT"/>
@ -17,18 +16,15 @@
<category android:name="android.intent.category.DEFAULT"/>
<data android:mimeType="image/*"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.PICK"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.OPENABLE"/>
<data android:mimeType="image/*"/>
</intent-filter>
<!-- Application arguments -->
<!-- meta-data android:name="android.app.arguments" android:value="arg1 arg2 arg3"/ -->
<!-- Application arguments -->
<meta-data android:name="android.app.lib_name" android:value="-- %%INSERT_APP_LIB_NAME%% --"/>
<meta-data android:name="android.app.qt_sources_resource_id" android:resource="@array/qt_sources"/>
<meta-data android:name="android.app.repository" android:value="default"/>
@ -36,7 +32,6 @@
<meta-data android:name="android.app.bundled_libs_resource_id" android:resource="@array/bundled_libs"/>
<!-- Deploy Qt libs as part of package -->
<meta-data android:name="android.app.bundle_local_qt_libs" android:value="-- %%BUNDLE_LOCAL_QT_LIBS%% --"/>
<!-- Run with local libs -->
<meta-data android:name="android.app.use_local_qt_libs" android:value="-- %%USE_LOCAL_QT_LIBS%% --"/>
<meta-data android:name="android.app.libs_prefix" android:value="/data/local/tmp/qt/"/>
@ -51,7 +46,6 @@
<meta-data android:value="@string/fatal_error_msg" android:name="android.app.fatal_error_msg"/>
<meta-data android:value="@string/unsupported_android_version" android:name="android.app.unsupported_android_version"/>
<!-- Messages maps -->
<!-- Splash screen -->
<!-- Orientation-specific (portrait/landscape) data is checked first. If not available for current orientation,
then android.app.splash_screen_drawable. For best results, use together with splash_screen_sticky and
@ -59,10 +53,7 @@
are done populating your window with content. -->
<!-- meta-data android:name="android.app.splash_screen_drawable_portrait" android:resource="@drawable/logo_portrait" / -->
<!-- meta-data android:name="android.app.splash_screen_drawable_landscape" android:resource="@drawable/logo_landscape" / -->
<meta-data android:name="android.app.splash_screen_drawable" android:resource="@drawable/splashscreen"/>
<meta-data android:name="android.app.splash_screen_sticky" android:value="true"/>
<!-- Splash screen -->
<!-- Background running -->
<!-- Warning: changing this value to true may cause unexpected crashes if the
application still try to draw after
@ -70,11 +61,9 @@
signal is sent! -->
<meta-data android:name="android.app.background_running" android:value="false"/>
<!-- Background running -->
<!-- auto screen scale factor -->
<meta-data android:name="android.app.auto_screen_scale_factor" android:value="false"/>
<!-- auto screen scale factor -->
<!-- extract android style -->
<!-- available android:values :
* default - In most cases this will be the same as "full", but it can also be something else if needed, e.g., for compatibility reasons
@ -84,19 +73,22 @@
-->
<meta-data android:name="android.app.extract_android_style" android:value="default"/>
<!-- extract android style -->
</activity>
<!-- For adding service(s) please check: https://wiki.qt.io/AndroidServices -->
<service android:name="com.google.android.vending.expansion.downloader.impl.DownloaderService" android:enabled="true"/>
<receiver android:name="com.google.android.vending.expansion.downloader.impl.DownloaderService$AlarmReceiver" android:enabled="true"/>
<provider android:name="android.support.v4.content.FileProvider" android:authorities="${applicationId}.qtandroidtoolsfileprovider" android:grantUriPermissions="true" android:exported="false">
<meta-data android:name="android.app.splash_screen_drawable" android:resource="@drawable/logo"/>
<meta-data android:name="android.app.splash_screen_sticky" android:value="true"/>
</activity>
<!-- For adding service(s) please check: https://wiki.qt.io/AndroidServices -->
<service android:name="com.google.android.vending.expansion.downloader.impl.DownloaderService">
<meta-data android:name="android.app.lib_name" android:value="-- %%INSERT_APP_LIB_NAME%% --"/>
</service>
<receiver android:name="com.google.android.vending.expansion.downloader.impl.DownloaderService$AlarmReceiver" android:enabled="true"/>
<provider android:name="androidx.core.content.FileProvider" android:authorities="${applicationId}.qtandroidtoolsfileprovider" android:grantUriPermissions="true" android:exported="false">
<meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/sharedfilepaths"/>
</provider>
</provider>
<meta-data android:name="com.google.android.gms.ads.APPLICATION_ID" android:value="ca-app-pub-3940256099942544~3347511713"/>
<meta-data android:name="com.google.android.gms.ads.AD_MANAGER_APP" android:value="true"/>
</application>
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="28"/>
<supports-screens android:largeScreens="true" android:normalScreens="true" android:anyDensity="true" android:smallScreens="true"/>
<!-- The following comment will be replaced upon deployment with default permissions based on the dependencies of the application.
@ -112,11 +104,11 @@
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_CALENDAR"/>
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
<uses-permission android:name="android.permission.READ_CONTACTS"/>
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
<uses-permission android:name="android.permission.READ_CONTACTS"/>
<uses-permission android:name="android.permission.GET_ACCOUNTS"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
</manifest>

View File

@ -18,9 +18,8 @@ apply plugin: 'com.android.application'
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar'])
implementation 'com.android.support:support-v4:26.+'
implementation 'com.google.android.gms:play-services-ads:16.+'
implementation 'com.google.android.gms:play-services-auth:16.+'
implementation 'com.google.android.gms:play-services-ads:19.+'
implementation 'com.google.android.gms:play-services-auth:18.+'
implementation 'com.google.http-client:google-http-client-gson:1.26.0'
implementation('com.google.api-client:google-api-client-android:1.26.0') {
exclude group: 'org.apache.httpcomponents'
@ -28,6 +27,9 @@ dependencies {
implementation ('com.google.apis:google-api-services-drive:v3-rev173-1.25.0') {
exclude group: 'org.apache.httpcomponents'
}
implementation 'com.google.android.ump:user-messaging-platform:1.0.0'
implementation 'androidx.annotation:annotation:1.1.0'
implementation 'com.google.guava:guava:28.2-android'
}
android {

View File

@ -0,0 +1,2 @@
android.useAndroidX=true
android.enableJetifier=true

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.8 KiB

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

View File

@ -1,11 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape android:shape="rectangle" >
<solid android:color="#FFFFFF" />
</shape>
</item>
<item>
<bitmap android:src="@drawable/icon" android:gravity="center" />
</item>
</layer-list>

View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="SplashScreenTheme">
<item name="android:windowBackground">@drawable/splashscreen</item>
</style>
</resources>

View File

@ -4,7 +4,7 @@ import QtQuick.Controls 2.12
import QtQuick.Dialogs 1.3
import QtAndroidTools 1.0
Page {
ScrollablePage {
id: page
padding: 0
@ -12,7 +12,7 @@ Page {
width: parent.wdith
height: parent.height * 0.9
anchors.centerIn: parent
spacing: 20
spacing: 10
Label {
anchors.horizontalCenter: parent.horizontalCenter
@ -74,12 +74,43 @@ Page {
text: "Banner not loaded"
}
Label {
anchors.horizontalCenter: parent.horizontalCenter
font.bold: true
font.pixelSize: 15
text: "Adaptive Banner"
}
Rectangle {
anchors.horizontalCenter: parent.horizontalCenter
border.width: 1
border.color: "black"
width: banner3.width
height: banner3.height
QtAndroidAdMobBanner {
id: banner3
unitId: "ca-app-pub-3940256099942544/6300978111"
type: QtAndroidAdMobBanner.TYPE_ADAPTIVE_BANNER
keywords: ["keyword_1", "keyword_2", "keyword_3"]
onLoading: banner3state.text = "Loading"
onLoaded: banner3state.text = "Loaded"
onLoadError: banner3state.text = "Error " + errorId
}
}
Label {
id: banner3state
anchors.horizontalCenter: parent.horizontalCenter
font.pixelSize: 13
text: "Banner not loaded"
}
Button {
anchors.horizontalCenter: parent.horizontalCenter
text: "Show banners"
onClicked: {
banner1.show();
banner2.show();
banner3.show();
}
}
Button {
@ -88,6 +119,7 @@ Page {
onClicked: {
banner1.hide();
banner2.hide();
banner3.hide();
}
}
Button {
@ -96,6 +128,7 @@ Page {
onClicked: {
banner1.reload();
banner2.reload();
banner3.reload();
}
}
}

View File

@ -0,0 +1,38 @@
import QtQuick 2.12
import QtQuick.Controls 2.12
import QtQuick.Dialogs 1.3
import QtAndroidTools 1.0
Page {
id: page
padding: 40
Column {
width: parent.width * 0.9
height: parent.height * 0.9
anchors.centerIn: parent
spacing: 15
Label {
anchors.horizontalCenter: parent.horizontalCenter
font.pixelSize: 15
text: "Focus:"
}
Label {
anchors.horizontalCenter: parent.horizontalCenter
font.pixelSize: 15
text: QtAndroidAudio.focus ? "Yes" : "No"
}
Button {
anchors.horizontalCenter: parent.horizontalCenter
text: "requestFocus"
onClicked: QtAndroidAudio.requestFocus()
}
Button {
anchors.horizontalCenter: parent.horizontalCenter
text: "abandonFocus"
onClicked: QtAndroidAudio.abandonFocus()
}
}
}

View File

@ -0,0 +1,95 @@
import QtQuick 2.12
import QtQuick.Controls 2.12
import QtQuick.Dialogs 1.3
import QtAndroidTools 1.0
Page {
id: page
padding: 20
Connections {
target: QtAndroidUserMessagingPlatform
function onConsentFormRequestResult(eventId)
{
switch(eventId)
{
case QtAndroidUserMessagingPlatform.CONSENT_FORM_INFO_UPDATE_FAILURE:
consentFormShowResult.text = "CONSENT_FORM_INFO_UPDATE_FAILURE";
break;
case QtAndroidUserMessagingPlatform.CONSENT_FORM_NOT_AVAILABLE:
consentFormShowResult.text = "CONSENT_FORM_NOT_AVAILABLE";
break;
case QtAndroidUserMessagingPlatform.CONSENT_FORM_LOAD_SUCCESS:
consentFormShowResult.text = "CONSENT_FORM_LOAD_SUCCESS";
break;
case QtAndroidUserMessagingPlatform.CONSENT_FORM_LOAD_FAILURE:
consentFormShowResult.text = "CONSENT_FORM_LOAD_FAILURE";
break;
}
}
function onConsentFormClosed()
{
}
}
Column {
anchors.fill: parent
spacing: 5
Label {
id: consentFormRequestResult
width: parent.width
horizontalAlignment: Text.AlignHCenter
font.pixelSize: 15
text: "-----"
}
Button {
anchors.horizontalCenter: parent.horizontalCenter
text: "requestConsentForm"
onClicked: QtAndroidUserMessagingPlatform.requestConsentForm()
}
Label {
id: consentStatus
width: parent.width
horizontalAlignment: Text.AlignHCenter
font.pixelSize: 15
text: "-----"
}
Button {
anchors.horizontalCenter: parent.horizontalCenter
text: "consentStatus"
onClicked: {
var status = QtAndroidUserMessagingPlatform.consentStatus();
switch(eventId)
{
case QtAndroidUserMessagingPlatform.CONSENT_FORM_STATUS_UNKNOWN:
consentStatus.text = "CONSENT_FORM_STATUS_UNKNOWN";
break;
case QtAndroidUserMessagingPlatform.CONSENT_FORM_STATUS_REQUIRED:
consentStatus.text = "CONSENT_FORM_STATUS_REQUIRED";
break;
case QtAndroidUserMessagingPlatform.CONSENT_FORM_STATUS_NOT_REQUIRED:
consentStatus.text = "CONSENT_FORM_STATUS_NOT_REQUIRED";
break;
case QtAndroidUserMessagingPlatform.CONSENT_FORM_STATUS_OBTAINED:
consentStatus.text = "CONSENT_FORM_STATUS_OBTAINED";
break;
}
}
}
Button {
anchors.horizontalCenter: parent.horizontalCenter
text: "showConsentForm"
onClicked: QtAndroidUserMessagingPlatform.showConsentForm()
}
Button {
anchors.horizontalCenter: parent.horizontalCenter
text: "resetConsentInformation"
onClicked: QtAndroidUserMessagingPlatform.resetConsentInformation()
}
}
}

View File

@ -52,5 +52,11 @@ Allow access to Google Drive files and folders
**Sharing**
Allow to use the Android sharing operation
**UserMessagingPlatform**
Allow to manage consent form
**Audio**
Allow to request audio focus
**System**
Export methods for get some system info