mirror of
https://github.com/KDE/kdeconnect-android
synced 2025-08-30 13:47:41 +00:00
Simplify receiving multiple files using only 1 notification
Summary: Sequentially receiving multiple files was overly complicated sometimes leading to a progress notification being shown indefinitely. When files are shared using an older kdeconnect-kde version fallback to receiving each file individually Test Plan: Send multiple files from desktop to android and observe that android receives the files sequentially using only 1 notification Send multiple files from an kdeconnect-kde installation and observe that android receives the files in parallel using multiple notifications Reviewers: #kde_connect, nicolasfella, albertvaka Reviewed By: #kde_connect, albertvaka Subscribers: albertvaka, nicolasfella, kdeconnect Tags: #kde_connect Differential Revision: https://phabricator.kde.org/D17627
This commit is contained in:
parent
5608fe1237
commit
4c6b74a2eb
@ -0,0 +1,307 @@
|
||||
/*
|
||||
* Copyright 2018 Erik Duisters <e.duisters1@gmail.com>
|
||||
*
|
||||
* 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 2 of
|
||||
* the License or (at your option) version 3 or any later version
|
||||
* accepted by the membership of KDE e.V. (or its successor approved
|
||||
* by the membership of KDE e.V.), which shall act as a proxy
|
||||
* defined in Section 14 of version 3 of the license.
|
||||
*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
package org.kde.kdeconnect.Plugins.SharePlugin;
|
||||
|
||||
import android.app.DownloadManager;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.util.Log;
|
||||
|
||||
import org.kde.kdeconnect.Device;
|
||||
import org.kde.kdeconnect.Helpers.FilesHelper;
|
||||
import org.kde.kdeconnect.Helpers.MediaStoreHelper;
|
||||
import org.kde.kdeconnect.NetworkPacket;
|
||||
import org.kde.kdeconnect_tp.R;
|
||||
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import androidx.core.content.FileProvider;
|
||||
import androidx.documentfile.provider.DocumentFile;
|
||||
|
||||
public class CompositeReceiveFileRunnable implements Runnable {
|
||||
interface CallBack {
|
||||
void onSuccess(CompositeReceiveFileRunnable runnable);
|
||||
void onError(CompositeReceiveFileRunnable runnable, Throwable error);
|
||||
}
|
||||
|
||||
private final Device device;
|
||||
private final ShareNotification shareNotification;
|
||||
private NetworkPacket currentNetworkPacket;
|
||||
private String currentFileName;
|
||||
private int currentFileNum;
|
||||
private long totalReceived;
|
||||
private long lastProgressTimeMillis;
|
||||
private long prevProgressPercentage;
|
||||
|
||||
private final CallBack callBack;
|
||||
private final Handler handler;
|
||||
|
||||
private final Object lock; //Use to protect concurrent access to the variables below
|
||||
private final List<NetworkPacket> networkPacketList;
|
||||
private int totalNumFiles;
|
||||
private long totalPayloadSize;
|
||||
|
||||
CompositeReceiveFileRunnable(Device device, CallBack callBack) {
|
||||
this.device = device;
|
||||
this.callBack = callBack;
|
||||
|
||||
lock = new Object();
|
||||
networkPacketList = new ArrayList<>();
|
||||
shareNotification = new ShareNotification(device);
|
||||
currentFileNum = 0;
|
||||
totalNumFiles = 0;
|
||||
totalPayloadSize = 0;
|
||||
totalReceived = 0;
|
||||
lastProgressTimeMillis = 0;
|
||||
prevProgressPercentage = 0;
|
||||
handler = new Handler(Looper.getMainLooper());
|
||||
}
|
||||
|
||||
void addNetworkPacket(NetworkPacket networkPacket) {
|
||||
if (!networkPacketList.contains(networkPacket)) {
|
||||
synchronized (lock) {
|
||||
networkPacketList.add(networkPacket);
|
||||
|
||||
totalNumFiles = networkPacket.getInt(SharePlugin.KEY_NUMBER_OF_FILES, 1);
|
||||
totalPayloadSize = networkPacket.getLong(SharePlugin.KEY_TOTAL_PAYLOAD_SIZE);
|
||||
|
||||
shareNotification.setTitle(device.getContext().getResources()
|
||||
.getQuantityString(R.plurals.incoming_file_title, totalNumFiles, totalNumFiles, device.getName()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
boolean done;
|
||||
OutputStream outputStream = null;
|
||||
|
||||
synchronized (lock) {
|
||||
done = networkPacketList.isEmpty();
|
||||
}
|
||||
|
||||
try {
|
||||
DocumentFile fileDocument = null;
|
||||
|
||||
while (!done) {
|
||||
synchronized (lock) {
|
||||
currentNetworkPacket = networkPacketList.get(0);
|
||||
}
|
||||
currentFileName = currentNetworkPacket.getString("filename", Long.toString(System.currentTimeMillis()));
|
||||
currentFileNum++;
|
||||
|
||||
setProgress((int)prevProgressPercentage);
|
||||
|
||||
fileDocument = getDocumentFileFor(currentFileName, currentNetworkPacket.getBoolean("open"));
|
||||
|
||||
if (currentNetworkPacket.hasPayload()) {
|
||||
outputStream = new BufferedOutputStream(device.getContext().getContentResolver().openOutputStream(fileDocument.getUri()));
|
||||
InputStream inputStream = currentNetworkPacket.getPayload().getInputStream();
|
||||
|
||||
long received = receiveFile(inputStream, outputStream);
|
||||
|
||||
currentNetworkPacket.getPayload().close();
|
||||
|
||||
if ( received != currentNetworkPacket.getPayloadSize()) {
|
||||
fileDocument.delete();
|
||||
throw new RuntimeException("Failed to receive: " + currentFileName + " received:" + received + " bytes, expected: " + currentNetworkPacket.getPayloadSize() + " bytes");
|
||||
} else {
|
||||
publishFile(fileDocument, received);
|
||||
}
|
||||
} else {
|
||||
setProgress(100);
|
||||
publishFile(fileDocument, 0);
|
||||
}
|
||||
|
||||
boolean listIsEmpty;
|
||||
|
||||
synchronized (lock) {
|
||||
networkPacketList.remove(0);
|
||||
listIsEmpty = networkPacketList.isEmpty();
|
||||
}
|
||||
|
||||
if (listIsEmpty) {
|
||||
try {
|
||||
Thread.sleep(250);
|
||||
} catch (InterruptedException ignored) {}
|
||||
|
||||
synchronized (lock) {
|
||||
if (currentFileNum < totalNumFiles && networkPacketList.isEmpty()) {
|
||||
throw new RuntimeException("Failed to receive " + (totalNumFiles - currentFileNum + 1) + " files");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
synchronized (lock) {
|
||||
done = networkPacketList.isEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
int numFiles;
|
||||
synchronized (lock) {
|
||||
numFiles = totalNumFiles;
|
||||
}
|
||||
|
||||
if (numFiles == 1 && currentNetworkPacket.has("open")) {
|
||||
shareNotification.cancel();
|
||||
openFile(fileDocument);
|
||||
} else {
|
||||
//Update the notification and allow to open the file from it
|
||||
shareNotification.setFinished(device.getContext().getResources().getQuantityString(R.plurals.received_files_title, numFiles, device.getName(), numFiles));
|
||||
|
||||
if (totalNumFiles == 1 && fileDocument != null) {
|
||||
shareNotification.setURI(fileDocument.getUri(), fileDocument.getType(), fileDocument.getName());
|
||||
}
|
||||
|
||||
shareNotification.show();
|
||||
}
|
||||
handler.post(() -> callBack.onSuccess(this));
|
||||
} catch (Exception e) {
|
||||
int failedFiles;
|
||||
synchronized (lock) {
|
||||
failedFiles = (totalNumFiles - currentFileNum + 1);
|
||||
}
|
||||
shareNotification.setFinished(device.getContext().getResources().getQuantityString(R.plurals.received_files_fail_title, failedFiles, device.getName(), failedFiles, totalNumFiles));
|
||||
shareNotification.show();
|
||||
handler.post(() -> callBack.onError(this, e));
|
||||
} finally {
|
||||
closeAllInputStreams();
|
||||
networkPacketList.clear();
|
||||
if (outputStream != null) {
|
||||
try {
|
||||
outputStream.close();
|
||||
} catch (IOException ignored) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private DocumentFile getDocumentFileFor(final String filename, final boolean open) throws RuntimeException {
|
||||
final DocumentFile destinationFolderDocument;
|
||||
|
||||
String filenameToUse = filename;
|
||||
|
||||
//We need to check for already existing files only when storing in the default path.
|
||||
//User-defined paths use the new Storage Access Framework that already handles this.
|
||||
//If the file should be opened immediately store it in the standard location to avoid the FileProvider trouble (See ShareNotification::setURI)
|
||||
if (open || !ShareSettingsFragment.isCustomDestinationEnabled(device.getContext())) {
|
||||
final String defaultPath = ShareSettingsFragment.getDefaultDestinationDirectory().getAbsolutePath();
|
||||
filenameToUse = FilesHelper.findNonExistingNameForNewFile(defaultPath, filenameToUse);
|
||||
destinationFolderDocument = DocumentFile.fromFile(new File(defaultPath));
|
||||
} else {
|
||||
destinationFolderDocument = ShareSettingsFragment.getDestinationDirectory(device.getContext());
|
||||
}
|
||||
String displayName = FilesHelper.getFileNameWithoutExt(filenameToUse);
|
||||
String mimeType = FilesHelper.getMimeTypeFromFile(filenameToUse);
|
||||
|
||||
if ("*/*".equals(mimeType)) {
|
||||
displayName = filenameToUse;
|
||||
}
|
||||
|
||||
DocumentFile fileDocument = destinationFolderDocument.createFile(mimeType, displayName);
|
||||
|
||||
if (fileDocument == null) {
|
||||
throw new RuntimeException(device.getContext().getString(R.string.cannot_create_file, filenameToUse));
|
||||
}
|
||||
|
||||
return fileDocument;
|
||||
}
|
||||
|
||||
private long receiveFile(InputStream input, OutputStream output) throws IOException {
|
||||
byte data[] = new byte[4096];
|
||||
int count;
|
||||
long received = 0;
|
||||
|
||||
while ((count = input.read(data)) >= 0) {
|
||||
received += count;
|
||||
totalReceived += count;
|
||||
|
||||
output.write(data, 0, count);
|
||||
|
||||
long progressPercentage;
|
||||
synchronized (lock) {
|
||||
progressPercentage = (totalReceived * 100 / totalPayloadSize);
|
||||
}
|
||||
long curTimeMillis = System.currentTimeMillis();
|
||||
|
||||
if (progressPercentage != prevProgressPercentage &&
|
||||
(progressPercentage == 100 || curTimeMillis - lastProgressTimeMillis >= 500)) {
|
||||
prevProgressPercentage = progressPercentage;
|
||||
lastProgressTimeMillis = curTimeMillis;
|
||||
setProgress((int)progressPercentage);
|
||||
}
|
||||
}
|
||||
|
||||
output.flush();
|
||||
|
||||
return received;
|
||||
}
|
||||
|
||||
private void closeAllInputStreams() {
|
||||
for (NetworkPacket np : networkPacketList) {
|
||||
np.getPayload().close();
|
||||
}
|
||||
}
|
||||
|
||||
private void setProgress(int progress) {
|
||||
synchronized (lock) {
|
||||
shareNotification.setProgress(progress, device.getContext().getResources()
|
||||
.getQuantityString(R.plurals.incoming_files_text, totalNumFiles, currentFileName, currentFileNum, totalNumFiles));
|
||||
}
|
||||
shareNotification.show();
|
||||
}
|
||||
|
||||
private void publishFile(DocumentFile fileDocument, long size) {
|
||||
if (!ShareSettingsFragment.isCustomDestinationEnabled(device.getContext())) {
|
||||
Log.i("SharePlugin", "Adding to downloads");
|
||||
DownloadManager manager = (DownloadManager) device.getContext().getSystemService(Context.DOWNLOAD_SERVICE);
|
||||
manager.addCompletedDownload(fileDocument.getUri().getLastPathSegment(), device.getName(), true, fileDocument.getType(), fileDocument.getUri().getPath(), size, false);
|
||||
} else {
|
||||
//Make sure it is added to the Android Gallery anyway
|
||||
Log.i("SharePlugin", "Adding to gallery");
|
||||
MediaStoreHelper.indexFile(device.getContext(), fileDocument.getUri());
|
||||
}
|
||||
}
|
||||
|
||||
private void openFile(DocumentFile fileDocument) {
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW);
|
||||
if (Build.VERSION.SDK_INT >= 24) {
|
||||
//Nougat and later require "content://" uris instead of "file://" uris
|
||||
File file = new File(fileDocument.getUri().getPath());
|
||||
Uri contentUri = FileProvider.getUriForFile(device.getContext(), "org.kde.kdeconnect_tp.fileprovider", file);
|
||||
intent.setDataAndType(contentUri, fileDocument.getType());
|
||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
} else {
|
||||
intent.setDataAndType(fileDocument.getUri(), fileDocument.getType());
|
||||
}
|
||||
|
||||
device.getContext().startActivity(intent);
|
||||
}
|
||||
}
|
@ -1,92 +0,0 @@
|
||||
/*
|
||||
* Copyright 2018 Erik Duisters <e.duisters1@gmail.com>
|
||||
*
|
||||
* 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 2 of
|
||||
* the License or (at your option) version 3 or any later version
|
||||
* accepted by the membership of KDE e.V. (or its successor approved
|
||||
* by the membership of KDE e.V.), which shall act as a proxy
|
||||
* defined in Section 14 of version 3 of the license.
|
||||
*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
package org.kde.kdeconnect.Plugins.SharePlugin;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
public class ReceiveFileRunnable implements Runnable {
|
||||
interface CallBack {
|
||||
void onProgress(ShareInfo info, int progress);
|
||||
void onSuccess(ShareInfo info);
|
||||
void onError(ShareInfo info, Throwable error);
|
||||
}
|
||||
|
||||
private final ShareInfo info;
|
||||
private final CallBack callBack;
|
||||
private final Handler handler;
|
||||
|
||||
ReceiveFileRunnable(ShareInfo info, CallBack callBack) {
|
||||
this.info = info;
|
||||
this.callBack = callBack;
|
||||
this.handler = new Handler(Looper.getMainLooper());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
byte data[] = new byte[4096];
|
||||
long received = 0, prevProgressPercentage = 0;
|
||||
int count;
|
||||
|
||||
callBack.onProgress(info, 0);
|
||||
|
||||
InputStream inputStream = info.payload.getInputStream();
|
||||
|
||||
while ((count = inputStream.read(data)) >= 0) {
|
||||
received += count;
|
||||
|
||||
if (received > info.fileSize) {
|
||||
break;
|
||||
}
|
||||
|
||||
info.outputStream.write(data, 0, count);
|
||||
if (info.fileSize > 0) {
|
||||
long progressPercentage = (received * 100 / info.fileSize);
|
||||
if (progressPercentage != prevProgressPercentage) {
|
||||
prevProgressPercentage = progressPercentage;
|
||||
handler.post(() -> callBack.onProgress(info, (int)progressPercentage));
|
||||
}
|
||||
}
|
||||
//else Log.e("SharePlugin", "Infinite loop? :D");
|
||||
}
|
||||
|
||||
info.outputStream.flush();
|
||||
|
||||
if (received != info.fileSize) {
|
||||
throw new RuntimeException("Received:" + received + " bytes, expected: " + info.fileSize + " bytes");
|
||||
}
|
||||
|
||||
handler.post(() -> callBack.onSuccess(info));
|
||||
} catch (IOException e) {
|
||||
handler.post(() -> callBack.onError(info, e));
|
||||
} finally {
|
||||
info.payload.close();
|
||||
|
||||
try {
|
||||
info.outputStream.close();
|
||||
} catch (IOException ignored) {}
|
||||
}
|
||||
}
|
||||
}
|
@ -22,7 +22,6 @@ package org.kde.kdeconnect.Plugins.SharePlugin;
|
||||
|
||||
import android.Manifest;
|
||||
import android.app.Activity;
|
||||
import android.app.DownloadManager;
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
@ -34,7 +33,6 @@ import android.content.res.Resources;
|
||||
import android.database.Cursor;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
@ -42,17 +40,13 @@ import android.provider.MediaStore;
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.kde.kdeconnect.Helpers.FilesHelper;
|
||||
import org.kde.kdeconnect.Helpers.MediaStoreHelper;
|
||||
import org.kde.kdeconnect.Helpers.NotificationHelper;
|
||||
import org.kde.kdeconnect.NetworkPacket;
|
||||
import org.kde.kdeconnect.Plugins.Plugin;
|
||||
import org.kde.kdeconnect.UserInterface.PluginSettingsFragment;
|
||||
import org.kde.kdeconnect_tp.R;
|
||||
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
@ -62,23 +56,23 @@ import java.util.concurrent.Executors;
|
||||
import androidx.annotation.WorkerThread;
|
||||
import androidx.core.app.NotificationCompat;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.core.content.FileProvider;
|
||||
import androidx.documentfile.provider.DocumentFile;
|
||||
|
||||
public class SharePlugin extends Plugin implements ReceiveFileRunnable.CallBack {
|
||||
|
||||
public class SharePlugin extends Plugin {
|
||||
private final static String PACKET_TYPE_SHARE_REQUEST = "kdeconnect.share.request";
|
||||
|
||||
final static String KEY_NUMBER_OF_FILES = "numberOfFiles";
|
||||
final static String KEY_TOTAL_PAYLOAD_SIZE = "totalPayloadSize";
|
||||
|
||||
private final static boolean openUrlsDirectly = true;
|
||||
private ShareNotification shareNotification;
|
||||
private FinishReceivingRunnable finishReceivingRunnable;
|
||||
private ExecutorService executorService;
|
||||
private ShareInfo currentShareInfo;
|
||||
private Handler handler;
|
||||
private final Handler handler;
|
||||
CompositeReceiveFileRunnable receiveFileRunnable;
|
||||
private final Callback receiveFileRunnableCallback;
|
||||
|
||||
public SharePlugin() {
|
||||
executorService = Executors.newSingleThreadExecutor();
|
||||
executorService = Executors.newFixedThreadPool(5);
|
||||
handler = new Handler(Looper.getMainLooper());
|
||||
receiveFileRunnableCallback = new Callback();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -197,78 +191,29 @@ public class SharePlugin extends Plugin implements ReceiveFileRunnable.CallBack
|
||||
|
||||
@WorkerThread
|
||||
private void receiveFile(NetworkPacket np) {
|
||||
if (finishReceivingRunnable != null) {
|
||||
Log.i("SharePlugin", "receiveFile: canceling finishReceivingRunnable");
|
||||
handler.removeCallbacks(finishReceivingRunnable);
|
||||
finishReceivingRunnable = null;
|
||||
}
|
||||
CompositeReceiveFileRunnable runnable;
|
||||
|
||||
ShareInfo info = new ShareInfo();
|
||||
info.currentFileNumber = currentShareInfo == null ? 1 : currentShareInfo.currentFileNumber + 1;
|
||||
info.payload = np.getPayload();
|
||||
info.fileSize = np.getPayloadSize();
|
||||
info.fileName = np.getString("filename", Long.toString(System.currentTimeMillis()));
|
||||
info.shouldOpen = np.getBoolean("open");
|
||||
info.setNumberOfFiles(np.getInt("numberOfFiles", 1));
|
||||
info.setTotalTransferSize(np.getLong("totalPayloadSize", 1));
|
||||
boolean hasNumberOfFiles = np.has(KEY_NUMBER_OF_FILES);
|
||||
boolean hasOpen = np.has("open");
|
||||
|
||||
if (currentShareInfo == null) {
|
||||
currentShareInfo = info;
|
||||
if (hasNumberOfFiles && !hasOpen && receiveFileRunnable != null) {
|
||||
runnable = receiveFileRunnable;
|
||||
} else {
|
||||
synchronized (currentShareInfo) {
|
||||
currentShareInfo.setNumberOfFiles(info.numberOfFiles());
|
||||
currentShareInfo.setTotalTransferSize(info.totalTransferSize());
|
||||
runnable = new CompositeReceiveFileRunnable(device, receiveFileRunnableCallback);
|
||||
}
|
||||
|
||||
if (!hasNumberOfFiles) {
|
||||
np.set(KEY_NUMBER_OF_FILES, 1);
|
||||
np.set(KEY_TOTAL_PAYLOAD_SIZE, np.getPayloadSize());
|
||||
}
|
||||
|
||||
runnable.addNetworkPacket(np);
|
||||
|
||||
if (runnable != receiveFileRunnable) {
|
||||
if (hasNumberOfFiles && !hasOpen) {
|
||||
receiveFileRunnable = runnable;
|
||||
}
|
||||
}
|
||||
|
||||
String filename = info.fileName;
|
||||
final DocumentFile destinationFolderDocument;
|
||||
|
||||
//We need to check for already existing files only when storing in the default path.
|
||||
//User-defined paths use the new Storage Access Framework that already handles this.
|
||||
//If the file should be opened immediately store it in the standard location to avoid the FileProvider trouble (See ShareNotification::setURI)
|
||||
if (np.getBoolean("open") || !ShareSettingsFragment.isCustomDestinationEnabled(context)) {
|
||||
final String defaultPath = ShareSettingsFragment.getDefaultDestinationDirectory().getAbsolutePath();
|
||||
filename = FilesHelper.findNonExistingNameForNewFile(defaultPath, filename);
|
||||
destinationFolderDocument = DocumentFile.fromFile(new File(defaultPath));
|
||||
} else {
|
||||
destinationFolderDocument = ShareSettingsFragment.getDestinationDirectory(context);
|
||||
}
|
||||
String displayName = FilesHelper.getFileNameWithoutExt(filename);
|
||||
String mimeType = FilesHelper.getMimeTypeFromFile(filename);
|
||||
|
||||
if ("*/*".equals(mimeType)) {
|
||||
displayName = filename;
|
||||
}
|
||||
|
||||
info.fileDocument = destinationFolderDocument.createFile(mimeType, displayName);
|
||||
assert info.fileDocument != null;
|
||||
|
||||
if (shareNotification == null) {
|
||||
shareNotification = new ShareNotification(device);
|
||||
}
|
||||
|
||||
if (info.fileDocument == null) {
|
||||
onError(info, new RuntimeException(context.getString(R.string.cannot_create_file, filename)));
|
||||
return;
|
||||
}
|
||||
|
||||
shareNotification.setTitle(context.getResources().getQuantityString(R.plurals.incoming_file_title, info.numberOfFiles(), info.numberOfFiles(), device.getName()));
|
||||
shareNotification.show();
|
||||
|
||||
if (np.hasPayload()) {
|
||||
try {
|
||||
info.outputStream = new BufferedOutputStream(context.getContentResolver().openOutputStream(info.fileDocument.getUri()));
|
||||
} catch (FileNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
return;
|
||||
}
|
||||
|
||||
ReceiveFileRunnable runnable = new ReceiveFileRunnable(info, this);
|
||||
executorService.execute(runnable);
|
||||
} else {
|
||||
onProgress(info, 100);
|
||||
onSuccess(info);
|
||||
}
|
||||
}
|
||||
|
||||
@ -456,92 +401,20 @@ public class SharePlugin extends Plugin implements ReceiveFileRunnable.CallBack
|
||||
return new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProgress(ShareInfo info, int progress) {
|
||||
if (progress == 0 && currentShareInfo != info) {
|
||||
currentShareInfo = info;
|
||||
}
|
||||
|
||||
shareNotification.setProgress(progress, context.getResources().getQuantityString(R.plurals.incoming_files_text, info.numberOfFiles(), info.fileName, info.currentFileNumber, info.numberOfFiles()));
|
||||
shareNotification.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSuccess(ShareInfo info) {
|
||||
Log.i("SharePlugin", "onSuccess() - Transfer finished for file: " + info.fileDocument.getUri().getPath());
|
||||
|
||||
if (info.shouldOpen) {
|
||||
shareNotification.cancel();
|
||||
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW);
|
||||
if (Build.VERSION.SDK_INT >= 24) {
|
||||
//Nougat and later require "content://" uris instead of "file://" uris
|
||||
File file = new File(info.fileDocument.getUri().getPath());
|
||||
Uri contentUri = FileProvider.getUriForFile(device.getContext(), "org.kde.kdeconnect_tp.fileprovider", file);
|
||||
intent.setDataAndType(contentUri, info.fileDocument.getType());
|
||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
} else {
|
||||
intent.setDataAndType(info.fileDocument.getUri(), info.fileDocument.getType());
|
||||
private class Callback implements CompositeReceiveFileRunnable.CallBack {
|
||||
@Override
|
||||
public void onSuccess(CompositeReceiveFileRunnable runnable) {
|
||||
if (runnable == receiveFileRunnable) {
|
||||
receiveFileRunnable = null;
|
||||
}
|
||||
|
||||
context.startActivity(intent);
|
||||
} else {
|
||||
if (!ShareSettingsFragment.isCustomDestinationEnabled(context)) {
|
||||
Log.i("SharePlugin", "Adding to downloads");
|
||||
DownloadManager manager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
|
||||
manager.addCompletedDownload(info.fileDocument.getUri().getLastPathSegment(), device.getName(), true, info.fileDocument.getType(), info.fileDocument.getUri().getPath(), info.fileSize, false);
|
||||
} else {
|
||||
//Make sure it is added to the Android Gallery anyway
|
||||
MediaStoreHelper.indexFile(context, info.fileDocument.getUri());
|
||||
}
|
||||
|
||||
if (info.numberOfFiles() == 1 || info.currentFileNumber == info.numberOfFiles()) {
|
||||
finishReceivingRunnable = new FinishReceivingRunnable(info);
|
||||
Log.i("SharePlugin", "onSuccess() - scheduling finishReceivingRunnable");
|
||||
handler.postDelayed(finishReceivingRunnable, 1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ShareInfo info, Throwable error) {
|
||||
Log.e("SharePlugin", "onError: " + error.getMessage());
|
||||
|
||||
info.fileDocument.delete();
|
||||
|
||||
//TODO: Show error in notification
|
||||
int failedFiles = info.numberOfFiles() - (info.currentFileNumber - 1);
|
||||
shareNotification.setFinished(context.getResources().getQuantityString(R.plurals.received_files_fail_title, failedFiles, device.getName(), failedFiles, info.numberOfFiles()));
|
||||
shareNotification.show();
|
||||
shareNotification = null;
|
||||
currentShareInfo = null;
|
||||
}
|
||||
|
||||
private class FinishReceivingRunnable implements Runnable {
|
||||
private final ShareInfo info;
|
||||
|
||||
private FinishReceivingRunnable(ShareInfo info) {
|
||||
this.info = info;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
Log.i("SharePlugin", "FinishReceivingRunnable: Finishing up");
|
||||
|
||||
if (shareNotification != null) {
|
||||
//Update the notification and allow to open the file from it
|
||||
shareNotification.setFinished(context.getResources().getQuantityString(R.plurals.received_files_title, info.numberOfFiles(), device.getName(), info.numberOfFiles()));
|
||||
|
||||
if (info.numberOfFiles() == 1) {
|
||||
shareNotification.setURI(info.fileDocument.getUri(), info.fileDocument.getType(), info.fileName);
|
||||
}
|
||||
|
||||
shareNotification.show();
|
||||
shareNotification = null;
|
||||
public void onError(CompositeReceiveFileRunnable runnable, Throwable error) {
|
||||
Log.e("SharePlugin", "onError() - " + error.getMessage());
|
||||
if (runnable == receiveFileRunnable) {
|
||||
receiveFileRunnable = null;
|
||||
}
|
||||
|
||||
finishReceivingRunnable = null;
|
||||
currentShareInfo = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user