Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: make sms list live update #353

Merged
merged 1 commit into from
Jan 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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
Loading