forked from upstream-mirrors/quik
Allow manual sending of scheduled messages
This commit is contained in:
@@ -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<PendingIntent> sPI = new ArrayList<>();
|
||||
ArrayList<PendingIntent> 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<String> 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<String> 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<String> parts, final ArrayList<PendingIntent> sPI,
|
||||
final ArrayList<PendingIntent> 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<Message.Part> 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");
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
<manifest package="com.moez.QKSMS"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android" />
|
||||
@@ -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"
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -18,10 +18,13 @@
|
||||
*/
|
||||
package com.moez.QKSMS.util
|
||||
|
||||
import timber.log.Timber
|
||||
|
||||
fun <T> tryOrNull(body: () -> T?): T? {
|
||||
return try {
|
||||
body()
|
||||
} catch (e: Exception) {
|
||||
Timber.w(e)
|
||||
null
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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<Long>() {
|
||||
|
||||
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) }
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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')
|
||||
|
||||
@@ -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()
|
||||
|
||||
+5
-1
@@ -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<ScheduledMessage>() {
|
||||
|
||||
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
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
include ':presentation', ':android-smsmms', ':domain', ':data'
|
||||
include ':presentation', ':android-smsmms', ':domain', ':data', ':common'
|
||||
|
||||
Reference in New Issue
Block a user