mirror of
https://github.com/KDE/kdeconnect-android
synced 2025-08-30 21:55:10 +00:00
Receive multiple files using 1 notification
Summary: When multiple files are shared from desktop to android the transfer of the files should be handled using 1 notification Depends on: D17081 Test Plan: Share multiple biggish files from desktop to android The sharing progress is displayed using 1 notification Reviewers: #kde_connect, albertvaka Reviewed By: #kde_connect, albertvaka Subscribers: kdeconnect Tags: #kde_connect Differential Revision: https://phabricator.kde.org/D17157
This commit is contained in:
@@ -127,16 +127,28 @@
|
|||||||
<string name="pairing_request_from">Pairing request from %1s</string>
|
<string name="pairing_request_from">Pairing request from %1s</string>
|
||||||
<string name="received_url_title">Received link from %1s</string>
|
<string name="received_url_title">Received link from %1s</string>
|
||||||
<string name="received_url_text">Tap to open \'%1s\'</string>
|
<string name="received_url_text">Tap to open \'%1s\'</string>
|
||||||
<string name="incoming_file_title">Incoming file from %1s</string>
|
<plurals name="incoming_file_title">Receiving file from %1s>
|
||||||
<string name="incoming_file_text">%1s</string>
|
<item quantity="one">Receiving %1$d file from %2$s</item>
|
||||||
|
<item quantity="other">Receiving %1$d files from %2$s</item>
|
||||||
|
</plurals>
|
||||||
|
<plurals name="incoming_files_text">
|
||||||
|
<item quantity="one">File: %1s</item>
|
||||||
|
<item quantity="other">(File %2$d of %3$d) : %1$s</item>
|
||||||
|
</plurals>
|
||||||
<string name="outgoing_file_title">Sending file to %1s</string>
|
<string name="outgoing_file_title">Sending file to %1s</string>
|
||||||
<string name="outgoing_files_title">Sending files to %1s</string>
|
<string name="outgoing_files_title">Sending files to %1s</string>
|
||||||
<plurals name="outgoing_files_text">
|
<plurals name="outgoing_files_text">
|
||||||
<item quantity="one">Sent %1$d file</item>
|
<item quantity="one">Sent %1$d file</item>
|
||||||
<item quantity="other">Sent %1$d out of %2$d files</item>
|
<item quantity="other">Sent %1$d out of %2$d files</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
<string name="received_file_title">Received file from %1s</string>
|
<plurals name="received_files_title">
|
||||||
<string name="received_file_fail_title">Failed receiving file from %1s</string>
|
<item quantity="one">Received file from %2$s</item>
|
||||||
|
<item quantity="other">Received %1$d files from %2$s</item>
|
||||||
|
</plurals>
|
||||||
|
<plurals name="received_files_fail_title">
|
||||||
|
<item quantity="one">Failed receiving file from %3$s</item>
|
||||||
|
<item quantity="other">Failed receiving %1$d of %2$d files from %3$s</item>
|
||||||
|
</plurals>
|
||||||
<string name="received_file_text">Tap to open \'%1s\'</string>
|
<string name="received_file_text">Tap to open \'%1s\'</string>
|
||||||
<string name="sent_file_title">Sent file to %1s</string>
|
<string name="sent_file_title">Sent file to %1s</string>
|
||||||
<string name="sent_file_text">%1s</string>
|
<string name="sent_file_text">%1s</string>
|
||||||
|
@@ -0,0 +1,92 @@
|
|||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
while ((count = info.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 {
|
||||||
|
try {
|
||||||
|
info.inputStream.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
info.outputStream.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
64
src/org/kde/kdeconnect/Plugins/SharePlugin/ShareInfo.java
Normal file
64
src/org/kde/kdeconnect/Plugins/SharePlugin/ShareInfo.java
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
/*
|
||||||
|
* 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.support.v4.provider.DocumentFile;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
|
||||||
|
class ShareInfo {
|
||||||
|
String fileName;
|
||||||
|
long fileSize;
|
||||||
|
int currentFileNumber;
|
||||||
|
DocumentFile fileDocument;
|
||||||
|
InputStream inputStream;
|
||||||
|
OutputStream outputStream;
|
||||||
|
boolean shouldOpen;
|
||||||
|
|
||||||
|
private final Object lock = new Object(); // To protect access to numberOfFiles and totalTransferSize
|
||||||
|
private int numberOfFiles;
|
||||||
|
private long totalTransferSize;
|
||||||
|
|
||||||
|
int numberOfFiles() {
|
||||||
|
synchronized (lock) {
|
||||||
|
return numberOfFiles;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void setNumberOfFiles(int numberOfFiles) {
|
||||||
|
synchronized (lock) {
|
||||||
|
this.numberOfFiles = numberOfFiles;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
long totalTransferSize() {
|
||||||
|
synchronized (lock) {
|
||||||
|
return totalTransferSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void setTotalTransferSize(long totalTransferSize) {
|
||||||
|
synchronized (lock) {
|
||||||
|
this.totalTransferSize = totalTransferSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -42,8 +42,8 @@ import java.io.File;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
//TODO: Starting API 24 notification title and text are both displayed on 1 line above the progress bar. Because title can be long, the text is often not displayed
|
||||||
class ShareNotification {
|
class ShareNotification {
|
||||||
private final String filename;
|
|
||||||
private final NotificationManager notificationManager;
|
private final NotificationManager notificationManager;
|
||||||
private final int notificationId;
|
private final int notificationId;
|
||||||
private NotificationCompat.Builder builder;
|
private NotificationCompat.Builder builder;
|
||||||
@@ -53,15 +53,12 @@ class ShareNotification {
|
|||||||
private static final int bigImageWidth = 1440;
|
private static final int bigImageWidth = 1440;
|
||||||
private static final int bigImageHeight = 720;
|
private static final int bigImageHeight = 720;
|
||||||
|
|
||||||
public ShareNotification(Device device, String filename) {
|
public ShareNotification(Device device) {
|
||||||
this.device = device;
|
this.device = device;
|
||||||
this.filename = filename;
|
|
||||||
notificationId = (int) System.currentTimeMillis();
|
notificationId = (int) System.currentTimeMillis();
|
||||||
notificationManager = (NotificationManager) device.getContext().getSystemService(Context.NOTIFICATION_SERVICE);
|
notificationManager = (NotificationManager) device.getContext().getSystemService(Context.NOTIFICATION_SERVICE);
|
||||||
builder = new NotificationCompat.Builder(device.getContext(), NotificationHelper.Channels.FILETRANSFER)
|
builder = new NotificationCompat.Builder(device.getContext(), NotificationHelper.Channels.FILETRANSFER)
|
||||||
.setContentTitle(device.getContext().getResources().getString(R.string.incoming_file_title, device.getName()))
|
|
||||||
.setContentText(device.getContext().getResources().getString(R.string.incoming_file_text, filename))
|
|
||||||
.setTicker(device.getContext().getResources().getString(R.string.incoming_file_title, device.getName()))
|
|
||||||
.setSmallIcon(android.R.drawable.stat_sys_download)
|
.setSmallIcon(android.R.drawable.stat_sys_download)
|
||||||
.setAutoCancel(true)
|
.setAutoCancel(true)
|
||||||
.setOngoing(true)
|
.setOngoing(true)
|
||||||
@@ -80,13 +77,17 @@ class ShareNotification {
|
|||||||
return notificationId;
|
return notificationId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setProgress(int progress) {
|
public void setTitle(String title) {
|
||||||
builder.setProgress(100, progress, false)
|
builder.setContentTitle(title);
|
||||||
.setContentTitle(device.getContext().getResources().getString(R.string.incoming_file_title, device.getName()) + " (" + progress + "%)");
|
builder.setTicker(title);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setFinished(boolean success) {
|
public void setProgress(int progress, String progressMessage) {
|
||||||
String message = success ? device.getContext().getResources().getString(R.string.received_file_title, device.getName()) : device.getContext().getResources().getString(R.string.received_file_fail_title, device.getName());
|
builder.setProgress( 100, progress, false);
|
||||||
|
builder.setContentText(progressMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFinished(String message) {
|
||||||
builder = new NotificationCompat.Builder(device.getContext(), NotificationHelper.Channels.DEFAULT);
|
builder = new NotificationCompat.Builder(device.getContext(), NotificationHelper.Channels.DEFAULT);
|
||||||
builder.setContentTitle(message)
|
builder.setContentTitle(message)
|
||||||
.setTicker(message)
|
.setTicker(message)
|
||||||
@@ -100,7 +101,7 @@ class ShareNotification {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setURI(Uri destinationUri, String mimeType) {
|
public void setURI(Uri destinationUri, String mimeType, String filename) {
|
||||||
/*
|
/*
|
||||||
* We only support file URIs (because sending a content uri to another app does not work for security reasons).
|
* We only support file URIs (because sending a content uri to another app does not work for security reasons).
|
||||||
* In effect, that means only the default download folder currently works.
|
* In effect, that means only the default download folder currently works.
|
||||||
|
@@ -36,7 +36,10 @@ import android.graphics.drawable.Drawable;
|
|||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Looper;
|
||||||
import android.provider.MediaStore;
|
import android.provider.MediaStore;
|
||||||
|
import android.support.annotation.WorkerThread;
|
||||||
import android.support.v4.app.NotificationCompat;
|
import android.support.v4.app.NotificationCompat;
|
||||||
import android.support.v4.content.ContextCompat;
|
import android.support.v4.content.ContextCompat;
|
||||||
import android.support.v4.content.FileProvider;
|
import android.support.v4.content.FileProvider;
|
||||||
@@ -44,7 +47,6 @@ import android.support.v4.provider.DocumentFile;
|
|||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import org.kde.kdeconnect.Device;
|
|
||||||
import org.kde.kdeconnect.Helpers.FilesHelper;
|
import org.kde.kdeconnect.Helpers.FilesHelper;
|
||||||
import org.kde.kdeconnect.Helpers.MediaStoreHelper;
|
import org.kde.kdeconnect.Helpers.MediaStoreHelper;
|
||||||
import org.kde.kdeconnect.Helpers.NotificationHelper;
|
import org.kde.kdeconnect.Helpers.NotificationHelper;
|
||||||
@@ -53,18 +55,30 @@ import org.kde.kdeconnect.Plugins.Plugin;
|
|||||||
import org.kde.kdeconnect.UserInterface.DeviceSettingsActivity;
|
import org.kde.kdeconnect.UserInterface.DeviceSettingsActivity;
|
||||||
import org.kde.kdeconnect_tp.R;
|
import org.kde.kdeconnect_tp.R;
|
||||||
|
|
||||||
|
import java.io.BufferedOutputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
|
||||||
public class SharePlugin extends Plugin {
|
public class SharePlugin extends Plugin implements ReceiveFileRunnable.CallBack {
|
||||||
|
|
||||||
private final static String PACKET_TYPE_SHARE_REQUEST = "kdeconnect.share.request";
|
private final static String PACKET_TYPE_SHARE_REQUEST = "kdeconnect.share.request";
|
||||||
|
|
||||||
private final static boolean openUrlsDirectly = true;
|
private final static boolean openUrlsDirectly = true;
|
||||||
|
private ShareNotification shareNotification;
|
||||||
|
private FinishReceivingRunnable finishReceivingRunnable;
|
||||||
|
private ExecutorService executorService;
|
||||||
|
private ShareInfo currentShareInfo;
|
||||||
|
private Handler handler;
|
||||||
|
|
||||||
|
public SharePlugin() {
|
||||||
|
executorService = Executors.newSingleThreadExecutor();
|
||||||
|
handler = new Handler(Looper.getMainLooper());
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onCreate() {
|
public boolean onCreate() {
|
||||||
@@ -110,13 +124,10 @@ public class SharePlugin extends Plugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@WorkerThread
|
||||||
public boolean onPacketReceived(NetworkPacket np) {
|
public boolean onPacketReceived(NetworkPacket np) {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (np.hasPayload()) {
|
if (np.hasPayload()) {
|
||||||
|
|
||||||
Log.i("SharePlugin", "hasPayload");
|
|
||||||
|
|
||||||
if (isPermissionGranted(Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
|
if (isPermissionGranted(Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
|
||||||
receiveFile(np);
|
receiveFile(np);
|
||||||
} else {
|
} else {
|
||||||
@@ -183,13 +194,33 @@ public class SharePlugin extends Plugin {
|
|||||||
Toast.makeText(context, R.string.shareplugin_text_saved, Toast.LENGTH_LONG).show();
|
Toast.makeText(context, R.string.shareplugin_text_saved, Toast.LENGTH_LONG).show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@WorkerThread
|
||||||
private void receiveFile(NetworkPacket np) {
|
private void receiveFile(NetworkPacket np) {
|
||||||
|
if (finishReceivingRunnable != null) {
|
||||||
|
Log.i("SharePlugin", "receiveFile: canceling finishReceivingRunnable");
|
||||||
|
handler.removeCallbacks(finishReceivingRunnable);
|
||||||
|
finishReceivingRunnable = null;
|
||||||
|
}
|
||||||
|
|
||||||
final InputStream input = np.getPayload();
|
ShareInfo info = new ShareInfo();
|
||||||
final long fileLength = np.getPayloadSize();
|
info.currentFileNumber = currentShareInfo == null ? 1 : currentShareInfo.currentFileNumber + 1;
|
||||||
final String originalFilename = np.getString("filename", Long.toString(System.currentTimeMillis()));
|
info.inputStream = 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));
|
||||||
|
|
||||||
String filename = originalFilename;
|
if (currentShareInfo == null) {
|
||||||
|
currentShareInfo = info;
|
||||||
|
} else {
|
||||||
|
synchronized (currentShareInfo) {
|
||||||
|
currentShareInfo.setNumberOfFiles(info.numberOfFiles());
|
||||||
|
currentShareInfo.setTotalTransferSize(info.totalTransferSize());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String filename = info.fileName;
|
||||||
final DocumentFile destinationFolderDocument;
|
final DocumentFile destinationFolderDocument;
|
||||||
|
|
||||||
//We need to check for already existing files only when storing in the default path.
|
//We need to check for already existing files only when storing in the default path.
|
||||||
@@ -197,107 +228,38 @@ public class SharePlugin extends Plugin {
|
|||||||
//If the file should be opened immediately store it in the standard location to avoid the FileProvider trouble (See ShareNotification::setURI)
|
//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") || !ShareSettingsActivity.isCustomDestinationEnabled(context)) {
|
if (np.getBoolean("open") || !ShareSettingsActivity.isCustomDestinationEnabled(context)) {
|
||||||
final String defaultPath = ShareSettingsActivity.getDefaultDestinationDirectory().getAbsolutePath();
|
final String defaultPath = ShareSettingsActivity.getDefaultDestinationDirectory().getAbsolutePath();
|
||||||
filename = FilesHelper.findNonExistingNameForNewFile(defaultPath, originalFilename);
|
filename = FilesHelper.findNonExistingNameForNewFile(defaultPath, filename);
|
||||||
destinationFolderDocument = DocumentFile.fromFile(new File(defaultPath));
|
destinationFolderDocument = DocumentFile.fromFile(new File(defaultPath));
|
||||||
} else {
|
} else {
|
||||||
destinationFolderDocument = ShareSettingsActivity.getDestinationDirectory(context);
|
destinationFolderDocument = ShareSettingsActivity.getDestinationDirectory(context);
|
||||||
}
|
}
|
||||||
String displayName = FilesHelper.getFileNameWithoutExt(filename);
|
String displayName = FilesHelper.getFileNameWithoutExt(filename);
|
||||||
final String mimeType = FilesHelper.getMimeTypeFromFile(filename);
|
String mimeType = FilesHelper.getMimeTypeFromFile(filename);
|
||||||
|
|
||||||
if ("*/*".equals(mimeType)) {
|
if ("*/*".equals(mimeType)) {
|
||||||
displayName = filename;
|
displayName = filename;
|
||||||
}
|
}
|
||||||
|
|
||||||
final DocumentFile destinationDocument = destinationFolderDocument.createFile(mimeType, displayName);
|
info.fileDocument = destinationFolderDocument.createFile(mimeType, displayName);
|
||||||
final OutputStream destinationOutput;
|
assert info.fileDocument != null;
|
||||||
|
info.fileDocument.getType();
|
||||||
try {
|
try {
|
||||||
destinationOutput = context.getContentResolver().openOutputStream(destinationDocument.getUri());
|
info.outputStream = new BufferedOutputStream(context.getContentResolver().openOutputStream(info.fileDocument.getUri()));
|
||||||
} catch (FileNotFoundException e) {
|
} catch (FileNotFoundException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final Uri destinationUri = destinationDocument.getUri();
|
|
||||||
|
|
||||||
final ShareNotification notification = new ShareNotification(device, filename);
|
if (shareNotification == null) {
|
||||||
notification.show();
|
shareNotification = new ShareNotification(device);
|
||||||
|
}
|
||||||
|
|
||||||
new Thread(() -> {
|
shareNotification.setTitle(context.getResources().getQuantityString(R.plurals.incoming_file_title, info.numberOfFiles(), info.numberOfFiles(), device.getName()));
|
||||||
try {
|
//shareNotification.setProgress(0, context.getResources().getQuantityString(R.plurals.incoming_files_text, numFiles, filename, currentFileNum, numFiles));
|
||||||
byte data[] = new byte[4096];
|
shareNotification.show();
|
||||||
long progress = 0, prevProgressPercentage = -1;
|
|
||||||
int count;
|
|
||||||
long lastUpdate = 0;
|
|
||||||
while ((count = input.read(data)) >= 0) {
|
|
||||||
progress += count;
|
|
||||||
destinationOutput.write(data, 0, count);
|
|
||||||
if (fileLength > 0) {
|
|
||||||
if (progress >= fileLength) break;
|
|
||||||
long progressPercentage = (progress * 100 / fileLength);
|
|
||||||
if (progressPercentage != prevProgressPercentage &&
|
|
||||||
System.currentTimeMillis() - lastUpdate > 100) {
|
|
||||||
prevProgressPercentage = progressPercentage;
|
|
||||||
lastUpdate = System.currentTimeMillis();
|
|
||||||
|
|
||||||
notification.setProgress((int) progressPercentage);
|
ReceiveFileRunnable runnable = new ReceiveFileRunnable(info, this);
|
||||||
notification.show();
|
executorService.execute(runnable);
|
||||||
}
|
|
||||||
}
|
|
||||||
//else Log.e("SharePlugin", "Infinite loop? :D");
|
|
||||||
}
|
|
||||||
|
|
||||||
destinationOutput.flush();
|
|
||||||
|
|
||||||
Log.i("SharePlugin", "Transfer finished: " + destinationUri.getPath());
|
|
||||||
|
|
||||||
if (np.getBoolean("open")) {
|
|
||||||
|
|
||||||
notification.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(destinationUri.getPath());
|
|
||||||
Uri contentUri = FileProvider.getUriForFile(device.getContext(), "org.kde.kdeconnect_tp.fileprovider", file);
|
|
||||||
intent.setDataAndType(contentUri, mimeType);
|
|
||||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
|
||||||
} else {
|
|
||||||
intent.setDataAndType(destinationUri, mimeType);
|
|
||||||
}
|
|
||||||
|
|
||||||
context.startActivity(intent);
|
|
||||||
} else {
|
|
||||||
|
|
||||||
//Update the notification and allow to open the file from it
|
|
||||||
notification.setFinished(true);
|
|
||||||
notification.setURI(destinationUri, mimeType);
|
|
||||||
notification.show();
|
|
||||||
|
|
||||||
if (!ShareSettingsActivity.isCustomDestinationEnabled(context)) {
|
|
||||||
Log.i("SharePlugin", "Adding to downloads");
|
|
||||||
DownloadManager manager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
|
|
||||||
manager.addCompletedDownload(destinationUri.getLastPathSegment(), device.getName(), true, mimeType, destinationUri.getPath(), fileLength, false);
|
|
||||||
} else {
|
|
||||||
//Make sure it is added to the Android Gallery anyway
|
|
||||||
MediaStoreHelper.indexFile(context, destinationUri);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
Log.e("SharePlugin", "Receiver thread exception");
|
|
||||||
e.printStackTrace();
|
|
||||||
notification.setFinished(false);
|
|
||||||
notification.show();
|
|
||||||
} finally {
|
|
||||||
try {
|
|
||||||
destinationOutput.close();
|
|
||||||
} catch (Exception e) {
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
input.close();
|
|
||||||
} catch (Exception e) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}).start();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -482,4 +444,88 @@ public class SharePlugin extends Plugin {
|
|||||||
public String[] getOptionalPermissions() {
|
public String[] getOptionalPermissions() {
|
||||||
return new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE};
|
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());
|
||||||
|
}
|
||||||
|
|
||||||
|
context.startActivity(intent);
|
||||||
|
} else {
|
||||||
|
if (!ShareSettingsActivity.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();
|
||||||
|
|
||||||
|
int failedFiles = info.numberOfFiles() - (info.currentFileNumber - 1);
|
||||||
|
shareNotification.setFinished(context.getResources().getQuantityString(R.plurals.received_files_fail_title, failedFiles, failedFiles, info.numberOfFiles(), device.getName()));
|
||||||
|
shareNotification.show();
|
||||||
|
shareNotification = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class FinishReceivingRunnable implements Runnable {
|
||||||
|
private final ShareInfo info;
|
||||||
|
|
||||||
|
private FinishReceivingRunnable(ShareInfo info) {
|
||||||
|
this.info = info;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
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(), info.numberOfFiles(), device.getName()));
|
||||||
|
|
||||||
|
if (info.numberOfFiles() == 1) {
|
||||||
|
shareNotification.setURI(info.fileDocument.getUri(), info.fileDocument.getType(), info.fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
shareNotification.show();
|
||||||
|
Log.i("SharePlugin", "FinishReceivingRunnable: Setting shareNotification to null");
|
||||||
|
shareNotification = null;
|
||||||
|
finishReceivingRunnable = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user