Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
b36e9b0
Merge commit '19c0d48f468dcc1746330dbc05d9ed7dc240244f'
eriksson Mar 15, 2023
089b8ae
Merge commit 'ca94a6a1e121f1ce27dbcae5be3c99492e54610f'
eriksson Mar 16, 2023
36ff818
remove unused type MessageAPISentPayloadV1
eriksson Mar 16, 2023
805f46d
allow defining which SIM card to use to send a message. use the defau…
eriksson Mar 16, 2023
9f04169
update web ui - add components to select the SIM card to use when sen…
eriksson Mar 16, 2023
95f3f52
Allow to connect to HTTP endpoints if not in the production environment.
eriksson Mar 18, 2023
8df8cf3
Allow selecting which SIM to use to send messages #130
eriksson Mar 18, 2023
ccc81a7
Merge branch 'handle_local_event_queue'
eriksson Mar 19, 2023
f1085d5
use SIM1 and SIM2 instead of ISMS and ISMS2
eriksson Mar 19, 2023
feee539
refactor: empty string check
eriksson Mar 19, 2023
6974f1a
Merge branch 'handle_local_event_queue'
eriksson Mar 19, 2023
4ce0e86
Merge branch 'handle_local_event_queue' into message_sim_card
eriksson Mar 19, 2023
af88444
Merge branch 'message_sim_card'
eriksson Mar 19, 2023
61c0958
Merge commit '48f9b0a4862e1699079666181991d7c21b53f89a'
eriksson Mar 21, 2023
dc30e78
Revert "Allow to connect to HTTP endpoints if not in the production e…
eriksson Mar 21, 2023
ac21f1b
refactor: access Android permissions using Manifest.permission.
eriksson Mar 21, 2023
6f9e4e6
bug fix: remove CopyHeaders (should be used to get response headers) …
eriksson Mar 23, 2023
c0c02b5
add field IsDualSIM to the Phone model.
eriksson Mar 23, 2023
d68cb68
Merge commit '0f3bd6348dad06fe72a743c0c8ad9cc36a2ce26a' into message_…
eriksson Mar 23, 2023
3073e2b
refactor: use SIM1 and SIM2 instead of ISMS and ISMS2 [web]
eriksson Mar 24, 2023
7a8d3f3
chore: responsive SIM card select component.
eriksson Mar 26, 2023
ef30031
chore: only show SIM card selector if the active phone has two active…
eriksson Mar 26, 2023
4992445
improve SIM labels
eriksson Mar 26, 2023
e6a1720
add a broadcast receiver to update phone is_dual_sim when the phone s…
eriksson Mar 26, 2023
e407bed
chore: hide the SIM card selector if the phone only has one active SI…
eriksson Mar 26, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,15 @@
</intent-filter>
</receiver>

<receiver android:enabled="true"
android:name=".receivers.SimChangeReceiver"
android:exported="true"
android:permission="android.permission.READ_PHONE_STATE">
<intent-filter>
<action android:name="android.intent.action.SIM_STATE_CHANGED"/>
</intent-filter>
</receiver>


<meta-data
android:name="com.google.firebase.messaging.default_notification_channel_id"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ class MyFirebaseMessagingService : FirebaseMessagingService() {

if (Settings.isLoggedIn(this)) {
Timber.d("updating phone with new fcm token")
HttpSmsApiService.create(this).updatePhone(Settings.getOwnerOrDefault(this), token)
HttpSmsApiService.create(this).updatePhone(Settings.getOwnerOrDefault(this), token, SmsManagerService.isDualSIM(this))
}

}
Expand Down Expand Up @@ -131,7 +131,7 @@ class MyFirebaseMessagingService : FirebaseMessagingService() {
sentIntents.add(createPendingIntent(id, SmsManagerService.sentAction(id)))
deliveredIntents.add(createPendingIntent(id, SmsManagerService.deliveredAction(id)))
}
SmsManagerService().sendMultipartMessage(this.applicationContext,message.contact, parts, sentIntents, deliveredIntents)
SmsManagerService().sendMultipartMessage(this.applicationContext,message.contact, parts, message.sim, sentIntents, deliveredIntents)
Timber.d("sent SMS for message with ID [${message.id}] in [${parts.size}] parts")
Result.success()
} catch (e: Exception) {
Expand Down Expand Up @@ -185,7 +185,7 @@ class MyFirebaseMessagingService : FirebaseMessagingService() {
private fun sendMessage(message: Message, sentIntent: PendingIntent, deliveredIntent: PendingIntent) {
Timber.d("sending SMS for message with ID [${message.id}]")
try {
SmsManagerService().sendTextMessage(this.applicationContext,message.contact, message.content, sentIntent, deliveredIntent)
SmsManagerService().sendTextMessage(this.applicationContext,message.contact, message.content, message.sim, sentIntent, deliveredIntent)
} catch (e: Exception) {
Timber.e(e)
Timber.d("could not send SMS for message with ID [${message.id}]")
Expand Down
5 changes: 3 additions & 2 deletions android/app/src/main/java/com/httpsms/HttpSmsApiService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -156,11 +156,12 @@ class HttpSmsApiService(private val apiKey: String, private val baseURL: URI) {
}


fun updatePhone(phoneNumber: String, fcmToken: String): Boolean {
fun updatePhone(phoneNumber: String, fcmToken: String, isDualSIM: Boolean): Boolean {
val body = """
{
"fcm_token": "$fcmToken",
"phone_number": "$phoneNumber"
"phone_number": "$phoneNumber",
"is_dual_sim": $isDualSIM
}
""".trimIndent()

Expand Down
13 changes: 9 additions & 4 deletions android/app/src/main/java/com/httpsms/MainActivity.kt
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
package com.httpsms

import android.Manifest
import android.Manifest.permission.READ_PHONE_NUMBERS
import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle
Expand All @@ -26,6 +26,7 @@ import com.google.android.material.button.MaterialButton
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.progressindicator.LinearProgressIndicator
import com.google.android.material.switchmaterial.SwitchMaterial
import com.httpsms.receivers.SimChangeReceiver
import com.httpsms.services.StickyNotificationService
import com.httpsms.worker.HeartbeatWorker
import okhttp3.internal.format
Expand Down Expand Up @@ -64,6 +65,7 @@ class MainActivity : AppCompatActivity() {
setLastHeartbeatTimestamp(this)
setVersion()
setHeartbeatListener(this)
registerReceivers()
}

override fun onResume() {
Expand Down Expand Up @@ -147,7 +149,7 @@ class MainActivity : AppCompatActivity() {
}

Thread {
val updated = HttpSmsApiService.create(context).updatePhone(Settings.getOwnerOrDefault(context), Settings.getFcmToken(context) ?: "")
val updated = HttpSmsApiService.create(context).updatePhone(Settings.getOwnerOrDefault(context), Settings.getFcmToken(context) ?: "", SmsManagerService.isDualSIM(this))
if (updated) {
Settings.setFcmTokenLastUpdateTimestampAsync(context, currentTimeStamp)
Timber.i("fcm token uploaded successfully")
Expand All @@ -173,6 +175,9 @@ class MainActivity : AppCompatActivity() {
findViewById<MaterialButton>(R.id.mainLogoutButton).setOnClickListener { onLogoutClick() }
}

private fun registerReceivers() {
registerReceiver(SimChangeReceiver(), IntentFilter("android.intent.action.SIM_STATE_CHANGED"))
}
private fun onLogoutClick() {
Timber.d("logout button clicked")
MaterialAlertDialogBuilder(this)
Expand Down Expand Up @@ -219,7 +224,7 @@ class MainActivity : AppCompatActivity() {
Manifest.permission.SEND_SMS
) == PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(
context,
READ_PHONE_NUMBERS
Manifest.permission.READ_PHONE_NUMBERS
) == PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(
context,
Manifest.permission.RECEIVE_SMS
Expand Down Expand Up @@ -274,7 +279,7 @@ class MainActivity : AppCompatActivity() {
var permissions = arrayOf(
Manifest.permission.SEND_SMS,
Manifest.permission.RECEIVE_SMS,
READ_PHONE_NUMBERS,
Manifest.permission.READ_PHONE_NUMBERS,
Manifest.permission.READ_SMS,
Manifest.permission.READ_PHONE_STATE
)
Expand Down
1 change: 1 addition & 0 deletions android/app/src/main/java/com/httpsms/Models.kt
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ data class ResponseMessage (
data class Message (
val contact: String,
val content: String,
val sim: String,

@Json(name = "created_at")
val createdAt: String,
Expand Down
41 changes: 34 additions & 7 deletions android/app/src/main/java/com/httpsms/SmsManagerService.kt
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package com.httpsms

import android.annotation.SuppressLint
import android.app.PendingIntent
import android.content.Context
import android.os.Build
import android.telephony.SmsManager
import android.telephony.SubscriptionManager


class SmsManagerService {
Expand All @@ -18,26 +20,51 @@ class SmsManagerService {
fun deliveredAction(messageID: String): String {
return "$ACTION_SMS_DELIVERED.$messageID"
}

@SuppressLint("MissingPermission")
fun isDualSIM(context: Context) : Boolean {
val localSubscriptionManager: SubscriptionManager = if (Build.VERSION.SDK_INT < 31) {
SubscriptionManager.from(context)
} else {
context.getSystemService(SubscriptionManager::class.java)
}
return localSubscriptionManager.activeSubscriptionInfoList.size > 1
}
}

fun messageParts(context: Context, content: String): ArrayList<String> {
return getSmsManager(context).divideMessage(content)
}

fun sendMultipartMessage(context: Context, contact: String, parts: ArrayList<String>, sendIntents: ArrayList<PendingIntent>, deliveryIntents: ArrayList<PendingIntent>) {
getSmsManager(context).sendMultipartTextMessage(contact, null, parts, sendIntents, deliveryIntents)
fun sendMultipartMessage(context: Context, contact: String, parts: ArrayList<String>, sim: String, sendIntents: ArrayList<PendingIntent>, deliveryIntents: ArrayList<PendingIntent>) {
getSmsManager(context, sim).sendMultipartTextMessage(contact, null, parts, sendIntents, deliveryIntents)
}

fun sendTextMessage(context: Context, contact: String, content: String, sentIntent:PendingIntent, deliveryIntent: PendingIntent) {
getSmsManager(context).sendTextMessage(contact, null, content, sentIntent, deliveryIntent)
fun sendTextMessage(context: Context, contact: String, content: String, sim: String, sentIntent:PendingIntent, deliveryIntent: PendingIntent) {
getSmsManager(context, sim).sendTextMessage(contact, null, content, sentIntent, deliveryIntent)
}

@Suppress("DEPRECATION")
private fun getSmsManager(context: Context): SmsManager {
@SuppressLint("MissingPermission")
private fun getSmsManager(context: Context, sim: String = "DEFAULT"): SmsManager {
val localSubscriptionManager: SubscriptionManager = if (Build.VERSION.SDK_INT < 31) {
SubscriptionManager.from(context)
} else {
context.getSystemService(SubscriptionManager::class.java)
}

val subscriptionId = if (sim == "SIM1" && localSubscriptionManager.activeSubscriptionInfoList.size > 0) {
localSubscriptionManager.activeSubscriptionInfoList[0].subscriptionId
} else if (sim == "SIM2" && localSubscriptionManager.activeSubscriptionInfoList.size > 1) {
localSubscriptionManager.activeSubscriptionInfoList[1].subscriptionId
} else{
SubscriptionManager.getDefaultSmsSubscriptionId()
}

return if (Build.VERSION.SDK_INT < 31) {
SmsManager.getDefault()
SmsManager.getSmsManagerForSubscriptionId(subscriptionId)
} else {
context.getSystemService(SmsManager::class.java)
context.getSystemService(SmsManager::class.java).createForSubscriptionId(subscriptionId)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.httpsms.receivers

import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import com.httpsms.HttpSmsApiService
import com.httpsms.Settings
import com.httpsms.SmsManagerService
import timber.log.Timber


class SimChangeReceiver : BroadcastReceiver() {
private var lastDualSIMState: Boolean = false
override fun onReceive(context: Context, intent: Intent) {
Timber.d("SIM state changed")

Thread {
val currentTimeStamp = System.currentTimeMillis()
val isDualSIM = SmsManagerService.isDualSIM(context)
if (isDualSIM == lastDualSIMState) {
return@Thread
}
val updated = HttpSmsApiService.create(context).updatePhone(
Settings.getOwnerOrDefault(context),
Settings.getFcmToken(context) ?: "",
isDualSIM
)

if (updated) {
lastDualSIMState = isDualSIM
Settings.setFcmTokenLastUpdateTimestampAsync(context, currentTimeStamp)
Timber.i("fcm token uploaded successfully")
return@Thread
}

Timber.e("could not update fcm token")
}.start()
}
}
16 changes: 16 additions & 0 deletions api/pkg/entities/message.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,17 @@ const (
MessageEventNameFailed = MessageEventName("FAILED")
)

type SIM string

const (
// SIM_1 use the SIM card in slot 1 to send the message
SIM_1 = SIM("SIM1")
// SIM_2 use the SIM card in slot 2 to send the message
SIM_2 = SIM("SIM2")
// SIM_DEFAULT use the SIM card configured as default communication card to send the message
SIM_DEFAULT = SIM("DEFAULT")
)

// Message represents a message sent between 2 phone numbers
type Message struct {
ID uuid.UUID `json:"id" gorm:"primaryKey;type:uuid;" example:"32343a19-da5e-4b1b-a767-3298a73703cb"`
Expand All @@ -69,6 +80,11 @@ type Message struct {
Content string `json:"content" example:"This is a sample text message"`
Type MessageType `json:"type" example:"mobile-terminated"`
Status MessageStatus `json:"status" gorm:"index:idx_messages_status" example:"pending"`
// SIM is the type of event
// * ISMS: use the SIM card in slot 1
// * ISMS2: use the SIM card in slot 2
// * DEFAULT: used the default communication SIM card
SIM SIM `json:"sim" example:"DEFAULT"`

// SendDuration is the number of nanoseconds from when the request was received until when the mobile phone send the message
SendDuration *int64 `json:"send_time" example:"133414"`
Expand Down
1 change: 1 addition & 0 deletions api/pkg/entities/phone.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ type Phone struct {
FcmToken *string `json:"fcm_token" example:"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzd....."`
PhoneNumber string `json:"phone_number" example:"+18005550199"`
MessagesPerMinute uint `json:"messages_per_minute" example:"1"`
IsDualSIM bool `json:"is_dual_sim" example:"false"`

// MaxSendAttempts determines how many times to retry sending an SMS message
MaxSendAttempts uint `json:"max_send_attempts" example:"1"`
Expand Down
12 changes: 1 addition & 11 deletions api/pkg/events/message_api_sent_event.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,5 @@ type MessageAPISentPayload struct {
Contact string `json:"contact"`
RequestReceivedAt time.Time `json:"request_received_at"`
Content string `json:"content"`
}

// MessageAPISentPayloadV1 is the old event
type MessageAPISentPayloadV1 struct {
MessageID uuid.UUID `json:"id"`
UserID entities.UserID `json:"userID"`
Owner string `json:"owner"`
MaxSendAttempts uint `json:"max_send_attempts"`
Contact string `json:"contact"`
RequestReceivedAt time.Time `json:"request_received_at"`
Content string `json:"content"`
SIM entities.SIM `json:"sim"`
}
1 change: 1 addition & 0 deletions api/pkg/events/message_notification_scheduled_event.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ type MessageNotificationScheduledPayload struct {
Owner string `json:"owner"`
Contact string `json:"contact"`
Content string `json:"content"`
SIM entities.SIM `json:"sim"`
UserID entities.UserID `json:"user_id"`
PhoneID uuid.UUID `json:"phone_id"`
ScheduledAt time.Time `json:"scheduled_at"`
Expand Down
1 change: 1 addition & 0 deletions api/pkg/events/message_phone_delivered_event.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,5 @@ type MessagePhoneDeliveredPayload struct {
UserID entities.UserID `json:"user_id"`
Timestamp time.Time `json:"timestamp"`
Content string `json:"content"`
SIM entities.SIM `json:"sim"`
}
1 change: 1 addition & 0 deletions api/pkg/events/message_phone_received_event.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,5 @@ type MessagePhoneReceivedPayload struct {
Contact string `json:"contact"`
Timestamp time.Time `json:"timestamp"`
Content string `json:"content"`
SIM entities.SIM `json:"sim"`
}
1 change: 1 addition & 0 deletions api/pkg/events/message_phone_sending_event.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,5 @@ type MessagePhoneSendingPayload struct {
Owner string `json:"owner"`
Contact string `json:"contact"`
Content string `json:"content"`
SIM entities.SIM `json:"sim"`
}
1 change: 1 addition & 0 deletions api/pkg/events/message_phone_sent_event.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,5 @@ type MessagePhoneSentPayload struct {
Contact string `json:"contact"`
Timestamp time.Time `json:"timestamp"`
Content string `json:"content"`
SIM entities.SIM `json:"sim"`
}
1 change: 1 addition & 0 deletions api/pkg/events/message_send_expired_event.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,5 @@ type MessageSendExpiredPayload struct {
UserID entities.UserID `json:"user_id"`
Timestamp time.Time `json:"timestamp"`
Content string `json:"content"`
SIM entities.SIM `json:"sim"`
}
1 change: 1 addition & 0 deletions api/pkg/events/message_send_failed_event.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,5 @@ type MessageSendFailedPayload struct {
Contact string `json:"contact"`
Timestamp time.Time `json:"timestamp"`
Content string `json:"content"`
SIM entities.SIM `json:"sim"`
}
1 change: 1 addition & 0 deletions api/pkg/events/message_send_retry_event.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,5 @@ type MessageSendRetryPayload struct {
UserID entities.UserID `json:"user_id"`
Timestamp time.Time `json:"timestamp"`
Content string `json:"content"`
SIM entities.SIM `json:"sim"`
}
1 change: 1 addition & 0 deletions api/pkg/events/phone_deleted_event.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,5 @@ type PhoneDeletedPayload struct {
UserID entities.UserID `json:"user_id"`
Timestamp time.Time `json:"timestamp"`
Owner string `json:"owner"`
IsDualSIM bool `json:"is_dual_sim"`
}
1 change: 1 addition & 0 deletions api/pkg/events/phone_updated_event.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,5 @@ type PhoneUpdatedPayload struct {
UserID entities.UserID `json:"user_id"`
Timestamp time.Time `json:"timestamp"`
Owner string `json:"owner"`
IsDualSIM bool `json:"is_dual_sim"`
}
2 changes: 2 additions & 0 deletions api/pkg/listeners/phone_notification_listener.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ func (listener *PhoneNotificationListener) onMessageAPISent(ctx context.Context,
Owner: payload.Owner,
Contact: payload.Contact,
Content: payload.Content,
SIM: payload.SIM,
Source: event.Source(),
MessageID: payload.MessageID,
}
Expand Down Expand Up @@ -83,6 +84,7 @@ func (listener *PhoneNotificationListener) onMessageSendRetry(ctx context.Contex
Owner: payload.Owner,
Contact: payload.Contact,
Content: payload.Content,
SIM: payload.SIM,
Source: event.Source(),
MessageID: payload.MessageID,
}
Expand Down
Loading