Generate placeholder avatars from two characters, re-fetch missed avatars (#856)

* feat: splitting names in the avatar generation

* fix: re-fetch avatars if initial downloads fail

* fix: remove shadowed name, add tests for common labels
This commit is contained in:
Harris 2022-03-15 09:24:15 +11:00 committed by GitHub
parent 11d49426d3
commit 6649a9a745
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 66 additions and 18 deletions

View File

@ -254,7 +254,7 @@ public class JobManager implements ConstraintObserver.Notifier {
public static class Builder {
private ExecutorFactory executorFactory = new DefaultExecutorFactory();
private int jobThreadCount = Math.max(2, Math.min(Runtime.getRuntime().availableProcessors() - 1, 4));
private int jobThreadCount = 1;
private Map<String, Job.Factory> jobFactories = new HashMap<>();
private Map<String, Constraint.Factory> constraintFactories = new HashMap<>();
private List<ConstraintObserver> constraintObservers = new ArrayList<>();

View File

@ -84,7 +84,7 @@ public class RetrieveProfileAvatarJob extends BaseJob {
return;
}
if (Util.equals(profileAvatar, recipient.resolve().getProfileAvatar())) {
if (AvatarHelper.avatarFileExists(context, recipient.resolve().getAddress()) && Util.equals(profileAvatar, recipient.resolve().getProfileAvatar())) {
Log.w(TAG, "Already retrieved profile avatar: " + profileAvatar);
return;
}

View File

@ -1,14 +1,20 @@
package org.thoughtcrime.securesms.util
import android.content.Context
import android.graphics.*
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.Rect
import android.graphics.RectF
import android.graphics.Typeface
import android.graphics.drawable.BitmapDrawable
import android.text.TextPaint
import android.text.TextUtils
import network.loki.messenger.R
import java.math.BigInteger
import java.security.MessageDigest
import java.util.*
import java.util.Locale
object AvatarPlaceholderGenerator {
@ -28,7 +34,7 @@ object AvatarPlaceholderGenerator {
val colorPrimary = colorArray[(hash % colorArray.size).toInt()]
val labelText = when {
!TextUtils.isEmpty(displayName) -> extractLabel(displayName!!.capitalize())
!TextUtils.isEmpty(displayName) -> extractLabel(displayName!!.capitalize(Locale.ROOT))
!TextUtils.isEmpty(hashString) -> extractLabel(hashString)
else -> EMPTY_LABEL
}
@ -57,14 +63,19 @@ object AvatarPlaceholderGenerator {
return BitmapDrawable(context.resources, bitmap)
}
private fun extractLabel(content: String): String {
var content = content.trim()
if (content.isEmpty()) return EMPTY_LABEL
return if (content.length > 2 && content.startsWith("05")) {
content[2].toString().toUpperCase(Locale.ROOT)
fun extractLabel(content: String): String {
val trimmedContent = content.trim()
if (trimmedContent.isEmpty()) return EMPTY_LABEL
return if (trimmedContent.length > 2 && trimmedContent.startsWith("05")) {
trimmedContent[2].toString()
} else {
content.first().toString().toUpperCase(Locale.ROOT)
}
val splitWords = trimmedContent.split(Regex("\\W"))
if (splitWords.size < 2) {
trimmedContent.take(2)
} else {
splitWords.filter { word -> word.isNotEmpty() }.take(2).map { it.first() }.joinToString("")
}
}.uppercase()
}
private fun getSha512(input: String): String {

View File

@ -0,0 +1,26 @@
package org.thoughtcrime.securesms.recipients
import org.junit.Assert.assertEquals
import org.junit.Test
import org.thoughtcrime.securesms.util.AvatarPlaceholderGenerator
class AvatarGeneratorTest {
@Test
fun testCommonAvatarFormats() {
val testNamesAndResults = mapOf(
"H " to "H",
"Test Name" to "TN",
"test name" to "TN",
"howdy partner" to "HP",
"testname" to "TE", //
"05aaapubkey" to "A", // pubkey values only return first non-05 character
"Test" to "TE"
)
testNamesAndResults.forEach { (test, expected) ->
val processed = AvatarPlaceholderGenerator.extractLabel(test)
assertEquals(expected, processed)
}
}
}

View File

@ -46,6 +46,11 @@ public class AvatarHelper {
return new File(avatarDirectory, new File(address.serialize()).getName());
}
public static boolean avatarFileExists(@NonNull Context context , @NonNull Address address) {
File avatarFile = getAvatarFile(context, address);
return avatarFile.exists();
}
public static void setAvatar(@NonNull Context context, @NonNull Address address, @Nullable byte[] data)
throws IOException
{

View File

@ -1,6 +1,7 @@
package org.session.libsession.messaging.sending_receiving
import android.text.TextUtils
import org.session.libsession.avatars.AvatarHelper
import org.session.libsession.messaging.MessagingModuleConfiguration
import org.session.libsession.messaging.jobs.AttachmentDownloadJob
import org.session.libsession.messaging.jobs.JobQueue
@ -188,28 +189,33 @@ fun MessageReceiver.handleVisibleMessage(message: VisibleMessage, proto: SignalS
val storage = MessagingModuleConfiguration.shared.storage
val context = MessagingModuleConfiguration.shared.context
val userPublicKey = storage.getUserPublicKey()
val messageSender: String? = message.sender
// Get or create thread
// FIXME: In case this is an open group this actually * doesn't * create the thread if it doesn't yet
// exist. This is intentional, but it's very non-obvious.
val threadID = storage.getOrCreateThreadIdFor(message.syncTarget
?: message.sender!!, message.groupPublicKey, openGroupID)
?: messageSender!!, message.groupPublicKey, openGroupID)
if (threadID < 0) {
// Thread doesn't exist; should only be reached in a case where we are processing open group messages for a no longer existent thread
throw MessageReceiver.Error.NoThread
}
// Update profile if needed
val recipient = Recipient.from(context, Address.fromSerialized(message.sender!!), false)
val recipient = Recipient.from(context, Address.fromSerialized(messageSender!!), false)
val profile = message.profile
if (profile != null && userPublicKey != message.sender) {
if (profile != null && userPublicKey != messageSender) {
val profileManager = SSKEnvironment.shared.profileManager
val name = profile.displayName!!
if (name.isNotEmpty()) {
profileManager.setName(context, recipient, name)
}
val newProfileKey = profile.profileKey
if (newProfileKey?.isNotEmpty() == true && (newProfileKey.size == 16 || newProfileKey.size == 32) && profile.profilePictureURL?.isNotEmpty() == true
&& (recipient.profileKey == null || !MessageDigest.isEqual(recipient.profileKey, newProfileKey))) {
profileManager.setProfileKey(context, recipient, newProfileKey)
val needsProfilePicture = !AvatarHelper.avatarFileExists(context, Address.fromSerialized(messageSender))
val profileKeyValid = newProfileKey?.isNotEmpty() == true && (newProfileKey.size == 16 || newProfileKey.size == 32) && profile.profilePictureURL?.isNotEmpty() == true
val profileKeyChanged = (recipient.profileKey == null || !MessageDigest.isEqual(recipient.profileKey, newProfileKey))
if ((profileKeyValid && profileKeyChanged) || (profileKeyValid && needsProfilePicture)) {
profileManager.setProfileKey(context, recipient, newProfileKey!!)
profileManager.setUnidentifiedAccessMode(context, recipient, Recipient.UnidentifiedAccessMode.UNKNOWN)
profileManager.setProfilePictureURL(context, recipient, profile.profilePictureURL!!)
}