diff --git a/src/org/kde/kdeconnect/Backends/BluetoothBackend/BluetoothLink.java b/src/org/kde/kdeconnect/Backends/BluetoothBackend/BluetoothLink.java --- a/src/org/kde/kdeconnect/Backends/BluetoothBackend/BluetoothLink.java +++ b/src/org/kde/kdeconnect/Backends/BluetoothBackend/BluetoothLink.java @@ -20,7 +20,6 @@ package org.kde.kdeconnect.Backends.BluetoothBackend; -import android.annotation.TargetApi; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothServerSocket; import android.bluetooth.BluetoothSocket; @@ -102,7 +101,7 @@ UUID transferUuid = UUID.fromString(np.getPayloadTransferInfo().getString("uuid")); transferSocket = socket.getRemoteDevice().createRfcommSocketToServiceRecord(transferUuid); transferSocket.connect(); - np.setPayload(transferSocket.getInputStream(), np.getPayloadSize()); + np.setPayload(new NetworkPacket.Payload(transferSocket.getInputStream(), np.getPayloadSize())); } catch (Exception e) { if (transferSocket != null) { try { @@ -211,7 +210,7 @@ byte[] buffer = new byte[idealBufferLength]; int bytesRead; long progress = 0; - InputStream stream = np.getPayload(); + InputStream stream = np.getPayload().getInputStream(); while ((bytesRead = stream.read(buffer)) != -1) { progress += bytesRead; transferSocket.getOutputStream().write(buffer, 0, bytesRead); diff --git a/src/org/kde/kdeconnect/Backends/LanBackend/LanLink.java b/src/org/kde/kdeconnect/Backends/LanBackend/LanLink.java --- a/src/org/kde/kdeconnect/Backends/LanBackend/LanLink.java +++ b/src/org/kde/kdeconnect/Backends/LanBackend/LanLink.java @@ -193,7 +193,7 @@ } outputStream = payloadSocket.getOutputStream(); - inputStream = np.getPayload(); + inputStream = np.getPayload().getInputStream(); Log.i("KDE/LanLink", "Beginning to send payload"); byte[] buffer = new byte[4096]; @@ -214,12 +214,11 @@ } } outputStream.flush(); - outputStream.close(); Log.i("KDE/LanLink", "Finished sending payload ("+progress+" bytes written)"); } finally { try { server.close(); } catch (Exception e) { } try { payloadSocket.close(); } catch (Exception e) { } - try { inputStream.close(); } catch (Exception e) { } + np.getPayload().close(); try { outputStream.close(); } catch (Exception e) { } } } @@ -233,8 +232,9 @@ return false; } finally { //Make sure we close the payload stream, if any - InputStream stream = np.getPayload(); - try { stream.close(); } catch (Exception e) { } + if (np.hasPayload()) { + np.getPayload().close(); + } } } @@ -272,7 +272,7 @@ if (socket instanceof SSLSocket) { payloadSocket = SslHelper.convertToSslSocket(context, payloadSocket, getDeviceId(), true, true); } - np.setPayload(payloadSocket.getInputStream(), np.getPayloadSize()); + np.setPayload(new NetworkPacket.Payload(payloadSocket, np.getPayloadSize())); } catch (Exception e) { try { payloadSocket.close(); } catch(Exception ignored) { } e.printStackTrace(); diff --git a/src/org/kde/kdeconnect/Backends/LoopbackBackend/LoopbackLink.java b/src/org/kde/kdeconnect/Backends/LoopbackBackend/LoopbackLink.java --- a/src/org/kde/kdeconnect/Backends/LoopbackBackend/LoopbackLink.java +++ b/src/org/kde/kdeconnect/Backends/LoopbackBackend/LoopbackLink.java @@ -51,7 +51,7 @@ packageReceived(in); if (in.hasPayload()) { callback.onProgressChanged(0); - in.setPayload(in.getPayload(), in.getPayloadSize()); + in.setPayload(in.getPayload()); callback.onProgressChanged(100); } callback.onSuccess(); diff --git a/src/org/kde/kdeconnect/Helpers/SecurityHelpers/RsaHelper.java b/src/org/kde/kdeconnect/Helpers/SecurityHelpers/RsaHelper.java --- a/src/org/kde/kdeconnect/Helpers/SecurityHelpers/RsaHelper.java +++ b/src/org/kde/kdeconnect/Helpers/SecurityHelpers/RsaHelper.java @@ -116,7 +116,7 @@ NetworkPacket encrypted = new NetworkPacket(NetworkPacket.PACKET_TYPE_ENCRYPTED); encrypted.set("data", chunks); - encrypted.setPayload(np.getPayload(), np.getPayloadSize()); + encrypted.setPayload(np.getPayload()); return encrypted; } @@ -136,7 +136,7 @@ } NetworkPacket decrypted = NetworkPacket.unserialize(decryptedJson.toString()); - decrypted.setPayload(np.getPayload(), np.getPayloadSize()); + decrypted.setPayload(np.getPayload()); return decrypted; } diff --git a/src/org/kde/kdeconnect/NetworkPacket.java b/src/org/kde/kdeconnect/NetworkPacket.java --- a/src/org/kde/kdeconnect/NetworkPacket.java +++ b/src/org/kde/kdeconnect/NetworkPacket.java @@ -30,7 +30,9 @@ import org.kde.kdeconnect.Plugins.PluginFactory; import java.io.ByteArrayInputStream; +import java.io.IOException; import java.io.InputStream; +import java.net.Socket; import java.util.ArrayList; import java.util.HashSet; import java.util.List; @@ -53,9 +55,8 @@ private long mId; String mType; private JSONObject mBody; - private InputStream mPayload; + private Payload mPayload; private JSONObject mPayloadTransferInfo; - private long mPayloadSize; private NetworkPacket() { @@ -66,7 +67,6 @@ mType = type; mBody = new JSONObject(); mPayload = null; - mPayloadSize = 0; mPayloadTransferInfo = new JSONObject(); } @@ -242,7 +242,7 @@ jo.put("type", mType); jo.put("body", mBody); if (hasPayload()) { - jo.put("payloadSize", mPayloadSize); + jo.put("payloadSize", mPayload.payloadSize); jo.put("payloadTransferInfo", mPayloadTransferInfo); } //QJSon does not escape slashes, but Java JSONObject does. Converting to QJson format. @@ -258,10 +258,10 @@ np.mBody = jo.getJSONObject("body"); if (jo.has("payloadSize")) { np.mPayloadTransferInfo = jo.getJSONObject("payloadTransferInfo"); - np.mPayloadSize = jo.getLong("payloadSize"); + np.mPayload = new Payload(jo.getLong("payloadSize")); } else { np.mPayloadTransferInfo = new JSONObject(); - np.mPayloadSize = 0; + np.mPayload = new Payload(0); } return np; } @@ -287,29 +287,18 @@ } - public void setPayload(byte[] data) { - setPayload(new ByteArrayInputStream(data), data.length); - } - - public void setPayload(InputStream stream, long size) { - mPayload = stream; - mPayloadSize = size; - } + public void setPayload(Payload payload) { mPayload = payload; } - /*public void setPayload(InputStream stream) { - setPayload(stream, -1); - }*/ - - public InputStream getPayload() { + public Payload getPayload() { return mPayload; } public long getPayloadSize() { - return mPayloadSize; + return mPayload == null ? 0 : mPayload.payloadSize; } public boolean hasPayload() { - return (mPayloadSize != 0); + return (mPayload != null && mPayload.payloadSize != 0); } public boolean hasPayloadTransferInfo() { @@ -323,4 +312,54 @@ public void setPayloadTransferInfo(JSONObject payloadTransferInfo) { mPayloadTransferInfo = payloadTransferInfo; } + + public static class Payload { + private InputStream inputStream; + private Socket inputSocket; + private long payloadSize; + + public Payload(long payloadSize) { + this((InputStream)null, payloadSize); + } + + public Payload(byte[] data) { + this(new ByteArrayInputStream(data), data.length); + } + + /** + * NOTE: Do not use this to set an SSLSockets InputStream as the payload, use Payload(Socket, long) instead because of this bug + */ + public Payload(InputStream inputStream, long payloadSize) { + this.inputSocket = null; + this.inputStream = inputStream; + this.payloadSize = payloadSize; + } + + public Payload(Socket inputSocket, long payloadSize) throws IOException { + this.inputSocket = inputSocket; + this.inputStream = inputSocket.getInputStream(); + this.payloadSize = payloadSize; + } + + /** + * NOTE: Do not close the InputStream directly call Payload.close() instead, this is because of this bug + */ + public InputStream getInputStream() { return inputStream; } + long getPayloadSize() { return payloadSize; } + + public void close() { + //TODO: If socket only close socket if that also closes the streams that is + try { + if (inputStream != null) { + inputStream.close(); + } + } catch(IOException ignored) {} + + try { + if (inputSocket != null) { + inputSocket.close(); + } + } catch (IOException ignored) {} + } + } } diff --git a/src/org/kde/kdeconnect/Plugins/MprisPlugin/AlbumArtCache.java b/src/org/kde/kdeconnect/Plugins/MprisPlugin/AlbumArtCache.java --- a/src/org/kde/kdeconnect/Plugins/MprisPlugin/AlbumArtCache.java +++ b/src/org/kde/kdeconnect/Plugins/MprisPlugin/AlbumArtCache.java @@ -33,6 +33,8 @@ import com.jakewharton.disklrucache.DiskLruCache; +import org.kde.kdeconnect.NetworkPacket; + import java.io.File; import java.io.IOException; import java.io.InputStream; @@ -243,20 +245,20 @@ private static final class FetchURLTask extends AsyncTask { private final URL url; - private InputStream input; + private NetworkPacket.Payload payload; private final DiskLruCache.Editor cacheItem; private OutputStream output; /** * Initialize an url fetch * * @param url The url being fetched - * @param payloadInput A payload input stream (if from the connected device). null if fetched from http(s) + * @param payload A NetworkPacket Payload (if from the connected device). null if fetched from http(s) * @param cacheItem The disk cache item to edit */ - FetchURLTask(URL url, InputStream payloadInput, DiskLruCache.Editor cacheItem) throws IOException { + FetchURLTask(URL url, NetworkPacket.Payload payload, DiskLruCache.Editor cacheItem) throws IOException { this.url = url; - this.input = payloadInput; + this.payload = payload; this.cacheItem = cacheItem; output = cacheItem.newOutputStream(0); } @@ -266,7 +268,7 @@ * * @return True if succeeded */ - private boolean openHttp() throws IOException { + private InputStream openHttp() throws IOException { //Default android behaviour does not follow https -> http urls, so do this manually if (!url.getProtocol().equals("http") && !url.getProtocol().equals("https")) { throw new AssertionError("Invalid url: not http(s) in background album art fetch"); @@ -287,28 +289,25 @@ currentUrl = new URL(currentUrl, location); // Deal with relative URLs //Again, only support http(s) if (!currentUrl.getProtocol().equals("http") && !currentUrl.getProtocol().equals("https")) { - return false; + return null; } connection.disconnect(); continue; } //Found a non-redirecting connection, so do something with it - input = connection.getInputStream(); - return true; + return connection.getInputStream(); } - return false; + return null; } @Override protected Boolean doInBackground(Void... params) { - try { - //See if we need to open a http(s) connection here, or if we use a payload input stream + //See if we need to open a http(s) connection here, or if we use a payload input stream + try (InputStream input = payload == null ? openHttp() : payload.getInputStream()) { if (input == null) { - if (!openHttp()) { - return false; - } + return false; } byte[] buffer = new byte[4096]; @@ -321,6 +320,10 @@ return true; } catch (IOException e) { return false; + } finally { + if (payload != null) { + payload.close(); + } } } @@ -425,59 +428,48 @@ * @param albumUrl The url of the album art (should be a file:// url) * @param payload The payload input stream */ - static void payloadToDiskCache(String albumUrl, InputStream payload) { + static void payloadToDiskCache(String albumUrl, NetworkPacket.Payload payload) { //We need the disk cache for this - if (diskCache == null) { - Log.e("KDE/Mpris/AlbumArtCache", "The disk cache is not intialized!"); - try { - payload.close(); - } catch (IOException ignored) {} + if (payload == null) { return; } - if (payload == null) { + + if (diskCache == null) { + Log.e("KDE/Mpris/AlbumArtCache", "The disk cache is not intialized!"); + payload.close(); return; } URL url; try { url = new URL(albumUrl); } catch (MalformedURLException e) { //Shouldn't happen (checked on receival of the url), but just to be sure - try { - payload.close(); - } catch (IOException ignored) {} + payload.close(); return; } if (!"file".equals(url.getProtocol())) { //Shouldn't happen (otherwise we wouldn't have asked for the payload), but just to be sure - try { - payload.close(); - } catch (IOException ignored) {} + payload.close(); return; } //Only fetch the URL if we're not fetching it already if (isFetchingList.contains(url)) { - try { - payload.close(); - } catch (IOException ignored) {} + payload.close(); return; } //Check if we already have this art try { if (memoryCache.get(albumUrl) != null || diskCache.get(urlToDiskCacheKey(albumUrl)) != null) { - try { - payload.close(); - } catch (IOException ignored) {} + payload.close(); return; } } catch (IOException e) { Log.e("KDE/Mpris/AlbumArtCache", "Disk cache problem!", e); - try { - payload.close(); - } catch (IOException ignored) {} + payload.close(); return; } @@ -491,9 +483,7 @@ Log.e("KDE/Mpris/AlbumArtCache", "Two disk cache edits happened at the same time, should be impossible!"); --numFetching; - try { - payload.close(); - } catch (IOException ignored) {} + payload.close(); return; } diff --git a/src/org/kde/kdeconnect/Plugins/NotificationsPlugin/NotificationsPlugin.java b/src/org/kde/kdeconnect/Plugins/NotificationsPlugin/NotificationsPlugin.java --- a/src/org/kde/kdeconnect/Plugins/NotificationsPlugin/NotificationsPlugin.java +++ b/src/org/kde/kdeconnect/Plugins/NotificationsPlugin/NotificationsPlugin.java @@ -232,7 +232,7 @@ Log.e("PAYLOAD", "PAYLOAD: " + getChecksum(bitmapData)); - np.setPayload(bitmapData); + np.setPayload(new NetworkPacket.Payload(bitmapData)); np.set("payloadHash", getChecksum(bitmapData)); } diff --git a/src/org/kde/kdeconnect/Plugins/ReceiveNotificationsPlugin/ReceiveNotificationsPlugin.java b/src/org/kde/kdeconnect/Plugins/ReceiveNotificationsPlugin/ReceiveNotificationsPlugin.java --- a/src/org/kde/kdeconnect/Plugins/ReceiveNotificationsPlugin/ReceiveNotificationsPlugin.java +++ b/src/org/kde/kdeconnect/Plugins/ReceiveNotificationsPlugin/ReceiveNotificationsPlugin.java @@ -86,12 +86,10 @@ int height = 64; width = context.getResources().getDimensionPixelSize(android.R.dimen.notification_large_icon_width); height = context.getResources().getDimensionPixelSize(android.R.dimen.notification_large_icon_height); - final InputStream input = np.getPayload(); - largeIcon = BitmapFactory.decodeStream(np.getPayload()); - try { - input.close(); - } catch (Exception e) { - } + final InputStream input = np.getPayload().getInputStream(); + largeIcon = BitmapFactory.decodeStream(input); + np.getPayload().close(); + if (largeIcon != null) { //Log.i("NotificationsPlugin", "hasPayload: size=" + largeIcon.getWidth() + "/" + largeIcon.getHeight() + " opti=" + width + "/" + height); if (largeIcon.getWidth() > width || largeIcon.getHeight() > height) { @@ -131,5 +129,4 @@ public String[] getOutgoingPacketTypes() { return new String[]{PACKET_TYPE_NOTIFICATION_REQUEST}; } - } diff --git a/src/org/kde/kdeconnect/Plugins/SharePlugin/ReceiveFileRunnable.java b/src/org/kde/kdeconnect/Plugins/SharePlugin/ReceiveFileRunnable.java --- a/src/org/kde/kdeconnect/Plugins/SharePlugin/ReceiveFileRunnable.java +++ b/src/org/kde/kdeconnect/Plugins/SharePlugin/ReceiveFileRunnable.java @@ -24,6 +24,7 @@ import android.os.Looper; import java.io.IOException; +import java.io.InputStream; public class ReceiveFileRunnable implements Runnable { interface CallBack { @@ -51,7 +52,9 @@ callBack.onProgress(info, 0); - while ((count = info.inputStream.read(data)) >= 0) { + InputStream inputStream = info.payload.getInputStream(); + + while ((count = inputStream.read(data)) >= 0) { received += count; if (received > info.fileSize) { @@ -79,14 +82,11 @@ } catch (IOException e) { handler.post(() -> callBack.onError(info, e)); } finally { - try { - info.inputStream.close(); - } catch (IOException e) { - } + info.payload.close(); + try { info.outputStream.close(); - } catch (IOException e) { - } + } catch (IOException ignored) {} } } } diff --git a/src/org/kde/kdeconnect/Plugins/SharePlugin/ShareInfo.java b/src/org/kde/kdeconnect/Plugins/SharePlugin/ShareInfo.java --- a/src/org/kde/kdeconnect/Plugins/SharePlugin/ShareInfo.java +++ b/src/org/kde/kdeconnect/Plugins/SharePlugin/ShareInfo.java @@ -22,15 +22,16 @@ import android.support.v4.provider.DocumentFile; -import java.io.InputStream; +import org.kde.kdeconnect.NetworkPacket; + import java.io.OutputStream; class ShareInfo { String fileName; long fileSize; int currentFileNumber; DocumentFile fileDocument; - InputStream inputStream; + NetworkPacket.Payload payload; OutputStream outputStream; boolean shouldOpen; diff --git a/src/org/kde/kdeconnect/Plugins/SharePlugin/SharePlugin.java b/src/org/kde/kdeconnect/Plugins/SharePlugin/SharePlugin.java --- a/src/org/kde/kdeconnect/Plugins/SharePlugin/SharePlugin.java +++ b/src/org/kde/kdeconnect/Plugins/SharePlugin/SharePlugin.java @@ -204,7 +204,7 @@ ShareInfo info = new ShareInfo(); info.currentFileNumber = currentShareInfo == null ? 1 : currentShareInfo.currentFileNumber + 1; - info.inputStream = np.getPayload(); + info.payload = np.getPayload(); info.fileSize = np.getPayloadSize(); info.fileName = np.getString("filename", Long.toString(System.currentTimeMillis())); info.shouldOpen = np.getBoolean("open"); @@ -379,7 +379,7 @@ } - np.setPayload(inputStream, size); + np.setPayload(new NetworkPacket.Payload(inputStream, size)); return np; } catch (Exception e) {