2
0
mirror of https://github.com/KDE/kdeconnect-android synced 2025-08-30 21:55:10 +00:00

[SMS App] Add support to extract attachments from network packet and send it MMS

## Summary

Add support for handling message request packets which have a list of attachments

## Test Plan

- Sent message with no attached file from desktop which was sent and received correctly
- Sent message with attached file from desktop which was sent and received correctly
- Sent message with two attached files which was sent and received correctly
This commit is contained in:
Aniket Kumar
2021-03-09 09:00:24 -08:00
committed by Simon Redman
parent 88ef4cbca1
commit 10925b9724
3 changed files with 75 additions and 98 deletions

View File

@@ -1,5 +1,6 @@
/*
* SPDX-FileCopyrightText: 2019 Simon Redman <simon@ergotech.com>
* SPDX-FileCopyrightText: 2021 Simon Redman <simon@ergotech.com>
* SPDX-FileCopyrightText: 2020 Aniket Kumar <anikketkumar786@gmail.com>
*
* 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<String> smsSelectionArgs = new ArrayList<String>(2);
List<String> smsSelectionArgs = new ArrayList<>(2);
smsSelectionArgs.add(startTimestamp.toString());
List<String> mmsSelectionArgs = new ArrayList<String>(2);
List<String> 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<String> 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<Attachment>
*
* The structure of the input is expected to be as follows:
* [
* {
* "fileName": <String> // Name of the file
* "base64EncodedFile": <String> // Base64 encoded file
* "mimeType": <String> // File type (eg: image/jpg, video/mp4 etc.)
* },
* ...
* ]
*/
public static @NonNull List<Attachment> jsonArrayToAttachmentsList(
@Nullable JSONArray jsonArray) {
if (jsonArray == null) {
return Collections.emptyList();
}
List<Attachment> 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;

View File

@@ -1,6 +1,7 @@
/*
* SPDX-FileCopyrightText: 2014 Albert Vaca Cintora <albertvaka@gmail.com>
* SPDX-FileCopyrightText: 2019 Simon Redman <simon@ergotech.com>
* SPDX-FileCopyrightText: 2021 Simon Redman <simon@ergotech.com>
* SPDX-FileCopyrightText: 2020 Aniket Kumar <anikketkumar786@gmail.com>
*
* 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
* <p>
* 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!
* <p>
*
* 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<SMSHelper.Attachment> 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:

View File

@@ -1,5 +1,6 @@
/*
* SPDX-FileCopyrightText: 2020 Aniket Kumar <anikketkumar786@gmail.com>
* SPDX-FileCopyrightText: 2021 Simon Redman <simon@ergotech.com>
*
* 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<SMSHelper.Address> addressList, int subID) {
public static void sendMessage(
Context context,
String textMessage,
@NonNull List<SMSHelper.Attachment> attachedFiles,
List<SMSHelper.Address> 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<SMSHelper.Address> 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<SMSHelper.Address> uniqueNumbers = new ArrayList<>();
for (String number : numbers) {
// noinspection SuspiciousMethodCalls
if (!uniqueNumbers.contains(number.trim())) {
uniqueNumbers.add(new SMSHelper.Address(number.trim()));
}