From 536e78e98e9bf0a780b8c16e8ae391d03507924d Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Thu, 15 Jul 2021 13:21:00 +0200 Subject: [PATCH] Added a contacts cache at app level + get notified on low memory and clear glide cache when it happens --- .../linphone/activities/main/MainActivity.kt | 18 ++++++++++++++++++ .../chat/fragments/MasterChatRoomsFragment.kt | 2 +- .../fragments/MasterContactsFragment.kt | 2 +- .../fragments/MasterCallLogsFragment.kt | 2 +- .../viewmodels/CallLogsListViewModel.kt | 3 +++ .../settings/fragments/SettingsFragment.kt | 2 +- .../compatibility/Api21Compatibility.kt | 5 +++++ .../compatibility/Api29Compatibility.kt | 6 ++++++ .../linphone/compatibility/Compatibility.kt | 10 ++++++++++ .../linphone/contact/ContactDataInterface.kt | 14 +++++++------- .../org/linphone/contact/ContactsManager.kt | 14 +++++++++++++- .../main/java/org/linphone/utils/ImageUtils.kt | 12 +++++------- 12 files changed, 71 insertions(+), 19 deletions(-) diff --git a/app/src/main/java/org/linphone/activities/main/MainActivity.kt b/app/src/main/java/org/linphone/activities/main/MainActivity.kt index f2f709ab6..d234bf280 100644 --- a/app/src/main/java/org/linphone/activities/main/MainActivity.kt +++ b/app/src/main/java/org/linphone/activities/main/MainActivity.kt @@ -20,7 +20,9 @@ package org.linphone.activities.main import android.app.Activity +import android.content.ComponentCallbacks2 import android.content.Intent +import android.content.res.Configuration import android.net.Uri import android.os.Bundle import android.os.Parcelable @@ -58,6 +60,7 @@ import org.linphone.databinding.MainActivityBinding import org.linphone.utils.AppUtils import org.linphone.utils.Event import org.linphone.utils.FileUtils +import org.linphone.utils.GlideApp class MainActivity : GenericActivity(), SnackBarActivity, NavController.OnDestinationChangedListener { private lateinit var binding: MainActivityBinding @@ -84,6 +87,19 @@ class MainActivity : GenericActivity(), SnackBarActivity, NavController.OnDestin private var initPosY = 0f 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?) { sharedViewModel.layoutChangedEvent.value = Event(true) } @@ -155,6 +171,7 @@ class MainActivity : GenericActivity(), SnackBarActivity, NavController.OnDestin override fun onPostCreate(savedInstanceState: Bundle?) { super.onPostCreate(savedInstanceState) + registerComponentCallbacks(componentCallbacks) findNavController(R.id.nav_host_fragment).addOnDestinationChangedListener(this) if (intent != null) handleIntentParams(intent) @@ -162,6 +179,7 @@ class MainActivity : GenericActivity(), SnackBarActivity, NavController.OnDestin override fun onDestroy() { findNavController(R.id.nav_host_fragment).removeOnDestinationChangedListener(this) + unregisterComponentCallbacks(componentCallbacks) super.onDestroy() } diff --git a/app/src/main/java/org/linphone/activities/main/chat/fragments/MasterChatRoomsFragment.kt b/app/src/main/java/org/linphone/activities/main/chat/fragments/MasterChatRoomsFragment.kt index f0c0174dd..dcf871b96 100644 --- a/app/src/main/java/org/linphone/activities/main/chat/fragments/MasterChatRoomsFragment.kt +++ b/app/src/main/java/org/linphone/activities/main/chat/fragments/MasterChatRoomsFragment.kt @@ -120,7 +120,7 @@ class MasterChatRoomsFragment : MasterFragment() val missedList = arrayListOf() diff --git a/app/src/main/java/org/linphone/activities/main/settings/fragments/SettingsFragment.kt b/app/src/main/java/org/linphone/activities/main/settings/fragments/SettingsFragment.kt index 0c031abcb..def889166 100644 --- a/app/src/main/java/org/linphone/activities/main/settings/fragments/SettingsFragment.kt +++ b/app/src/main/java/org/linphone/activities/main/settings/fragments/SettingsFragment.kt @@ -70,7 +70,7 @@ class SettingsFragment : SecureFragment() { }) 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) binding.viewModel = viewModel diff --git a/app/src/main/java/org/linphone/compatibility/Api21Compatibility.kt b/app/src/main/java/org/linphone/compatibility/Api21Compatibility.kt index cd731c223..76cc93000 100644 --- a/app/src/main/java/org/linphone/compatibility/Api21Compatibility.kt +++ b/app/src/main/java/org/linphone/compatibility/Api21Compatibility.kt @@ -25,6 +25,7 @@ import android.app.Activity import android.bluetooth.BluetoothAdapter import android.content.ContentValues import android.content.Context +import android.graphics.Bitmap import android.net.Uri import android.os.Build import android.os.Environment @@ -63,6 +64,10 @@ class Api21Compatibility { 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 { if (!PermissionHelper.get().hasWriteExternalStorage()) { Log.e("[Media Store] Write external storage permission denied") diff --git a/app/src/main/java/org/linphone/compatibility/Api29Compatibility.kt b/app/src/main/java/org/linphone/compatibility/Api29Compatibility.kt index 035a190a2..c7608e047 100644 --- a/app/src/main/java/org/linphone/compatibility/Api29Compatibility.kt +++ b/app/src/main/java/org/linphone/compatibility/Api29Compatibility.kt @@ -25,6 +25,8 @@ import android.app.NotificationManager import android.content.ContentValues import android.content.Context import android.content.Intent +import android.graphics.Bitmap +import android.graphics.ImageDecoder import android.net.Uri import android.os.Environment import android.provider.MediaStore @@ -83,6 +85,10 @@ class Api29Compatibility { 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 { val filePath = content.filePath if (filePath == null) { diff --git a/app/src/main/java/org/linphone/compatibility/Compatibility.kt b/app/src/main/java/org/linphone/compatibility/Compatibility.kt index cc4b3a773..3d5cb32c2 100644 --- a/app/src/main/java/org/linphone/compatibility/Compatibility.kt +++ b/app/src/main/java/org/linphone/compatibility/Compatibility.kt @@ -23,6 +23,8 @@ import android.app.Activity import android.content.Context import android.content.Intent import android.content.pm.PackageManager +import android.graphics.Bitmap +import android.net.Uri import android.os.Vibrator import android.view.View 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 */ fun createNotificationChannels( diff --git a/app/src/main/java/org/linphone/contact/ContactDataInterface.kt b/app/src/main/java/org/linphone/contact/ContactDataInterface.kt index 224f27445..5e58786ed 100644 --- a/app/src/main/java/org/linphone/contact/ContactDataInterface.kt +++ b/app/src/main/java/org/linphone/contact/ContactDataInterface.kt @@ -20,7 +20,7 @@ package org.linphone.contact import androidx.lifecycle.MutableLiveData -import org.linphone.LinphoneApplication +import org.linphone.LinphoneApplication.Companion.coreContext import org.linphone.activities.main.viewmodels.ErrorReportingViewModel import org.linphone.core.Address import org.linphone.core.ChatRoomSecurityLevel @@ -50,18 +50,18 @@ open class GenericContactData(private val sipAddress: Address) : ContactDataInte init { securityLevel.value = ChatRoomSecurityLevel.ClearText - LinphoneApplication.coreContext.contactsManager.addListener(contactsUpdatedListener) + coreContext.contactsManager.addListener(contactsUpdatedListener) contactLookup() } open fun destroy() { - LinphoneApplication.coreContext.contactsManager.removeListener(contactsUpdatedListener) + coreContext.contactsManager.removeListener(contactsUpdatedListener) } private fun contactLookup() { displayName.value = LinphoneUtils.getDisplayName(sipAddress) contact.value = - LinphoneApplication.coreContext.contactsManager.findContactByAddress(sipAddress) + coreContext.contactsManager.findContactByAddress(sipAddress) } } @@ -78,18 +78,18 @@ abstract class GenericContactViewModel(private val sipAddress: Address) : ErrorR init { securityLevel.value = ChatRoomSecurityLevel.ClearText - LinphoneApplication.coreContext.contactsManager.addListener(contactsUpdatedListener) + coreContext.contactsManager.addListener(contactsUpdatedListener) contactLookup() } override fun onCleared() { - LinphoneApplication.coreContext.contactsManager.removeListener(contactsUpdatedListener) + coreContext.contactsManager.removeListener(contactsUpdatedListener) super.onCleared() } private fun contactLookup() { displayName.value = LinphoneUtils.getDisplayName(sipAddress) - contact.value = LinphoneApplication.coreContext.contactsManager.findContactByAddress(sipAddress) + contact.value = coreContext.contactsManager.findContactByAddress(sipAddress) } } diff --git a/app/src/main/java/org/linphone/contact/ContactsManager.kt b/app/src/main/java/org/linphone/contact/ContactsManager.kt index 463acf9db..715564d3e 100644 --- a/app/src/main/java/org/linphone/contact/ContactsManager.kt +++ b/app/src/main/java/org/linphone/contact/ContactsManager.kt @@ -93,6 +93,8 @@ class ContactsManager(private val context: Context) { var latestContactFetch: String = "" + private val friendsMap: HashMap = HashMap() + private val contactsUpdatedListeners = ArrayList() private var loadContactsTask: AsyncContactsLoader? = null @@ -227,7 +229,9 @@ class ContactsManager(private val context: Context) { @Synchronized 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 } @@ -240,8 +244,14 @@ class ContactsManager(private val context: Context) { } 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 contact: Contact? = friend?.userData as? Contact + if (cacheFriend == null && friend != null) friendsMap[cleanStringAddress] = friend if (contact != null) return contact val username = address.username @@ -283,12 +293,14 @@ class ContactsManager(private val context: Context) { context.contentResolver.unregisterContentObserver(contactsObserver) loadContactsTask?.cancel(true) + friendsMap.clear() // 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 for (contact in contacts) { contact.friend = null } contacts.clear() + for (contact in sipContacts) { contact.friend = null } diff --git a/app/src/main/java/org/linphone/utils/ImageUtils.kt b/app/src/main/java/org/linphone/utils/ImageUtils.kt index 9d5931c00..900585595 100644 --- a/app/src/main/java/org/linphone/utils/ImageUtils.kt +++ b/app/src/main/java/org/linphone/utils/ImageUtils.kt @@ -27,6 +27,7 @@ import android.net.Uri import android.provider.MediaStore import java.io.File import org.linphone.LinphoneApplication.Companion.coreContext +import org.linphone.compatibility.Compatibility import org.linphone.core.tools.Log class ImageUtils { @@ -36,22 +37,19 @@ class ImageUtils { fromPictureUri: Uri? ): Bitmap? { var bm: Bitmap? = null - val roundBm: Bitmap? if (fromPictureUri != null) { bm = try { - @Suppress("DEPRECATION") - MediaStore.Images.Media.getBitmap( - context.contentResolver, fromPictureUri - ) + Compatibility.getBitmapFromUri(context, fromPictureUri) } catch (e: Exception) { + Log.e("[Image Utils] Failed to get bitmap from URI [$fromPictureUri]: $e") return null } } if (bm != null) { - roundBm = getRoundBitmap(bm) + val roundBm = getRoundBitmap(bm) if (roundBm != null) { bm.recycle() - bm = roundBm + return roundBm } } return bm