Skip to content

Commit

Permalink
Merge pull request #353 from SuhasDissa/refactor
Browse files Browse the repository at this point in the history
refactor: make sms list live update
  • Loading branch information
SuhasDissa authored Jan 11, 2024
2 parents 3f67123 + 5b8c786 commit 48425ad
Show file tree
Hide file tree
Showing 14 changed files with 196 additions and 207 deletions.
22 changes: 20 additions & 2 deletions app/src/main/java/com/bnyro/contacts/App.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@ package com.bnyro.contacts
import android.app.Application
import com.bnyro.contacts.db.DatabaseHolder
import com.bnyro.contacts.repo.DeviceContactsRepository
import com.bnyro.contacts.repo.DeviceSmsRepo
import com.bnyro.contacts.repo.LocalContactsRepository
import com.bnyro.contacts.repo.LocalSmsRepo
import com.bnyro.contacts.repo.SmsRepository
import com.bnyro.contacts.util.NotificationHelper
import com.bnyro.contacts.util.Preferences
import com.bnyro.contacts.util.ShortcutHelper
import com.bnyro.contacts.util.SmsUtil
import com.bnyro.contacts.workers.BackupWorker

class App : Application() {
Expand All @@ -17,6 +19,22 @@ class App : Application() {
val localContactsRepository by lazy {
LocalContactsRepository(this)
}
val localSmsRepo by lazy {
LocalSmsRepo()
}
val deviceSmsRepo by lazy {
DeviceSmsRepo()
}

lateinit var smsRepo: SmsRepository

fun initSmsRepo() {
smsRepo = if (Preferences.getBoolean(Preferences.storeSmsLocallyKey, false)) {
LocalSmsRepo()
} else {
DeviceSmsRepo()
}
}

override fun onCreate() {
super.onCreate()
Expand All @@ -31,6 +49,6 @@ class App : Application() {

NotificationHelper.createChannels(this)

SmsUtil.initSmsRepo()
initSmsRepo()
}
}
3 changes: 2 additions & 1 deletion app/src/main/java/com/bnyro/contacts/db/dao/LocalSmsDao.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@ import androidx.room.Dao
import androidx.room.Insert
import androidx.room.Query
import com.bnyro.contacts.db.obj.SmsData
import kotlinx.coroutines.flow.Flow

@Dao
interface LocalSmsDao {
@Query("SELECT * from localSms")
suspend fun getAll(): List<SmsData>
fun getStream(): Flow<List<SmsData>>

@Query("DELETE FROM localSms WHERE id = :id")
suspend fun deleteSms(id: Long)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@ import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import androidx.core.app.NotificationManagerCompat
import com.bnyro.contacts.ui.activities.MainActivity
import com.bnyro.contacts.util.SmsUtil
import com.bnyro.contacts.App
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
Expand All @@ -19,10 +18,7 @@ class DeleteSmsReceiver : BroadcastReceiver() {
NotificationManagerCompat.from(context).cancel(notificationId)

CoroutineScope(Dispatchers.IO).launch {
MainActivity.smsModel?.deleteSms(context, smsId, threadId) ?: run {
// if the UI is not currently opened, only delete the SMS and don't touch the UI
SmsUtil.deleteMessage(context, smsId)
}
(context.applicationContext as App).smsRepo.deleteSms(context, smsId)
}
}
}
18 changes: 10 additions & 8 deletions app/src/main/java/com/bnyro/contacts/receivers/SmsReceiver.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,14 @@ import android.provider.Telephony
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import androidx.core.app.RemoteInput
import com.bnyro.contacts.App
import com.bnyro.contacts.R
import com.bnyro.contacts.db.obj.SmsData
import com.bnyro.contacts.enums.IntentActionType
import com.bnyro.contacts.ui.activities.MainActivity
import com.bnyro.contacts.util.IntentHelper
import com.bnyro.contacts.util.NotificationHelper
import com.bnyro.contacts.util.NotificationHelper.MESSAGES_CHANNEL_ID
import com.bnyro.contacts.util.PermissionHelper
import com.bnyro.contacts.util.SmsUtil
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking

Expand All @@ -33,16 +32,19 @@ class SmsReceiver : BroadcastReceiver() {
val body = message.displayMessageBody
val timestamp = message.timestampMillis
val threadId =
runBlocking(Dispatchers.IO) { SmsUtil.getOrCreateThreadId(context, address) }
runBlocking(Dispatchers.IO) {
(context.applicationContext as App).smsRepo.getOrCreateThreadId(
context,
address
)
}
val bareSmsData =
SmsData(-1, address, body, timestamp, threadId, Telephony.Sms.MESSAGE_TYPE_INBOX)
SmsData(0, address, body, timestamp, threadId, Telephony.Sms.MESSAGE_TYPE_INBOX)

createNotification(context, notificationId, bareSmsData)
val smsData = runBlocking(Dispatchers.IO) {
SmsUtil.persistMessage(context, bareSmsData)
runBlocking(Dispatchers.IO) {
(context.applicationContext as App).smsRepo.persistSms(context, bareSmsData)
}

MainActivity.smsModel?.addSmsToList(smsData)
}
}

Expand Down
70 changes: 48 additions & 22 deletions app/src/main/java/com/bnyro/contacts/repo/DeviceSmsRepo.kt
Original file line number Diff line number Diff line change
@@ -1,31 +1,56 @@
package com.bnyro.contacts.repo

import android.Manifest
import android.annotation.SuppressLint
import android.content.ContentResolver
import android.content.ContentValues
import android.content.Context
import android.database.ContentObserver
import android.net.Uri
import android.os.Build
import android.provider.Telephony
import android.util.Log
import androidx.annotation.RequiresPermission
import com.bnyro.contacts.db.obj.SmsData
import com.bnyro.contacts.ext.intValue
import com.bnyro.contacts.ext.longValue
import com.bnyro.contacts.ext.stringValue
import com.bnyro.contacts.util.PermissionHelper
import com.bnyro.contacts.util.SmsUtil
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.withContext
import kotlin.random.Random

class DeviceSmsRepo : SmsRepository {
private val contentUri = Telephony.Sms.CONTENT_URI

override suspend fun getSmsList(context: Context): List<SmsData> {
@SuppressLint("MissingPermission")
override fun getSmsStream(context: Context): Flow<List<SmsData>> {
return if (PermissionHelper.hasPermission(context, Manifest.permission.READ_SMS)) {
context.contentResolver.observe(contentUri).map {
getSmsList(context)
}
} else {
emptyFlow()
}
}

@RequiresPermission(Manifest.permission.READ_SMS)
private suspend fun getSmsList(context: Context): List<SmsData> = withContext(Dispatchers.IO) {
val simSlotMap = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) {
SmsUtil.getSubscriptions(context)
.associateBy({ it.subscriptionId }, { it.simSlotIndex })
} else {
null
}
context.contentResolver
.query(contentUri, null, null, null, null)
.query(contentUri, null, null, null, "date ASC")
?.use { cursor ->
if (!cursor.moveToFirst()) return emptyList()
if (!cursor.moveToFirst()) return@withContext emptyList()

val smsList = mutableListOf<SmsData>()
do {
Expand All @@ -46,13 +71,13 @@ class DeviceSmsRepo : SmsRepository {
smsList.add(SmsData(id, address, body, timestamp, threadId, type, simIndex))
} while (cursor.moveToNext())

return smsList
return@withContext smsList
}

return emptyList()
return@withContext emptyList()
}

override suspend fun persistSms(context: Context, smsData: SmsData): SmsData {
override suspend fun persistSms(context: Context, smsData: SmsData) {
val values = ContentValues()
values.put(Telephony.Sms.ADDRESS, smsData.address)
values.put(Telephony.Sms.BODY, smsData.body)
Expand All @@ -61,20 +86,7 @@ class DeviceSmsRepo : SmsRepository {
values.put(Telephony.Sms.TYPE, smsData.type)
values.put(Telephony.Sms.THREAD_ID, smsData.threadId)

val messageUri = context.contentResolver.insert(contentUri, values) ?: return smsData

Log.v("send_transaction", "inserted to uri: $messageUri")

context.contentResolver.query(
messageUri,
arrayOf(Telephony.Sms._ID),
null,
null,
null
)?.use {
if (it.moveToFirst()) smsData.id = it.longValue(Telephony.Sms._ID)!!
}
return smsData
context.contentResolver.insert(contentUri, values)
}

override suspend fun deleteSms(context: Context, id: Long) {
Expand All @@ -92,7 +104,7 @@ class DeviceSmsRepo : SmsRepository {
)?.use { cursor ->
while (cursor.moveToNext()) {
val id = cursor.longValue(Telephony.Sms._ID) ?: continue
SmsUtil.deleteMessage(context, id)
deleteSms(context, id)
}
}
}
Expand All @@ -115,3 +127,17 @@ class DeviceSmsRepo : SmsRepository {
return Random.nextLong()
}
}

fun ContentResolver.observe(uri: Uri) = callbackFlow {
val observer = object : ContentObserver(null) {
override fun onChange(selfChange: Boolean) {
trySend(selfChange)
}
}
registerContentObserver(uri, true, observer)
// trigger first.
trySend(false)
awaitClose {
unregisterContentObserver(observer)
}
}
11 changes: 5 additions & 6 deletions app/src/main/java/com/bnyro/contacts/repo/LocalSmsRepo.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,15 @@ package com.bnyro.contacts.repo
import android.content.Context
import com.bnyro.contacts.db.DatabaseHolder
import com.bnyro.contacts.db.obj.SmsData
import kotlinx.coroutines.flow.Flow
import kotlin.random.Random

class LocalSmsRepo : SmsRepository {
override suspend fun getSmsList(context: Context): List<SmsData> {
return DatabaseHolder.Db.localSmsDao().getAll()
}
override fun getSmsStream(context: Context): Flow<List<SmsData>> =
DatabaseHolder.Db.localSmsDao().getStream()

override suspend fun persistSms(context: Context, smsData: SmsData): SmsData {
val id = DatabaseHolder.Db.localSmsDao().createSms(smsData)
return smsData.copy(id = id)
override suspend fun persistSms(context: Context, smsData: SmsData) {
DatabaseHolder.Db.localSmsDao().createSms(smsData)
}

override suspend fun getOrCreateThreadId(context: Context, address: String): Long {
Expand Down
5 changes: 3 additions & 2 deletions app/src/main/java/com/bnyro/contacts/repo/SmsRepository.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@ package com.bnyro.contacts.repo

import android.content.Context
import com.bnyro.contacts.db.obj.SmsData
import kotlinx.coroutines.flow.Flow

interface SmsRepository {
suspend fun getSmsList(context: Context): List<SmsData>
suspend fun persistSms(context: Context, smsData: SmsData): SmsData
fun getSmsStream(context: Context): Flow<List<SmsData>>
suspend fun persistSms(context: Context, smsData: SmsData)
suspend fun getOrCreateThreadId(context: Context, address: String): Long
suspend fun deleteSms(context: Context, id: Long)
suspend fun deleteThread(context: Context, threadId: Long)
Expand Down
56 changes: 52 additions & 4 deletions app/src/main/java/com/bnyro/contacts/ui/activities/BaseActivity.kt
Original file line number Diff line number Diff line change
@@ -1,31 +1,79 @@
package com.bnyro.contacts.ui.activities

import android.Manifest
import android.app.role.RoleManager
import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.Bundle
import android.provider.Telephony
import androidx.activity.ComponentActivity
import androidx.activity.viewModels
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.get
import com.bnyro.contacts.ui.models.ContactsModel
import com.bnyro.contacts.ui.models.SmsModel
import com.bnyro.contacts.ui.models.ThemeModel
import com.bnyro.contacts.util.NotificationHelper
import com.bnyro.contacts.util.PermissionHelper

abstract class BaseActivity : ComponentActivity() {
lateinit var themeModel: ThemeModel
val contactsModel by viewModels<ContactsModel> {
ContactsModel.Factory
}
val smsModel by viewModels<SmsModel> { SmsModel.Factory }

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val requiredPermissions =
smsPermissions + contactPermissions + NotificationHelper.notificationPermissions

requestDefaultSMSApp(this)
PermissionHelper.checkPermissions(
this,
arrayOf(
Manifest.permission.WRITE_CONTACTS,
Manifest.permission.READ_CONTACTS
)
requiredPermissions
)
val viewModelProvider = ViewModelProvider(this)
themeModel = viewModelProvider.get()
}

private fun requestDefaultSMSApp(context: Context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
val roleManager = context.getSystemService(RoleManager::class.java)
if (roleManager!!.isRoleAvailable(RoleManager.ROLE_SMS)) {
if (roleManager.isRoleHeld(RoleManager.ROLE_SMS)) {
getSmsPermissions(context)
} else {
val intent = roleManager.createRequestRoleIntent(RoleManager.ROLE_SMS)
context.startActivity(intent)
}
}
} else {
if (Telephony.Sms.getDefaultSmsPackage(context) == context.packageName) {
getSmsPermissions(context)
} else {
val intent = Intent(Telephony.Sms.Intents.ACTION_CHANGE_DEFAULT)
intent.putExtra(Telephony.Sms.Intents.EXTRA_PACKAGE_NAME, context.packageName)
context.startActivity(intent)
}
}
}

private fun getSmsPermissions(context: Context) {
PermissionHelper.checkPermissions(context, smsPermissions)
}

companion object {
private val smsPermissions = arrayOf(
Manifest.permission.SEND_SMS,
Manifest.permission.READ_SMS,
Manifest.permission.RECEIVE_SMS,
Manifest.permission.READ_PHONE_STATE
)
private val contactPermissions = arrayOf(
Manifest.permission.WRITE_CONTACTS,
Manifest.permission.READ_CONTACTS
)
}
}
Loading

0 comments on commit 48425ad

Please sign in to comment.