Added a contacts cache at app level + get notified on low memory and clear glide cache when it happens
This commit is contained in:
parent
37cb30047e
commit
536e78e98e
12 changed files with 71 additions and 19 deletions
|
@ -20,7 +20,9 @@
|
||||||
package org.linphone.activities.main
|
package org.linphone.activities.main
|
||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
|
import android.content.ComponentCallbacks2
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.content.res.Configuration
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
|
@ -58,6 +60,7 @@ import org.linphone.databinding.MainActivityBinding
|
||||||
import org.linphone.utils.AppUtils
|
import org.linphone.utils.AppUtils
|
||||||
import org.linphone.utils.Event
|
import org.linphone.utils.Event
|
||||||
import org.linphone.utils.FileUtils
|
import org.linphone.utils.FileUtils
|
||||||
|
import org.linphone.utils.GlideApp
|
||||||
|
|
||||||
class MainActivity : GenericActivity(), SnackBarActivity, NavController.OnDestinationChangedListener {
|
class MainActivity : GenericActivity(), SnackBarActivity, NavController.OnDestinationChangedListener {
|
||||||
private lateinit var binding: MainActivityBinding
|
private lateinit var binding: MainActivityBinding
|
||||||
|
@ -84,6 +87,19 @@ class MainActivity : GenericActivity(), SnackBarActivity, NavController.OnDestin
|
||||||
private var initPosY = 0f
|
private var initPosY = 0f
|
||||||
private var overlay: View? = null
|
private var overlay: View? = null
|
||||||
|
|
||||||
|
private val componentCallbacks = object : ComponentCallbacks2 {
|
||||||
|
override fun onConfigurationChanged(newConfig: Configuration) { }
|
||||||
|
|
||||||
|
override fun onLowMemory() {
|
||||||
|
Log.w("[Main Activity] onLowMemory !")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onTrimMemory(level: Int) {
|
||||||
|
Log.w("[Main Activity] onTrimMemory called with level $level !")
|
||||||
|
GlideApp.get(this@MainActivity).clearMemory()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onLayoutChanges(foldingFeature: FoldingFeature?) {
|
override fun onLayoutChanges(foldingFeature: FoldingFeature?) {
|
||||||
sharedViewModel.layoutChangedEvent.value = Event(true)
|
sharedViewModel.layoutChangedEvent.value = Event(true)
|
||||||
}
|
}
|
||||||
|
@ -155,6 +171,7 @@ class MainActivity : GenericActivity(), SnackBarActivity, NavController.OnDestin
|
||||||
|
|
||||||
override fun onPostCreate(savedInstanceState: Bundle?) {
|
override fun onPostCreate(savedInstanceState: Bundle?) {
|
||||||
super.onPostCreate(savedInstanceState)
|
super.onPostCreate(savedInstanceState)
|
||||||
|
registerComponentCallbacks(componentCallbacks)
|
||||||
findNavController(R.id.nav_host_fragment).addOnDestinationChangedListener(this)
|
findNavController(R.id.nav_host_fragment).addOnDestinationChangedListener(this)
|
||||||
|
|
||||||
if (intent != null) handleIntentParams(intent)
|
if (intent != null) handleIntentParams(intent)
|
||||||
|
@ -162,6 +179,7 @@ class MainActivity : GenericActivity(), SnackBarActivity, NavController.OnDestin
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
findNavController(R.id.nav_host_fragment).removeOnDestinationChangedListener(this)
|
findNavController(R.id.nav_host_fragment).removeOnDestinationChangedListener(this)
|
||||||
|
unregisterComponentCallbacks(componentCallbacks)
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -120,7 +120,7 @@ class MasterChatRoomsFragment : MasterFragment<ChatRoomMasterFragmentBinding, Ch
|
||||||
}
|
}
|
||||||
})*/
|
})*/
|
||||||
|
|
||||||
/* End of hared view model & sliding pane related */
|
/* End of shared view model & sliding pane related */
|
||||||
|
|
||||||
_adapter = ChatRoomsListAdapter(listSelectionViewModel, viewLifecycleOwner)
|
_adapter = ChatRoomsListAdapter(listSelectionViewModel, viewLifecycleOwner)
|
||||||
// SubmitList is done on a background thread
|
// SubmitList is done on a background thread
|
||||||
|
|
|
@ -108,7 +108,7 @@ class MasterContactsFragment : MasterFragment<ContactMasterFragmentBinding, Cont
|
||||||
}
|
}
|
||||||
})*/
|
})*/
|
||||||
|
|
||||||
/* End of hared view model & sliding pane related */
|
/* End of shared view model & sliding pane related */
|
||||||
|
|
||||||
_adapter = ContactsListAdapter(listSelectionViewModel, viewLifecycleOwner)
|
_adapter = ContactsListAdapter(listSelectionViewModel, viewLifecycleOwner)
|
||||||
binding.contactsList.setHasFixedSize(true)
|
binding.contactsList.setHasFixedSize(true)
|
||||||
|
|
|
@ -111,7 +111,7 @@ class MasterCallLogsFragment : MasterFragment<HistoryMasterFragmentBinding, Call
|
||||||
}
|
}
|
||||||
})*/
|
})*/
|
||||||
|
|
||||||
/* End of hared view model & sliding pane related */
|
/* End of shared view model & sliding pane related */
|
||||||
|
|
||||||
_adapter = CallLogsListAdapter(listSelectionViewModel, viewLifecycleOwner)
|
_adapter = CallLogsListAdapter(listSelectionViewModel, viewLifecycleOwner)
|
||||||
// SubmitList is done on a background thread
|
// SubmitList is done on a background thread
|
||||||
|
|
|
@ -97,6 +97,9 @@ class CallLogsListViewModel : ViewModel() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateCallLogs() {
|
private fun updateCallLogs() {
|
||||||
|
callLogs.value.orEmpty().forEach(GroupedCallLogData::destroy)
|
||||||
|
missedCallLogs.value.orEmpty().forEach(GroupedCallLogData::destroy)
|
||||||
|
|
||||||
val list = arrayListOf<GroupedCallLogData>()
|
val list = arrayListOf<GroupedCallLogData>()
|
||||||
val missedList = arrayListOf<GroupedCallLogData>()
|
val missedList = arrayListOf<GroupedCallLogData>()
|
||||||
|
|
||||||
|
|
|
@ -70,7 +70,7 @@ class SettingsFragment : SecureFragment<SettingsFragmentBinding>() {
|
||||||
})
|
})
|
||||||
binding.slidingPane.lockMode = SlidingPaneLayout.LOCK_MODE_LOCKED
|
binding.slidingPane.lockMode = SlidingPaneLayout.LOCK_MODE_LOCKED
|
||||||
|
|
||||||
/* End of hared view model & sliding pane related */
|
/* End of shared view model & sliding pane related */
|
||||||
|
|
||||||
viewModel = ViewModelProvider(this).get(SettingsViewModel::class.java)
|
viewModel = ViewModelProvider(this).get(SettingsViewModel::class.java)
|
||||||
binding.viewModel = viewModel
|
binding.viewModel = viewModel
|
||||||
|
|
|
@ -25,6 +25,7 @@ import android.app.Activity
|
||||||
import android.bluetooth.BluetoothAdapter
|
import android.bluetooth.BluetoothAdapter
|
||||||
import android.content.ContentValues
|
import android.content.ContentValues
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.graphics.Bitmap
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Environment
|
import android.os.Environment
|
||||||
|
@ -63,6 +64,10 @@ class Api21Compatibility {
|
||||||
vibrator.vibrate(pattern, -1)
|
vibrator.vibrate(pattern, -1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getBitmapFromUri(context: Context, uri: Uri): Bitmap {
|
||||||
|
return MediaStore.Images.Media.getBitmap(context.contentResolver, uri)
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun addImageToMediaStore(context: Context, content: Content): Boolean {
|
suspend fun addImageToMediaStore(context: Context, content: Content): Boolean {
|
||||||
if (!PermissionHelper.get().hasWriteExternalStorage()) {
|
if (!PermissionHelper.get().hasWriteExternalStorage()) {
|
||||||
Log.e("[Media Store] Write external storage permission denied")
|
Log.e("[Media Store] Write external storage permission denied")
|
||||||
|
|
|
@ -25,6 +25,8 @@ import android.app.NotificationManager
|
||||||
import android.content.ContentValues
|
import android.content.ContentValues
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import android.graphics.ImageDecoder
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Environment
|
import android.os.Environment
|
||||||
import android.provider.MediaStore
|
import android.provider.MediaStore
|
||||||
|
@ -83,6 +85,10 @@ class Api29Compatibility {
|
||||||
return bubblesAllowed
|
return bubblesAllowed
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getBitmapFromUri(context: Context, uri: Uri): Bitmap {
|
||||||
|
return ImageDecoder.decodeBitmap(ImageDecoder.createSource(context.contentResolver, uri))
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun addImageToMediaStore(context: Context, content: Content): Boolean {
|
suspend fun addImageToMediaStore(context: Context, content: Content): Boolean {
|
||||||
val filePath = content.filePath
|
val filePath = content.filePath
|
||||||
if (filePath == null) {
|
if (filePath == null) {
|
||||||
|
|
|
@ -23,6 +23,8 @@ import android.app.Activity
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import android.net.Uri
|
||||||
import android.os.Vibrator
|
import android.os.Vibrator
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.WindowManager
|
import android.view.WindowManager
|
||||||
|
@ -74,6 +76,14 @@ class Compatibility {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getBitmapFromUri(context: Context, uri: Uri): Bitmap {
|
||||||
|
return if (Version.sdkStrictlyBelow(Version.API29_ANDROID_10)) {
|
||||||
|
Api21Compatibility.getBitmapFromUri(context, uri)
|
||||||
|
} else {
|
||||||
|
Api29Compatibility.getBitmapFromUri(context, uri)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* Notifications */
|
/* Notifications */
|
||||||
|
|
||||||
fun createNotificationChannels(
|
fun createNotificationChannels(
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
package org.linphone.contact
|
package org.linphone.contact
|
||||||
|
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import org.linphone.LinphoneApplication
|
import org.linphone.LinphoneApplication.Companion.coreContext
|
||||||
import org.linphone.activities.main.viewmodels.ErrorReportingViewModel
|
import org.linphone.activities.main.viewmodels.ErrorReportingViewModel
|
||||||
import org.linphone.core.Address
|
import org.linphone.core.Address
|
||||||
import org.linphone.core.ChatRoomSecurityLevel
|
import org.linphone.core.ChatRoomSecurityLevel
|
||||||
|
@ -50,18 +50,18 @@ open class GenericContactData(private val sipAddress: Address) : ContactDataInte
|
||||||
|
|
||||||
init {
|
init {
|
||||||
securityLevel.value = ChatRoomSecurityLevel.ClearText
|
securityLevel.value = ChatRoomSecurityLevel.ClearText
|
||||||
LinphoneApplication.coreContext.contactsManager.addListener(contactsUpdatedListener)
|
coreContext.contactsManager.addListener(contactsUpdatedListener)
|
||||||
contactLookup()
|
contactLookup()
|
||||||
}
|
}
|
||||||
|
|
||||||
open fun destroy() {
|
open fun destroy() {
|
||||||
LinphoneApplication.coreContext.contactsManager.removeListener(contactsUpdatedListener)
|
coreContext.contactsManager.removeListener(contactsUpdatedListener)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun contactLookup() {
|
private fun contactLookup() {
|
||||||
displayName.value = LinphoneUtils.getDisplayName(sipAddress)
|
displayName.value = LinphoneUtils.getDisplayName(sipAddress)
|
||||||
contact.value =
|
contact.value =
|
||||||
LinphoneApplication.coreContext.contactsManager.findContactByAddress(sipAddress)
|
coreContext.contactsManager.findContactByAddress(sipAddress)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,18 +78,18 @@ abstract class GenericContactViewModel(private val sipAddress: Address) : ErrorR
|
||||||
|
|
||||||
init {
|
init {
|
||||||
securityLevel.value = ChatRoomSecurityLevel.ClearText
|
securityLevel.value = ChatRoomSecurityLevel.ClearText
|
||||||
LinphoneApplication.coreContext.contactsManager.addListener(contactsUpdatedListener)
|
coreContext.contactsManager.addListener(contactsUpdatedListener)
|
||||||
contactLookup()
|
contactLookup()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCleared() {
|
override fun onCleared() {
|
||||||
LinphoneApplication.coreContext.contactsManager.removeListener(contactsUpdatedListener)
|
coreContext.contactsManager.removeListener(contactsUpdatedListener)
|
||||||
|
|
||||||
super.onCleared()
|
super.onCleared()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun contactLookup() {
|
private fun contactLookup() {
|
||||||
displayName.value = LinphoneUtils.getDisplayName(sipAddress)
|
displayName.value = LinphoneUtils.getDisplayName(sipAddress)
|
||||||
contact.value = LinphoneApplication.coreContext.contactsManager.findContactByAddress(sipAddress)
|
contact.value = coreContext.contactsManager.findContactByAddress(sipAddress)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -93,6 +93,8 @@ class ContactsManager(private val context: Context) {
|
||||||
|
|
||||||
var latestContactFetch: String = ""
|
var latestContactFetch: String = ""
|
||||||
|
|
||||||
|
private val friendsMap: HashMap<String, Friend> = HashMap()
|
||||||
|
|
||||||
private val contactsUpdatedListeners = ArrayList<ContactsUpdatedListener>()
|
private val contactsUpdatedListeners = ArrayList<ContactsUpdatedListener>()
|
||||||
|
|
||||||
private var loadContactsTask: AsyncContactsLoader? = null
|
private var loadContactsTask: AsyncContactsLoader? = null
|
||||||
|
@ -227,7 +229,9 @@ class ContactsManager(private val context: Context) {
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
fun findContactByPhoneNumber(number: String): Contact? {
|
fun findContactByPhoneNumber(number: String): Contact? {
|
||||||
val friend: Friend? = coreContext.core.findFriendByPhoneNumber(number)
|
val cacheFriend = friendsMap[number]
|
||||||
|
val friend: Friend? = cacheFriend ?: coreContext.core.findFriendByPhoneNumber(number)
|
||||||
|
if (cacheFriend == null && friend != null) friendsMap[number] = friend
|
||||||
return friend?.userData as? Contact
|
return friend?.userData as? Contact
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -240,8 +244,14 @@ class ContactsManager(private val context: Context) {
|
||||||
}
|
}
|
||||||
if (localContact != null) return localContact
|
if (localContact != null) return localContact
|
||||||
|
|
||||||
|
val cleanAddress = address.clone()
|
||||||
|
cleanAddress.clean() // To remove gruu if any
|
||||||
|
val cleanStringAddress = cleanAddress.asStringUriOnly()
|
||||||
|
|
||||||
|
val cacheFriend = friendsMap[cleanStringAddress]
|
||||||
val friend: Friend? = coreContext.core.findFriend(address)
|
val friend: Friend? = coreContext.core.findFriend(address)
|
||||||
val contact: Contact? = friend?.userData as? Contact
|
val contact: Contact? = friend?.userData as? Contact
|
||||||
|
if (cacheFriend == null && friend != null) friendsMap[cleanStringAddress] = friend
|
||||||
if (contact != null) return contact
|
if (contact != null) return contact
|
||||||
|
|
||||||
val username = address.username
|
val username = address.username
|
||||||
|
@ -283,12 +293,14 @@ class ContactsManager(private val context: Context) {
|
||||||
context.contentResolver.unregisterContentObserver(contactsObserver)
|
context.contentResolver.unregisterContentObserver(contactsObserver)
|
||||||
loadContactsTask?.cancel(true)
|
loadContactsTask?.cancel(true)
|
||||||
|
|
||||||
|
friendsMap.clear()
|
||||||
// Contact has a Friend field and Friend can have a Contact has userData
|
// Contact has a Friend field and Friend can have a Contact has userData
|
||||||
// Friend also keeps a ref on the Core, so we have to clean them
|
// Friend also keeps a ref on the Core, so we have to clean them
|
||||||
for (contact in contacts) {
|
for (contact in contacts) {
|
||||||
contact.friend = null
|
contact.friend = null
|
||||||
}
|
}
|
||||||
contacts.clear()
|
contacts.clear()
|
||||||
|
|
||||||
for (contact in sipContacts) {
|
for (contact in sipContacts) {
|
||||||
contact.friend = null
|
contact.friend = null
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,6 +27,7 @@ import android.net.Uri
|
||||||
import android.provider.MediaStore
|
import android.provider.MediaStore
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import org.linphone.LinphoneApplication.Companion.coreContext
|
import org.linphone.LinphoneApplication.Companion.coreContext
|
||||||
|
import org.linphone.compatibility.Compatibility
|
||||||
import org.linphone.core.tools.Log
|
import org.linphone.core.tools.Log
|
||||||
|
|
||||||
class ImageUtils {
|
class ImageUtils {
|
||||||
|
@ -36,22 +37,19 @@ class ImageUtils {
|
||||||
fromPictureUri: Uri?
|
fromPictureUri: Uri?
|
||||||
): Bitmap? {
|
): Bitmap? {
|
||||||
var bm: Bitmap? = null
|
var bm: Bitmap? = null
|
||||||
val roundBm: Bitmap?
|
|
||||||
if (fromPictureUri != null) {
|
if (fromPictureUri != null) {
|
||||||
bm = try {
|
bm = try {
|
||||||
@Suppress("DEPRECATION")
|
Compatibility.getBitmapFromUri(context, fromPictureUri)
|
||||||
MediaStore.Images.Media.getBitmap(
|
|
||||||
context.contentResolver, fromPictureUri
|
|
||||||
)
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
Log.e("[Image Utils] Failed to get bitmap from URI [$fromPictureUri]: $e")
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (bm != null) {
|
if (bm != null) {
|
||||||
roundBm = getRoundBitmap(bm)
|
val roundBm = getRoundBitmap(bm)
|
||||||
if (roundBm != null) {
|
if (roundBm != null) {
|
||||||
bm.recycle()
|
bm.recycle()
|
||||||
bm = roundBm
|
return roundBm
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return bm
|
return bm
|
||||||
|
|
Loading…
Reference in a new issue