Refresh notification drawer in a background thread. It also fixes the person and room avatar display

This commit is contained in:
Benoit Marty 2019-07-08 10:26:22 +02:00
parent 568e8c8bc0
commit 62a81a556e
3 changed files with 42 additions and 136 deletions

View File

@ -18,73 +18,38 @@ package im.vector.riotx.features.notifications


import android.content.Context import android.content.Context
import android.graphics.Bitmap import android.graphics.Bitmap
import android.os.Handler
import android.os.HandlerThread
import androidx.annotation.WorkerThread import androidx.annotation.WorkerThread
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import com.bumptech.glide.load.DecodeFormat import com.bumptech.glide.load.DecodeFormat
import timber.log.Timber import timber.log.Timber


/** class BitmapLoader(val context: Context) {
* FIXME It works, but it does not refresh the notification, when it's already displayed
*/
class BitmapLoader(val context: Context,
val listener: BitmapLoaderListener) {


/** /**
* Avatar Url -> Icon * Avatar Url -> Icon
*/ */
private val cache = HashMap<String, Bitmap>() private val cache = HashMap<String, Bitmap?>()

// URLs to load
private val toLoad = HashSet<String>()


// Black list of URLs (broken URL, etc.) // Black list of URLs (broken URL, etc.)
private val blacklist = HashSet<String>() private val blacklist = HashSet<String>()


private var uiHandler = Handler()

private val handlerThread: HandlerThread = HandlerThread("BitmapLoader", Thread.MIN_PRIORITY)
private var backgroundHandler: Handler

init {
handlerThread.start()
backgroundHandler = Handler(handlerThread.looper)
}

/** /**
* Get icon of a room. * Get icon of a room.
* If already in cache, use it, else load it and call BitmapLoaderListener.onBitmapsLoaded() when ready * If already in cache, use it, else load it and call BitmapLoaderListener.onBitmapsLoaded() when ready
*/ */
@WorkerThread
fun getRoomBitmap(path: String?): Bitmap? { fun getRoomBitmap(path: String?): Bitmap? {
if (path == null) { if (path == null) {
return null return null
} }


synchronized(cache) { return cache.getOrPut(path) {
if (cache[path] != null) { loadRoomBitmap(path)
return cache[path]
}

// Add to the queue, if not blacklisted
if (!blacklist.contains(path)) {
if (toLoad.contains(path)) {
// Wait
} else {
toLoad.add(path)

backgroundHandler.post {
loadRoomBitmap(path)
}
}
}
} }

return null
} }


@WorkerThread @WorkerThread
private fun loadRoomBitmap(path: String) { private fun loadRoomBitmap(path: String): Bitmap? {
val bitmap = path.let { val bitmap = path.let {
try { try {
Glide.with(context) Glide.with(context)
@ -99,26 +64,11 @@ class BitmapLoader(val context: Context,
} }
} }


synchronized(cache) { if (bitmap == null) {
if (bitmap == null) { // Add to the blacklist
// Add to the blacklist blacklist.add(path)
blacklist.add(path)
} else {
cache[path] = bitmap
}

toLoad.remove(path)

if (toLoad.isEmpty()) {
uiHandler.post {
listener.onBitmapsLoaded()
}
}
} }
}



return bitmap
interface BitmapLoaderListener {
fun onBitmapsLoaded()
} }
} }

View File

@ -18,8 +18,6 @@ package im.vector.riotx.features.notifications


import android.content.Context import android.content.Context
import android.os.Build import android.os.Build
import android.os.Handler
import android.os.HandlerThread
import androidx.annotation.WorkerThread import androidx.annotation.WorkerThread
import androidx.core.graphics.drawable.IconCompat import androidx.core.graphics.drawable.IconCompat
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
@ -27,67 +25,34 @@ import com.bumptech.glide.load.DecodeFormat
import com.bumptech.glide.request.RequestOptions import com.bumptech.glide.request.RequestOptions
import timber.log.Timber import timber.log.Timber


/** class IconLoader(val context: Context) {
* FIXME It works, but it does not refresh the notification, when it's already displayed
*/
class IconLoader(val context: Context,
val listener: IconLoaderListener) {


/** /**
* Avatar Url -> Icon * Avatar Url -> Icon
*/ */
private val cache = HashMap<String, IconCompat>() private val cache = HashMap<String, IconCompat?>()

// URLs to load
private val toLoad = HashSet<String>()


// Black list of URLs (broken URL, etc.) // Black list of URLs (broken URL, etc.)
private val blacklist = HashSet<String>() private val blacklist = HashSet<String>()


private var uiHandler = Handler()

private val handlerThread: HandlerThread = HandlerThread("IconLoader", Thread.MIN_PRIORITY)
private var backgroundHandler: Handler

init {
handlerThread.start()
backgroundHandler = Handler(handlerThread.looper)
}

/** /**
* Get icon of a user. * Get icon of a user.
* If already in cache, use it, else load it and call IconLoaderListener.onIconsLoaded() when ready * If already in cache, use it, else load it and call IconLoaderListener.onIconsLoaded() when ready
* Before Android P, this does nothing because the icon won't be used * Before Android P, this does nothing because the icon won't be used
*/ */
@WorkerThread
fun getUserIcon(path: String?): IconCompat? { fun getUserIcon(path: String?): IconCompat? {
if (path == null || Build.VERSION.SDK_INT < Build.VERSION_CODES.P) { if (path == null || Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
return null return null
} }


synchronized(cache) { return cache.getOrPut(path) {
if (cache[path] != null) { loadUserIcon(path)
return cache[path]
}

// Add to the queue, if not blacklisted
if (!blacklist.contains(path)) {
if (toLoad.contains(path)) {
// Wait
} else {
toLoad.add(path)

backgroundHandler.post {
loadUserIcon(path)
}
}
}
} }

return null
} }


@WorkerThread @WorkerThread
private fun loadUserIcon(path: String) { fun loadUserIcon(path: String): IconCompat? {
val iconCompat = path.let { val iconCompat = path.let {
try { try {
Glide.with(context) Glide.with(context)
@ -105,26 +70,11 @@ class IconLoader(val context: Context,
} }
} }


synchronized(cache) { if (iconCompat == null) {
if (iconCompat == null) { // Add to the blacklist
// Add to the blacklist blacklist.add(path)
blacklist.add(path)
} else {
cache[path] = iconCompat
}

toLoad.remove(path)

if (toLoad.isEmpty()) {
uiHandler.post {
listener.onIconsLoaded()
}
}
} }
}



return iconCompat
interface IconLoaderListener {
fun onIconsLoaded()
} }
} }

View File

@ -18,6 +18,9 @@ package im.vector.riotx.features.notifications
import android.app.Notification import android.app.Notification
import android.content.Context import android.content.Context
import android.graphics.Bitmap import android.graphics.Bitmap
import android.os.Handler
import android.os.HandlerThread
import androidx.annotation.WorkerThread
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import androidx.core.app.Person import androidx.core.app.Person
import im.vector.matrix.android.api.session.content.ContentUrlResolver import im.vector.matrix.android.api.session.content.ContentUrlResolver
@ -44,6 +47,14 @@ class NotificationDrawerManager @Inject constructor(private val context: Context
private val activeSessionHolder: ActiveSessionHolder, private val activeSessionHolder: ActiveSessionHolder,
private val outdatedDetector: OutdatedEventDetector?) { private val outdatedDetector: OutdatedEventDetector?) {


private val handlerThread: HandlerThread = HandlerThread("NotificationDrawerManager", Thread.MIN_PRIORITY)
private var backgroundHandler: Handler

init {
handlerThread.start()
backgroundHandler = Handler(handlerThread.looper)
}

//The first time the notification drawer is refreshed, we force re-render of all notifications //The first time the notification drawer is refreshed, we force re-render of all notifications
private var firstTime = true private var firstTime = true


@ -53,21 +64,9 @@ class NotificationDrawerManager @Inject constructor(private val context: Context


private var currentRoomId: String? = null private var currentRoomId: String? = null


private var iconLoader = IconLoader(context, private var iconLoader = IconLoader(context)
object : IconLoader.IconLoaderListener {
override fun onIconsLoaded() {
// Force refresh
refreshNotificationDrawer()
}
})


private var bitmapLoader = BitmapLoader(context, private var bitmapLoader = BitmapLoader(context)
object : BitmapLoader.BitmapLoaderListener {
override fun onBitmapsLoaded() {
// Force refresh
refreshNotificationDrawer()
}
})


/** /**
Should be called as soon as a new event is ready to be displayed. Should be called as soon as a new event is ready to be displayed.
@ -171,6 +170,13 @@ class NotificationDrawerManager @Inject constructor(private val context: Context




fun refreshNotificationDrawer() { fun refreshNotificationDrawer() {
backgroundHandler.post {
refreshNotificationDrawerBg()
}
}

@WorkerThread
private fun refreshNotificationDrawerBg() {
val session = activeSessionHolder.getActiveSession() val session = activeSessionHolder.getActiveSession()
val user = session.getUser(session.sessionParams.credentials.userId) val user = session.getUser(session.sessionParams.credentials.userId)
val myUserDisplayName = user?.displayName ?: "" val myUserDisplayName = user?.displayName ?: ""