From 51377834adf693cdaa80a93fd77ae0c6015e30a5 Mon Sep 17 00:00:00 2001 From: St John Karp Date: Sun, 8 Apr 2018 09:10:17 -0700 Subject: [PATCH] Implement support for MMS messages Implemented support for MMS messages, including a variety of formats of image and video. Works by observing changes to content://mms-sms, parsing out the new MMS contents, uploading them as assets to the Matrix server, and posting a new image or video message to the room. --- app/src/main/AndroidManifest.xml | 1 + .../eu/droogers/smsmatrix/MMSMonitor.java | 302 ++++++++++++++++++ .../eu/droogers/smsmatrix/MainActivity.java | 1 + .../java/eu/droogers/smsmatrix/Matrix.java | 47 +++ .../eu/droogers/smsmatrix/MatrixService.java | 21 +- .../droogers/smsmatrix/ReceiverListener.java | 12 +- .../java/eu/droogers/smsmatrix/Utilities.java | 24 ++ build.gradle | 2 + 8 files changed, 398 insertions(+), 12 deletions(-) create mode 100644 app/src/main/java/eu/droogers/smsmatrix/MMSMonitor.java create mode 100644 app/src/main/java/eu/droogers/smsmatrix/Utilities.java diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 2c9166f..96900c5 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -3,6 +3,7 @@ xmlns:tools="http://schemas.android.com/tools" package="eu.droogers.smsmatrix"> + diff --git a/app/src/main/java/eu/droogers/smsmatrix/MMSMonitor.java b/app/src/main/java/eu/droogers/smsmatrix/MMSMonitor.java new file mode 100644 index 0000000..76c9764 --- /dev/null +++ b/app/src/main/java/eu/droogers/smsmatrix/MMSMonitor.java @@ -0,0 +1,302 @@ +package eu.droogers.smsmatrix; + +import android.content.ContentResolver; +import android.content.Context; +import android.database.ContentObserver; +import android.database.Cursor; +import android.net.Uri; +import android.os.Handler; +import android.os.Message; +import android.provider.Telephony; +import android.util.Log; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; + +public class MMSMonitor { + private MatrixService mainActivity; + private ContentResolver contentResolver = null; + private Context mainContext; + private Handler mmshandler = null; + private ContentObserver mmsObserver = null; + public boolean monitorStatus = false; + private int mmsCount = 0; + private static final String TAG = "MMSMonitor"; + + public MMSMonitor(final MatrixService mainActivity, final Context mainContext) { + this.mainActivity = mainActivity; + contentResolver = mainActivity.getContentResolver(); + this.mainContext = mainContext; + mmshandler = new MMSHandler(); + mmsObserver = new MMSObserver(mmshandler); + Log.i(TAG, "***** Start MMS Monitor *****"); + } + + + public void startMMSMonitoring() { + try { + monitorStatus = false; + if (!monitorStatus) { + contentResolver.registerContentObserver( + Uri.parse("content://mms"), + true, + mmsObserver + ); + + // Save the count of MMS messages on start-up. + Uri uriMMSURI = Uri.parse("content://mms-sms"); + Cursor mmsCur = mainActivity.getContentResolver().query( + uriMMSURI, + null, + Telephony.Mms.MESSAGE_BOX + " = " + Telephony.Mms.MESSAGE_BOX_INBOX, + null, + Telephony.Mms._ID + ); + if (mmsCur != null && mmsCur.getCount() > 0) { + mmsCount = mmsCur.getCount(); + Log.d(TAG, "Init MMSCount = " + mmsCount); + } + } + } catch (Exception e) { + Log.e(TAG, e.getMessage()); + } + } + + + public void stopMMSMonitoring() { + try { + monitorStatus = false; + if (!monitorStatus){ + contentResolver.unregisterContentObserver(mmsObserver); + } + } catch (Exception e) { + Log.e(TAG, e.getMessage()); + } + } + + + class MMSHandler extends Handler { + public void handleMessage(final Message msg) { + //Log.i(TAG, "Handler"); + } + } + + + class MMSObserver extends ContentObserver { + private Handler mms_handle = null; + public MMSObserver(final Handler mmshandle) { + super(mmshandle); + mms_handle = mmshandle; + } + + public void onChange(final boolean bSelfChange) { + super.onChange(bSelfChange); + Log.i(TAG, "Onchange"); + + try { + monitorStatus = true; + + // Send message to Activity. + Message msg = new Message(); + mms_handle.sendMessage(msg); + + // Get the MMS count. + Uri uriMMSURI = Uri.parse("content://mms/"); + Cursor mmsCur = mainActivity.getContentResolver().query( + uriMMSURI, + null, + Telephony.Mms.MESSAGE_BOX + " = " + Telephony.Mms.MESSAGE_BOX_INBOX, + null, + Telephony.Mms._ID + ); + + int currMMSCount = 0; + if (mmsCur != null && mmsCur.getCount() > 0) { + currMMSCount = mmsCur.getCount(); + } + + // Proceed if there is a new message. + if (currMMSCount > mmsCount) { + mmsCount = currMMSCount; + mmsCur.moveToLast(); + + // Get the message id and subject. + String subject = mmsCur.getString(mmsCur.getColumnIndex(Telephony.Mms.SUBJECT)); + int id = Integer.parseInt(mmsCur.getString(mmsCur.getColumnIndex(Telephony.Mms._ID))); + Log.d(TAG, "_id = " + id); + Log.d(TAG, "Subject = " + subject); + + byte[] mediaData = null; + String message = ""; + String address = ""; + String fileName = ""; + String fileType = ""; + String messageType = ""; + + // Get parts. + Uri uriMMSPart = Uri.parse("content://mms/part"); + Cursor curPart = mainActivity.getContentResolver().query( + uriMMSPart, + null, + Telephony.Mms.Part.MSG_ID + " = " + id, + null, + Telephony.Mms.Part._ID + ); + Log.d(TAG, "Parts records length = " + curPart.getCount()); + curPart.moveToLast(); + do { + String contentType = curPart.getString(curPart.getColumnIndex(Telephony.Mms.Part.CONTENT_TYPE)); + String partId = curPart.getString(curPart.getColumnIndex(Telephony.Mms.Part._ID)); + fileName = curPart.getString(curPart.getColumnIndex(Telephony.Mms.Part.NAME)); + Log.d(TAG, "partId = " + partId); + Log.d(TAG, "Part mime type = " + contentType); + + if (contentType.equalsIgnoreCase("text/plain")) + { + // Get the message. + + Log.i(TAG,"==== Get the message start ===="); + messageType = Matrix.MESSAGE_TYPE_TEXT; + byte[] messageData = readMMSPart(partId); + if (messageData != null && messageData.length > 0) { + message = new String(messageData); + } + + if (message.isEmpty()) { + Cursor curPart1 = mainActivity.getContentResolver().query( + uriMMSPart, + null, + Telephony.Mms.Part.MSG_ID + " = " + id + " and "+ Telephony.Mms.Part._ID + " = " + partId, + null, + Telephony.Mms.Part._ID + ); + for (int i = 0; i < curPart1.getColumnCount(); i++) + { + Log.d(TAG,"Column Name : " + curPart1.getColumnName(i)); + } + curPart1.moveToLast(); + message = curPart1.getString(13); + } + Log.d(TAG,"Txt Message = " + message); + } else if (isImageType(contentType) || isVideoType(contentType)) { + // Get the media. + + if (isImageType(contentType)) { + messageType = Matrix.MESSAGE_TYPE_IMAGE; + } else if (isVideoType(contentType)) { + messageType = Matrix.MESSAGE_TYPE_VIDEO; + } + Log.i(TAG, "==== Get the media start ===="); + fileType = contentType; + mediaData = readMMSPart(partId); + Log.i(TAG, "Media data length == " + mediaData.length); + } + } while (curPart.moveToPrevious()); + + + + // Get the sender's address. + Uri uriMMSAddr = Uri.parse("content://mms/" + id + "/addr"); + Cursor addrCur = mainActivity.getContentResolver().query( + uriMMSAddr, + null, + Telephony.Mms.Addr.TYPE + " = 137", // PduHeaders.FROM + null, + Telephony.Mms.Addr._ID + ); + if (addrCur != null) { + addrCur.moveToLast(); + do{ + Log.d(TAG, "addrCur records length = " + addrCur.getCount()); + if (addrCur.getCount() > 0) { + address = addrCur.getString(addrCur.getColumnIndex(Telephony.Mms.Addr.ADDRESS)); + } + Log.d(TAG, "address = " + address); + + if (!message.isEmpty()) { + Utilities.sendMatrix(mainActivity, message, address, messageType); + } + if (mediaData != null) { + Utilities.sendMatrix( + mainActivity, + mediaData, + address, + messageType, + fileName, + fileType + ); + } + } while (addrCur.moveToPrevious()); + } + } + + } catch (Exception e) { + Log.e(TAG, e.getMessage()); + } + } + } + + + private byte[] readMMSPart(String partId) { + byte[] partData = null; + Uri partURI = Uri.parse("content://mms/part/" + partId); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + InputStream is = null; + + try { + + Log.i(TAG,"Entered into readMMSPart try."); + ContentResolver mContentResolver = mainActivity.getContentResolver(); + is = mContentResolver.openInputStream(partURI); + + byte[] buffer = new byte[256]; + int len = is.read(buffer); + while (len >= 0) { + baos.write(buffer, 0, len); + len = is.read(buffer); + } + partData = baos.toByteArray(); + //Log.i(TAG, "Text Msg :: " + new String(partData)); + + } catch (IOException e) { + Log.e(TAG, "Exception == Failed to load part data"); + } finally { + if (is != null) { + try { + is.close(); + } catch (IOException e) { + Log.e(TAG, "Exception :: Failed to close stream"); + } + } + } + return partData; + } + + + private boolean isImageType(String mime) { + boolean result = false; + if (mime.equalsIgnoreCase("image/jpg") + || mime.equalsIgnoreCase("image/jpeg") + || mime.equalsIgnoreCase("image/png") + || mime.equalsIgnoreCase("image/gif") + || mime.equalsIgnoreCase("image/bmp")) { + result = true; + } + return result; + } + + + private boolean isVideoType(String mime) { + boolean result = false; + if (mime.equalsIgnoreCase("video/3gpp") + || mime.equalsIgnoreCase("video/3gpp2") + || mime.equalsIgnoreCase("video/avi") + || mime.equalsIgnoreCase("video/mp4") + || mime.equalsIgnoreCase("video/mpeg") + || mime.equalsIgnoreCase("video/webm")) { + result = true; + } + return result; + } +} \ No newline at end of file diff --git a/app/src/main/java/eu/droogers/smsmatrix/MainActivity.java b/app/src/main/java/eu/droogers/smsmatrix/MainActivity.java index 5923873..8f1ff99 100644 --- a/app/src/main/java/eu/droogers/smsmatrix/MainActivity.java +++ b/app/src/main/java/eu/droogers/smsmatrix/MainActivity.java @@ -72,6 +72,7 @@ public class MainActivity extends Activity { } private void checkPermissions() { + askPermission(Manifest.permission.READ_SMS); askPermission(Manifest.permission.SEND_SMS); askPermission(Manifest.permission.READ_PHONE_STATE); askPermission(Manifest.permission.READ_CONTACTS); diff --git a/app/src/main/java/eu/droogers/smsmatrix/Matrix.java b/app/src/main/java/eu/droogers/smsmatrix/Matrix.java index e7c252b..d06ba3b 100644 --- a/app/src/main/java/eu/droogers/smsmatrix/Matrix.java +++ b/app/src/main/java/eu/droogers/smsmatrix/Matrix.java @@ -22,12 +22,14 @@ import org.matrix.androidsdk.data.store.IMXStoreListener; import org.matrix.androidsdk.data.store.MXFileStore; import org.matrix.androidsdk.data.store.MXMemoryStore; import org.matrix.androidsdk.listeners.IMXEventListener; +import org.matrix.androidsdk.listeners.MXMediaUploadListener; import org.matrix.androidsdk.rest.callback.SimpleApiCallback; import org.matrix.androidsdk.rest.client.LoginRestClient; import org.matrix.androidsdk.rest.model.Event; import org.matrix.androidsdk.rest.model.Message; import org.matrix.androidsdk.rest.model.login.Credentials; +import java.io.ByteArrayInputStream; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; @@ -58,6 +60,12 @@ public class Matrix { private String realUserid; + // Message type constants. + public static final String MESSAGE_TYPE_TEXT = "m.text"; + public static final String MESSAGE_TYPE_IMAGE = "m.image"; + public static final String MESSAGE_TYPE_VIDEO = "m.video"; + public static final String MESSAGE_TYPE_NOTICE = "m.notice"; + public Matrix(final Context context, String url, String botUsername, String botPassword, String username, String device, String syncDelay, String syncTimeout) { this.context = context; hsConfig = new HomeServerConnectionConfig(Uri.parse(url)); @@ -172,6 +180,45 @@ public class Matrix { } } + public void sendFile( + final String phoneNumber, + final byte[] body, + final String type, + final String fileName, + final String contentType + ) { + String uploadID = String.valueOf(transaction); + transaction++; + session.getMediasCache().uploadContent( + new ByteArrayInputStream(body), + fileName, + contentType, + uploadID, + new MXMediaUploadListener() + { + @Override + public void onUploadComplete(final String uploadId, final String contentUri) { + Room room = getRoomByPhonenumber(phoneNumber); + JsonObject json = new JsonObject(); + json.addProperty("body", fileName); + json.addProperty("msgtype", type); + json.addProperty("url", contentUri); + JsonObject info = new JsonObject(); + info.addProperty("mimetype", contentType); + json.add("info", info); + session.getRoomsApiClient().sendEventToRoom( + String.valueOf(transaction), + room.getRoomId(), + "m.room.message", + json, + new SimpleApiCallback() + ); + transaction++; + } + } + ); + } + private void changeDisplayname(String roomId, String displayname) { Map params = new HashMap<>(); params.put("displayname", displayname); diff --git a/app/src/main/java/eu/droogers/smsmatrix/MatrixService.java b/app/src/main/java/eu/droogers/smsmatrix/MatrixService.java index c8443c0..f08aa0c 100644 --- a/app/src/main/java/eu/droogers/smsmatrix/MatrixService.java +++ b/app/src/main/java/eu/droogers/smsmatrix/MatrixService.java @@ -23,6 +23,7 @@ public class MatrixService extends Service { private String hsUrl; private String syncDelay; private String syncTimeout; + private MMSMonitor mms; @Override public void onCreate() { @@ -49,11 +50,25 @@ public class MatrixService extends Service { Log.e(TAG, "onStartCommand: Service"); String phone = intent.getStringExtra("SendSms_phone"); - String body = intent.getStringExtra("SendSms_body"); String type = intent.getStringExtra("SendSms_type"); if (phone != null) { - mx.sendMessage(phone, body, type); + if (type.equals(Matrix.MESSAGE_TYPE_TEXT)) + { + String body = intent.getStringExtra("SendSms_body"); + mx.sendMessage(phone, body, type); + } else { + byte[] body = intent.getByteArrayExtra("SendSms_body"); + String fileName = intent.getStringExtra("SendSms_fileName"); + String contentType = intent.getStringExtra("SendSms_contentType"); + mx.sendFile(phone, body, type, fileName, contentType); + } } + + if (this.mms == null) { + this.mms = new MMSMonitor(this , getApplicationContext()); + this.mms.startMMSMonitoring(); + } + return START_NOT_STICKY; } @@ -61,6 +76,8 @@ public class MatrixService extends Service { @Override public void onDestroy() { mx.destroy(); + this.mms.stopMMSMonitoring(); + this.mms = null; super.onDestroy(); } diff --git a/app/src/main/java/eu/droogers/smsmatrix/ReceiverListener.java b/app/src/main/java/eu/droogers/smsmatrix/ReceiverListener.java index caada80..db19e36 100644 --- a/app/src/main/java/eu/droogers/smsmatrix/ReceiverListener.java +++ b/app/src/main/java/eu/droogers/smsmatrix/ReceiverListener.java @@ -36,7 +36,7 @@ public class ReceiverListener extends BroadcastReceiver { msgs[i] = SmsMessage.createFromPdu((byte[])pdus[i]); msg_from = msgs[i].getOriginatingAddress(); String msgBody = msgs[i].getMessageBody(); - sendMatrix(context, msgBody, msg_from, "m.text"); + Utilities.sendMatrix(context, msgBody, msg_from, Matrix.MESSAGE_TYPE_TEXT); } }catch(Exception e){ Log.d("Exception caught",e.getMessage()); @@ -59,14 +59,6 @@ public class ReceiverListener extends BroadcastReceiver { body += " is calling"; break; } - sendMatrix(context, body, cal_from, "m.notice"); - } - - private void sendMatrix(Context context, String body, String phone, String type) { - Intent intent = new Intent(context, MatrixService.class); - intent.putExtra("SendSms_phone", phone); - intent.putExtra("SendSms_body", body); - intent.putExtra("SendSms_type", type); - context.startService(intent); + Utilities.sendMatrix(context, body, cal_from, Matrix.MESSAGE_TYPE_NOTICE); } } diff --git a/app/src/main/java/eu/droogers/smsmatrix/Utilities.java b/app/src/main/java/eu/droogers/smsmatrix/Utilities.java new file mode 100644 index 0000000..cd2b27e --- /dev/null +++ b/app/src/main/java/eu/droogers/smsmatrix/Utilities.java @@ -0,0 +1,24 @@ +package eu.droogers.smsmatrix; + +import android.content.Context; +import android.content.Intent; + +public class Utilities { + public static void sendMatrix(Context context, String body, String phone, String type) { + Intent intent = new Intent(context, MatrixService.class); + intent.putExtra("SendSms_phone", phone); + intent.putExtra("SendSms_body", body); + intent.putExtra("SendSms_type", type); + context.startService(intent); + } + + public static void sendMatrix(Context context, byte[] body, String phone, String type, String fileName, String contentType) { + Intent intent = new Intent(context, MatrixService.class); + intent.putExtra("SendSms_phone", phone); + intent.putExtra("SendSms_body", body); + intent.putExtra("SendSms_type", type); + intent.putExtra("SendSms_fileName", fileName); + intent.putExtra("SendSms_contentType", contentType); + context.startService(intent); + } +} diff --git a/build.gradle b/build.gradle index a47fa4b..7e01a0a 100644 --- a/build.gradle +++ b/build.gradle @@ -3,6 +3,7 @@ buildscript { repositories { jcenter() + google() } dependencies { classpath 'com.android.tools.build:gradle:3.0.1' @@ -15,6 +16,7 @@ buildscript { allprojects { repositories { jcenter() + google() } }