From 45f1cb1bc877960f657b8be4a3d25e970b0c9efe Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Sun, 14 Jan 2018 18:59:53 -0500 Subject: [PATCH] Sync MMS on sending and on failure --- .../android/send_message/MmsSentReceiver.java | 56 ------- .../android/send_message/MmsSentReceiver.kt | 78 ++++++++++ .../android/send_message/Transaction.java | 141 +++++++++--------- qksms/src/main/AndroidManifest.xml | 7 +- qksms/src/main/java/common/di/AppComponent.kt | 1 + .../src/main/java/common/util/SyncManager.kt | 29 +++- qksms/src/main/java/data/model/Message.kt | 2 +- .../{MarkMmsSent.kt => SyncMessage.kt} | 2 +- .../feature/compose/MessagesAdapter.kt | 1 - .../presentation/receiver/MmsSentReceiver.kt | 6 +- .../receiver/MmsUpdatedReceiver.kt | 45 ++++++ 11 files changed, 229 insertions(+), 139 deletions(-) delete mode 100755 android-smsmms/src/main/java/com/klinker/android/send_message/MmsSentReceiver.java create mode 100755 android-smsmms/src/main/java/com/klinker/android/send_message/MmsSentReceiver.kt rename qksms/src/main/java/interactor/{MarkMmsSent.kt => SyncMessage.kt} (90%) create mode 100644 qksms/src/main/java/presentation/receiver/MmsUpdatedReceiver.kt diff --git a/android-smsmms/src/main/java/com/klinker/android/send_message/MmsSentReceiver.java b/android-smsmms/src/main/java/com/klinker/android/send_message/MmsSentReceiver.java deleted file mode 100755 index 041f151a..00000000 --- a/android-smsmms/src/main/java/com/klinker/android/send_message/MmsSentReceiver.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (C) 2015 Jacob Klinker - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.klinker.android.send_message; - -import android.content.BroadcastReceiver; -import android.content.ContentValues; -import android.content.Context; -import android.content.Intent; -import android.net.Uri; -import android.provider.Telephony; - -import com.google.android.mms.util_alt.SqliteWrapper; -import com.klinker.android.logger.Log; - -import java.io.File; - -public class MmsSentReceiver extends BroadcastReceiver { - - private static final String TAG = "MmsSentReceiver"; - - public static final String MMS_SENT = "com.klinker.android.messaging.MMS_SENT"; - public static final String EXTRA_CONTENT_URI = "content_uri"; - public static final String EXTRA_FILE_PATH = "file_path"; - - @Override - public void onReceive(Context context, Intent intent) { - Log.v(TAG, "MMS has finished sending, marking it as so in the database"); - - Uri uri = Uri.parse(intent.getStringExtra(EXTRA_CONTENT_URI)); - Log.v(TAG, uri.toString()); - - ContentValues values = new ContentValues(1); - values.put(Telephony.Mms.MESSAGE_BOX, Telephony.Mms.MESSAGE_BOX_SENT); - SqliteWrapper.update(context, context.getContentResolver(), uri, values, - null, null); - - String filePath = intent.getStringExtra(EXTRA_FILE_PATH); - Log.v(TAG, filePath); - new File(filePath).delete(); - } - -} diff --git a/android-smsmms/src/main/java/com/klinker/android/send_message/MmsSentReceiver.kt b/android-smsmms/src/main/java/com/klinker/android/send_message/MmsSentReceiver.kt new file mode 100755 index 00000000..f789d517 --- /dev/null +++ b/android-smsmms/src/main/java/com/klinker/android/send_message/MmsSentReceiver.kt @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2015 Jacob Klinker + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.klinker.android.send_message + +import android.app.Activity +import android.content.* +import android.net.Uri +import android.provider.Telephony +import android.util.Log +import com.google.android.mms.MmsException +import com.google.android.mms.util_alt.SqliteWrapper +import java.io.File + +open class MmsSentReceiver : BroadcastReceiver() { + + companion object { + private val TAG = "MmsSentReceiver" + + const val MMS_SENT = "com.klinker.android.messaging.MMS_SENT" + const val EXTRA_CONTENT_URI = "content_uri" + const val EXTRA_FILE_PATH = "file_path" + } + + override fun onReceive(context: Context, intent: Intent) { + Log.v(TAG, "MMS sending result: " + resultCode) + + val uri = Uri.parse(intent.getStringExtra(EXTRA_CONTENT_URI)) + Log.v(TAG, uri.toString()) + + if (resultCode == Activity.RESULT_OK) { + Log.v(TAG, "MMS has finished sending, marking it as so in the database") + val values = ContentValues(1) + values.put(Telephony.Mms.MESSAGE_BOX, Telephony.Mms.MESSAGE_BOX_SENT) + SqliteWrapper.update(context, context.contentResolver, uri, values, null, null) + } else { + Log.v(TAG, "MMS has failed to send, marking it as so in the database") + try { + val messageId = ContentUris.parseId(uri) + + val values = ContentValues(1) + values.put(Telephony.Mms.MESSAGE_BOX, Telephony.Mms.MESSAGE_BOX_FAILED) + SqliteWrapper.update(context, context.contentResolver, Telephony.Mms.CONTENT_URI, values, + "${Telephony.Mms._ID} = ?", arrayOf(messageId.toString())) + + // TODO this query isn't able to find any results + // Need to figure out why the message isn't appearing in the PendingMessages Uri, + // so that we can properly assign the error type + val errorTypeValues = ContentValues(1) + errorTypeValues.put(Telephony.MmsSms.PendingMessages.ERROR_TYPE, Telephony.MmsSms.ERR_TYPE_GENERIC_PERMANENT) + SqliteWrapper.update(context, context.contentResolver, Telephony.MmsSms.PendingMessages.CONTENT_URI, + errorTypeValues, "${Telephony.MmsSms.PendingMessages.MSG_ID} = ?", arrayOf(messageId.toString())) + + } catch (e: MmsException) { + e.printStackTrace() + } + + } + + val filePath = intent.getStringExtra(EXTRA_FILE_PATH) + Log.v(TAG, filePath) + File(filePath).delete() + } + +} diff --git a/android-smsmms/src/main/java/com/klinker/android/send_message/Transaction.java b/android-smsmms/src/main/java/com/klinker/android/send_message/Transaction.java index 773499ee..2b9ab84b 100755 --- a/android-smsmms/src/main/java/com/klinker/android/send_message/Transaction.java +++ b/android-smsmms/src/main/java/com/klinker/android/send_message/Transaction.java @@ -81,12 +81,17 @@ public class Transaction { public String SMS_DELIVERED = ".SMS_DELIVERED"; public static String NOTIFY_SMS_FAILURE = ".NOTIFY_SMS_FAILURE"; + public static final String MMS_UPDATED = "com.moez.QKSMS.MMS_UPDATED"; public static final String MMS_ERROR = "com.klinker.android.send_message.MMS_ERROR"; public static final String REFRESH = "com.klinker.android.send_message.REFRESH"; public static final String MMS_PROGRESS = "com.klinker.android.send_message.MMS_PROGRESS"; public static final String NOTIFY_OF_DELIVERY = "com.klinker.android.send_message.NOTIFY_DELIVERY"; public static final String NOTIFY_OF_MMS = "com.klinker.android.messaging.NEW_MMS_DOWNLOADED"; + + public static final long DEFAULT_EXPIRY_TIME = 7 * 24 * 60 * 60; + public static final int DEFAULT_PRIORITY = PduHeaders.PRIORITY_NORMAL; + public static final long NO_THREAD_ID = 0; /** @@ -371,7 +376,71 @@ public class Transaction { } Log.v(TAG, "sending mms"); - sendMmsThroughSystem(context, subject, data, addresses); + try { + final String fileName = "send." + String.valueOf(Math.abs(new Random().nextLong())) + ".dat"; + File mSendFile = new File(context.getCacheDir(), fileName); + + SendReq sendReq = buildPdu(context, addresses, subject, data); + PduPersister persister = PduPersister.getPduPersister(context); + Uri messageUri = persister.persist(sendReq, Uri.parse("content://mms/outbox"), true, true, null); + + Intent sentIntent = new Intent(MmsSentReceiver.MMS_SENT); + BroadcastUtils.addClassName(context, sentIntent, MmsSentReceiver.MMS_SENT); + + sentIntent.putExtra(MmsSentReceiver.EXTRA_CONTENT_URI, messageUri.toString()); + sentIntent.putExtra(MmsSentReceiver.EXTRA_FILE_PATH, mSendFile.getPath()); + final PendingIntent sentPI = PendingIntent.getBroadcast( + context, 0, sentIntent, PendingIntent.FLAG_CANCEL_CURRENT); + + Intent updatedIntent = new Intent(MMS_UPDATED); + updatedIntent.putExtra("uri", messageUri.toString()); + BroadcastUtils.addClassName(context, updatedIntent, MMS_UPDATED); + context.sendBroadcast(updatedIntent); + + Uri writerUri = (new Uri.Builder()) + .authority(context.getPackageName() + ".MmsFileProvider") + .path(fileName) + .scheme(ContentResolver.SCHEME_CONTENT) + .build(); + FileOutputStream writer = null; + Uri contentUri = null; + try { + writer = new FileOutputStream(mSendFile); + writer.write(new PduComposer(context, sendReq).make()); + contentUri = writerUri; + } catch (final IOException e) { + Log.e(TAG, "Error writing send file", e); + } finally { + if (writer != null) { + try { + writer.close(); + } catch (IOException e) { + } + } + } + + Bundle configOverrides = new Bundle(); + configOverrides.putBoolean(SmsManager.MMS_CONFIG_GROUP_MMS_ENABLED, true); + String httpParams = MmsConfig.getHttpParams(); + if (!TextUtils.isEmpty(httpParams)) { + configOverrides.putString(SmsManager.MMS_CONFIG_HTTP_PARAMS, httpParams); + } + configOverrides.putInt(SmsManager.MMS_CONFIG_MAX_MESSAGE_SIZE, MmsConfig.getMaxMessageSize()); + + if (contentUri != null) { + SmsManagerFactory.INSTANCE.createSmsManager(settings).sendMultimediaMessage(context, + contentUri, null, configOverrides, sentPI); + } else { + Log.e(TAG, "Error writing sending Mms"); + try { + sentPI.send(SmsManager.MMS_ERROR_IO_ERROR); + } catch (PendingIntent.CanceledException ex) { + Log.e(TAG, "Mms pending intent cancelled?", ex); + } + } + } catch (Exception e) { + Log.e(TAG, "error using system sending method", e); + } } public static MessageInfo getBytes(Context context, boolean saveMessage, String[] recipients, MMSPart[] parts, @@ -494,75 +563,7 @@ public class Transaction { return info; } - public static final long DEFAULT_EXPIRY_TIME = 7 * 24 * 60 * 60; - public static final int DEFAULT_PRIORITY = PduHeaders.PRIORITY_NORMAL; - - private static void sendMmsThroughSystem(Context context, String subject, List parts, String[] addresses) { - try { - final String fileName = "send." + String.valueOf(Math.abs(new Random().nextLong())) + ".dat"; - File mSendFile = new File(context.getCacheDir(), fileName); - - SendReq sendReq = buildPdu(context, addresses, subject, parts); - PduPersister persister = PduPersister.getPduPersister(context); - Uri messageUri = persister.persist(sendReq, Uri.parse("content://mms/outbox"), - true, true, null); - - Intent intent = new Intent(MmsSentReceiver.MMS_SENT); - BroadcastUtils.addClassName(context, intent, MmsSentReceiver.MMS_SENT); - - intent.putExtra(MmsSentReceiver.EXTRA_CONTENT_URI, messageUri.toString()); - intent.putExtra(MmsSentReceiver.EXTRA_FILE_PATH, mSendFile.getPath()); - final PendingIntent pendingIntent = PendingIntent.getBroadcast( - context, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT); - - Uri writerUri = (new Uri.Builder()) - .authority(context.getPackageName() + ".MmsFileProvider") - .path(fileName) - .scheme(ContentResolver.SCHEME_CONTENT) - .build(); - FileOutputStream writer = null; - Uri contentUri = null; - try { - writer = new FileOutputStream(mSendFile); - writer.write(new PduComposer(context, sendReq).make()); - contentUri = writerUri; - } catch (final IOException e) { - Log.e(TAG, "Error writing send file", e); - } finally { - if (writer != null) { - try { - writer.close(); - } catch (IOException e) { - } - } - } - - Bundle configOverrides = new Bundle(); - configOverrides.putBoolean(SmsManager.MMS_CONFIG_GROUP_MMS_ENABLED, true); - String httpParams = MmsConfig.getHttpParams(); - if (!TextUtils.isEmpty(httpParams)) { - configOverrides.putString(SmsManager.MMS_CONFIG_HTTP_PARAMS, httpParams); - } - configOverrides.putInt(SmsManager.MMS_CONFIG_MAX_MESSAGE_SIZE, MmsConfig.getMaxMessageSize()); - - if (contentUri != null) { - SmsManagerFactory.INSTANCE.createSmsManager(settings).sendMultimediaMessage(context, - contentUri, null, configOverrides, pendingIntent); - } else { - Log.e(TAG, "Error writing sending Mms"); - try { - pendingIntent.send(SmsManager.MMS_ERROR_IO_ERROR); - } catch (PendingIntent.CanceledException ex) { - Log.e(TAG, "Mms pending intent cancelled?", ex); - } - } - } catch (Exception e) { - Log.e(TAG, "error using system sending method", e); - } - } - - private static SendReq buildPdu(Context context, String[] recipients, String subject, - List parts) { + private static SendReq buildPdu(Context context, String[] recipients, String subject, List parts) { final SendReq req = new SendReq(); // From, per spec final String lineNumber = Utils.getMyPhoneNumber(context); diff --git a/qksms/src/main/AndroidManifest.xml b/qksms/src/main/AndroidManifest.xml index eb64171c..56bafb4b 100644 --- a/qksms/src/main/AndroidManifest.xml +++ b/qksms/src/main/AndroidManifest.xml @@ -83,7 +83,10 @@ + android:taskAffinity="com.klinker.android.messaging.MMS_SENT" /> + @@ -93,7 +96,7 @@ - + { - val id = uri.lastPathSegment - val isMms = uri.toString().contains("mms") + val id = ContentUris.parseId(uri) + val type = when { + uri.toString().contains("mms") -> "mms" + uri.toString().contains("sms") -> "sms" + else -> return Flowable.empty() + } + + // Check if the message already exists, so we can reuse the id + val existingId = Realm.getDefaultInstance().use { realm -> + realm.refresh() + realm.where(Message::class.java) + .equalTo("type", type) + .equalTo("contentId", id) + .findFirst() + ?.id + } // The uri might be something like content://mms/inbox/id // The box might change though, so we should just use the mms/id uri - val stableUri = when(isMms) { - true -> Uri.withAppendedPath(Telephony.Mms.CONTENT_URI, id) - false -> Uri.withAppendedPath(Telephony.Sms.CONTENT_URI, id) + val stableUri = when (type) { + "mms" -> ContentUris.withAppendedId(Telephony.Mms.CONTENT_URI, id) + else -> ContentUris.withAppendedId(Telephony.Sms.CONTENT_URI, id) } val cursor = context.contentResolver.query(stableUri, null, null, null, null) @@ -123,6 +138,10 @@ class SyncManager @Inject constructor( // Map the cursor to a message return cursor.asFlowable() .map { cursorToMessage.map(Pair(it, columnsMap)) } + .map { message -> + existingId?.let { message.id = it } + message + } .doOnNext { message -> messageRepo.getOrCreateConversation(message.threadId) } .doOnNext { message -> message.insertOrUpdate() } } diff --git a/qksms/src/main/java/data/model/Message.kt b/qksms/src/main/java/data/model/Message.kt index 101a3554..e312134e 100644 --- a/qksms/src/main/java/data/model/Message.kt +++ b/qksms/src/main/java/data/model/Message.kt @@ -139,7 +139,7 @@ open class Message : RealmObject() { } fun isFailedMessage(): Boolean { - val isFailedMms = isMms() && errorType >= MmsSms.ERR_TYPE_GENERIC_PERMANENT + val isFailedMms = isMms() && (errorType >= MmsSms.ERR_TYPE_GENERIC_PERMANENT || boxId == Mms.MESSAGE_BOX_FAILED) val isFailedSms = isSms() && boxId == Sms.MESSAGE_TYPE_FAILED return isFailedMms || isFailedSms } diff --git a/qksms/src/main/java/interactor/MarkMmsSent.kt b/qksms/src/main/java/interactor/SyncMessage.kt similarity index 90% rename from qksms/src/main/java/interactor/MarkMmsSent.kt rename to qksms/src/main/java/interactor/SyncMessage.kt index bf675839..b1395f69 100644 --- a/qksms/src/main/java/interactor/MarkMmsSent.kt +++ b/qksms/src/main/java/interactor/SyncMessage.kt @@ -24,7 +24,7 @@ import data.model.Message import io.reactivex.Flowable import javax.inject.Inject -class MarkMmsSent @Inject constructor(private val syncManager: SyncManager) : Interactor() { +class SyncMessage @Inject constructor(private val syncManager: SyncManager): Interactor() { override fun buildObservable(params: Uri): Flowable { return Flowable.just(params) diff --git a/qksms/src/main/java/presentation/feature/compose/MessagesAdapter.kt b/qksms/src/main/java/presentation/feature/compose/MessagesAdapter.kt index ba6b7199..10fd32ec 100644 --- a/qksms/src/main/java/presentation/feature/compose/MessagesAdapter.kt +++ b/qksms/src/main/java/presentation/feature/compose/MessagesAdapter.kt @@ -134,7 +134,6 @@ class MessagesAdapter @Inject constructor( bindMmsPreview(view, position) bindStatus(view, position) bindGrouping(view, position) - view.messageBackground.invalidateOutline() } private fun bindMmsPreview(view: View, position: Int) { diff --git a/qksms/src/main/java/presentation/receiver/MmsSentReceiver.kt b/qksms/src/main/java/presentation/receiver/MmsSentReceiver.kt index 4a109772..71a90fdf 100644 --- a/qksms/src/main/java/presentation/receiver/MmsSentReceiver.kt +++ b/qksms/src/main/java/presentation/receiver/MmsSentReceiver.kt @@ -23,12 +23,12 @@ import android.content.Intent import android.net.Uri import com.klinker.android.send_message.MmsSentReceiver import common.di.appComponent -import interactor.MarkMmsSent +import interactor.SyncMessage import javax.inject.Inject class MmsSentReceiver : MmsSentReceiver() { - @Inject lateinit var markMmsSent: MarkMmsSent + @Inject lateinit var syncMessage: SyncMessage override fun onReceive(context: Context, intent: Intent) { super.onReceive(context, intent) @@ -36,7 +36,7 @@ class MmsSentReceiver : MmsSentReceiver() { Uri.parse(intent.getStringExtra("content_uri"))?.let { uri -> val pendingResult = goAsync() - markMmsSent.execute(uri) { pendingResult.finish() } + syncMessage.execute(uri) { pendingResult.finish() } } } diff --git a/qksms/src/main/java/presentation/receiver/MmsUpdatedReceiver.kt b/qksms/src/main/java/presentation/receiver/MmsUpdatedReceiver.kt new file mode 100644 index 00000000..b27143df --- /dev/null +++ b/qksms/src/main/java/presentation/receiver/MmsUpdatedReceiver.kt @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2017 Moez Bhatti + * + * This file is part of QKSMS. + * + * QKSMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * QKSMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with QKSMS. If not, see . + */ +package presentation.receiver + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.net.Uri +import common.di.appComponent +import interactor.SyncMessage +import javax.inject.Inject + +class MmsUpdatedReceiver : BroadcastReceiver() { + + companion object { + const val URI = "uri" + } + + @Inject lateinit var syncMessage: SyncMessage + + override fun onReceive(context: Context, intent: Intent) { + appComponent.inject(this) + intent.getStringExtra(URI)?.let { uriString -> + val pendingResult = goAsync() + syncMessage.execute(Uri.parse(uriString)) { pendingResult.finish() } + } + } + +} \ No newline at end of file