From 12f4071e3396598f35abc58ada50c9f471f8875f Mon Sep 17 00:00:00 2001 From: Grisha Kruglov Date: Mon, 30 Mar 2020 21:27:41 -0700 Subject: [PATCH] Closes #9530: Don't crash on failed avatar fetches --- .../main/java/org/mozilla/fenix/ext/String.kt | 37 ++++++++++++------- .../fenix/settings/SettingsFragment.kt | 19 +++------- 2 files changed, 29 insertions(+), 27 deletions(-) diff --git a/app/src/main/java/org/mozilla/fenix/ext/String.kt b/app/src/main/java/org/mozilla/fenix/ext/String.kt index b9366294c..f0a90094a 100644 --- a/app/src/main/java/org/mozilla/fenix/ext/String.kt +++ b/app/src/main/java/org/mozilla/fenix/ext/String.kt @@ -5,20 +5,23 @@ package org.mozilla.fenix.ext import android.content.Context +import android.graphics.Bitmap import android.graphics.BitmapFactory import android.net.Uri import android.util.Patterns import android.webkit.URLUtil -import androidx.core.graphics.drawable.RoundedBitmapDrawable import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory import androidx.core.net.toUri +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.withContext +import mozilla.components.concept.fetch.Client +import mozilla.components.concept.fetch.Request import mozilla.components.lib.publicsuffixlist.PublicSuffixList import mozilla.components.lib.publicsuffixlist.ext.urlToTrimmedHost import mozilla.components.support.ktx.android.net.hostWithoutCommonPrefixes +import java.io.IOException import java.net.IDN -import java.net.MalformedURLException -import java.net.URL import java.util.Locale const val FILE_PREFIX = "file://" @@ -113,17 +116,23 @@ fun String.simplifiedUrl(): String { } /** - * Gets a rounded drawable from a URL if possible, else null. Must be called off main thread. + * Gets a rounded drawable from a URL if possible, else null. */ -fun String.decodeUrlToRoundedDrawable(context: Context): RoundedBitmapDrawable? { - val avatarUrl = try { - URL(this) - } catch (e: MalformedURLException) { - return null +suspend fun String.toRoundedDrawable(context: Context, client: Client) = bitmapForUrl(this, client)?.let { bitmap -> + RoundedBitmapDrawableFactory.create(context.resources, bitmap).also { + it.isCircular = true + it.setAntiAlias(true) } - val bitmap = BitmapFactory.decodeStream(avatarUrl.openConnection().getInputStream()) - val roundedBitmapDrawable = RoundedBitmapDrawableFactory.create(context.resources, bitmap) - roundedBitmapDrawable.isCircular = true - roundedBitmapDrawable.setAntiAlias(true) - return roundedBitmapDrawable +} + +suspend fun bitmapForUrl(url: String, client: Client): Bitmap? = withContext(Dispatchers.IO) { + // TODO cache this image, see https://github.com/mozilla-mobile/fenix/issues/9531 + // Code below will cache it in Gecko's cache, which ensures that as long as we've fetched it once, + // we will be able to display this avatar as long as the cache isn't purged (e.g. via 'clear user data'). + val body = try { + client.fetch(Request(url, useCaches = true)).body + } catch (e: IOException) { + return@withContext null + } + body.useStream { BitmapFactory.decodeStream(it) } } diff --git a/app/src/main/java/org/mozilla/fenix/settings/SettingsFragment.kt b/app/src/main/java/org/mozilla/fenix/settings/SettingsFragment.kt index f7e529aa1..f4a0b5c5e 100644 --- a/app/src/main/java/org/mozilla/fenix/settings/SettingsFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/settings/SettingsFragment.kt @@ -20,11 +20,9 @@ import androidx.preference.PreferenceCategory import androidx.preference.PreferenceFragmentCompat import androidx.recyclerview.widget.RecyclerView import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.Main import kotlinx.coroutines.delay import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext import mozilla.components.concept.sync.AccountObserver import mozilla.components.concept.sync.AuthType import mozilla.components.concept.sync.OAuthAccount @@ -37,7 +35,7 @@ import org.mozilla.fenix.R import org.mozilla.fenix.components.metrics.Event import org.mozilla.fenix.ext.application import org.mozilla.fenix.ext.components -import org.mozilla.fenix.ext.decodeUrlToRoundedDrawable +import org.mozilla.fenix.ext.toRoundedDrawable import org.mozilla.fenix.ext.getPreferenceKey import org.mozilla.fenix.ext.metrics import org.mozilla.fenix.ext.requireComponents @@ -347,16 +345,11 @@ class SettingsFragment : PreferenceFragmentCompat() { if (account != null && !accountManager.accountNeedsReauth()) { preferenceSignIn?.isVisible = false - profile?.avatar?.url?.let { - lifecycleScope.launch(IO) { - val roundedDrawable = it.decodeUrlToRoundedDrawable(context) - withContext(Main) { - preferenceFirefoxAccount?.icon = - roundedDrawable ?: AppCompatResources.getDrawable( - context, - R.drawable.ic_account - ) - } + profile?.avatar?.url?.let { avatarUrl -> + lifecycleScope.launch(Main) { + val roundedDrawable = avatarUrl.toRoundedDrawable(context, requireComponents.core.client) + preferenceFirefoxAccount?.icon = + roundedDrawable ?: AppCompatResources.getDrawable(context, R.drawable.ic_account) } } preferenceSignIn?.onPreferenceClickListener = null