diff --git a/src/org/kde/kdeconnect/Helpers/SMSHelper.java b/src/org/kde/kdeconnect/Helpers/SMSHelper.java index 59f66e56..6f935ed0 100644 --- a/src/org/kde/kdeconnect/Helpers/SMSHelper.java +++ b/src/org/kde/kdeconnect/Helpers/SMSHelper.java @@ -1,5 +1,6 @@ /* - * SPDX-FileCopyrightText: 2019 Simon Redman + * SPDX-FileCopyrightText: 2021 Simon Redman + * SPDX-FileCopyrightText: 2020 Aniket Kumar * * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL */ @@ -183,10 +184,10 @@ public class SMSHelper { selection = Message.DATE + " >= ?"; } - List smsSelectionArgs = new ArrayList(2); + List smsSelectionArgs = new ArrayList<>(2); smsSelectionArgs.add(startTimestamp.toString()); - List mmsSelectionArgs = new ArrayList(2); + List mmsSelectionArgs = new ArrayList<>(2); mmsSelectionArgs.add(Long.toString(startTimestamp / 1000)); if (threadID != null) { @@ -359,83 +360,6 @@ public class SMSHelper { return toReturn; } - /** - * Deletes messages which are failed to send due to some reason - * - * @param uri Uri indicating the messages database to read - * @param context android.content.Context running the request. - * @param fetchColumns List of columns to fetch - * @param selection Parameterizable filter to use with the ContentResolver query. May be null. - * @param selectionArgs Parameters for selection. May be null. - * @param sortOrder Sort ordering passed to Android's content resolver. May be null for unspecified - */ - private static void deleteFailedMessages( - @NonNull Uri uri, - @NonNull Context context, - @NonNull Collection fetchColumns, - @Nullable String selection, - @Nullable String[] selectionArgs, - @Nullable String sortOrder - ) { - try (Cursor myCursor = context.getContentResolver().query( - uri, - fetchColumns.toArray(ArrayUtils.EMPTY_STRING_ARRAY), - selection, - selectionArgs, - sortOrder) - ) { - if (myCursor != null && myCursor.moveToFirst()) { - do { - String id = null; - String type = null; - String msgBox = null; - - for (int columnIdx = 0; columnIdx < myCursor.getColumnCount(); columnIdx++) { - String colName = myCursor.getColumnName(columnIdx); - - if (colName.equals("_id")) { - id = myCursor.getString(columnIdx); - } - - if(colName.equals("type")) { - type = myCursor.getString(columnIdx); - } - - if (colName.equals("msg_box")) { - msgBox = myCursor.getString(columnIdx); - } - } - - if (type != null && id != null) { - if (type.equals(Telephony.Sms.MESSAGE_TYPE_OUTBOX) || type.equals(Telephony.Sms.MESSAGE_TYPE_FAILED)) { - Log.v("Deleting sms", "content://sms/" + id); - context.getContentResolver().delete(Uri.parse("content://sms/" + id), null, null); - } - } - - if (msgBox != null && id != null) { - if (msgBox.equals(Telephony.Mms.MESSAGE_BOX_OUTBOX) || msgBox.equals(Telephony.Mms.MESSAGE_BOX_FAILED)) { - Log.v("Deleting mms", "content://mms/" + id); - context.getContentResolver().delete(Uri.parse("content://mms/" + id), null, null); - } - } - } while (myCursor.moveToNext()); - } - } catch (SQLiteException e) { - String[] unfilteredColumns = {}; - try (Cursor unfilteredColumnsCursor = context.getContentResolver().query(uri, null, null, null, null)) { - if (unfilteredColumnsCursor != null) { - unfilteredColumns = unfilteredColumnsCursor.getColumnNames(); - } - } - if (unfilteredColumns.length == 0) { - throw new MessageAccessException(uri, e); - } else { - throw new MessageAccessException(unfilteredColumns, uri, e); - } - } - } - /** * Gets messages which match the selection * @@ -648,7 +572,6 @@ public class SMSHelper { } event = addEventFlag(event, Message.EVENT_TEXT_MESSAGE); } else if (MimeType.isTypeImage(contentType)) { - String mimeType = contentType; String fileName = data.substring(data.lastIndexOf('/') + 1); // Get the actual image from the mms database convert it into thumbnail and encode to Base64 @@ -656,9 +579,8 @@ public class SMSHelper { Bitmap thumbnailImage = ThumbnailUtils.extractThumbnail(image, THUMBNAIL_WIDTH, THUMBNAIL_HEIGHT); String encodedThumbnail = SmsMmsUtils.bitMapToBase64(thumbnailImage); - attachments.add(new Attachment(partID, mimeType, encodedThumbnail, fileName)); + attachments.add(new Attachment(partID, contentType, encodedThumbnail, fileName)); } else if (MimeType.isTypeVideo(contentType)) { - String mimeType = contentType; String fileName = data.substring(data.lastIndexOf('/') + 1); MediaMetadataRetriever retriever = new MediaMetadataRetriever(); @@ -669,12 +591,11 @@ public class SMSHelper { Bitmap.createScaledBitmap(videoThumbnail, THUMBNAIL_WIDTH, THUMBNAIL_HEIGHT, true) ); - attachments.add(new Attachment(partID, mimeType, encodedThumbnail, fileName)); + attachments.add(new Attachment(partID, contentType, encodedThumbnail, fileName)); } else if (MimeType.isTypeAudio(contentType)) { - String mimeType = contentType; String fileName = data.substring(data.lastIndexOf('/') + 1); - attachments.add(new Attachment(partID, mimeType, null, fileName)); + attachments.add(new Attachment(partID, contentType, null, fileName)); } else { Log.v("SMSHelper", "Unsupported attachment type: " + contentType); } @@ -847,6 +768,10 @@ public class SMSHelper { this.uniqueIdentifier = uniqueIdentifier; } + public String getBase64EncodedFile() { return base64EncodedFile; } + public String getMimeType() { return mimeType; } + public String getUniqueIdentifier() { return uniqueIdentifier; } + public JSONObject toJson() throws JSONException { JSONObject json = new JSONObject(); @@ -862,6 +787,41 @@ public class SMSHelper { } } + /** + * Converts a given JSONArray of attachments into List + * + * The structure of the input is expected to be as follows: + * [ + * { + * "fileName": // Name of the file + * "base64EncodedFile": // Base64 encoded file + * "mimeType": // File type (eg: image/jpg, video/mp4 etc.) + * }, + * ... + * ] + */ + public static @NonNull List jsonArrayToAttachmentsList( + @Nullable JSONArray jsonArray) { + if (jsonArray == null) { + return Collections.emptyList(); + } + + List attachedFiles = new ArrayList<>(jsonArray.length()); + try { + for (int i = 0; i < jsonArray.length(); i++) { + JSONObject jsonObject = jsonArray.getJSONObject(i); + String base64EncodedFile = jsonObject.getString("base64EncodedFile"); + String mimeType = jsonObject.getString("mimeType"); + String fileName = jsonObject.getString("fileName"); + attachedFiles.add(new Attachment(-1, mimeType, base64EncodedFile, fileName)); + } + } catch (Exception e) { + e.printStackTrace(); + } + + return attachedFiles; + } + public static class Address { public final String address; diff --git a/src/org/kde/kdeconnect/Plugins/SMSPlugin/SMSPlugin.java b/src/org/kde/kdeconnect/Plugins/SMSPlugin/SMSPlugin.java index f06ed215..6bb02725 100644 --- a/src/org/kde/kdeconnect/Plugins/SMSPlugin/SMSPlugin.java +++ b/src/org/kde/kdeconnect/Plugins/SMSPlugin/SMSPlugin.java @@ -1,6 +1,7 @@ /* * SPDX-FileCopyrightText: 2014 Albert Vaca Cintora - * SPDX-FileCopyrightText: 2019 Simon Redman + * SPDX-FileCopyrightText: 2021 Simon Redman + * SPDX-FileCopyrightText: 2020 Aniket Kumar * * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL */ @@ -118,10 +119,7 @@ public class SMSPlugin extends Plugin { /** * Packet sent to request a message be sent - *

- * This will almost certainly need to be replaced or augmented to support MMS, - * but be sure the Android side remains compatible with old desktop apps! - *

+ * * The body should look like so: * { * "version": 2, // The version of the packet being sent. Compare to SMS_REQUEST_PACKET_VERSION before attempting to handle. @@ -410,8 +408,9 @@ public class SMSPlugin extends Plugin { addressList = new ArrayList<>(); addressList.add(new SMSHelper.Address(np.getString("phoneNumber"))); } + List attachedFiles = SMSHelper.jsonArrayToAttachmentsList(np.getJSONArray("attachments")); - SmsMmsUtils.sendMessage(context, textMessage, addressList, (int) subID); + SmsMmsUtils.sendMessage(context, textMessage, attachedFiles, addressList, (int) subID); break; case TelephonyPlugin.PACKET_TYPE_TELEPHONY_REQUEST: diff --git a/src/org/kde/kdeconnect/Plugins/SMSPlugin/SmsMmsUtils.java b/src/org/kde/kdeconnect/Plugins/SMSPlugin/SmsMmsUtils.java index 4bc8f8fa..bba7947f 100644 --- a/src/org/kde/kdeconnect/Plugins/SMSPlugin/SmsMmsUtils.java +++ b/src/org/kde/kdeconnect/Plugins/SMSPlugin/SmsMmsUtils.java @@ -1,5 +1,6 @@ /* * SPDX-FileCopyrightText: 2020 Aniket Kumar + * SPDX-FileCopyrightText: 2021 Simon Redman * * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL */ @@ -39,11 +40,11 @@ import android.graphics.BitmapFactory; import android.provider.Telephony; import android.net.Uri; import android.telephony.SmsManager; -import android.telephony.TelephonyManager; import android.text.TextUtils; import android.util.Base64; import android.util.Log; +import androidx.annotation.NonNull; import androidx.annotation.RequiresApi; import org.apache.commons.lang3.ArrayUtils; @@ -75,10 +76,17 @@ public class SmsMmsUtils { * @param context context in which the method is called. * @param textMessage text body of the message to be sent. * @param addressList List of addresses. + * @param attachedFiles List of attachments. Pass empty list if none. * @param subID Note that here subID is of type int and not long because klinker library requires it as int * I don't really know the exact reason why they implemented it as int instead of long */ - public static void sendMessage(Context context, String textMessage, List addressList, int subID) { + public static void sendMessage( + Context context, + String textMessage, + @NonNull List attachedFiles, + List addressList, + int subID + ) { SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); boolean longTextAsMms = prefs.getBoolean(context.getString(R.string.set_long_text_as_mms), false); boolean groupMessageAsMms = prefs.getBoolean(context.getString(R.string.set_group_message_as_mms), true); @@ -138,6 +146,15 @@ public class SmsMmsUtils { } Message message = new Message(textMessage, addresses.toArray(ArrayUtils.EMPTY_STRING_ARRAY)); + + // If there are any attachment files add those into the message + for (SMSHelper.Attachment attachedFile : attachedFiles) { + byte[] file = Base64.decode(attachedFile.getBase64EncodedFile(), Base64.DEFAULT); + String mimeType = attachedFile.getMimeType(); + String fileName = attachedFile.getUniqueIdentifier(); + message.addMedia(file, mimeType, fileName); + } + message.setFromAddress(sendingPhoneNumber.number); message.setSave(true); @@ -202,7 +219,7 @@ public class SmsMmsUtils { configOverrides.putBoolean(SmsManager.MMS_CONFIG_GROUP_MMS_ENABLED, klinkerSettings.getGroup()); // Write the PDUs to disk so that we can pass them to the SmsManager - final String fileName = "send." + String.valueOf(Math.abs(new Random().nextLong())) + ".dat"; + final String fileName = "send." + Math.abs(new Random().nextLong()) + ".dat"; File mSendFile = new File(context.getCacheDir(), fileName); Uri contentUri = (new Uri.Builder()) @@ -332,14 +349,14 @@ public class SmsMmsUtils { public static List getMmsTo(MultimediaMessagePdu msg) { if (msg == null) { return null; } StringBuilder toBuilder = new StringBuilder(); - EncodedStringValue to[] = msg.getTo(); + EncodedStringValue[] to = msg.getTo(); if (to != null) { toBuilder.append(EncodedStringValue.concat(to)); } if (msg instanceof RetrieveConf) { - EncodedStringValue cc[] = ((RetrieveConf) msg).getCc(); + EncodedStringValue[] cc = ((RetrieveConf) msg).getCc(); if (cc != null && cc.length == 0) { toBuilder.append(";"); toBuilder.append(EncodedStringValue.concat(cc)); @@ -362,11 +379,12 @@ public class SmsMmsUtils { return null; } - String numbers[] = phoneNumbers.split(", "); + String[] numbers = phoneNumbers.split(", "); List uniqueNumbers = new ArrayList<>(); for (String number : numbers) { + // noinspection SuspiciousMethodCalls if (!uniqueNumbers.contains(number.trim())) { uniqueNumbers.add(new SMSHelper.Address(number.trim())); }