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:
committed by
Simon Redman
parent
88ef4cbca1
commit
10925b9724
@@ -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;
|
||||
|
||||
|
@@ -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:
|
||||
|
@@ -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()));
|
||||
}
|
||||
|
Reference in New Issue
Block a user