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 3bed9ca3..65ad543c 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 @@ -16,7 +16,6 @@ package com.klinker.android.send_message; -import android.app.Activity; import android.app.PendingIntent; import android.content.ContentResolver; import android.content.ContentValues; @@ -26,11 +25,8 @@ import android.database.Cursor; import android.graphics.Bitmap; import android.net.Uri; import android.os.Bundle; -import android.os.Looper; import android.telephony.SmsManager; -import android.telephony.SmsMessage; import android.text.TextUtils; -import android.widget.Toast; import com.android.mms.MmsConfig; import com.android.mms.dom.smil.parser.SmilXmlSerializer; import com.android.mms.util.DownloadManager; @@ -96,8 +92,6 @@ public class Transaction { 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; - /** * Sets context and initializes settings to default values * @@ -135,208 +129,9 @@ public class Transaction { public void sendNewMessage(Message message, long threadId) { this.saveMessage = message.getSave(); - // if message: - // 1) Has images attached - // or - // 1) is enabled to send long messages as mms - // 2) number of pages for that sms exceeds value stored in settings for when to send the mms by - // 3) prefer voice is disabled - // or - // 1) more than one address is attached - // 2) group messaging is enabled - // - // then, send as MMS, else send as Voice or SMS - if (checkMMS(message)) { - try { - Looper.prepare(); - } catch (Exception e) { - } - RateController.init(context); - DownloadManager.init(context); - sendMmsMessage(message.getText(), message.getAddresses(), message.getImages(), message.getImageNames(), message.getParts(), message.getSubject()); - } else { - sendSmsMessage(message.getText(), message.getAddresses(), threadId, message.getDelay()); - } - - } - - private void sendSmsMessage(String text, String[] addresses, long threadId, int delay) { - Log.v("send_transaction", "message text: " + text); - Uri messageUri; - int messageId = 0; - if (saveMessage) { - Log.v("send_transaction", "saving message"); - // add signature to original text to be saved in database (does not strip unicode for saving though) - if (!settings.getSignature().equals("")) { - text += "\n" + settings.getSignature(); - } - - // save the message for each of the addresses - for (String address : addresses) { - Calendar cal = Calendar.getInstance(); - ContentValues values = new ContentValues(); - values.put("address", address); - values.put("body", settings.getStripUnicode() ? StripAccents.stripAccents(text) : text); - values.put("date", cal.getTimeInMillis() + ""); - values.put("read", 1); - values.put("type", 4); - - // attempt to create correct thread id if one is not supplied - if (threadId == NO_THREAD_ID || addresses.length > 1) { - threadId = Utils.getOrCreateThreadId(context, address); - } - - Log.v("send_transaction", "saving message with thread id: " + threadId); - - values.put("thread_id", threadId); - messageUri = context.getContentResolver().insert(Uri.parse("content://sms/"), values); - - Log.v("send_transaction", "inserted to uri: " + messageUri); - - Cursor query = context.getContentResolver().query(messageUri, new String[]{"_id"}, null, null, null); - if (query != null && query.moveToFirst()) { - messageId = query.getInt(0); - query.close(); - } - - Log.v("send_transaction", "message id: " + messageId); - - // set up sent and delivered pending intents to be used with message request - Intent sentIntent = new Intent(SMS_SENT); - BroadcastUtils.addClassName(context, sentIntent, SMS_SENT); - - sentIntent.putExtra("message_uri", messageUri.toString()); - PendingIntent sentPI = PendingIntent.getBroadcast( - context, messageId, sentIntent, PendingIntent.FLAG_UPDATE_CURRENT); - - Intent deliveredIntent = new Intent(SMS_DELIVERED); - BroadcastUtils.addClassName(context, deliveredIntent, SMS_DELIVERED); - - deliveredIntent.putExtra("message_uri", messageUri.toString()); - PendingIntent deliveredPI = PendingIntent.getBroadcast( - context, messageId, deliveredIntent, PendingIntent.FLAG_UPDATE_CURRENT); - - ArrayList sPI = new ArrayList<>(); - ArrayList dPI = new ArrayList<>(); - - String body = text; - - // edit the body of the text if unicode needs to be stripped - if (settings.getStripUnicode()) { - body = StripAccents.stripAccents(body); - } - - if (!settings.getPreText().equals("")) { - body = settings.getPreText() + " " + body; - } - - SmsManager smsManager = SmsManagerFactory.INSTANCE.createSmsManager(settings); - Log.v("send_transaction", "found sms manager"); - - if (settings.getSplit()) { - Log.v("send_transaction", "splitting message"); - // figure out the length of supported message - int[] splitData = SmsMessage.calculateLength(body, false); - - // we take the current length + the remaining length to get the total number of characters - // that message set can support, and then divide by the number of message that will require - // to get the length supported by a single message - int length = (body.length() + splitData[2]) / splitData[0]; - Log.v("send_transaction", "length: " + length); - - boolean counter = false; - if (settings.getSplitCounter() && body.length() > length) { - counter = true; - length -= 6; - } - - // get the split messages - String[] textToSend = splitByLength(body, length, counter); - - // send each message part to each recipient attached to message - for (String aTextToSend : textToSend) { - ArrayList parts = smsManager.divideMessage(aTextToSend); - - for (int k = 0; k < parts.size(); k++) { - sPI.add(saveMessage ? sentPI : null); - dPI.add(settings.getDeliveryReports() && saveMessage ? deliveredPI : null); - } - - Log.v("send_transaction", "sending split message"); - sendDelayedSms(smsManager, address, parts, sPI, dPI, delay, messageUri); - } - } else { - Log.v("send_transaction", "sending without splitting"); - // send the message normally without forcing anything to be split - ArrayList parts = smsManager.divideMessage(body); - - for (int j = 0; j < parts.size(); j++) { - sPI.add(saveMessage ? sentPI : null); - dPI.add(settings.getDeliveryReports() && saveMessage ? deliveredPI : null); - } - - if (Utils.isDefaultSmsApp(context)) { - try { - Log.v("send_transaction", "sent message"); - sendDelayedSms(smsManager, address, parts, sPI, dPI, delay, messageUri); - } catch (Exception e) { - // whoops... - Log.v("send_transaction", "error sending message"); - Log.e(TAG, "exception thrown", e); - - try { - ((Activity) context).getWindow().getDecorView().findViewById(android.R.id.content).post(new Runnable() { - - @Override - public void run() { - Toast.makeText(context, "Message could not be sent", Toast.LENGTH_LONG).show(); - } - }); - } catch (Exception f) { - } - } - } else { - // not default app, so just fire it off right away for the hell of it - smsManager.sendMultipartTextMessage(address, null, parts, sPI, dPI); - } - } - } - } - } - - private void sendDelayedSms(final SmsManager smsManager, final String address, - final ArrayList parts, final ArrayList sPI, - final ArrayList dPI, final int delay, final Uri messageUri) { - new Thread(new Runnable() { - @Override - public void run() { - try { - Thread.sleep(delay); - } catch (Exception e) { - } - - if (checkIfMessageExistsAfterDelay(messageUri)) { - Log.v("send_transaction", "message sent after delay"); - try { - smsManager.sendMultipartTextMessage(address, null, parts, sPI, dPI); - } catch (Exception e) { - Log.e(TAG, "exception thrown", e); - } - } else { - Log.v("send_transaction", "message not sent after delay, no longer exists"); - } - } - }).start(); - } - - private boolean checkIfMessageExistsAfterDelay(Uri messageUti) { - Cursor query = context.getContentResolver().query(messageUti, new String[]{"_id"}, null, null, null); - if (query != null && query.moveToFirst()) { - query.close(); - return true; - } else { - return false; - } + RateController.init(context); + DownloadManager.init(context); + sendMmsMessage(message.getText(), message.getAddresses(), message.getImages(), message.getImageNames(), message.getParts(), message.getSubject()); } private void sendMmsMessage(String text, String[] addresses, Bitmap[] image, String[] imageNames, List parts, String subject) { @@ -651,30 +446,6 @@ public class Transaction { public byte[] bytes; } - // splits text and adds split counter when applicable - private String[] splitByLength(String s, int chunkSize, boolean counter) { - int arraySize = (int) Math.ceil((double) s.length() / chunkSize); - - String[] returnArray = new String[arraySize]; - - int index = 0; - for (int i = 0; i < s.length(); i = i + chunkSize) { - if (s.length() - i < chunkSize) { - returnArray[index++] = s.substring(i); - } else { - returnArray[index++] = s.substring(i, i + chunkSize); - } - } - - if (counter && returnArray.length > 1) { - for (int i = 0; i < returnArray.length; i++) { - returnArray[i] = "(" + (i + 1) + "/" + returnArray.length + ") " + returnArray[i]; - } - } - - return returnArray; - } - private static Uri insert(Context context, String[] to, MMSPart[] parts, String subject) { try { Uri destUri = Uri.parse("content://mms"); diff --git a/common/build.gradle b/common/build.gradle new file mode 100644 index 00000000..219e4fb0 --- /dev/null +++ b/common/build.gradle @@ -0,0 +1,25 @@ +apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' + +android { + compileSdkVersion 28 + + defaultConfig { + minSdkVersion 21 + targetSdkVersion 28 + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + + } +} + +dependencies { + implementation "androidx.core:core-ktx:$ktx_version" + implementation "com.jakewharton.timber:timber:$timber_version" + implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" + + implementation project(":android-smsmms") +} + +repositories { + mavenCentral() +} diff --git a/common/src/main/AndroidManifest.xml b/common/src/main/AndroidManifest.xml new file mode 100644 index 00000000..fdcd3214 --- /dev/null +++ b/common/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + diff --git a/data/src/main/java/com/moez/QKSMS/compat/TelephonyCompat.kt b/common/src/main/java/com/moez/QKSMS/compat/TelephonyCompat.kt similarity index 100% rename from data/src/main/java/com/moez/QKSMS/compat/TelephonyCompat.kt rename to common/src/main/java/com/moez/QKSMS/compat/TelephonyCompat.kt diff --git a/data/build.gradle b/data/build.gradle index 620d5e25..7f3263e9 100644 --- a/data/build.gradle +++ b/data/build.gradle @@ -55,6 +55,7 @@ dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" implementation "me.leolin:ShortcutBadger:1.1.21" implementation project(":android-smsmms") + implementation project(':common') implementation project(':domain') withAnalyticsImplementation "com.amplitude:android-sdk:2.16.0" withAnalyticsImplementation "com.mixpanel.android:mixpanel-android:5.2.1" diff --git a/data/src/main/java/com/moez/QKSMS/repository/MessageRepositoryImpl.kt b/data/src/main/java/com/moez/QKSMS/repository/MessageRepositoryImpl.kt index 122af2d7..9ee7f815 100644 --- a/data/src/main/java/com/moez/QKSMS/repository/MessageRepositoryImpl.kt +++ b/data/src/main/java/com/moez/QKSMS/repository/MessageRepositoryImpl.kt @@ -45,6 +45,7 @@ import com.moez.QKSMS.model.Conversation import com.moez.QKSMS.model.Message import com.moez.QKSMS.model.MmsPart import com.moez.QKSMS.util.Preferences +import com.moez.QKSMS.util.tryOrNull import io.realm.Case import io.realm.Realm import io.realm.RealmResults @@ -276,7 +277,7 @@ class MessageRepositoryImpl @Inject constructor( attachments .filter { attachment -> !attachment.isGif(context) } .mapNotNull { attachment -> attachment.getUri() } - .mapNotNull { uri -> imageRepository.loadImage(uri) } + .mapNotNull { uri -> tryOrNull { imageRepository.loadImage(uri) } } .also { totalImageBytes = it.sumBy { it.allocationByteCount } } .map { bitmap -> val byteRatio = bitmap.allocationByteCount / totalImageBytes.toFloat() diff --git a/data/src/main/java/com/moez/QKSMS/util/Utils.kt b/data/src/main/java/com/moez/QKSMS/util/Utils.kt index 5c133b21..cd42f58c 100644 --- a/data/src/main/java/com/moez/QKSMS/util/Utils.kt +++ b/data/src/main/java/com/moez/QKSMS/util/Utils.kt @@ -18,10 +18,13 @@ */ package com.moez.QKSMS.util +import timber.log.Timber + fun tryOrNull(body: () -> T?): T? { return try { body() } catch (e: Exception) { + Timber.w(e) null } } \ No newline at end of file diff --git a/domain/build.gradle b/domain/build.gradle index 5980de43..f12742a6 100644 --- a/domain/build.gradle +++ b/domain/build.gradle @@ -31,6 +31,7 @@ dependencies { implementation "androidx.core:core-ktx:$ktx_version" implementation "com.jakewharton.timber:timber:$timber_version" implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" + implementation project(":common") } repositories { diff --git a/domain/src/main/java/com/moez/QKSMS/interactor/SendScheduledMessage.kt b/domain/src/main/java/com/moez/QKSMS/interactor/SendScheduledMessage.kt new file mode 100644 index 00000000..7f7aeba2 --- /dev/null +++ b/domain/src/main/java/com/moez/QKSMS/interactor/SendScheduledMessage.kt @@ -0,0 +1,43 @@ +package com.moez.QKSMS.interactor + +import android.content.Context +import android.net.Uri +import com.moez.QKSMS.compat.TelephonyCompat +import com.moez.QKSMS.extensions.mapNotNull +import com.moez.QKSMS.model.Attachment +import com.moez.QKSMS.repository.ConversationRepository +import com.moez.QKSMS.repository.MessageRepository +import com.moez.QKSMS.repository.ScheduledMessageRepository +import io.reactivex.Flowable +import javax.inject.Inject + +class SendScheduledMessage @Inject constructor( + private val context: Context, + private val conversationRepo: ConversationRepository, + private val messageRepo: MessageRepository, + private val scheduledMessageRepo: ScheduledMessageRepository +) : Interactor() { + + override fun buildObservable(params: Long): Flowable<*> { + return Flowable.just(params) + .mapNotNull(scheduledMessageRepo::getScheduledMessage) + .map { message -> + TelephonyCompat.getOrCreateThreadId(context, message.recipients).also { threadId -> + if ((message.recipients.size == 1 || !message.sendAsGroup) && message.attachments.isEmpty()) { + message.recipients.forEach { address -> + messageRepo.sendSmsAndPersist(message.subId, threadId, address, message.body) + } + } else { + val attachments = message.attachments.mapNotNull(Uri::parse).map { Attachment(it) } + messageRepo.sendMms(message.subId, threadId, message.recipients, message.body, attachments) + } + } + } + .doOnNext { scheduledMessageRepo.deleteScheduledMessage(params) } + // If this was the first message sent in the conversation, the conversation might not exist yet + .doOnNext { conversationRepo.getOrCreateConversation(it) } + .doOnNext { conversationRepo.updateConversations(it) } + .doOnNext { conversationRepo.markUnarchived(it) } + } + +} \ No newline at end of file diff --git a/presentation/build.gradle b/presentation/build.gradle index 471fd91c..cf4152c1 100644 --- a/presentation/build.gradle +++ b/presentation/build.gradle @@ -156,6 +156,7 @@ dependencies { implementation "com.jakewharton.timber:timber:$timber_version" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation project(":android-smsmms") + implementation project(":common") implementation project(':data') implementation project(':domain') noAnalyticsDebug project(path: ':data', configuration: 'noAnalyticsDebug') diff --git a/presentation/src/main/java/com/moez/QKSMS/common/QKApplication.kt b/presentation/src/main/java/com/moez/QKSMS/common/QKApplication.kt index e6f30de2..49a4ab61 100644 --- a/presentation/src/main/java/com/moez/QKSMS/common/QKApplication.kt +++ b/presentation/src/main/java/com/moez/QKSMS/common/QKApplication.kt @@ -22,9 +22,9 @@ import android.app.Activity import android.app.Application import android.app.Service import android.content.BroadcastReceiver +import androidx.core.provider.FontRequest import androidx.emoji.text.EmojiCompat import androidx.emoji.text.FontRequestEmojiCompatConfig -import androidx.core.provider.FontRequest import com.akaita.java.rxjava2debug.RxJava2Debug import com.bugsnag.android.Bugsnag import com.bugsnag.android.Configuration @@ -69,7 +69,7 @@ class QKApplication : Application(), HasActivityInjector, HasBroadcastReceiverIn projectPackages = packages }) - RxJava2Debug.enableRxJava2AssemblyTracking(packages) + RxJava2Debug.enableRxJava2AssemblyTracking() Realm.init(this) Realm.setDefaultConfiguration(RealmConfiguration.Builder() diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/scheduled/ScheduledMessageAdapter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/scheduled/ScheduledMessageAdapter.kt index 1183883b..b81ca520 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/scheduled/ScheduledMessageAdapter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/scheduled/ScheduledMessageAdapter.kt @@ -7,6 +7,7 @@ import com.moez.QKSMS.R import com.moez.QKSMS.common.base.QkRealmAdapter import com.moez.QKSMS.common.base.QkViewHolder import com.moez.QKSMS.common.util.DateFormatter +import com.moez.QKSMS.interactor.SendScheduledMessage import com.moez.QKSMS.model.Contact import com.moez.QKSMS.model.PhoneNumber import com.moez.QKSMS.model.Recipient @@ -18,7 +19,8 @@ import javax.inject.Inject class ScheduledMessageAdapter @Inject constructor( private val contactRepo: ContactRepository, - private val dateFormatter: DateFormatter + private val dateFormatter: DateFormatter, + private val sendScheduledMessage: SendScheduledMessage ) : QkRealmAdapter() { private val contacts by lazy { contactRepo.getContacts() } @@ -32,6 +34,8 @@ class ScheduledMessageAdapter @Inject constructor( val message = getItem(position)!! val view = holder.itemView + view.setOnClickListener { sendScheduledMessage.execute(message.id) } + message.recipients.forEach { address -> if (!contactMap.containsKey(address)) { contactMap[address] = contacts diff --git a/settings.gradle b/settings.gradle index 5542e3f1..2f9fa8d7 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1 @@ -include ':presentation', ':android-smsmms', ':domain', ':data' +include ':presentation', ':android-smsmms', ':domain', ':data', ':common'