Commit 54172504 authored by Karel Slaný's avatar Karel Slaný

Merge branch 'android-file-provider-wip' into 'develop'

Android file provider

See merge request !140
parents b5f3cb70 131f6f5e
......@@ -13,6 +13,13 @@
<data android:host="*" />
<data android:pathPattern=".*\\.zfo" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="content" />
<data android:scheme="file" />
<data android:mimeType="*/*"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.BROWSABLE" />
......@@ -76,9 +83,17 @@
<!-- For adding service(s) please check: https://wiki.qt.io/AndroidServices -->
<provider android:name="android.support.v4.content.FileProvider"
android:authorities="cz.nic.mobiledatovka.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="16" android:targetSdkVersion="16"/>
<uses-sdk android:minSdkVersion="16" android:targetSdkVersion="26"/>
<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.
......
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.2.3'
}
}
allprojects {
repositories {
jcenter()
}
}
apply plugin: 'com.android.application'
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile'com.android.support:support-v4:25.3.1'
}
android {
/*******************************************************
* The following variables:
* - androidBuildToolsVersion,
* - androidCompileSdkVersion
* - qt5AndroidDir - holds the path to qt android files
* needed to build any Qt application
* on Android.
*
* are defined in gradle.properties file. This file is
* updated by QtCreator and androiddeployqt tools.
* Changing them manually might break the compilation!
*******************************************************/
compileSdkVersion androidCompileSdkVersion.toInteger()
buildToolsVersion androidBuildToolsVersion
sourceSets {
main {
manifest.srcFile 'AndroidManifest.xml'
java.srcDirs = [qt5AndroidDir + '/src', 'src', 'java']
aidl.srcDirs = [qt5AndroidDir + '/src', 'src', 'aidl']
res.srcDirs = [qt5AndroidDir + '/res', 'res']
resources.srcDirs = ['src']
renderscript.srcDirs = ['src']
assets.srcDirs = ['assets']
jniLibs.srcDirs = ['libs']
}
}
lintOptions {
abortOnError false
}
}
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<files-path name="my_shared_files" path="share_example_x_files/" />
</paths>
\ No newline at end of file
/*
* Copyright (C) 2014-2018 CZ.NIC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* In addition, as a special exception, the copyright holders give
* permission to link the code of portions of this program with the
* OpenSSL library under certain conditions as described in each
* individual source file, and distribute linked combinations including
* the two.
*/
#include <QAndroidJniObject>
#include <QtAndroidExtras>
#include <QDesktopServices>
#include <QUrl>
#include "android/src/android_io.h"
AndroidIO::AndroidIO(void)
{
}
bool AndroidIO::isSDKVersion24OrNewest(void)
{
jboolean ok = QAndroidJniObject::callStaticMethod<jboolean>(
"cz/nic/mobiledatovka/java/QFileProvider",
"isSDKVersion24OrNewest");
return (ok);
}
bool AndroidIO::openFile(const QString &filePath)
{
/*
* TODO - must be tested and reimplement.
* Use file provider only because SDK detection fails.
* QDesktopService generat empty files.
*/
/*
if (!isSDKVersion24OrNewest()) {
return openWithQDesktopServices(filePath);
}
*/
return openWithFileProvider(filePath);
}
bool AndroidIO::openWithQDesktopServices(const QString &filePath)
{
return QDesktopServices::openUrl(QUrl::fromLocalFile(filePath));
}
bool AndroidIO::openWithFileProvider(const QString &filePath)
{
QAndroidJniObject jsPath = QAndroidJniObject::fromString(filePath);
jboolean ok = QAndroidJniObject::callStaticMethod<jboolean>(
"cz/nic/mobiledatovka/java/QFileProvider",
"viewFile", "(Ljava/lang/String;I)Z",
jsPath.object<jstring>(), 0);
return (ok);
}
/*
* Copyright (C) 2014-2018 CZ.NIC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* In addition, as a special exception, the copyright holders give
* permission to link the code of portions of this program with the
* OpenSSL library under certain conditions as described in each
* individual source file, and distribute linked combinations including
* the two.
*/
#pragma once
#include <QtAndroid>
#include <QAndroidActivityResultReceiver>
#include <QObject>
/*!
* @brief Wraps Android intent retrieval.
*
*/
class AndroidIO : public QAndroidActivityResultReceiver {
public:
/*!
* @brief Constructor.
*
* @param[in] parent Parent object.
*/
explicit AndroidIO(void);
/*!
* @brief Check if Android OS SDK Version is level 24 or newest.
*
* @return True if SDK Version is 24 or higher.
*/
static
bool isSDKVersion24OrNewest(void);
/*!
* @brief Open file.
*
* @param[in] filePath File path.
* @return True if success.
*/
static
bool openFile(const QString &filePath);
private:
/*!
* @brief Open file with QDesktopService.
*
* @param[in] filePath File path.
* @return True if success.
*/
static
bool openWithQDesktopServices(const QString &filePath);
/*!
* @brief Open file with Java file provider.
*
* @param[in] filePath File path.
* @return True if success.
*/
static
bool openWithFileProvider(const QString &filePath);
};
/*
* Copyright (C) 2014-2018 CZ.NIC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* In addition, as a special exception, the copyright holders give
* permission to link the code of portions of this program with the
* OpenSSL library under certain conditions as described in each
* individual source file, and distribute linked combinations including
* the two.
*/
package cz.nic.mobiledatovka.java;
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 QFileProvider
{
// Authority defined in AndroidManifest.xml
private static String AUTHORITY="cz.nic.mobiledatovka.fileprovider";
protected QFileProvider() {}
public static boolean isSDKVersion24OrNewest() {
// 24 and newest use fileprovider only
return (Build.VERSION.SDK_INT >= 24);
}
public static boolean startActivity(Intent theIntent, Uri uri) {
final Context context = QtNative.activity();
final PackageManager packageManager = context.getPackageManager();
final boolean isLowerOrEqualsKitKat = Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT;
ResolveInfo defaultAppInfo = packageManager.resolveActivity(theIntent, PackageManager.MATCH_DEFAULT_ONLY);
if (defaultAppInfo == null) {
return false;
}
List<ResolveInfo> appInfoList = packageManager.queryIntentActivities(theIntent, PackageManager.MATCH_DEFAULT_ONLY);
if (appInfoList.isEmpty()) {
return false;
}
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>();
for (ResolveInfo appInfo : appInfoList) {
String targetPackageName = appInfo.activityInfo.packageName;
if (targetPackageName.equals(context.getPackageName())) {
continue;
}
Intent targetedIntent = new Intent(theIntent);
targetedIntent.setPackage(targetPackageName);
targetedIntents.add(targetedIntent);
if (isLowerOrEqualsKitKat) {
context.grantUriPermission(targetPackageName,
uri, Intent.FLAG_GRANT_READ_URI_PERMISSION
| Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
}
}
if (targetedIntents.isEmpty()) {
return false;
}
Intent chooserIntent = Intent.createChooser(targetedIntents.remove(targetedIntents.size() - 1), "View File");
if (targetedIntents.isEmpty()) {
} else {
chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, targetedIntents.toArray(new Parcelable[] {}));
}
if (chooserIntent.resolveActivity(QtNative.activity().getPackageManager()) != null) {
QtNative.activity().startActivity(chooserIntent);
return true;
}
return false;
}
public static boolean viewFile(String filePath, int id) {
if (QtNative.activity() == null) {
return false;
}
// Intent viewIntent = new Intent();
Intent viewIntent = ShareCompat.IntentBuilder.from(QtNative.activity()).getIntent();
viewIntent.setAction(Intent.ACTION_VIEW);
File imageFileToShare = new File(filePath);
Uri uri;
try {
uri = FileProvider.getUriForFile(QtNative.activity(), AUTHORITY, imageFileToShare);
} catch (IllegalArgumentException e) {
Log.d("Error viewFile - cannot be shared: ", filePath);
return false;
}
Log.d("File path: ", filePath);
Log.d("File content URI: ", uri.toString());
String mimeType = QtNative.activity().getContentResolver().getType(uri);
viewIntent.setDataAndType(uri, mimeType);
viewIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
viewIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
return startActivity(viewIntent, uri);
}
}
......@@ -88,6 +88,7 @@ TRANSLATIONS_FILES += \
res/locale/datovka_en.qm
SOURCES += \
android/src/android_io.cpp \
src/accounts.cpp \
src/auxiliaries/email_helper.cpp \
src/datovka_shared/gov_services/helper.cpp \
......@@ -209,6 +210,7 @@ SOURCES += \
src/zfo.cpp
HEADERS += \
android/src/android_io.h \
src/accounts.h \
src/auxiliaries/email_helper.h \
src/common.h \
......@@ -372,6 +374,8 @@ android {
}
ANDROID_PACKAGE_SOURCE_DIR = $$PWD/android
include(android/android.pri)
OTHER_FILES += android/src/cz/nic/mobiledatovka/java/QFileProvider.java
}
# winphone|winrt target.
......
......@@ -22,10 +22,14 @@
*/
#include <QDesktopServices>
#include <QDir>
#include <QFileInfo>
#include <QQmlEngine>
#include <QStringBuilder>
#if defined (Q_OS_ANDROID)
#include "android/src/android_io.h"
#endif
#include "ios/src/url_opener.h"
#include "src/auxiliaries/email_helper.h"
#include "src/common.h"
......@@ -204,7 +208,28 @@ void Files::openAttachment(const QString &fileName,
return;
}
QString filePath(writeFile(appTmpDirPath(), fileName, binaryData));
QString docLocationRoot = appTmpDirPath();
#if defined (Q_OS_ANDROID)
docLocationRoot = QStandardPaths::standardLocations(
QStandardPaths::AppDataLocation).value(0);
QString documentsWorkPath = docLocationRoot.append("/share_example_x_files");
if (!QDir(documentsWorkPath).exists()) {
if (!QDir("").mkpath(documentsWorkPath)) {
logErrorNL("Failed to create share directory: '%s'.",
documentsWorkPath.toUtf8().constData());
Dialogues::errorMessage(Dialogues::CRITICAL,
tr("Open attachment error"),
tr("Cannot save selected file to disk for opening."),
QString());
return;
}
}
#endif
QString filePath(writeFile(docLocationRoot, fileName, binaryData));
if (!filePath.isEmpty()) {
logInfoNL("Creating temporary file '%s'.",
......@@ -240,6 +265,15 @@ void Files::openAttachmentFromPath(const QString &filePath)
UrlOpener urlOpener;
urlOpener.openFile(filePath);
#elif defined (Q_OS_ANDROID)
if (!AndroidIO::openFile(filePath)) {
Dialogues::errorMessage(Dialogues::CRITICAL,
tr("Open attachment error"),
tr("There is no application to open this file format."),
tr("File: '%1'").arg(filePath));
}
#else
if (!QDesktopServices::openUrl(QUrl::fromLocalFile(filePath))) {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment