Merge v0.6.0 and v0.6.1 #29
24 changed files with 1657 additions and 26 deletions
|
@ -10,7 +10,7 @@
|
|||
|
||||
<supports-screens android:largeScreens="true" android:normalScreens="true" android:anyDensity="true" android:smallScreens="true"/>
|
||||
<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">
|
||||
<activity android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|layoutDirection|locale|fontScale|keyboard|keyboardHidden|navigation|mcc|mnc|density" android:name="de.itsblue.blueROCK.MainActivity" android:label="-- %%INSERT_APP_NAME%% --" android:screenOrientation="unspecified" android:launchMode="singleTop">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
|
@ -68,8 +68,24 @@
|
|||
-->
|
||||
<meta-data android:name="android.app.extract_android_style" android:value="default"/>
|
||||
<!-- extract android style -->
|
||||
<!-- Handle shared incoming urls -->
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SEND"/>
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<data android:mimeType="*/*"/>
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<data android:mimeType="*/*"/>
|
||||
<data android:scheme="file"/>
|
||||
<data android:scheme="content"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<!-- For adding service(s) please check: https://wiki.qt.io/AndroidServices -->
|
||||
<provider android:name="android.support.v4.content.FileProvider" android:authorities="de.itsblue.blueROCK.fileprovider" android:grantUriPermissions="true" android:exported="false">
|
||||
<meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/filepaths"/>
|
||||
</provider>
|
||||
</application>
|
||||
|
||||
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="29"/>
|
||||
|
|
|
@ -18,6 +18,7 @@ apply plugin: 'com.android.application'
|
|||
|
||||
dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar'])
|
||||
compile 'com.android.support:support-v4:25.3.1'
|
||||
}
|
||||
|
||||
android {
|
||||
|
@ -77,5 +78,6 @@ android {
|
|||
|
||||
lintOptions {
|
||||
checkReleaseBuilds false
|
||||
abortOnError false
|
||||
}
|
||||
}
|
||||
|
|
3
android/res/xml/filepaths.xml
Normal file
3
android/res/xml/filepaths.xml
Normal file
|
@ -0,0 +1,3 @@
|
|||
<paths xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<files-path name="my_shared_files" path="share_example_x_files/" />
|
||||
</paths>
|
228
android/src/de/itsblue/blueROCK/MainActivity.java
Executable file
228
android/src/de/itsblue/blueROCK/MainActivity.java
Executable file
|
@ -0,0 +1,228 @@
|
|||
// (c) 2017 Ekkehard Gentz (ekke)
|
||||
// this project is based on ideas from
|
||||
// http://blog.lasconic.com/share-on-ios-and-android-using-qml/
|
||||
// see github project https://github.com/lasconic/ShareUtils-QML
|
||||
// also inspired by:
|
||||
// https://www.androidcode.ninja/android-share-intent-example/
|
||||
// https://www.calligra.org/blogs/sharing-with-qt-on-android/
|
||||
// https://stackoverflow.com/questions/7156932/open-file-in-another-app
|
||||
// http://www.qtcentre.org/threads/58668-How-to-use-QAndroidJniObject-for-intent-setData
|
||||
// OpenURL in At Android: got ideas from:
|
||||
// https://github.com/BernhardWenzel/open-url-in-qt-android
|
||||
// https://github.com/tobiatesan/android_intents_qt
|
||||
//
|
||||
// see also /COPYRIGHT and /LICENSE
|
||||
|
||||
package de.itsblue.blueROCK;
|
||||
|
||||
import org.qtproject.qt5.android.QtNative;
|
||||
|
||||
import org.qtproject.qt5.android.bindings.QtActivity;
|
||||
import android.os.*;
|
||||
import android.content.*;
|
||||
import android.app.*;
|
||||
|
||||
import java.lang.String;
|
||||
import android.content.Intent;
|
||||
import java.io.File;
|
||||
import android.net.Uri;
|
||||
import android.util.Log;
|
||||
import android.content.ContentResolver;
|
||||
import android.webkit.MimeTypeMap;
|
||||
|
||||
import org.ekkescorner.utils.*;
|
||||
|
||||
|
||||
|
||||
public class MainActivity extends QtActivity
|
||||
{
|
||||
// native - must be implemented in Cpp via JNI
|
||||
// 'file' scheme or resolved from 'content' scheme:
|
||||
public static native void setFileUrlReceived(String url);
|
||||
// InputStream from 'content' scheme:
|
||||
public static native void setFileReceivedAndSaved(String url);
|
||||
//
|
||||
public static native void fireActivityResult(int requestCode, int resultCode);
|
||||
//
|
||||
public static native boolean checkFileExits(String url);
|
||||
|
||||
public static boolean isIntentPending;
|
||||
public static boolean isInitialized;
|
||||
public static String workingDirPath;
|
||||
|
||||
// Use a custom Chooser without providing own App as share target !
|
||||
// see QShareUtils.java createCustomChooserAndStartActivity()
|
||||
// Selecting your own App as target could cause AndroidOS to call
|
||||
// onCreate() instead of onNewIntent()
|
||||
// and then you are in trouble because we're using 'singleInstance' as LaunchMode
|
||||
// more details: my blog at Qt
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
Log.d("ekkescorner", "onCreate QShareActivity");
|
||||
// now we're checking if the App was started from another Android App via Intent
|
||||
Intent theIntent = getIntent();
|
||||
if (theIntent != null){
|
||||
String theAction = theIntent.getAction();
|
||||
if (theAction != null){
|
||||
Log.d("ekkescorner onCreate ", theAction);
|
||||
// QML UI not ready yet
|
||||
// delay processIntent();
|
||||
isIntentPending = true;
|
||||
}
|
||||
}
|
||||
} // onCreate
|
||||
|
||||
// WIP - trying to find a solution to survive a 2nd onCreate
|
||||
// ongoing discussion in QtMob (Slack)
|
||||
// from other Apps not respecting that you only have a singleInstance
|
||||
// there are problems per ex. sharing a file from Google Files App,
|
||||
// but working well using Xiaomi FileManager App
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
Log.d("ekkescorner", "onDestroy QShareActivity");
|
||||
// super.onDestroy();
|
||||
// System.exit() closes the App before doing onCreate() again
|
||||
// then the App was restarted, but looses context
|
||||
// This works for Samsung My Files
|
||||
// but Google Files doesn't call onDestroy()
|
||||
System.exit(0);
|
||||
}
|
||||
|
||||
// we start Activity with result code
|
||||
// to test JNI with QAndroidActivityResultReceiver you must comment or rename
|
||||
// this method here - otherwise you'll get wrong request or result codes
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
// Check which request we're responding to
|
||||
Log.d("ekkescorner onActivityResult", "requestCode: "+requestCode);
|
||||
if (resultCode == RESULT_OK) {
|
||||
Log.d("ekkescorner onActivityResult - resultCode: ", "SUCCESS");
|
||||
} else {
|
||||
Log.d("ekkescorner onActivityResult - resultCode: ", "CANCEL");
|
||||
}
|
||||
// hint: result comes back too fast for Action SEND
|
||||
// if you want to delete/move the File add a Timer w 500ms delay
|
||||
// see Example App main.qml - delayDeleteTimer
|
||||
// if you want to revoke permissions for older OS
|
||||
// it makes sense also do this after the delay
|
||||
fireActivityResult(requestCode, resultCode);
|
||||
}
|
||||
|
||||
// if we are opened from other apps:
|
||||
@Override
|
||||
public void onNewIntent(Intent intent) {
|
||||
Log.d("ekkescorner", "onNewIntent");
|
||||
super.onNewIntent(intent);
|
||||
setIntent(intent);
|
||||
// Intent will be processed, if all is initialized and Qt / QML can handle the event
|
||||
if(isInitialized) {
|
||||
processIntent();
|
||||
} else {
|
||||
isIntentPending = true;
|
||||
}
|
||||
} // onNewIntent
|
||||
|
||||
public void checkPendingIntents(String workingDir) {
|
||||
isInitialized = true;
|
||||
workingDirPath = workingDir;
|
||||
Log.d("ekkescorner", workingDirPath);
|
||||
if(isIntentPending) {
|
||||
isIntentPending = false;
|
||||
Log.d("ekkescorner", "checkPendingIntents: true");
|
||||
processIntent();
|
||||
} else {
|
||||
Log.d("ekkescorner", "nothingPending");
|
||||
}
|
||||
} // checkPendingIntents
|
||||
|
||||
// process the Intent if Action is SEND or VIEW
|
||||
private void processIntent(){
|
||||
Intent intent = getIntent();
|
||||
|
||||
Uri intentUri;
|
||||
String intentScheme;
|
||||
String intentAction;
|
||||
// we are listening to android.intent.action.SEND or VIEW (see Manifest)
|
||||
if (intent.getAction().equals("android.intent.action.VIEW")){
|
||||
intentAction = "VIEW";
|
||||
intentUri = intent.getData();
|
||||
} else if (intent.getAction().equals("android.intent.action.SEND")){
|
||||
intentAction = "SEND";
|
||||
Bundle bundle = intent.getExtras();
|
||||
intentUri = (Uri)bundle.get(Intent.EXTRA_STREAM);
|
||||
} else {
|
||||
Log.d("ekkescorner Intent unknown action:", intent.getAction());
|
||||
return;
|
||||
}
|
||||
Log.d("ekkescorner action:", intentAction);
|
||||
if (intentUri == null){
|
||||
Log.d("ekkescorner Intent URI:", "is null");
|
||||
return;
|
||||
}
|
||||
|
||||
Log.d("ekkescorner Intent URI:", intentUri.toString());
|
||||
|
||||
// content or file
|
||||
intentScheme = intentUri.getScheme();
|
||||
if (intentScheme == null){
|
||||
Log.d("ekkescorner Intent URI Scheme:", "is null");
|
||||
return;
|
||||
}
|
||||
if(intentScheme.equals("file")){
|
||||
// URI as encoded string
|
||||
Log.d("ekkescorner Intent File URI: ", intentUri.toString());
|
||||
setFileUrlReceived(intentUri.toString());
|
||||
// we are done Qt can deal with file scheme
|
||||
return;
|
||||
}
|
||||
if(!intentScheme.equals("content")){
|
||||
Log.d("ekkescorner Intent URI unknown scheme: ", intentScheme);
|
||||
return;
|
||||
}
|
||||
// ok - it's a content scheme URI
|
||||
// we will try to resolve the Path to a File URI
|
||||
// if this won't work or if the File cannot be opened,
|
||||
// we'll try to copy the file into our App working dir via InputStream
|
||||
// hopefully in most cases PathResolver will give a path
|
||||
|
||||
// you need the file extension, MimeType or Name from ContentResolver ?
|
||||
// here's HowTo get it:
|
||||
Log.d("ekkescorner Intent Content URI: ", intentUri.toString());
|
||||
ContentResolver cR = this.getContentResolver();
|
||||
MimeTypeMap mime = MimeTypeMap.getSingleton();
|
||||
String fileExtension = mime.getExtensionFromMimeType(cR.getType(intentUri));
|
||||
Log.d("ekkescorner","Intent extension: "+fileExtension);
|
||||
String mimeType = cR.getType(intentUri);
|
||||
Log.d("ekkescorner"," Intent MimeType: "+mimeType);
|
||||
String name = QShareUtils.getContentName(cR, intentUri);
|
||||
if(name != null) {
|
||||
Log.d("ekkescorner Intent Name:", name);
|
||||
} else {
|
||||
Log.d("ekkescorner Intent Name:", "is NULL");
|
||||
}
|
||||
String filePath;
|
||||
filePath = QSharePathResolver.getRealPathFromURI(this, intentUri);
|
||||
if(filePath == null) {
|
||||
Log.d("ekkescorner QSharePathResolver:", "filePath is NULL");
|
||||
} else {
|
||||
Log.d("ekkescorner QSharePathResolver:", filePath);
|
||||
// to be safe check if this File Url really can be opened by Qt
|
||||
// there were problems with MS office apps on Android 7
|
||||
if (checkFileExits(filePath)) {
|
||||
setFileUrlReceived(filePath);
|
||||
// we are done Qt can deal with file scheme
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// trying the InputStream way:
|
||||
filePath = QShareUtils.createFile(cR, intentUri, workingDirPath);
|
||||
if(filePath == null) {
|
||||
Log.d("ekkescorner Intent FilePath:", "is NULL");
|
||||
return;
|
||||
}
|
||||
setFileReceivedAndSaved(filePath);
|
||||
} // processIntent
|
||||
|
||||
} // class QShareActivity
|
223
android/src/org/ekkescorner/utils/QSharePathResolver.java
Normal file
223
android/src/org/ekkescorner/utils/QSharePathResolver.java
Normal file
|
@ -0,0 +1,223 @@
|
|||
// from: https://github.com/wkh237/react-native-fetch-blob/blob/master/android/src/main/java/com/RNFetchBlob/Utils/PathResolver.java
|
||||
// MIT License, see: https://github.com/wkh237/react-native-fetch-blob/blob/master/LICENSE
|
||||
// original copyright: Copyright (c) 2017 xeiyan@gmail.com
|
||||
// src slightly modified to be used into Qt Projects: (c) 2017 ekke@ekkes-corner.org
|
||||
|
||||
package org.ekkescorner.utils;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.provider.DocumentsContract;
|
||||
import android.provider.MediaStore;
|
||||
import android.content.ContentUris;
|
||||
import android.os.Environment;
|
||||
import android.content.ContentResolver;
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import android.util.Log;
|
||||
import java.lang.NumberFormatException;
|
||||
|
||||
public class QSharePathResolver {
|
||||
public static String getRealPathFromURI(final Context context, final Uri uri) {
|
||||
|
||||
final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
|
||||
|
||||
// DocumentProvider
|
||||
if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) {
|
||||
// ExternalStorageProvider
|
||||
if (isExternalStorageDocument(uri)) {
|
||||
Log.d("ekkescorner"," isExternalStorageDocument");
|
||||
final String docId = DocumentsContract.getDocumentId(uri);
|
||||
final String[] split = docId.split(":");
|
||||
final String type = split[0];
|
||||
|
||||
if ("primary".equalsIgnoreCase(type)) {
|
||||
return Environment.getExternalStorageDirectory() + "/" + split[1];
|
||||
}
|
||||
|
||||
// TODO handle non-primary volumes
|
||||
}
|
||||
// DownloadsProvider
|
||||
else if (isDownloadsDocument(uri)) {
|
||||
Log.d("ekkescorner"," isDownloadsDocument");
|
||||
final String id = DocumentsContract.getDocumentId(uri);
|
||||
Log.d("ekkescorner"," getDocumentId "+id);
|
||||
long longId = 0;
|
||||
try
|
||||
{
|
||||
longId = Long.valueOf(id);
|
||||
}
|
||||
catch(NumberFormatException nfe)
|
||||
{
|
||||
return getDataColumn(context, uri, null, null);
|
||||
}
|
||||
final Uri contentUri = ContentUris.withAppendedId(
|
||||
Uri.parse("content://downloads/public_downloads"), longId);
|
||||
|
||||
return getDataColumn(context, contentUri, null, null);
|
||||
}
|
||||
// MediaProvider
|
||||
else if (isMediaDocument(uri)) {
|
||||
Log.d("ekkescorner"," isMediaDocument");
|
||||
final String docId = DocumentsContract.getDocumentId(uri);
|
||||
final String[] split = docId.split(":");
|
||||
final String type = split[0];
|
||||
|
||||
Uri contentUri = null;
|
||||
if ("image".equals(type)) {
|
||||
contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
|
||||
} else if ("video".equals(type)) {
|
||||
contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
|
||||
} else if ("audio".equals(type)) {
|
||||
contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
|
||||
}
|
||||
|
||||
final String selection = "_id=?";
|
||||
final String[] selectionArgs = new String[] {
|
||||
split[1]
|
||||
};
|
||||
|
||||
return getDataColumn(context, contentUri, selection, selectionArgs);
|
||||
}
|
||||
else if ("content".equalsIgnoreCase(uri.getScheme())) {
|
||||
Log.d("ekkescorner"," is uri.getScheme()");
|
||||
// Return the remote address
|
||||
if (isGooglePhotosUri(uri))
|
||||
return uri.getLastPathSegment();
|
||||
|
||||
return getDataColumn(context, uri, null, null);
|
||||
}
|
||||
// Other Providers
|
||||
else{
|
||||
Log.d("ekkescorner ","is Other Provider");
|
||||
try {
|
||||
InputStream attachment = context.getContentResolver().openInputStream(uri);
|
||||
if (attachment != null) {
|
||||
String filename = getContentName(context.getContentResolver(), uri);
|
||||
if (filename != null) {
|
||||
File file = new File(context.getCacheDir(), filename);
|
||||
FileOutputStream tmp = new FileOutputStream(file);
|
||||
byte[] buffer = new byte[1024];
|
||||
while (attachment.read(buffer) > 0) {
|
||||
tmp.write(buffer);
|
||||
}
|
||||
tmp.close();
|
||||
attachment.close();
|
||||
return file.getAbsolutePath();
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// TODO SIGNAL shareError()
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
// MediaStore (and general)
|
||||
else if ("content".equalsIgnoreCase(uri.getScheme())) {
|
||||
Log.d("ekkescorner ","NOT DocumentsContract.isDocumentUri");
|
||||
Log.d("ekkescorner"," is uri.getScheme()");
|
||||
// Return the remote address
|
||||
if (isGooglePhotosUri(uri))
|
||||
return uri.getLastPathSegment();
|
||||
Log.d("ekkescorner"," return: getDataColumn ");
|
||||
return getDataColumn(context, uri, null, null);
|
||||
}
|
||||
// File
|
||||
else if ("file".equalsIgnoreCase(uri.getScheme())) {
|
||||
Log.d("ekkescorner ","NOT DocumentsContract.isDocumentUri");
|
||||
Log.d("ekkescorner"," is file scheme");
|
||||
return uri.getPath();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static String getContentName(ContentResolver resolver, Uri uri) {
|
||||
Cursor cursor = resolver.query(uri, null, null, null, null);
|
||||
cursor.moveToFirst();
|
||||
int nameIndex = cursor.getColumnIndex(MediaStore.MediaColumns.DISPLAY_NAME);
|
||||
if (nameIndex >= 0) {
|
||||
String name = cursor.getString(nameIndex);
|
||||
cursor.close();
|
||||
return name;
|
||||
}
|
||||
cursor.close();
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value of the data column for this Uri. This is useful for
|
||||
* MediaStore Uris, and other file-based ContentProviders.
|
||||
*
|
||||
* @param context The context.
|
||||
* @param uri The Uri to query.
|
||||
* @param selection (Optional) Filter used in the query.
|
||||
* @param selectionArgs (Optional) Selection arguments used in the query.
|
||||
* @return The value of the _data column, which is typically a file path.
|
||||
*/
|
||||
public static String getDataColumn(Context context, Uri uri, String selection,
|
||||
String[] selectionArgs) {
|
||||
|
||||
Cursor cursor = null;
|
||||
String result = null;
|
||||
final String column = "_data";
|
||||
final String[] projection = {
|
||||
column
|
||||
};
|
||||
|
||||
try {
|
||||
cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs,
|
||||
null);
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
final int index = cursor.getColumnIndexOrThrow(column);
|
||||
result = cursor.getString(index);
|
||||
}
|
||||
}
|
||||
catch (Exception ex) {
|
||||
ex.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
finally {
|
||||
if (cursor != null)
|
||||
cursor.close();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param uri The Uri to check.
|
||||
* @return Whether the Uri authority is ExternalStorageProvider.
|
||||
*/
|
||||
public static boolean isExternalStorageDocument(Uri uri) {
|
||||
return "com.android.externalstorage.documents".equals(uri.getAuthority());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param uri The Uri to check.
|
||||
* @return Whether the Uri authority is DownloadsProvider.
|
||||
*/
|
||||
public static boolean isDownloadsDocument(Uri uri) {
|
||||
return "com.android.providers.downloads.documents".equals(uri.getAuthority());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param uri The Uri to check.
|
||||
* @return Whether the Uri authority is MediaProvider.
|
||||
*/
|
||||
public static boolean isMediaDocument(Uri uri) {
|
||||
return "com.android.providers.media.documents".equals(uri.getAuthority());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param uri The Uri to check.
|
||||
* @return Whether the Uri authority is Google Photos.
|
||||
*/
|
||||
public static boolean isGooglePhotosUri(Uri uri) {
|
||||
return "com.google.android.apps.photos.content".equals(uri.getAuthority());
|
||||
}
|
||||
|
||||
}
|
396
android/src/org/ekkescorner/utils/QShareUtils.java
Executable file
396
android/src/org/ekkescorner/utils/QShareUtils.java
Executable file
|
@ -0,0 +1,396 @@
|
|||
// (c) 2017 Ekkehard Gentz (ekke)
|
||||
// this project is based on ideas from
|
||||
// http://blog.lasconic.com/share-on-ios-and-android-using-qml/
|
||||
// see github project https://github.com/lasconic/ShareUtils-QML
|
||||
// also inspired by:
|
||||
// https://www.androidcode.ninja/android-share-intent-example/
|
||||
// https://www.calligra.org/blogs/sharing-with-qt-on-android/
|
||||
// https://stackoverflow.com/questions/7156932/open-file-in-another-app
|
||||
// http://www.qtcentre.org/threads/58668-How-to-use-QAndroidJniObject-for-intent-setData
|
||||
// https://stackoverflow.com/questions/5734678/custom-filtering-of-intent-chooser-based-on-installed-android-package-name
|
||||
// see also /COPYRIGHT and /LICENSE
|
||||
|
||||
package org.ekkescorner.utils;
|
||||
|
||||
import org.qtproject.qt5.android.QtNative;
|
||||
|
||||
import java.lang.String;
|
||||
import android.content.Intent;
|
||||
import java.io.File;
|
||||
import android.net.Uri;
|
||||
import android.util.Log;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.database.Cursor;
|
||||
import android.provider.MediaStore;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.FileOutputStream;
|
||||
|
||||
import java.util.List;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import java.util.ArrayList;
|
||||
import android.content.pm.PackageManager;
|
||||
import java.util.Comparator;
|
||||
import java.util.Collections;
|
||||
import android.content.Context;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import android.os.Build;
|
||||
|
||||
import android.support.v4.content.FileProvider;
|
||||
import android.support.v4.app.ShareCompat;
|
||||
|
||||
public class QShareUtils
|
||||
{
|
||||
// reference Authority as defined in AndroidManifest.xml
|
||||
private static String AUTHORITY="de.itsblue.blueROCK.fileprovider";
|
||||
|
||||
protected QShareUtils()
|
||||
{
|
||||
Log.d("ekkescorner", "QShareUtils()");
|
||||
}
|
||||
|
||||
public static boolean checkMimeTypeView(String mimeType) {
|
||||
if (QtNative.activity() == null)
|
||||
return false;
|
||||
Intent myIntent = new Intent();
|
||||
myIntent.setAction(Intent.ACTION_VIEW);
|
||||
// without an URI resolve always fails
|
||||
// an empty URI allows to resolve the Activity
|
||||
File fileToShare = new File("");
|
||||
Uri uri = Uri.fromFile(fileToShare);
|
||||
myIntent.setDataAndType(uri, mimeType);
|
||||
|
||||
// Verify that the intent will resolve to an activity
|
||||
if (myIntent.resolveActivity(QtNative.activity().getPackageManager()) != null) {
|
||||
Log.d("ekkescorner checkMime ", "YEP - we can go on and View");
|
||||
return true;
|
||||
} else {
|
||||
Log.d("ekkescorner checkMime", "sorry - no App available to View");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean checkMimeTypeEdit(String mimeType) {
|
||||
if (QtNative.activity() == null)
|
||||
return false;
|
||||
Intent myIntent = new Intent();
|
||||
myIntent.setAction(Intent.ACTION_EDIT);
|
||||
// without an URI resolve always fails
|
||||
// an empty URI allows to resolve the Activity
|
||||
File fileToShare = new File("");
|
||||
Uri uri = Uri.fromFile(fileToShare);
|
||||
myIntent.setDataAndType(uri, mimeType);
|
||||
|
||||
// Verify that the intent will resolve to an activity
|
||||
if (myIntent.resolveActivity(QtNative.activity().getPackageManager()) != null) {
|
||||
Log.d("ekkescorner checkMime ", "YEP - we can go on and Edit");
|
||||
return true;
|
||||
} else {
|
||||
Log.d("ekkescorner checkMime", "sorry - no App available to Edit");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean shareText(String text) {
|
||||
if (QtNative.activity() == null)
|
||||
return false;
|
||||
Intent sendIntent = new Intent();
|
||||
sendIntent.setAction(Intent.ACTION_SEND);
|
||||
sendIntent.putExtra(Intent.EXTRA_TEXT, text);
|
||||
sendIntent.setType("text/plain");
|
||||
|
||||
// Verify that the intent will resolve to an activity
|
||||
if (sendIntent.resolveActivity(QtNative.activity().getPackageManager()) != null) {
|
||||
QtNative.activity().startActivity(sendIntent);
|
||||
return true;
|
||||
} else {
|
||||
Log.d("ekkescorner share", "Intent not resolved");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// thx @oxied and @pooks for the idea: https://stackoverflow.com/a/18835895/135559
|
||||
// theIntent is already configured with all needed properties and flags
|
||||
// so we only have to add the packageName of targeted app
|
||||
public static boolean createCustomChooserAndStartActivity(Intent theIntent, String title, int requestId, Uri uri) {
|
||||
final Context context = QtNative.activity();
|
||||
final PackageManager packageManager = context.getPackageManager();
|
||||
final boolean isLowerOrEqualsKitKat = Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT;
|
||||
|
||||
// MATCH_DEFAULT_ONLY: Resolution and querying flag. if set, only filters that support the CATEGORY_DEFAULT will be considered for matching.
|
||||
// Check if there is a default app for this type of content.
|
||||
ResolveInfo defaultAppInfo = packageManager.resolveActivity(theIntent, PackageManager.MATCH_DEFAULT_ONLY);
|
||||
if(defaultAppInfo == null) {
|
||||
Log.d("ekkescorner", title+" PackageManager cannot resolve Activity");
|
||||
return false;
|
||||
}
|
||||
|
||||
// had to remove this check - there can be more Activity names, per ex
|
||||
// com.google.android.apps.docs.editors.kix.quickword.QuickWordDocumentOpenerActivityAlias
|
||||
// if (!defaultAppInfo.activityInfo.name.endsWith("ResolverActivity") && !defaultAppInfo.activityInfo.name.endsWith("EditActivity")) {
|
||||
// Log.d("ekkescorner", title+" defaultAppInfo not Resolver or EditActivity: "+defaultAppInfo.activityInfo.name);
|
||||
// return false;
|
||||
//}
|
||||
|
||||
// Retrieve all apps for our intent. Check if there are any apps returned
|
||||
List<ResolveInfo> appInfoList = packageManager.queryIntentActivities(theIntent, PackageManager.MATCH_DEFAULT_ONLY);
|
||||
if (appInfoList.isEmpty()) {
|
||||
Log.d("ekkescorner", title+" appInfoList.isEmpty");
|
||||
return false;
|
||||
}
|
||||
Log.d("ekkescorner", title+" appInfoList: "+appInfoList.size());
|
||||
|
||||
// Sort in alphabetical order
|
||||
Collections.sort(appInfoList, new Comparator<ResolveInfo>() {
|
||||
@Override
|
||||
public int compare(ResolveInfo first, ResolveInfo second) {
|
||||
String firstName = first.loadLabel(packageManager).toString();
|
||||
String secondName = second.loadLabel(packageManager).toString();
|
||||
return firstName.compareToIgnoreCase(secondName);
|
||||
}
|
||||
});
|
||||
|
||||
List<Intent> targetedIntents = new ArrayList<Intent>();
|
||||
// Filter itself and create intent with the rest of the apps.
|
||||
for (ResolveInfo appInfo : appInfoList) {
|
||||
// get the target PackageName
|
||||
String targetPackageName = appInfo.activityInfo.packageName;
|
||||
// we don't want to share with our own app
|
||||
// in fact sharing with own app with resultCode will crash because doesn't work well with launch mode 'singleInstance'
|
||||
if (targetPackageName.equals(context.getPackageName())) {
|
||||
continue;
|
||||
}
|
||||
// if you have a blacklist of apps please exclude them here
|
||||
|
||||
// we create the targeted Intent based on our already configured Intent
|
||||
Intent targetedIntent = new Intent(theIntent);
|
||||
// now add the target packageName so this Intent will only find the one specific App
|
||||
targetedIntent.setPackage(targetPackageName);
|
||||
// collect all these targetedIntents
|
||||
targetedIntents.add(targetedIntent);
|
||||
|
||||
// legacy support and Workaround for Android bug
|
||||
// grantUriPermission needed for KITKAT or older
|
||||
// see https://code.google.com/p/android/issues/detail?id=76683
|
||||
// also: https://stackoverflow.com/questions/18249007/how-to-use-support-fileprovider-for-sharing-content-to-other-apps
|
||||
if(isLowerOrEqualsKitKat) {
|
||||
Log.d("ekkescorner", "legacy support grantUriPermission");
|
||||
context.grantUriPermission(targetPackageName, uri, Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
|
||||
// attention: you must revoke the permission later, so this only makes sense with getting back a result to know that Intent was done
|
||||
// I always move or delete the file, so I don't revoke permission
|
||||
}
|
||||
}
|
||||
|
||||
// check if there are apps found for our Intent to avoid that there was only our own removed app before
|
||||
if (targetedIntents.isEmpty()) {
|
||||
Log.d("ekkescorner", title+" targetedIntents.isEmpty");
|
||||
return false;
|
||||
}
|
||||
|
||||
// now we can create our Intent with custom Chooser
|
||||
// we need all collected targetedIntents as EXTRA_INITIAL_INTENTS
|
||||
// we're using the last targetedIntent as initializing Intent, because
|
||||
// chooser adds its initializing intent to the end of EXTRA_INITIAL_INTENTS :)
|
||||
Intent chooserIntent = Intent.createChooser(targetedIntents.remove(targetedIntents.size() - 1), title);
|
||||
if (targetedIntents.isEmpty()) {
|
||||
Log.d("ekkescorner", title+" only one Intent left for Chooser");
|
||||
} else {
|
||||
chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, targetedIntents.toArray(new Parcelable[] {}));
|
||||
}
|
||||
// Verify that the intent will resolve to an activity
|
||||
if (chooserIntent.resolveActivity(QtNative.activity().getPackageManager()) != null) {
|
||||
if(requestId > 0) {
|
||||
QtNative.activity().startActivityForResult(chooserIntent, requestId);
|
||||
} else {
|
||||
QtNative.activity().startActivity(chooserIntent);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
Log.d("ekkescorner", title+" Chooser Intent not resolved. Should never happen");
|
||||
return false;
|
||||
}
|
||||
|
||||
// I am deleting the files from shared folder when Activity was done or canceled
|
||||
// so probably I don't have to revike FilePermissions for older OS
|
||||
// if you don't delete or move the file: here's what you must done to revoke the access
|
||||
public static void revokeFilePermissions(String filePath) {
|
||||
final Context context = QtNative.activity();
|
||||
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) {
|
||||
File file = new File(filePath);
|
||||
Uri uri = FileProvider.getUriForFile(context, AUTHORITY, file);
|
||||
context.revokeUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean sendFile(String filePath, String title, String mimeType, int requestId) {
|
||||
if (QtNative.activity() == null)
|
||||
return false;
|
||||
|
||||
// using v4 support library create the Intent from ShareCompat
|
||||
// Intent sendIntent = new Intent();
|
||||
Intent sendIntent = ShareCompat.IntentBuilder.from(QtNative.activity()).getIntent();
|
||||
sendIntent.setAction(Intent.ACTION_SEND);
|
||||
|
||||
File imageFileToShare = new File(filePath);
|
||||
|
||||
// Using FileProvider you must get the URI from FileProvider using your AUTHORITY
|
||||
// Uri uri = Uri.fromFile(imageFileToShare);
|
||||
Uri uri;
|
||||
try {
|
||||
uri = FileProvider.getUriForFile(QtNative.activity(), AUTHORITY, imageFileToShare);
|
||||
} catch (IllegalArgumentException e) {
|
||||
Log.d("ekkescorner sendFile - cannot be shared: ", filePath);
|
||||
return false;
|
||||
}
|
||||
|
||||
Log.d("ekkescorner sendFile", uri.toString());
|
||||
sendIntent.putExtra(Intent.EXTRA_STREAM, uri);
|
||||
|
||||
if(mimeType == null || mimeType.isEmpty()) {
|
||||
// fallback if mimeType not set
|
||||
mimeType = QtNative.activity().getContentResolver().getType(uri);
|
||||
Log.d("ekkescorner sendFile guessed mimeType:", mimeType);
|
||||
} else {
|
||||
Log.d("ekkescorner sendFile w mimeType:", mimeType);
|
||||
}
|
||||
|
||||
sendIntent.setType(mimeType);
|
||||
|
||||
sendIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
sendIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
|
||||
|
||||
return createCustomChooserAndStartActivity(sendIntent, title, requestId, uri);
|
||||
}
|
||||
|
||||
public static boolean viewFile(String filePath, String title, String mimeType, int requestId) {
|
||||
if (QtNative.activity() == null)
|
||||
return false;
|
||||
|
||||
// using v4 support library create the Intent from ShareCompat
|
||||
// Intent viewIntent = new Intent();
|
||||
Intent viewIntent = ShareCompat.IntentBuilder.from(QtNative.activity()).getIntent();
|
||||
viewIntent.setAction(Intent.ACTION_VIEW);
|
||||
|
||||
File imageFileToShare = new File(filePath);
|
||||
|
||||
// Using FileProvider you must get the URI from FileProvider using your AUTHORITY
|
||||
// Uri uri = Uri.fromFile(imageFileToShare);
|
||||
Uri uri;
|
||||
try {
|
||||
uri = FileProvider.getUriForFile(QtNative.activity(), AUTHORITY, imageFileToShare);
|
||||
} catch (IllegalArgumentException e) {
|
||||
Log.d("ekkescorner viewFile - cannot be shared: ", filePath);
|
||||
return false;
|
||||
}
|
||||
// now we got a content URI per ex
|
||||
// content://org.ekkescorner.examples.sharex.fileprovider/my_shared_files/qt-logo.png
|
||||
// from a fileUrl:
|
||||
// /data/user/0/org.ekkescorner.examples.sharex/files/share_example_x_files/qt-logo.png
|
||||
Log.d("ekkescorner viewFile from file path: ", filePath);
|
||||
Log.d("ekkescorner viewFile to content URI: ", uri.toString());
|
||||
|
||||
if(mimeType == null || mimeType.isEmpty()) {
|
||||
// fallback if mimeType not set
|
||||
mimeType = QtNative.activity().getContentResolver().getType(uri);
|
||||
Log.d("ekkescorner viewFile guessed mimeType:", mimeType);
|
||||
} else {
|
||||
Log.d("ekkescorner viewFile w mimeType:", mimeType);
|
||||
}
|
||||
|
||||
viewIntent.setDataAndType(uri, mimeType);
|
||||
|
||||
viewIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
viewIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
|
||||
|
||||
return createCustomChooserAndStartActivity(viewIntent, title, requestId, uri);
|
||||
}
|
||||
|
||||
public static boolean editFile(String filePath, String title, String mimeType, int requestId) {
|
||||
if (QtNative.activity() == null)
|
||||
return false;
|
||||
|
||||
// using v4 support library create the Intent from ShareCompat
|
||||
// Intent editIntent = new Intent();
|
||||
Intent editIntent = ShareCompat.IntentBuilder.from(QtNative.activity()).getIntent();
|
||||
editIntent.setAction(Intent.ACTION_EDIT);
|
||||
|
||||
File imageFileToShare = new File(filePath);
|
||||
|
||||
// Using FileProvider you must get the URI from FileProvider using your AUTHORITY
|
||||
// Uri uri = Uri.fromFile(imageFileToShare);
|
||||
Uri uri;
|
||||
try {
|
||||
uri = FileProvider.getUriForFile(QtNative.activity(), AUTHORITY, imageFileToShare);
|
||||
} catch (IllegalArgumentException e) {
|
||||
Log.d("ekkescorner editFile - cannot be shared: ", filePath);
|
||||
return false;
|
||||
}
|
||||
Log.d("ekkescorner editFile", uri.toString());
|
||||
|
||||
if(mimeType == null || mimeType.isEmpty()) {
|
||||
// fallback if mimeType not set
|
||||
mimeType = QtNative.activity().getContentResolver().getType(uri);
|
||||
Log.d("ekkescorner editFile guessed mimeType:", mimeType);
|
||||
} else {
|
||||
Log.d("ekkescorner editFile w mimeType:", mimeType);
|
||||
}
|
||||
|
||||
editIntent.setDataAndType(uri, mimeType);
|
||||
|
||||
editIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
editIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
|
||||
|
||||
return createCustomChooserAndStartActivity(editIntent, title, requestId, uri);
|
||||
}
|
||||
|
||||
public static String getContentName(ContentResolver cR, Uri uri) {
|
||||
Cursor cursor = cR.query(uri, null, null, null, null);
|
||||
cursor.moveToFirst();
|
||||
int nameIndex = cursor
|
||||
.getColumnIndex(MediaStore.MediaColumns.DISPLAY_NAME);
|
||||
if (nameIndex >= 0) {
|
||||
return cursor.getString(nameIndex);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static String createFile(ContentResolver cR, Uri uri, String fileLocation) {
|
||||
String filePath = null;
|
||||
try {
|
||||
InputStream iStream = cR.openInputStream(uri);
|
||||
if (iStream != null) {
|
||||
String name = getContentName(cR, uri);
|
||||
if (name != null) {
|
||||
filePath = fileLocation + "/" + name;
|
||||
Log.d("ekkescorner - create File", filePath);
|
||||
File f = new File(filePath);
|
||||
FileOutputStream tmp = new FileOutputStream(f);
|
||||
Log.d("ekkescorner - create File", "new FileOutputStream");
|
||||
|
||||
byte[] buffer = new byte[1024];
|
||||
while (iStream.read(buffer) > 0) {
|
||||
tmp.write(buffer);
|
||||
}
|
||||
tmp.close();
|
||||
iStream.close();
|
||||
return filePath;
|
||||
} // name
|
||||
} // iStream
|
||||
} catch (FileNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
return filePath;
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return filePath;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return filePath;
|
||||
}
|
||||
return filePath;
|
||||
}
|
||||
|
||||
}
|
50
blueROCK.pro
50
blueROCK.pro
|
@ -1,4 +1,4 @@
|
|||
QT += quick qml quickcontrols2 purchasing widgets
|
||||
QT += quick qml quickcontrols2 purchasing printsupport
|
||||
CONFIG += c++11
|
||||
|
||||
VERSION = 0.5.0
|
||||
|
@ -15,10 +15,23 @@ DEFINES += QT_DEPRECATED_WARNINGS
|
|||
# You can also select to disable deprecated APIs only up to a certain version of Qt.
|
||||
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
|
||||
|
||||
INCLUDEPATH *= $$PWD/headers
|
||||
|
||||
# Add version to define
|
||||
DEFINES += APP_VERSION=\"\\\"$${VERSION}\\\"\"
|
||||
|
||||
SOURCES += \
|
||||
sources/shareUtils/platformshareutils.cpp \
|
||||
sources/appsettings.cpp \
|
||||
sources/bluerockbackend.cpp \
|
||||
sources/main.cpp
|
||||
sources/shareUtils/shareutils.cpp \
|
||||
sources/main.cpp
|
||||
|
||||
HEADERS += \
|
||||
headers/appsettings.h \
|
||||
headers/bluerockbackend.h \
|
||||
headers/shareUtils/shareutils.h \
|
||||
headers/shareUtils/platformshareutils.h
|
||||
|
||||
RESOURCES += resources/qml/qml.qrc \
|
||||
resources/shared/shared.qrc \
|
||||
|
@ -37,27 +50,28 @@ qnx: target.path = /tmp/$${TARGET}/bin
|
|||
else: unix:!android: target.path = /opt/$${TARGET}/bin
|
||||
!isEmpty(target.path): INSTALLS += target
|
||||
|
||||
# Add version to define
|
||||
DEFINES += APP_VERSION=\"\\\"$${VERSION}\\\"\"
|
||||
|
||||
HEADERS += \
|
||||
headers/appsettings.h \
|
||||
headers/bluerockbackend.h
|
||||
|
||||
DISTFILES += \
|
||||
CHANGELOG.md \
|
||||
android/AndroidManifest.xml \
|
||||
android/build.gradle \
|
||||
android/gradle.properties \
|
||||
android/gradle/wrapper/gradle-wrapper.jar \
|
||||
android/gradle/wrapper/gradle-wrapper.properties \
|
||||
android/gradlew \
|
||||
android/gradlew.bat \
|
||||
android/res/values/libs.xml
|
||||
CHANGELOG.md
|
||||
|
||||
android {
|
||||
QT += androidextras
|
||||
|
||||
SOURCES += sources/shareUtils/androidshareutils.cpp
|
||||
HEADERS += headers/shareUtils/androidshareutils.h
|
||||
|
||||
OTHER_FILES += android/src/org/ekkescorner/utils/QShareUtils.java \
|
||||
android/src/org/ekkescorner/utils/QSharePathResolver.java \
|
||||
android/src/de/itsblue/blueROCK/MainActivity.java \
|
||||
android/AndroidManifest.xml \
|
||||
android/build.gradle \
|
||||
android/gradle.properties \
|
||||
android/gradle/wrapper/gradle-wrapper.jar \
|
||||
android/gradle/wrapper/gradle-wrapper.properties \
|
||||
android/gradlew \
|
||||
android/gradlew.bat \
|
||||
android/res/values/libs.xml \
|
||||
android/res/xml/filepaths.xml
|
||||
|
||||
defineReplace(droidVersionCode) {
|
||||
segments = $$split(1, ".")
|
||||
for (segment, segments): vCode = "$$first(vCode)$$format_number($$segment, width=3 zeropad)"
|
||||
|
|
|
@ -24,6 +24,17 @@
|
|||
#include <QEventLoop>
|
||||
#include <QTimer>
|
||||
#include <QUrl>
|
||||
#include <QPainter>
|
||||
#include <QPrinter>
|
||||
#include <QPixmap>
|
||||
#include <QBrush>
|
||||
#include <QTextCharFormat>
|
||||
#include <QTextDocument>
|
||||
#include <QTextCursor>
|
||||
#include <QPageSize>
|
||||
#include <QPdfWriter>
|
||||
|
||||
#include "shareUtils/shareutils.h"
|
||||
|
||||
class BlueRockBackend : public QObject
|
||||
{
|
||||
|
@ -34,13 +45,16 @@ public:
|
|||
private:
|
||||
QVariantMap senddata(QUrl serviceUrl, QUrlQuery pdata = QUrlQuery());
|
||||
|
||||
ShareUtils* _shareUtils;
|
||||
|
||||
signals:
|
||||
|
||||
public slots:
|
||||
|
||||
QVariant getWidgetData(QVariantMap params);
|
||||
|
||||
QVariantMap getParamsFromUrl(QString url);
|
||||
void shareResultsAsUrl(QString url);
|
||||
void shareResultsAsPoster(QString url);
|
||||
|
||||
};
|
||||
|
||||
|
|
48
headers/shareUtils/androidshareutils.h
Executable file
48
headers/shareUtils/androidshareutils.h
Executable file
|
@ -0,0 +1,48 @@
|
|||
// (c) 2017 Ekkehard Gentz (ekke) @ekkescorner
|
||||
// my blog about Qt for mobile: http://j.mp/qt-x
|
||||
// see also /COPYRIGHT and /LICENSE
|
||||
|
||||
#ifndef ANDROIDSHAREUTILS_H
|
||||
#define ANDROIDSHAREUTILS_H
|
||||
|
||||
#include <QtAndroid>
|
||||
#include <QAndroidActivityResultReceiver>
|
||||
|
||||
#include "shareUtils/platformshareutils.h"
|
||||
|
||||
class AndroidShareUtils : public PlatformShareUtils, public QAndroidActivityResultReceiver
|
||||
{
|
||||
public:
|
||||
AndroidShareUtils(QObject* parent = nullptr);
|
||||
bool checkMimeTypeView(const QString &mimeType) override;
|
||||
bool checkMimeTypeEdit(const QString &mimeType) override;
|
||||
void shareText(const QString &text) override;
|
||||
void sendFile(const QString &filePath, const QString &title, const QString &mimeType, const int &requestId) override;
|
||||
void viewFile(const QString &filePath, const QString &title, const QString &mimeType, const int &requestId) override;
|
||||
void editFile(const QString &filePath, const QString &title, const QString &mimeType, const int &requestId) override;
|
||||
|
||||
void handleActivityResult(int receiverRequestCode, int resultCode, const QAndroidJniObject &data) override;
|
||||
void onActivityResult(int requestCode, int resultCode);
|
||||
|
||||
void checkPendingIntents(const QString workingDirPath) override;
|
||||
|
||||
static AndroidShareUtils* getInstance();
|
||||
|
||||
public slots:
|
||||
void setFileUrlReceived(const QString &url);
|
||||
void setFileReceivedAndSaved(const QString &url);
|
||||
bool checkFileExits(const QString &url);
|
||||
|
||||
private:
|
||||
bool mIsEditMode;
|
||||
qint64 mLastModified;
|
||||
QString mCurrentFilePath;
|
||||
|
||||
static AndroidShareUtils* mInstance;
|
||||
|
||||
void processActivityResult(int requestCode, int resultCode);
|
||||
|
||||
};
|
||||
|
||||
|
||||
#endif // ANDROIDSHAREUTILS_H
|
46
headers/shareUtils/platformshareutils.h
Normal file
46
headers/shareUtils/platformshareutils.h
Normal file
|
@ -0,0 +1,46 @@
|
|||
// (c) 2017 Ekkehard Gentz (ekke)
|
||||
// this project is based on ideas from
|
||||
// http://blog.lasconic.com/share-on-ios-and-android-using-qml/
|
||||
// see github project https://github.com/lasconic/ShareUtils-QML
|
||||
// also inspired by:
|
||||
// https://www.androidcode.ninja/android-share-intent-example/
|
||||
// https://www.calligra.org/blogs/sharing-with-qt-on-android/
|
||||
// https://stackoverflow.com/questions/7156932/open-file-in-another-app
|
||||
// http://www.qtcentre.org/threads/58668-How-to-use-QAndroidJniObject-for-intent-setData
|
||||
// see also /COPYRIGHT and /LICENSE
|
||||
|
||||
// (c) 2017 Ekkehard Gentz (ekke) @ekkescorner
|
||||
// my blog about Qt for mobile: http://j.mp/qt-x
|
||||
// see also /COPYRIGHT and /LICENSE
|
||||
|
||||
#ifndef PLATFORMSHAREUTILS_H
|
||||
#define PLATFORMSHAREUTILS_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QDebug>
|
||||
|
||||
class PlatformShareUtils : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
signals:
|
||||
void shareEditDone(int requestCode);
|
||||
void shareFinished(int requestCode);
|
||||
void shareNoAppAvailable(int requestCode);
|
||||
void shareError(int requestCode, QString message);
|
||||
void fileUrlReceived(QString url);
|
||||
void fileReceivedAndSaved(QString url);
|
||||
|
||||
public:
|
||||
PlatformShareUtils(QObject *parent = 0);
|
||||
virtual ~PlatformShareUtils();
|
||||
virtual bool checkMimeTypeView(const QString &mimeType);
|
||||
virtual bool checkMimeTypeEdit(const QString &mimeType);
|
||||
virtual void shareText(const QString &text);
|
||||
virtual void sendFile(const QString &filePath, const QString &title, const QString &mimeType, const int &requestId);
|
||||
virtual void viewFile(const QString &filePath, const QString &title, const QString &mimeType, const int &requestId);
|
||||
virtual void editFile(const QString &filePath, const QString &title, const QString &mimeType, const int &requestId);
|
||||
|
||||
virtual void checkPendingIntents(const QString workingDirPath);
|
||||
};
|
||||
|
||||
#endif // PLATFORMSHAREUTILS_H
|
60
headers/shareUtils/shareutils.h
Executable file
60
headers/shareUtils/shareutils.h
Executable file
|
@ -0,0 +1,60 @@
|
|||
// (c) 2017 Ekkehard Gentz (ekke)
|
||||
// this project is based on ideas from
|
||||
// http://blog.lasconic.com/share-on-ios-and-android-using-qml/
|
||||
// see github project https://github.com/lasconic/ShareUtils-QML
|
||||
// also inspired by:
|
||||
// https://www.androidcode.ninja/android-share-intent-example/
|
||||
// https://www.calligra.org/blogs/sharing-with-qt-on-android/
|
||||
// https://stackoverflow.com/questions/7156932/open-file-in-another-app
|
||||
// http://www.qtcentre.org/threads/58668-How-to-use-QAndroidJniObject-for-intent-setData
|
||||
// see also /COPYRIGHT and /LICENSE
|
||||
|
||||
// (c) 2017 Ekkehard Gentz (ekke) @ekkescorner
|
||||
// my blog about Qt for mobile: http://j.mp/qt-x
|
||||
// see also /COPYRIGHT and /LICENSE
|
||||
|
||||
#ifndef SHAREUTILS_H
|
||||
#define SHAREUTILS_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QDebug>
|
||||
|
||||
#include "shareUtils/platformshareutils.h"
|
||||
|
||||
class ShareUtils : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
|
||||
signals:
|
||||
void shareEditDone(int requestCode);
|
||||
void shareFinished(int requestCode);
|
||||
void shareNoAppAvailable(int requestCode);
|
||||
void shareError(int requestCode, QString message);
|
||||
void fileUrlReceived(QString url);
|
||||
void fileReceivedAndSaved(QString url);
|
||||
|
||||
public slots:
|
||||
void onShareEditDone(int requestCode);
|
||||
void onShareFinished(int requestCode);
|
||||
void onShareNoAppAvailable(int requestCode);
|
||||
void onShareError(int requestCode, QString message);
|
||||
void onFileUrlReceived(QString url);
|
||||
void onFileReceivedAndSaved(QString url);
|
||||
|
||||
public:
|
||||
explicit ShareUtils(QObject *parent = 0);
|
||||
Q_INVOKABLE bool checkMimeTypeView(const QString &mimeType);
|
||||
Q_INVOKABLE bool checkMimeTypeEdit(const QString &mimeType);
|
||||
Q_INVOKABLE void shareText(const QString &text);
|
||||
Q_INVOKABLE void sendFile(const QString &filePath, const QString &title, const QString &mimeType, const int &requestId);
|
||||
Q_INVOKABLE void viewFile(const QString &filePath, const QString &title, const QString &mimeType, const int &requestId);
|
||||
Q_INVOKABLE void editFile(const QString &filePath, const QString &title, const QString &mimeType, const int &requestId);
|
||||
Q_INVOKABLE void checkPendingIntents(const QString workingDirPath);
|
||||
|
||||
private:
|
||||
PlatformShareUtils* mPlatformShareUtils;
|
||||
|
||||
};
|
||||
|
||||
#endif //SHAREUTILS_H
|
|
@ -7,7 +7,6 @@ Dialog {
|
|||
|
||||
property var dataObj
|
||||
property string subTitle: ""
|
||||
property int implicitY: parent.height - implicitHeight
|
||||
|
||||
signal selectionFinished(int index, var data)
|
||||
|
||||
|
|
43
resources/qml/Components/SharePopup.qml
Normal file
43
resources/qml/Components/SharePopup.qml
Normal file
|
@ -0,0 +1,43 @@
|
|||
import QtQuick 2.0
|
||||
import QtQuick.Controls 2.12
|
||||
import QtQuick.Layouts 1.12
|
||||
|
||||
Dialog {
|
||||
id: control
|
||||
|
||||
property string _shareUrl
|
||||
|
||||
parent: Overlay.overlay
|
||||
|
||||
x: (parent.width - width) * 0.5
|
||||
y: (parent.height - height) * 0.5
|
||||
|
||||
modal: true
|
||||
|
||||
title: "Share these results"
|
||||
|
||||
contentItem: RowLayout {
|
||||
Repeater {
|
||||
id: buttonRepeater
|
||||
property var buttons: [
|
||||
["\uf0c1", "Link", serverConn.shareResultsAsUrl],
|
||||
["\uf029", "QR-code", null],
|
||||
["\uf1c1", "Poster", serverConn.shareResultsAsPoster],
|
||||
]
|
||||
|
||||
model: buttons
|
||||
|
||||
delegate: Button {
|
||||
flat: true
|
||||
font.family: fa5solid.name
|
||||
text: "<font size=\"+4\">" + modelData[0] + "</font><br><br> " + modelData[1] + " "
|
||||
onClicked: buttonRepeater.buttons[index][2](_shareUrl)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function appear(shareUrl) {
|
||||
_shareUrl = shareUrl
|
||||
control.open()
|
||||
}
|
||||
}
|
|
@ -174,6 +174,19 @@ Page {
|
|||
return widgetType
|
||||
}
|
||||
|
||||
function encodeQueryData(data) {
|
||||
const ret = [];
|
||||
for (let d in data)
|
||||
ret.push(encodeURIComponent(d) + '=' + encodeURIComponent(data[d]));
|
||||
return ret.join('&');
|
||||
}
|
||||
|
||||
function shareWidget() {
|
||||
var url = "https://l.bluerock.dev/?" + encodeQueryData(params)
|
||||
sharePu.appear(url)
|
||||
console.log("Url will be:", url)
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: widgetLd
|
||||
|
||||
|
@ -207,9 +220,6 @@ Page {
|
|||
delete(widgetLd.sourceComponent)
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
}
|
||||
|
||||
function getFile(widgetType) {
|
||||
|
@ -295,4 +305,10 @@ Page {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
SharePopup {
|
||||
id: sharePu
|
||||
|
||||
Material.theme: root.Material.theme
|
||||
}
|
||||
}
|
||||
|
|
|
@ -65,6 +65,15 @@ DataListView {
|
|||
text: "\uf0e8"
|
||||
font.family: fa5solid.name
|
||||
}
|
||||
|
||||
ToolButton {
|
||||
id: shareToolBt
|
||||
|
||||
onClicked: shareWidget()
|
||||
|
||||
text: "\uf1e0"
|
||||
font.family: fa5solid.name
|
||||
}
|
||||
}
|
||||
|
||||
property var widgetData: currentWidgetData
|
||||
|
|
|
@ -132,9 +132,8 @@ Window {
|
|||
//app.openAthlete() // dorian: 53139 , rustam: 6933 , helen: 53300
|
||||
//openWidget({nation:'GER'})
|
||||
//mainStack.push("Pages/AthleteSearchPage.qml")
|
||||
//openWidget({comp: 11651, cat: 26})
|
||||
openWidget({comp: 11651, cat: 26})
|
||||
//openWidget({person: 6623})
|
||||
openWidget({cat: 35, comp: 11181, type: "starters"})
|
||||
}
|
||||
|
||||
FontLoader {
|
||||
|
|
|
@ -28,5 +28,6 @@
|
|||
<file>Components/DisclaimerDialog.qml</file>
|
||||
<file>Components/ColoredItemDelegate.qml</file>
|
||||
<file>Components/AlignedButton.qml</file>
|
||||
<file>Components/SharePopup.qml</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
|
BIN
resources/shared/PosterTemplate.png
Normal file
BIN
resources/shared/PosterTemplate.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 155 KiB |
BIN
resources/shared/PosterTemplate.xcf
Normal file
BIN
resources/shared/PosterTemplate.xcf
Normal file
Binary file not shown.
|
@ -20,5 +20,6 @@
|
|||
<file>icons/sac-dark.png</file>
|
||||
<file>fonts/fa5regular.otf</file>
|
||||
<file>fonts/fa5solid.otf</file>
|
||||
<file>PosterTemplate.png</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
|
|
@ -20,6 +20,8 @@
|
|||
|
||||
BlueRockBackend::BlueRockBackend(QObject *parent) : QObject(parent)
|
||||
{
|
||||
this->_shareUtils = new ShareUtils(this);
|
||||
this->shareResultsAsPoster("test");
|
||||
}
|
||||
|
||||
QVariant BlueRockBackend::getWidgetData(QVariantMap params) {
|
||||
|
@ -79,6 +81,23 @@ QVariantMap BlueRockBackend::getParamsFromUrl(QString stringUrl) {
|
|||
return params;
|
||||
}
|
||||
|
||||
void BlueRockBackend::shareResultsAsUrl(QString url) {
|
||||
this->_shareUtils->shareText(url);
|
||||
}
|
||||
|
||||
void BlueRockBackend::shareResultsAsPoster(QString url) {
|
||||
QPdfWriter writer("/tmp/test.pdf");
|
||||
writer.setPageSize(QPageSize(QPageSize::A4));
|
||||
writer.setPageMargins(QMargins(0, 0, 0, 0));
|
||||
writer.setResolution(600);
|
||||
QPainter painter(&writer);
|
||||
painter.drawText(QRect(0, 0, 1980, 100),Qt::AlignHCenter|Qt::AlignBottom,
|
||||
"Children's Health Checkup Form");
|
||||
QPixmap image(":/PosterTemplate.png");
|
||||
painter.drawPixmap(0,0, writer.width(), writer.height(), image);
|
||||
painter.end();
|
||||
}
|
||||
|
||||
// ------------------------
|
||||
// --- Helper functions ---
|
||||
// ------------------------
|
||||
|
|
348
sources/shareUtils/androidshareutils.cpp
Executable file
348
sources/shareUtils/androidshareutils.cpp
Executable file
|
@ -0,0 +1,348 @@
|
|||
// (c) 2017 Ekkehard Gentz (ekke) @ekkescorner
|
||||
// my blog about Qt for mobile: http://j.mp/qt-x
|
||||
// see also /COPYRIGHT and /LICENSE
|
||||
|
||||
#include "shareUtils/androidshareutils.h"
|
||||
|
||||
#include <QUrl>
|
||||
#include <QFileInfo>
|
||||
#include <QDateTime>
|
||||
|
||||
#include <QtAndroidExtras/QAndroidJniObject>
|
||||
#include <jni.h>
|
||||
|
||||
const static int RESULT_OK = -1;
|
||||
const static int RESULT_CANCELED = 0;
|
||||
|
||||
AndroidShareUtils* AndroidShareUtils::mInstance = NULL;
|
||||
|
||||
AndroidShareUtils::AndroidShareUtils(QObject* parent) : PlatformShareUtils(parent)
|
||||
{
|
||||
// we need the instance for JNI Call
|
||||
mInstance = this;
|
||||
}
|
||||
|
||||
AndroidShareUtils* AndroidShareUtils::getInstance()
|
||||
{
|
||||
if (!mInstance) {
|
||||
mInstance = new AndroidShareUtils;
|
||||
qWarning() << "AndroidShareUtils should be instantiated !";
|
||||
}
|
||||
|
||||
return mInstance;
|
||||
}
|
||||
|
||||
bool AndroidShareUtils::checkMimeTypeView(const QString &mimeType)
|
||||
{
|
||||
QAndroidJniObject jsMime = QAndroidJniObject::fromString(mimeType);
|
||||
jboolean verified = QAndroidJniObject::callStaticMethod<jboolean>("org/ekkescorner/utils/QShareUtils",
|
||||
"checkMimeTypeView",
|
||||
"(Ljava/lang/String;)Z",
|
||||
jsMime.object<jstring>());
|
||||
qDebug() << "View VERIFIED: " << mimeType << " - " << verified;
|
||||
return verified;
|
||||
}
|
||||
|
||||
bool AndroidShareUtils::checkMimeTypeEdit(const QString &mimeType)
|
||||
{
|
||||
QAndroidJniObject jsMime = QAndroidJniObject::fromString(mimeType);
|
||||
jboolean verified = QAndroidJniObject::callStaticMethod<jboolean>("org/ekkescorner/utils/QShareUtils",
|
||||
"checkMimeTypeEdit",
|
||||
"(Ljava/lang/String;)Z",
|
||||
jsMime.object<jstring>());
|
||||
qDebug() << "Edit VERIFIED: " << mimeType << " - " << verified;
|
||||
return verified;
|
||||
}
|
||||
|
||||
void AndroidShareUtils::shareText(const QString &text)
|
||||
{
|
||||
QAndroidJniObject jsText = QAndroidJniObject::fromString(text);
|
||||
jboolean ok = QAndroidJniObject::callStaticMethod<jboolean>("org/ekkescorner/utils/QShareUtils",
|
||||
"shareText",
|
||||
"(Ljava/lang/String;)Z",
|
||||
jsText.object<jstring>());
|
||||
|
||||
if(!ok) {
|
||||
qWarning() << "Unable to resolve activity from Java";
|
||||
emit shareNoAppAvailable(0);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* As default we're going the Java - way with one simple JNI call (recommended)
|
||||
* if altImpl is true we're going the pure JNI way
|
||||
*
|
||||
* If a requestId was set we want to get the Activity Result back (recommended)
|
||||
* We need the Request Id and Result Id to control our workflow
|
||||
*/
|
||||
void AndroidShareUtils::sendFile(const QString &filePath, const QString &title, const QString &mimeType, const int &requestId)
|
||||
{
|
||||
mIsEditMode = false;
|
||||
|
||||
QAndroidJniObject jsPath = QAndroidJniObject::fromString(filePath);
|
||||
QAndroidJniObject jsTitle = QAndroidJniObject::fromString(title);
|
||||
QAndroidJniObject jsMimeType = QAndroidJniObject::fromString(mimeType);
|
||||
jboolean ok = QAndroidJniObject::callStaticMethod<jboolean>("org/ekkescorner/utils/QShareUtils",
|
||||
"sendFile",
|
||||
"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;I)Z",
|
||||
jsPath.object<jstring>(), jsTitle.object<jstring>(), jsMimeType.object<jstring>(), requestId);
|
||||
if(!ok) {
|
||||
qWarning() << "Unable to resolve activity from Java";
|
||||
emit shareNoAppAvailable(requestId);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* As default we're going the Java - way with one simple JNI call (recommended)
|
||||
* if altImpl is true we're going the pure JNI way
|
||||
*
|
||||
* If a requestId was set we want to get the Activity Result back (recommended)
|
||||
* We need the Request Id and Result Id to control our workflow
|
||||
*/
|
||||
void AndroidShareUtils::viewFile(const QString &filePath, const QString &title, const QString &mimeType, const int &requestId)
|
||||
{
|
||||
mIsEditMode = false;
|
||||
|
||||
QAndroidJniObject jsPath = QAndroidJniObject::fromString(filePath);
|
||||
QAndroidJniObject jsTitle = QAndroidJniObject::fromString(title);
|
||||
QAndroidJniObject jsMimeType = QAndroidJniObject::fromString(mimeType);
|
||||
jboolean ok = QAndroidJniObject::callStaticMethod<jboolean>("org/ekkescorner/utils/QShareUtils",
|
||||
"viewFile",
|
||||
"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;I)Z",
|
||||
jsPath.object<jstring>(), jsTitle.object<jstring>(), jsMimeType.object<jstring>(), requestId);
|
||||
if(!ok) {
|
||||
qWarning() << "Unable to resolve activity from Java";
|
||||
emit shareNoAppAvailable(requestId);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* As default we're going the Java - way with one simple JNI call (recommended)
|
||||
* if altImpl is true we're going the pure JNI way
|
||||
*
|
||||
* If a requestId was set we want to get the Activity Result back (recommended)
|
||||
* We need the Request Id and Result Id to control our workflow
|
||||
*/
|
||||
void AndroidShareUtils::editFile(const QString &filePath, const QString &title, const QString &mimeType, const int &requestId)
|
||||
{
|
||||
mIsEditMode = true;
|
||||
mCurrentFilePath = filePath;
|
||||
QFileInfo fileInfo = QFileInfo(mCurrentFilePath);
|
||||
mLastModified = fileInfo.lastModified().toSecsSinceEpoch();
|
||||
qDebug() << "LAST MODIFIED: " << mLastModified;
|
||||
|
||||
QAndroidJniObject jsPath = QAndroidJniObject::fromString(filePath);
|
||||
QAndroidJniObject jsTitle = QAndroidJniObject::fromString(title);
|
||||
QAndroidJniObject jsMimeType = QAndroidJniObject::fromString(mimeType);
|
||||
|
||||
jboolean ok = QAndroidJniObject::callStaticMethod<jboolean>("org/ekkescorner/utils/QShareUtils",
|
||||
"editFile",
|
||||
"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;I)Z",
|
||||
jsPath.object<jstring>(), jsTitle.object<jstring>(), jsMimeType.object<jstring>(), requestId);
|
||||
|
||||
if(!ok) {
|
||||
qWarning() << "Unable to resolve activity from Java";
|
||||
emit shareNoAppAvailable(requestId);
|
||||
}
|
||||
}
|
||||
|
||||
// used from QAndroidActivityResultReceiver
|
||||
void AndroidShareUtils::handleActivityResult(int receiverRequestCode, int resultCode, const QAndroidJniObject &data)
|
||||
{
|
||||
Q_UNUSED(data);
|
||||
qDebug() << "From JNI QAndroidActivityResultReceiver: " << receiverRequestCode << "ResultCode:" << resultCode;
|
||||
processActivityResult(receiverRequestCode, resultCode);
|
||||
}
|
||||
|
||||
// used from Activity.java onActivityResult()
|
||||
void AndroidShareUtils::onActivityResult(int requestCode, int resultCode)
|
||||
{
|
||||
qDebug() << "From Java Activity onActivityResult: " << requestCode << "ResultCode:" << resultCode;
|
||||
processActivityResult(requestCode, resultCode);
|
||||
}
|
||||
|
||||
void AndroidShareUtils::processActivityResult(int requestCode, int resultCode)
|
||||
{
|
||||
// we're getting RESULT_OK only if edit is done
|
||||
if(resultCode == RESULT_OK) {
|
||||
emit shareEditDone(requestCode);
|
||||
} else if(resultCode == RESULT_CANCELED) {
|
||||
if(mIsEditMode) {
|
||||
// Attention: not all Apps will give you the correct ResultCode:
|
||||
// Google Fotos will send OK if saved and CANCELED if canceled
|
||||
// Some Apps always sends CANCELED even if you modified and Saved the File
|
||||
// so you should check the modified Timestamp of the File to know if
|
||||
// you should emit shareEditDone() or shareFinished() !!!
|
||||
QFileInfo fileInfo = QFileInfo(mCurrentFilePath);
|
||||
qint64 currentModified = fileInfo.lastModified().toSecsSinceEpoch();
|
||||
qDebug() << "CURRENT MODIFIED: " << currentModified;
|
||||
if(currentModified > mLastModified) {
|
||||
emit shareEditDone(requestCode);
|
||||
return;
|
||||
}
|
||||
}
|
||||
emit shareFinished(requestCode);
|
||||
} else {
|
||||
qDebug() << "wrong result code: " << resultCode << " from request: " << requestCode;
|
||||
emit shareError(requestCode, tr("Share: an Error occured"));
|
||||
}
|
||||
}
|
||||
|
||||
void AndroidShareUtils::checkPendingIntents(const QString workingDirPath)
|
||||
{
|
||||
QAndroidJniObject activity = QtAndroid::androidActivity();
|
||||
if(activity.isValid()) {
|
||||
// create a Java String for the Working Dir Path
|
||||
QAndroidJniObject jniWorkingDir = QAndroidJniObject::fromString(workingDirPath);
|
||||
if(!jniWorkingDir.isValid()) {
|
||||
qWarning() << "QAndroidJniObject jniWorkingDir not valid.";
|
||||
emit shareError(0, tr("Share: an Error occured\nWorkingDir not valid"));
|
||||
return;
|
||||
}
|
||||
activity.callMethod<void>("checkPendingIntents","(Ljava/lang/String;)V", jniWorkingDir.object<jstring>());
|
||||
qDebug() << "checkPendingIntents: " << workingDirPath;
|
||||
return;
|
||||
}
|
||||
qDebug() << "checkPendingIntents: Activity not valid";
|
||||
}
|
||||
|
||||
void AndroidShareUtils::setFileUrlReceived(const QString &url)
|
||||
{
|
||||
if(url.isEmpty()) {
|
||||
qWarning() << "setFileUrlReceived: we got an empty URL";
|
||||
emit shareError(0, tr("Empty URL received"));
|
||||
return;
|
||||
}
|
||||
qDebug() << "AndroidShareUtils setFileUrlReceived: we got the File URL from JAVA: " << url;
|
||||
QString myUrl;
|
||||
if(url.startsWith("file://")) {
|
||||
myUrl= url.right(url.length()-7);
|
||||
qDebug() << "QFile needs this URL: " << myUrl;
|
||||
} else {
|
||||
myUrl= url;
|
||||
}
|
||||
|
||||
// check if File exists
|
||||
QFileInfo fileInfo = QFileInfo(myUrl);
|
||||
if(fileInfo.exists()) {
|
||||
emit fileUrlReceived(myUrl);
|
||||
} else {
|
||||
qDebug() << "setFileUrlReceived: FILE does NOT exist ";
|
||||
emit shareError(0, tr("File does not exist: %1").arg(myUrl));
|
||||
}
|
||||
}
|
||||
|
||||
void AndroidShareUtils::setFileReceivedAndSaved(const QString &url)
|
||||
{
|
||||
if(url.isEmpty()) {
|
||||
qWarning() << "setFileReceivedAndSaved: we got an empty URL";
|
||||
emit shareError(0, tr("Empty URL received"));
|
||||
return;
|
||||
}
|
||||
qDebug() << "AndroidShareUtils setFileReceivedAndSaved: we got the File URL from JAVA: " << url;
|
||||
QString myUrl;
|
||||
if(url.startsWith("file://")) {
|
||||
myUrl= url.right(url.length()-7);
|
||||
qDebug() << "QFile needs this URL: " << myUrl;
|
||||
} else {
|
||||
myUrl= url;
|
||||
}
|
||||
|
||||
// check if File exists
|
||||
QFileInfo fileInfo = QFileInfo(myUrl);
|
||||
if(fileInfo.exists()) {
|
||||
emit fileReceivedAndSaved(myUrl);
|
||||
} else {
|
||||
qDebug() << "setFileReceivedAndSaved: FILE does NOT exist ";
|
||||
emit shareError(0, tr("File does not exist: %1").arg(myUrl));
|
||||
}
|
||||
}
|
||||
|
||||
// to be safe we check if a File Url from java really exists for Qt
|
||||
// if not on the Java side we'll try to read the content as Stream
|
||||
bool AndroidShareUtils::checkFileExits(const QString &url)
|
||||
{
|
||||
if(url.isEmpty()) {
|
||||
qWarning() << "checkFileExits: we got an empty URL";
|
||||
emit shareError(0, tr("Empty URL received"));
|
||||
return false;
|
||||
}
|
||||
qDebug() << "AndroidShareUtils checkFileExits: we got the File URL from JAVA: " << url;
|
||||
QString myUrl;
|
||||
if(url.startsWith("file://")) {
|
||||
myUrl= url.right(url.length()-7);
|
||||
qDebug() << "QFile needs this URL: " << myUrl;
|
||||
} else {
|
||||
myUrl= url;
|
||||
}
|
||||
|
||||
// check if File exists
|
||||
QFileInfo fileInfo = QFileInfo(myUrl);
|
||||
if(fileInfo.exists()) {
|
||||
qDebug() << "Yep: the File exists for Qt";
|
||||
return true;
|
||||
} else {
|
||||
qDebug() << "Uuups: FILE does NOT exist ";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// instead of defining all JNICALL as demonstrated below
|
||||
// there's another way, making it easier to manage all the methods
|
||||
// see https://www.kdab.com/qt-android-episode-5/
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_org_ekkescorner_examples_sharex_QShareActivity_setFileUrlReceived(JNIEnv *env,
|
||||
jobject obj,
|
||||
jstring url)
|
||||
{
|
||||
const char *urlStr = env->GetStringUTFChars(url, NULL);
|
||||
Q_UNUSED (obj)
|
||||
AndroidShareUtils::getInstance()->setFileUrlReceived(urlStr);
|
||||
env->ReleaseStringUTFChars(url, urlStr);
|
||||
return;
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_org_ekkescorner_examples_sharex_QShareActivity_setFileReceivedAndSaved(JNIEnv *env,
|
||||
jobject obj,
|
||||
jstring url)
|
||||
{
|
||||
const char *urlStr = env->GetStringUTFChars(url, NULL);
|
||||
Q_UNUSED (obj)
|
||||
AndroidShareUtils::getInstance()->setFileReceivedAndSaved(urlStr);
|
||||
env->ReleaseStringUTFChars(url, urlStr);
|
||||
return;
|
||||
}
|
||||
|
||||
JNIEXPORT bool JNICALL
|
||||
Java_org_ekkescorner_examples_sharex_QShareActivity_checkFileExits(JNIEnv *env,
|
||||
jobject obj,
|
||||
jstring url)
|
||||
{
|
||||
const char *urlStr = env->GetStringUTFChars(url, NULL);
|
||||
Q_UNUSED (obj)
|
||||
bool exists = AndroidShareUtils::getInstance()->checkFileExits(urlStr);
|
||||
env->ReleaseStringUTFChars(url, urlStr);
|
||||
return exists;
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_org_ekkescorner_examples_sharex_QShareActivity_fireActivityResult(JNIEnv *env,
|
||||
jobject obj,
|
||||
jint requestCode,
|
||||
jint resultCode)
|
||||
{
|
||||
Q_UNUSED (obj)
|
||||
Q_UNUSED (env)
|
||||
AndroidShareUtils::getInstance()->onActivityResult(requestCode, resultCode);
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
35
sources/shareUtils/platformshareutils.cpp
Normal file
35
sources/shareUtils/platformshareutils.cpp
Normal file
|
@ -0,0 +1,35 @@
|
|||
#include "shareUtils/platformshareutils.h"
|
||||
|
||||
PlatformShareUtils::PlatformShareUtils(QObject *parent) : QObject(parent)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
PlatformShareUtils::~PlatformShareUtils() {
|
||||
|
||||
}
|
||||
|
||||
bool PlatformShareUtils::checkMimeTypeView(const QString &mimeType) {
|
||||
qDebug() << "check view for " << mimeType;
|
||||
return true;
|
||||
}
|
||||
bool PlatformShareUtils::checkMimeTypeEdit(const QString &mimeType) {
|
||||
qDebug() << "check edit for " << mimeType;
|
||||
return true;
|
||||
}
|
||||
void PlatformShareUtils::shareText(const QString &text) {
|
||||
qDebug() << text;
|
||||
}
|
||||
void PlatformShareUtils::sendFile(const QString &filePath, const QString &title, const QString &mimeType, const int &requestId) {
|
||||
qDebug() << filePath << " - " << title << "requestId " << requestId << " - " << mimeType << "altImpl? ";
|
||||
}
|
||||
void PlatformShareUtils::viewFile(const QString &filePath, const QString &title, const QString &mimeType, const int &requestId) {
|
||||
qDebug() << filePath << " - " << title << " requestId: " << requestId << " - " << mimeType << "altImpl? ";
|
||||
}
|
||||
void PlatformShareUtils::editFile(const QString &filePath, const QString &title, const QString &mimeType, const int &requestId) {
|
||||
qDebug() << filePath << " - " << title << " requestId: " << requestId << " - " << mimeType << "altImpl? ";
|
||||
}
|
||||
|
||||
void PlatformShareUtils::checkPendingIntents(const QString workingDirPath) {
|
||||
qDebug() << "checkPendingIntents " << workingDirPath;
|
||||
}
|
111
sources/shareUtils/shareutils.cpp
Executable file
111
sources/shareUtils/shareutils.cpp
Executable file
|
@ -0,0 +1,111 @@
|
|||
// (c) 2017 Ekkehard Gentz (ekke) @ekkescorner
|
||||
// my blog about Qt for mobile: http://j.mp/qt-x
|
||||
// see also /COPYRIGHT and /LICENSE
|
||||
|
||||
#include "shareUtils/shareutils.h"
|
||||
|
||||
#ifdef Q_OS_IOS
|
||||
#include "cpp/ios/iosshareutils.hpp"
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_ANDROID
|
||||
#include "shareUtils/androidshareutils.h"
|
||||
#endif
|
||||
|
||||
ShareUtils::ShareUtils(QObject *parent)
|
||||
: QObject(parent)
|
||||
{
|
||||
#if defined(Q_OS_IOS)
|
||||
mPlatformShareUtils = new IosShareUtils(this);
|
||||
#elif defined(Q_OS_ANDROID)
|
||||
mPlatformShareUtils = new AndroidShareUtils(this);
|
||||
#else
|
||||
mPlatformShareUtils = new PlatformShareUtils(this);
|
||||
#endif
|
||||
|
||||
bool connectResult = connect(mPlatformShareUtils, &PlatformShareUtils::shareEditDone, this, &ShareUtils::onShareEditDone);
|
||||
Q_ASSERT(connectResult);
|
||||
|
||||
connectResult = connect(mPlatformShareUtils, &PlatformShareUtils::shareFinished, this, &ShareUtils::onShareFinished);
|
||||
Q_ASSERT(connectResult);
|
||||
|
||||
connectResult = connect(mPlatformShareUtils, &PlatformShareUtils::shareNoAppAvailable, this, &ShareUtils::onShareNoAppAvailable);
|
||||
Q_ASSERT(connectResult);
|
||||
|
||||
connectResult = connect(mPlatformShareUtils, &PlatformShareUtils::shareError, this, &ShareUtils::onShareError);
|
||||
Q_ASSERT(connectResult);
|
||||
|
||||
connectResult = connect(mPlatformShareUtils, &PlatformShareUtils::fileUrlReceived, this, &ShareUtils::onFileUrlReceived);
|
||||
Q_ASSERT(connectResult);
|
||||
|
||||
connectResult = connect(mPlatformShareUtils, &PlatformShareUtils::fileReceivedAndSaved, this, &ShareUtils::onFileReceivedAndSaved);
|
||||
Q_ASSERT(connectResult);
|
||||
|
||||
Q_UNUSED(connectResult);
|
||||
}
|
||||
|
||||
bool ShareUtils::checkMimeTypeView(const QString &mimeType)
|
||||
{
|
||||
return mPlatformShareUtils->checkMimeTypeView(mimeType);
|
||||
}
|
||||
|
||||
bool ShareUtils::checkMimeTypeEdit(const QString &mimeType)
|
||||
{
|
||||
return mPlatformShareUtils->checkMimeTypeEdit(mimeType);
|
||||
}
|
||||
|
||||
void ShareUtils::shareText(const QString &text)
|
||||
{
|
||||
mPlatformShareUtils->shareText(text);
|
||||
}
|
||||
|
||||
void ShareUtils::sendFile(const QString &filePath, const QString &title, const QString &mimeType, const int &requestId)
|
||||
{
|
||||
mPlatformShareUtils->sendFile(filePath, title, mimeType, requestId);
|
||||
}
|
||||
|
||||
void ShareUtils::viewFile(const QString &filePath, const QString &title, const QString &mimeType, const int &requestId)
|
||||
{
|
||||
mPlatformShareUtils->viewFile(filePath, title, mimeType, requestId);
|
||||
}
|
||||
|
||||
void ShareUtils::editFile(const QString &filePath, const QString &title, const QString &mimeType, const int &requestId)
|
||||
{
|
||||
mPlatformShareUtils->editFile(filePath, title, mimeType, requestId);
|
||||
}
|
||||
|
||||
void ShareUtils::checkPendingIntents(const QString workingDirPath)
|
||||
{
|
||||
mPlatformShareUtils->checkPendingIntents(workingDirPath);
|
||||
}
|
||||
|
||||
void ShareUtils::onShareEditDone(int requestCode)
|
||||
{
|
||||
emit shareEditDone(requestCode);
|
||||
}
|
||||
|
||||
void ShareUtils::onShareFinished(int requestCode)
|
||||
{
|
||||
emit shareFinished(requestCode);
|
||||
}
|
||||
|
||||
void ShareUtils::onShareNoAppAvailable(int requestCode)
|
||||
{
|
||||
emit shareNoAppAvailable(requestCode);
|
||||
}
|
||||
|
||||
void ShareUtils::onShareError(int requestCode, QString message)
|
||||
{
|
||||
emit shareError(requestCode, message);
|
||||
}
|
||||
|
||||
void ShareUtils::onFileUrlReceived(QString url)
|
||||
{
|
||||
emit fileUrlReceived(url);
|
||||
}
|
||||
|
||||
void ShareUtils::onFileReceivedAndSaved(QString url)
|
||||
{
|
||||
emit fileReceivedAndSaved(url);
|
||||
}
|
||||
|
Loading…
Reference in a new issue