mirror of
https://github.com/KDE/kdeconnect-android
synced 2025-09-04 08:05:10 +00:00
Upload files using a CompositeUploadFileJob making the upload cancelable
This commit is contained in:
@@ -135,11 +135,13 @@
|
|||||||
<item quantity="one">File: %1s</item>
|
<item quantity="one">File: %1s</item>
|
||||||
<item quantity="other">(File %2$d of %3$d) : %1$s</item>
|
<item quantity="other">(File %2$d of %3$d) : %1$s</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
<string name="outgoing_file_title">Sending file to %1s</string>
|
<plurals name="outgoing_file_title">
|
||||||
<string name="outgoing_files_title">Sending files to %1s</string>
|
<item quantity="one">Sending %1$d file to %2$s</item>
|
||||||
|
<item quantity="other">Sending %1$d files to %2$s</item>
|
||||||
|
</plurals>
|
||||||
<plurals name="outgoing_files_text">
|
<plurals name="outgoing_files_text">
|
||||||
<item quantity="one">Sent %1$d file</item>
|
<item quantity="one">File: %1$s</item>
|
||||||
<item quantity="other">Sent %1$d out of %2$d files</item>
|
<item quantity="other">(File %2$d of %3$d) : %1$s</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
<plurals name="received_files_title">
|
<plurals name="received_files_title">
|
||||||
<item quantity="one">Received file from %1$s</item>
|
<item quantity="one">Received file from %1$s</item>
|
||||||
@@ -149,12 +151,16 @@
|
|||||||
<item quantity="one">Failed receiving file from %1$s</item>
|
<item quantity="one">Failed receiving file from %1$s</item>
|
||||||
<item quantity="other">Failed receiving %2$d of %3$d files from %1$s</item>
|
<item quantity="other">Failed receiving %2$d of %3$d files from %1$s</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
|
<plurals name="sent_files_title">
|
||||||
|
<item quantity="one">Sent file to %1$s</item>
|
||||||
|
<item quantity="other">Sent %2$d files to %1$s"</item>
|
||||||
|
</plurals>
|
||||||
|
<plurals name="send_files_fail_title">
|
||||||
|
<item quantity="one">Failed sending file to %1$s</item>
|
||||||
|
<item quantity="other">Failed sending %2$d of %3$d files to %1$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="cannot_create_file">Cannot create file %s</string>
|
<string name="cannot_create_file">Cannot create file %s</string>
|
||||||
<string name="sent_file_title">Sent file to %1s</string>
|
|
||||||
<string name="sent_file_text">%1s</string>
|
|
||||||
<string name="sent_file_failed_title">Failed to send file to %1s</string>
|
|
||||||
<string name="sent_file_failed_text">%1s</string>
|
|
||||||
<string name="tap_to_answer">Tap to answer</string>
|
<string name="tap_to_answer">Tap to answer</string>
|
||||||
<string name="reconnect">Reconnect</string>
|
<string name="reconnect">Reconnect</string>
|
||||||
<string name="right_click">Send Right Click</string>
|
<string name="right_click">Send Right Click</string>
|
||||||
|
@@ -201,7 +201,7 @@ public class LanLink extends BaseLink {
|
|||||||
long size = np.getPayloadSize();
|
long size = np.getPayloadSize();
|
||||||
long progress = 0;
|
long progress = 0;
|
||||||
long timeSinceLastUpdate = -1;
|
long timeSinceLastUpdate = -1;
|
||||||
while ((bytesRead = inputStream.read(buffer)) != -1) {
|
while (!np.isCanceled() && (bytesRead = inputStream.read(buffer)) != -1) {
|
||||||
//Log.e("ok",""+bytesRead);
|
//Log.e("ok",""+bytesRead);
|
||||||
progress += bytesRead;
|
progress += bytesRead;
|
||||||
outputStream.write(buffer, 0, bytesRead);
|
outputStream.write(buffer, 0, bytesRead);
|
||||||
@@ -223,7 +223,9 @@ public class LanLink extends BaseLink {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
callback.onSuccess();
|
if (!np.isCanceled()) {
|
||||||
|
callback.onSuccess();
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
if (callback != null) {
|
if (callback != null) {
|
||||||
|
@@ -57,6 +57,7 @@ public class NetworkPacket {
|
|||||||
private JSONObject mBody;
|
private JSONObject mBody;
|
||||||
private Payload mPayload;
|
private Payload mPayload;
|
||||||
private JSONObject mPayloadTransferInfo;
|
private JSONObject mPayloadTransferInfo;
|
||||||
|
private volatile boolean canceled;
|
||||||
|
|
||||||
private NetworkPacket() {
|
private NetworkPacket() {
|
||||||
|
|
||||||
@@ -70,6 +71,9 @@ public class NetworkPacket {
|
|||||||
mPayloadTransferInfo = new JSONObject();
|
mPayloadTransferInfo = new JSONObject();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isCanceled() { return canceled; }
|
||||||
|
public void cancel() { canceled = true; }
|
||||||
|
|
||||||
public String getType() {
|
public String getType() {
|
||||||
return mType;
|
return mType;
|
||||||
}
|
}
|
||||||
@@ -317,7 +321,7 @@ public class NetworkPacket {
|
|||||||
private Socket inputSocket;
|
private Socket inputSocket;
|
||||||
private long payloadSize;
|
private long payloadSize;
|
||||||
|
|
||||||
Payload(long payloadSize) {
|
public Payload(long payloadSize) {
|
||||||
this((InputStream)null, payloadSize);
|
this((InputStream)null, payloadSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -47,7 +47,7 @@ import androidx.core.content.FileProvider;
|
|||||||
import androidx.documentfile.provider.DocumentFile;
|
import androidx.documentfile.provider.DocumentFile;
|
||||||
|
|
||||||
public class CompositeReceiveFileJob extends BackgroundJob<Device, Void> {
|
public class CompositeReceiveFileJob extends BackgroundJob<Device, Void> {
|
||||||
private final ShareNotification shareNotification;
|
private final ReceiveNotification receiveNotification;
|
||||||
private NetworkPacket currentNetworkPacket;
|
private NetworkPacket currentNetworkPacket;
|
||||||
private String currentFileName;
|
private String currentFileName;
|
||||||
private int currentFileNum;
|
private int currentFileNum;
|
||||||
@@ -66,8 +66,8 @@ public class CompositeReceiveFileJob extends BackgroundJob<Device, Void> {
|
|||||||
|
|
||||||
lock = new Object();
|
lock = new Object();
|
||||||
networkPacketList = new ArrayList<>();
|
networkPacketList = new ArrayList<>();
|
||||||
shareNotification = new ShareNotification(device);
|
receiveNotification = new ReceiveNotification(device);
|
||||||
shareNotification.addCancelAction(getId());
|
receiveNotification.addCancelAction(getId());
|
||||||
currentFileNum = 0;
|
currentFileNum = 0;
|
||||||
totalNumFiles = 0;
|
totalNumFiles = 0;
|
||||||
totalPayloadSize = 0;
|
totalPayloadSize = 0;
|
||||||
@@ -87,7 +87,7 @@ public class CompositeReceiveFileJob extends BackgroundJob<Device, Void> {
|
|||||||
this.totalNumFiles = numberOfFiles;
|
this.totalNumFiles = numberOfFiles;
|
||||||
this.totalPayloadSize = totalPayloadSize;
|
this.totalPayloadSize = totalPayloadSize;
|
||||||
|
|
||||||
shareNotification.setTitle(getDevice().getContext().getResources()
|
receiveNotification.setTitle(getDevice().getContext().getResources()
|
||||||
.getQuantityString(R.plurals.incoming_file_title, totalNumFiles, totalNumFiles, getDevice().getName()));
|
.getQuantityString(R.plurals.incoming_file_title, totalNumFiles, totalNumFiles, getDevice().getName()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -100,7 +100,7 @@ public class CompositeReceiveFileJob extends BackgroundJob<Device, Void> {
|
|||||||
totalNumFiles = networkPacket.getInt(SharePlugin.KEY_NUMBER_OF_FILES, 1);
|
totalNumFiles = networkPacket.getInt(SharePlugin.KEY_NUMBER_OF_FILES, 1);
|
||||||
totalPayloadSize = networkPacket.getLong(SharePlugin.KEY_TOTAL_PAYLOAD_SIZE);
|
totalPayloadSize = networkPacket.getLong(SharePlugin.KEY_TOTAL_PAYLOAD_SIZE);
|
||||||
|
|
||||||
shareNotification.setTitle(getDevice().getContext().getResources()
|
receiveNotification.setTitle(getDevice().getContext().getResources()
|
||||||
.getQuantityString(R.plurals.incoming_file_title, totalNumFiles, totalNumFiles, getDevice().getName()));
|
.getQuantityString(R.plurals.incoming_file_title, totalNumFiles, totalNumFiles, getDevice().getName()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -149,6 +149,7 @@ public class CompositeReceiveFileJob extends BackgroundJob<Device, Void> {
|
|||||||
publishFile(fileDocument, received);
|
publishFile(fileDocument, received);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
//TODO: Only set progress to 100 if this is the only file/packet to send
|
||||||
setProgress(100);
|
setProgress(100);
|
||||||
publishFile(fileDocument, 0);
|
publishFile(fileDocument, 0);
|
||||||
}
|
}
|
||||||
@@ -180,7 +181,7 @@ public class CompositeReceiveFileJob extends BackgroundJob<Device, Void> {
|
|||||||
isRunning = false;
|
isRunning = false;
|
||||||
|
|
||||||
if (canceled) {
|
if (canceled) {
|
||||||
shareNotification.cancel();
|
receiveNotification.cancel();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -190,23 +191,23 @@ public class CompositeReceiveFileJob extends BackgroundJob<Device, Void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (numFiles == 1 && currentNetworkPacket.has("open")) {
|
if (numFiles == 1 && currentNetworkPacket.has("open")) {
|
||||||
shareNotification.cancel();
|
receiveNotification.cancel();
|
||||||
openFile(fileDocument);
|
openFile(fileDocument);
|
||||||
} else {
|
} else {
|
||||||
//Update the notification and allow to open the file from it
|
//Update the notification and allow to open the file from it
|
||||||
shareNotification.setFinished(getDevice().getContext().getResources().getQuantityString(R.plurals.received_files_title, numFiles, getDevice().getName(), numFiles));
|
receiveNotification.setFinished(getDevice().getContext().getResources().getQuantityString(R.plurals.received_files_title, numFiles, getDevice().getName(), numFiles));
|
||||||
|
|
||||||
if (totalNumFiles == 1 && fileDocument != null) {
|
if (totalNumFiles == 1 && fileDocument != null) {
|
||||||
shareNotification.setURI(fileDocument.getUri(), fileDocument.getType(), fileDocument.getName());
|
receiveNotification.setURI(fileDocument.getUri(), fileDocument.getType(), fileDocument.getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
shareNotification.show();
|
receiveNotification.show();
|
||||||
}
|
}
|
||||||
reportResult(null);
|
reportResult(null);
|
||||||
|
|
||||||
} catch (ActivityNotFoundException e) {
|
} catch (ActivityNotFoundException e) {
|
||||||
shareNotification.setFinished(getDevice().getContext().getString(R.string.no_app_for_opening));
|
receiveNotification.setFinished(getDevice().getContext().getString(R.string.no_app_for_opening));
|
||||||
shareNotification.show();
|
receiveNotification.show();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
isRunning = false;
|
isRunning = false;
|
||||||
|
|
||||||
@@ -217,8 +218,8 @@ public class CompositeReceiveFileJob extends BackgroundJob<Device, Void> {
|
|||||||
failedFiles = (totalNumFiles - currentFileNum + 1);
|
failedFiles = (totalNumFiles - currentFileNum + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
shareNotification.setFinished(getDevice().getContext().getResources().getQuantityString(R.plurals.received_files_fail_title, failedFiles, getDevice().getName(), failedFiles, totalNumFiles));
|
receiveNotification.setFinished(getDevice().getContext().getResources().getQuantityString(R.plurals.received_files_fail_title, failedFiles, getDevice().getName(), failedFiles, totalNumFiles));
|
||||||
shareNotification.show();
|
receiveNotification.show();
|
||||||
reportError(e);
|
reportError(e);
|
||||||
} finally {
|
} finally {
|
||||||
closeAllInputStreams();
|
closeAllInputStreams();
|
||||||
@@ -238,7 +239,7 @@ public class CompositeReceiveFileJob extends BackgroundJob<Device, Void> {
|
|||||||
|
|
||||||
//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.
|
||||||
//User-defined paths use the new Storage Access Framework that already handles this.
|
//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 the file should be opened immediately store it in the standard location to avoid the FileProvider trouble (See ReceiveNotification::setURI)
|
||||||
if (open || !ShareSettingsFragment.isCustomDestinationEnabled(getDevice().getContext())) {
|
if (open || !ShareSettingsFragment.isCustomDestinationEnabled(getDevice().getContext())) {
|
||||||
final String defaultPath = ShareSettingsFragment.getDefaultDestinationDirectory().getAbsolutePath();
|
final String defaultPath = ShareSettingsFragment.getDefaultDestinationDirectory().getAbsolutePath();
|
||||||
filenameToUse = FilesHelper.findNonExistingNameForNewFile(defaultPath, filenameToUse);
|
filenameToUse = FilesHelper.findNonExistingNameForNewFile(defaultPath, filenameToUse);
|
||||||
@@ -300,10 +301,10 @@ public class CompositeReceiveFileJob extends BackgroundJob<Device, Void> {
|
|||||||
|
|
||||||
private void setProgress(int progress) {
|
private void setProgress(int progress) {
|
||||||
synchronized (lock) {
|
synchronized (lock) {
|
||||||
shareNotification.setProgress(progress, getDevice().getContext().getResources()
|
receiveNotification.setProgress(progress, getDevice().getContext().getResources()
|
||||||
.getQuantityString(R.plurals.incoming_files_text, totalNumFiles, currentFileName, currentFileNum, totalNumFiles));
|
.getQuantityString(R.plurals.incoming_files_text, totalNumFiles, currentFileName, currentFileNum, totalNumFiles));
|
||||||
}
|
}
|
||||||
shareNotification.show();
|
receiveNotification.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void publishFile(DocumentFile fileDocument, long size) {
|
private void publishFile(DocumentFile fileDocument, long size) {
|
||||||
|
@@ -0,0 +1,222 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.kde.kdeconnect.Plugins.SharePlugin;
|
||||||
|
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Looper;
|
||||||
|
|
||||||
|
import org.kde.kdeconnect.Device;
|
||||||
|
import org.kde.kdeconnect.NetworkPacket;
|
||||||
|
import org.kde.kdeconnect.async.BackgroundJob;
|
||||||
|
import org.kde.kdeconnect_tp.R;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
public class CompositeUploadFileJob extends BackgroundJob<Device, Void> {
|
||||||
|
private boolean isRunning;
|
||||||
|
private Handler handler;
|
||||||
|
private String currentFileName;
|
||||||
|
private int currentFileNum;
|
||||||
|
private boolean updatePacketPending;
|
||||||
|
private long totalSend;
|
||||||
|
private int prevProgressPercentage;
|
||||||
|
private UploadNotification uploadNotification;
|
||||||
|
|
||||||
|
private final Object lock; //Use to protect concurrent access to the variables below
|
||||||
|
private final List<NetworkPacket> networkPacketList;
|
||||||
|
private NetworkPacket currentNetworkPacket;
|
||||||
|
private final Device.SendPacketStatusCallback sendPacketStatusCallback;
|
||||||
|
private int totalNumFiles;
|
||||||
|
private long totalPayloadSize;
|
||||||
|
|
||||||
|
CompositeUploadFileJob(@NonNull Device device, @NonNull Callback<Void> callback) {
|
||||||
|
super(device, callback);
|
||||||
|
|
||||||
|
isRunning = false;
|
||||||
|
handler = new Handler(Looper.getMainLooper());
|
||||||
|
currentFileNum = 0;
|
||||||
|
currentFileName = "";
|
||||||
|
updatePacketPending = false;
|
||||||
|
|
||||||
|
lock = new Object();
|
||||||
|
networkPacketList = new ArrayList<>();
|
||||||
|
totalNumFiles = 0;
|
||||||
|
totalPayloadSize = 0;
|
||||||
|
totalSend = 0;
|
||||||
|
prevProgressPercentage = 0;
|
||||||
|
uploadNotification = new UploadNotification(getDevice());
|
||||||
|
uploadNotification.addCancelAction(getId());
|
||||||
|
|
||||||
|
sendPacketStatusCallback = new SendPacketStatusCallback();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Device getDevice() { return requestInfo; }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
boolean done;
|
||||||
|
|
||||||
|
isRunning = true;
|
||||||
|
|
||||||
|
synchronized (lock) {
|
||||||
|
done = networkPacketList.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
while (!done && !canceled) {
|
||||||
|
synchronized (lock) {
|
||||||
|
currentNetworkPacket = networkPacketList.remove(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
currentFileName = currentNetworkPacket.getString("filename");
|
||||||
|
currentFileNum++;
|
||||||
|
|
||||||
|
setProgress(prevProgressPercentage);
|
||||||
|
|
||||||
|
addTotalsToNetworkPacket(currentNetworkPacket);
|
||||||
|
|
||||||
|
if (!getDevice().sendPacketBlocking(currentNetworkPacket, sendPacketStatusCallback)) {
|
||||||
|
throw new RuntimeException("Sending packet failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized (lock) {
|
||||||
|
done = networkPacketList.isEmpty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (canceled) {
|
||||||
|
uploadNotification.cancel();
|
||||||
|
} else {
|
||||||
|
uploadNotification.setFinished(getDevice().getContext().getResources().getQuantityString(R.plurals.sent_files_title, currentFileNum, getDevice().getName(), currentFileNum));
|
||||||
|
uploadNotification.show();
|
||||||
|
|
||||||
|
reportResult(null);
|
||||||
|
}
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
int failedFiles;
|
||||||
|
synchronized (lock) {
|
||||||
|
failedFiles = (totalNumFiles - currentFileNum + 1);
|
||||||
|
uploadNotification.setFinished(getDevice().getContext().getResources()
|
||||||
|
.getQuantityString(R.plurals.send_files_fail_title, failedFiles, getDevice().getName(),
|
||||||
|
failedFiles, totalNumFiles));
|
||||||
|
}
|
||||||
|
|
||||||
|
uploadNotification.show();
|
||||||
|
reportError(e);
|
||||||
|
} finally {
|
||||||
|
isRunning = false;
|
||||||
|
|
||||||
|
for (NetworkPacket networkPacket : networkPacketList) {
|
||||||
|
networkPacket.getPayload().close();
|
||||||
|
}
|
||||||
|
networkPacketList.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addTotalsToNetworkPacket(NetworkPacket networkPacket) {
|
||||||
|
synchronized (lock) {
|
||||||
|
networkPacket.set(SharePlugin.KEY_NUMBER_OF_FILES, totalNumFiles);
|
||||||
|
networkPacket.set(SharePlugin.KEY_TOTAL_PAYLOAD_SIZE, totalPayloadSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setProgress(int progress) {
|
||||||
|
synchronized (lock) {
|
||||||
|
uploadNotification.setProgress(progress, getDevice().getContext().getResources()
|
||||||
|
.getQuantityString(R.plurals.outgoing_files_text, totalNumFiles, currentFileName, currentFileNum, totalNumFiles));
|
||||||
|
}
|
||||||
|
uploadNotification.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
void addNetworkPacket(@NonNull NetworkPacket networkPacket) {
|
||||||
|
synchronized (lock) {
|
||||||
|
networkPacketList.add(networkPacket);
|
||||||
|
|
||||||
|
totalNumFiles++;
|
||||||
|
|
||||||
|
if (networkPacket.getPayloadSize() >= 0) {
|
||||||
|
totalPayloadSize += networkPacket.getPayloadSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
uploadNotification.setTitle(getDevice().getContext().getResources()
|
||||||
|
.getQuantityString(R.plurals.outgoing_file_title, totalNumFiles, totalNumFiles, getDevice().getName()));
|
||||||
|
|
||||||
|
//Give SharePlugin some time to add more NetworkPackets
|
||||||
|
if (isRunning && !updatePacketPending) {
|
||||||
|
updatePacketPending = true;
|
||||||
|
handler.post(this::sendUpdatePacket);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendUpdatePacket() {
|
||||||
|
NetworkPacket np = new NetworkPacket(SharePlugin.PACKET_TYPE_SHARE_REQUEST_UPDATE);
|
||||||
|
|
||||||
|
synchronized (lock) {
|
||||||
|
np.set("numberOfFiles", totalNumFiles);
|
||||||
|
np.set("totalPayloadSize", totalPayloadSize);
|
||||||
|
updatePacketPending = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
getDevice().sendPacket(np);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void cancel() {
|
||||||
|
super.cancel();
|
||||||
|
|
||||||
|
currentNetworkPacket.cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
private class SendPacketStatusCallback extends Device.SendPacketStatusCallback {
|
||||||
|
@Override
|
||||||
|
public void onProgressChanged(int percent) {
|
||||||
|
float send = totalSend + (currentNetworkPacket.getPayloadSize() * ((float)percent / 100));
|
||||||
|
int progress = (int)((send * 100) / totalPayloadSize);
|
||||||
|
|
||||||
|
if (progress != prevProgressPercentage) {
|
||||||
|
setProgress(progress);
|
||||||
|
prevProgressPercentage = progress;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSuccess() {
|
||||||
|
if (currentNetworkPacket.getPayloadSize() == 0) {
|
||||||
|
synchronized (lock) {
|
||||||
|
if (networkPacketList.isEmpty()) {
|
||||||
|
setProgress(100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
totalSend += currentNetworkPacket.getPayloadSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Throwable e) {
|
||||||
|
//Ignored
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -1,118 +0,0 @@
|
|||||||
package org.kde.kdeconnect.Plugins.SharePlugin;
|
|
||||||
|
|
||||||
import android.app.NotificationManager;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.res.Resources;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import org.kde.kdeconnect.Device;
|
|
||||||
import org.kde.kdeconnect.Helpers.NotificationHelper;
|
|
||||||
import org.kde.kdeconnect.NetworkPacket;
|
|
||||||
import org.kde.kdeconnect_tp.R;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
|
|
||||||
import androidx.core.app.NotificationCompat;
|
|
||||||
|
|
||||||
class NotificationUpdateCallback extends Device.SendPacketStatusCallback {
|
|
||||||
|
|
||||||
private final Resources res;
|
|
||||||
private final Device device;
|
|
||||||
private final NotificationManager notificationManager;
|
|
||||||
private final NotificationCompat.Builder builder;
|
|
||||||
|
|
||||||
private final ArrayList<NetworkPacket> toSend;
|
|
||||||
|
|
||||||
private final int notificationId;
|
|
||||||
|
|
||||||
private int sentFiles = 0;
|
|
||||||
private final int numFiles;
|
|
||||||
|
|
||||||
NotificationUpdateCallback(Context context, Device device, ArrayList<NetworkPacket> toSend) {
|
|
||||||
this.toSend = toSend;
|
|
||||||
this.device = device;
|
|
||||||
this.res = context.getResources();
|
|
||||||
|
|
||||||
String title;
|
|
||||||
if (toSend.size() > 1) {
|
|
||||||
title = res.getString(R.string.outgoing_files_title, device.getName());
|
|
||||||
} else {
|
|
||||||
title = res.getString(R.string.outgoing_file_title, device.getName());
|
|
||||||
}
|
|
||||||
|
|
||||||
notificationManager = (NotificationManager)context.getSystemService(Context.NOTIFICATION_SERVICE);
|
|
||||||
builder = new NotificationCompat.Builder(context, NotificationHelper.Channels.FILETRANSFER)
|
|
||||||
.setSmallIcon(android.R.drawable.stat_sys_upload)
|
|
||||||
.setAutoCancel(true)
|
|
||||||
.setOngoing(true)
|
|
||||||
.setProgress(100, 0, false)
|
|
||||||
.setContentTitle(title)
|
|
||||||
.setTicker(title);
|
|
||||||
|
|
||||||
notificationId = (int) System.currentTimeMillis();
|
|
||||||
|
|
||||||
numFiles = toSend.size();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onProgressChanged(int progress) {
|
|
||||||
builder.setProgress(100 * numFiles, (100 * sentFiles) + progress, false);
|
|
||||||
NotificationHelper.notifyCompat(notificationManager, notificationId, builder.build());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onSuccess() {
|
|
||||||
sentFiles++;
|
|
||||||
if (sentFiles == numFiles) {
|
|
||||||
updateDone(true);
|
|
||||||
} else {
|
|
||||||
updateText();
|
|
||||||
}
|
|
||||||
NotificationHelper.notifyCompat(notificationManager, notificationId, builder.build());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFailure(Throwable e) {
|
|
||||||
updateDone(false);
|
|
||||||
NotificationHelper.notifyCompat(notificationManager, notificationId, builder.build());
|
|
||||||
Log.e("KDEConnect", "Exception", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateText() {
|
|
||||||
String text;
|
|
||||||
text = res.getQuantityString(R.plurals.outgoing_files_text, numFiles, sentFiles, numFiles);
|
|
||||||
builder.setContentText(text);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateDone(boolean successful) {
|
|
||||||
int icon;
|
|
||||||
String title;
|
|
||||||
String text;
|
|
||||||
|
|
||||||
if (successful) {
|
|
||||||
if (numFiles > 1) {
|
|
||||||
text = res.getQuantityString(R.plurals.outgoing_files_text, numFiles, sentFiles, numFiles);
|
|
||||||
} else {
|
|
||||||
final String filename = toSend.get(0).getString("filename");
|
|
||||||
text = res.getString(R.string.sent_file_text, filename);
|
|
||||||
}
|
|
||||||
title = res.getString(R.string.sent_file_title, device.getName());
|
|
||||||
icon = android.R.drawable.stat_sys_upload_done;
|
|
||||||
} else {
|
|
||||||
final String filename = toSend.get(sentFiles).getString("filename");
|
|
||||||
title = res.getString(R.string.sent_file_failed_title, device.getName());
|
|
||||||
text = res.getString(R.string.sent_file_failed_text, filename);
|
|
||||||
icon = android.R.drawable.stat_notify_error;
|
|
||||||
}
|
|
||||||
|
|
||||||
builder.setOngoing(false)
|
|
||||||
.setTicker(title)
|
|
||||||
.setContentTitle(title)
|
|
||||||
.setContentText(text)
|
|
||||||
.setSmallIcon(icon)
|
|
||||||
.setProgress(0, 0, false); //setting progress to 0 out of 0 remove the progress bar
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@@ -43,7 +43,7 @@ import java.io.InputStream;
|
|||||||
import androidx.core.app.NotificationCompat;
|
import androidx.core.app.NotificationCompat;
|
||||||
import androidx.core.content.FileProvider;
|
import androidx.core.content.FileProvider;
|
||||||
|
|
||||||
class ShareNotification {
|
class ReceiveNotification {
|
||||||
private final NotificationManager notificationManager;
|
private final NotificationManager notificationManager;
|
||||||
private final int notificationId;
|
private final int notificationId;
|
||||||
private NotificationCompat.Builder builder;
|
private NotificationCompat.Builder builder;
|
||||||
@@ -54,7 +54,7 @@ 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) {
|
public ReceiveNotification(Device device) {
|
||||||
this.device = device;
|
this.device = device;
|
||||||
|
|
||||||
notificationId = (int) System.currentTimeMillis();
|
notificationId = (int) System.currentTimeMillis();
|
@@ -89,7 +89,7 @@ public class SendFileActivity extends AppCompatActivity {
|
|||||||
if (uris.isEmpty()) {
|
if (uris.isEmpty()) {
|
||||||
Log.w("SendFileActivity", "No files to send?");
|
Log.w("SendFileActivity", "No files to send?");
|
||||||
} else {
|
} else {
|
||||||
BackgroundService.RunWithPlugin(this, mDeviceId, SharePlugin.class, plugin -> plugin.queuedSendUriList(uris));
|
BackgroundService.RunWithPlugin(this, mDeviceId, SharePlugin.class, plugin -> plugin.sendUriList(uris));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
finish();
|
finish();
|
||||||
|
@@ -56,7 +56,7 @@ public class SharePlugin extends Plugin {
|
|||||||
final static String CANCEL_SHARE_BACKGROUND_JOB_ID_EXTRA = "backgroundJobId";
|
final static String CANCEL_SHARE_BACKGROUND_JOB_ID_EXTRA = "backgroundJobId";
|
||||||
|
|
||||||
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 String PACKET_TYPE_SHARE_REQUEST_UPDATE = "kdeconnect.share.request.update";
|
final static String PACKET_TYPE_SHARE_REQUEST_UPDATE = "kdeconnect.share.request.update";
|
||||||
|
|
||||||
final static String KEY_NUMBER_OF_FILES = "numberOfFiles";
|
final static String KEY_NUMBER_OF_FILES = "numberOfFiles";
|
||||||
final static String KEY_TOTAL_PAYLOAD_SIZE = "totalPayloadSize";
|
final static String KEY_TOTAL_PAYLOAD_SIZE = "totalPayloadSize";
|
||||||
@@ -65,6 +65,7 @@ public class SharePlugin extends Plugin {
|
|||||||
private final Handler handler;
|
private final Handler handler;
|
||||||
|
|
||||||
private CompositeReceiveFileJob receiveFileJob;
|
private CompositeReceiveFileJob receiveFileJob;
|
||||||
|
private CompositeUploadFileJob uploadFileJob;
|
||||||
private final Callback receiveFileJobCallback;
|
private final Callback receiveFileJobCallback;
|
||||||
|
|
||||||
public SharePlugin() {
|
public SharePlugin() {
|
||||||
@@ -147,7 +148,8 @@ public class SharePlugin extends Plugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Log.e("SharePlugin", "Exception", e);
|
Log.e("SharePlugin", "Exception");
|
||||||
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -204,36 +206,28 @@ public class SharePlugin extends Plugin {
|
|||||||
return ShareSettingsFragment.newInstance(getPluginKey());
|
return ShareSettingsFragment.newInstance(getPluginKey());
|
||||||
}
|
}
|
||||||
|
|
||||||
void queuedSendUriList(final ArrayList<Uri> uriList) {
|
void sendUriList(final ArrayList<Uri> uriList) {
|
||||||
|
CompositeUploadFileJob job = null;
|
||||||
|
|
||||||
|
if (uploadFileJob == null) {
|
||||||
|
job = new CompositeUploadFileJob(device, this.receiveFileJobCallback);
|
||||||
|
} else {
|
||||||
|
job = uploadFileJob;
|
||||||
|
}
|
||||||
|
|
||||||
//Read all the data early, as we only have permissions to do it while the activity is alive
|
//Read all the data early, as we only have permissions to do it while the activity is alive
|
||||||
final ArrayList<NetworkPacket> toSend = new ArrayList<>();
|
|
||||||
for (Uri uri : uriList) {
|
for (Uri uri : uriList) {
|
||||||
NetworkPacket np = FilesHelper.uriToNetworkPacket(context, uri, PACKET_TYPE_SHARE_REQUEST);
|
NetworkPacket np = FilesHelper.uriToNetworkPacket(context, uri, PACKET_TYPE_SHARE_REQUEST);
|
||||||
|
|
||||||
if (np != null) {
|
if (np != null) {
|
||||||
toSend.add(np);
|
job.addNetworkPacket(np);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//Callback that shows a progress notification
|
if (job != uploadFileJob) {
|
||||||
final NotificationUpdateCallback notificationUpdateCallback = new NotificationUpdateCallback(context, device, toSend);
|
uploadFileJob = job;
|
||||||
|
backgroundJobHandler.runJob(uploadFileJob);
|
||||||
//Do the sending in background
|
}
|
||||||
new Thread(() -> {
|
|
||||||
//Actually send the files
|
|
||||||
try {
|
|
||||||
for (NetworkPacket np : toSend) {
|
|
||||||
boolean success = device.sendPacketBlocking(np, notificationUpdateCallback);
|
|
||||||
if (!success) {
|
|
||||||
Log.e("SharePlugin", "Error sending files");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
Log.e("SharePlugin", "Error sending files", e);
|
|
||||||
}
|
|
||||||
}).start();
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void share(Intent intent) {
|
public void share(Intent intent) {
|
||||||
@@ -252,9 +246,10 @@ public class SharePlugin extends Plugin {
|
|||||||
uriList.add(uri);
|
uriList.add(uri);
|
||||||
}
|
}
|
||||||
|
|
||||||
queuedSendUriList(uriList);
|
sendUriList(uriList);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Log.e("ShareActivity", "Exception", e);
|
Log.e("ShareActivity", "Exception");
|
||||||
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if (extras.containsKey(Intent.EXTRA_TEXT)) {
|
} else if (extras.containsKey(Intent.EXTRA_TEXT)) {
|
||||||
@@ -302,11 +297,13 @@ public class SharePlugin extends Plugin {
|
|||||||
return new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE};
|
return new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE};
|
||||||
}
|
}
|
||||||
|
|
||||||
private class Callback implements CompositeReceiveFileJob.Callback<Void> {
|
private class Callback implements BackgroundJob.Callback<Void> {
|
||||||
@Override
|
@Override
|
||||||
public void onResult(@NonNull BackgroundJob job, Void result) {
|
public void onResult(@NonNull BackgroundJob job, Void result) {
|
||||||
if (job == receiveFileJob) {
|
if (job == receiveFileJob) {
|
||||||
receiveFileJob = null;
|
receiveFileJob = null;
|
||||||
|
} else if (job == uploadFileJob) {
|
||||||
|
uploadFileJob = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -314,6 +311,8 @@ public class SharePlugin extends Plugin {
|
|||||||
public void onError(@NonNull BackgroundJob job, @NonNull Throwable error) {
|
public void onError(@NonNull BackgroundJob job, @NonNull Throwable error) {
|
||||||
if (job == receiveFileJob) {
|
if (job == receiveFileJob) {
|
||||||
receiveFileJob = null;
|
receiveFileJob = null;
|
||||||
|
} else if (job == uploadFileJob) {
|
||||||
|
uploadFileJob = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -327,6 +326,8 @@ public class SharePlugin extends Plugin {
|
|||||||
|
|
||||||
if (job == receiveFileJob) {
|
if (job == receiveFileJob) {
|
||||||
receiveFileJob = null;
|
receiveFileJob = null;
|
||||||
|
} else if (job == uploadFileJob) {
|
||||||
|
uploadFileJob = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,108 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.kde.kdeconnect.Plugins.SharePlugin;
|
||||||
|
|
||||||
|
import android.app.Notification;
|
||||||
|
import android.app.NotificationManager;
|
||||||
|
import android.app.PendingIntent;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
|
||||||
|
import org.kde.kdeconnect.Device;
|
||||||
|
import org.kde.kdeconnect.Helpers.NotificationHelper;
|
||||||
|
import org.kde.kdeconnect_tp.R;
|
||||||
|
|
||||||
|
import androidx.core.app.NotificationCompat;
|
||||||
|
import androidx.preference.PreferenceManager;
|
||||||
|
|
||||||
|
class UploadNotification {
|
||||||
|
private final NotificationManager notificationManager;
|
||||||
|
private NotificationCompat.Builder builder;
|
||||||
|
private final int notificationId;
|
||||||
|
private final Device device;
|
||||||
|
private long currentJobId;
|
||||||
|
|
||||||
|
UploadNotification(Device device) {
|
||||||
|
this.device = device;
|
||||||
|
|
||||||
|
notificationId = (int) System.currentTimeMillis();
|
||||||
|
notificationManager = (NotificationManager) device.getContext().getSystemService(Context.NOTIFICATION_SERVICE);
|
||||||
|
builder = new NotificationCompat.Builder(device.getContext(), NotificationHelper.Channels.FILETRANSFER)
|
||||||
|
.setSmallIcon(android.R.drawable.stat_sys_upload)
|
||||||
|
.setAutoCancel(true)
|
||||||
|
.setOngoing(true)
|
||||||
|
.setProgress(100, 0, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void addCancelAction(long jobId) {
|
||||||
|
builder.mActions.clear();
|
||||||
|
|
||||||
|
currentJobId = jobId;
|
||||||
|
Intent cancelIntent = new Intent(device.getContext(), ShareBroadcastReceiver.class);
|
||||||
|
cancelIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
|
||||||
|
cancelIntent.setAction(SharePlugin.ACTION_CANCEL_SHARE);
|
||||||
|
cancelIntent.putExtra(SharePlugin.CANCEL_SHARE_BACKGROUND_JOB_ID_EXTRA, jobId);
|
||||||
|
cancelIntent.putExtra(SharePlugin.CANCEL_SHARE_DEVICE_ID_EXTRA, device.getDeviceId());
|
||||||
|
PendingIntent cancelPendingIntent = PendingIntent.getBroadcast(device.getContext(), 0, cancelIntent, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||||
|
|
||||||
|
builder.addAction(R.drawable.ic_reject_pairing, device.getContext().getString(R.string.cancel), cancelPendingIntent);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTitle(String title) {
|
||||||
|
builder.setContentTitle(title);
|
||||||
|
builder.setTicker(title);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setProgress(int progress, String progressMessage) {
|
||||||
|
builder.setProgress( 100, progress, false);
|
||||||
|
builder.setContentText(progressMessage);
|
||||||
|
builder.setStyle(new NotificationCompat.BigTextStyle().bigText(progressMessage));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFinished(String message) {
|
||||||
|
builder = new NotificationCompat.Builder(device.getContext(), NotificationHelper.Channels.DEFAULT);
|
||||||
|
builder.setContentTitle(message)
|
||||||
|
.setTicker(message)
|
||||||
|
.setSmallIcon(android.R.drawable.stat_sys_upload_done)
|
||||||
|
.setAutoCancel(true)
|
||||||
|
.setOngoing(false);
|
||||||
|
|
||||||
|
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(device.getContext());
|
||||||
|
if (prefs.getBoolean("share_notification_preference", true)) {
|
||||||
|
builder.setDefaults(Notification.DEFAULT_ALL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFailed(String message) {
|
||||||
|
setFinished(message);
|
||||||
|
builder.setSmallIcon(android.R.drawable.stat_notify_error);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void cancel() {
|
||||||
|
notificationManager.cancel(notificationId);
|
||||||
|
}
|
||||||
|
|
||||||
|
void show() {
|
||||||
|
NotificationHelper.notifyCompat(notificationManager, notificationId, builder.build());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Reference in New Issue
Block a user