From 4417a7105a51b21acffdb178c93a16eafc61a3e2 Mon Sep 17 00:00:00 2001 From: adalpari Date: Mon, 15 Sep 2025 14:27:56 +0200 Subject: [PATCH 01/10] Calling a fallback image with the half resolution when Timeout happens --- .../ui/reader/views/ReaderPhotoView.java | 46 ++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/views/ReaderPhotoView.java b/WordPress/src/main/java/org/wordpress/android/ui/reader/views/ReaderPhotoView.java index 8bf58a596d74..da815e2fc271 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/reader/views/ReaderPhotoView.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/views/ReaderPhotoView.java @@ -33,6 +33,11 @@ public interface PhotoViewListener { void onTapPhotoView(); } + private String mImageUrl; + private int mHiResWidth; + private boolean mIsPrivate; + private boolean mIsPrivateAtSite; + private PhotoViewListener mPhotoViewListener; private String mLoResImageUrl; private String mHiResImageUrl; @@ -75,6 +80,11 @@ public void setImageUrl(String imageUrl, boolean isPrivate, boolean isPrivateAtSite, PhotoViewListener listener) { + mImageUrl = imageUrl; + mHiResWidth = hiResWidth; + mIsPrivate = isPrivate; + mIsPrivateAtSite = isPrivateAtSite; + int loResWidth = (int) (hiResWidth * 0.10f); mLoResImageUrl = ReaderUtils .getResizedImageUrl(imageUrl, loResWidth, 0, isPrivate, isPrivateAtSite, PhotonUtils.Quality.LOW); @@ -110,11 +120,16 @@ private void loadImage() { mImageManager .loadWithResultListener(mImageView, ImageType.IMAGE, mHiResImageUrl, ScaleType.CENTER, mLoResImageUrl, - new RequestListener() { + new RequestListener<>() { @Override public void onLoadFailed(@Nullable Exception e, @Nullable Object model) { if (e != null) { AppLog.e(AppLog.T.READER, e); + // A timeout could happen but there's no way to know it because the exception here is + // always a GlideException. So let's try to reload the image with a lower res + mImageView.post( + () -> loadFallbackImage() + ); } boolean lowResNotLoadedYet = isLoading(); if (lowResNotLoadedYet) { @@ -130,6 +145,35 @@ public void onResourceReady(@NonNull Drawable resource, @Nullable Object model) }); } + private void loadFallbackImage() { + if (!hasLayout()) { + return; + } + String resImageUrl = ReaderUtils.getResizedImageUrl(mImageUrl, (int) (mHiResWidth * 0.5), 0, mIsPrivate, + mIsPrivateAtSite, PhotonUtils.Quality.MEDIUM); + + mImageManager + .loadWithResultListener(mImageView, ImageType.IMAGE, resImageUrl, ScaleType.CENTER, mLoResImageUrl, + new RequestListener<>() { + @Override + public void onLoadFailed(@Nullable Exception e, @Nullable Object model) { + if (e != null) { + AppLog.e(AppLog.T.READER, e); + } + boolean lowResNotLoadedYet = isLoading(); + if (lowResNotLoadedYet) { + hideProgress(); + showError(); + } + } + + @Override + public void onResourceReady(@NonNull Drawable resource, @Nullable Object model) { + handleResponse(); + } + }); + } + private void handleResponse() { hideProgress(); hideError(); From 2076d0929f79314f2e2671ff60b3e2178003230d Mon Sep 17 00:00:00 2001 From: adalpari Date: Mon, 15 Sep 2025 14:30:10 +0200 Subject: [PATCH 02/10] Reducing a bit the timeout possibilities --- .../wordpress/android/ui/reader/ReaderPhotoViewerFragment.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderPhotoViewerFragment.java b/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderPhotoViewerFragment.java index 04e0dbcc0e2d..dfa592f139d3 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderPhotoViewerFragment.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderPhotoViewerFragment.java @@ -88,7 +88,7 @@ private void showImage() { if (isAdded() && !TextUtils.isEmpty(mImageUrl)) { // use max of width/height so image is cached the same regardless of orientation Rect pt = DisplayUtils.getWindowSize(requireActivity()); - int hiResWidth = Math.max(pt.height(), pt.width()); + int hiResWidth = (int) (Math.max(pt.height(), pt.width()) * 0.8); // don't use AT media proxy here mPhotoView.setImageUrl(mImageUrl, hiResWidth, mIsPrivate, false, mPhotoViewListener); } From 628ebfe1784e5b1cceffd6bd857358eeed7abc16 Mon Sep 17 00:00:00 2001 From: adalpari Date: Mon, 15 Sep 2025 14:41:06 +0200 Subject: [PATCH 03/10] Increase the timeout for image loading --- .../main/java/org/wordpress/android/util/image/ImageManager.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/WordPress/src/main/java/org/wordpress/android/util/image/ImageManager.kt b/WordPress/src/main/java/org/wordpress/android/util/image/ImageManager.kt index 10338de2b94b..3b37c8f35740 100644 --- a/WordPress/src/main/java/org/wordpress/android/util/image/ImageManager.kt +++ b/WordPress/src/main/java/org/wordpress/android/util/image/ImageManager.kt @@ -41,6 +41,7 @@ import com.bumptech.glide.request.target.ViewTarget import com.bumptech.glide.request.transition.Transition import com.bumptech.glide.signature.ObjectKey import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.time.withTimeout import org.wordpress.android.WordPress import org.wordpress.android.networking.MShot import org.wordpress.android.ui.media.VideoLoader @@ -294,6 +295,7 @@ class ImageManager @Inject constructor( if (!context.isAvailable()) return Glide.with(context) .load(imgUrl.toUri()) + .timeout(5000) .addFallback(imageType) .addPlaceholder(imageType) .addThumbnail(context, thumbnailUrl, requestListener) From a041f4cb13e17701748e13daade31f41033ef091 Mon Sep 17 00:00:00 2001 From: adalpari Date: Mon, 15 Sep 2025 15:08:46 +0200 Subject: [PATCH 04/10] Removing code moved to the second call --- .../wordpress/android/ui/reader/views/ReaderPhotoView.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/views/ReaderPhotoView.java b/WordPress/src/main/java/org/wordpress/android/ui/reader/views/ReaderPhotoView.java index da815e2fc271..595a460d2c02 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/reader/views/ReaderPhotoView.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/views/ReaderPhotoView.java @@ -131,11 +131,6 @@ public void onLoadFailed(@Nullable Exception e, @Nullable Object model) { () -> loadFallbackImage() ); } - boolean lowResNotLoadedYet = isLoading(); - if (lowResNotLoadedYet) { - hideProgress(); - showError(); - } } @Override From fb17020b221494653d70ef1c2518329ca39ee124 Mon Sep 17 00:00:00 2001 From: adalpari Date: Mon, 15 Sep 2025 15:09:12 +0200 Subject: [PATCH 05/10] Revertign tomout change --- .../main/java/org/wordpress/android/util/image/ImageManager.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/WordPress/src/main/java/org/wordpress/android/util/image/ImageManager.kt b/WordPress/src/main/java/org/wordpress/android/util/image/ImageManager.kt index 3b37c8f35740..0f8d0a4157f4 100644 --- a/WordPress/src/main/java/org/wordpress/android/util/image/ImageManager.kt +++ b/WordPress/src/main/java/org/wordpress/android/util/image/ImageManager.kt @@ -295,7 +295,6 @@ class ImageManager @Inject constructor( if (!context.isAvailable()) return Glide.with(context) .load(imgUrl.toUri()) - .timeout(5000) .addFallback(imageType) .addPlaceholder(imageType) .addThumbnail(context, thumbnailUrl, requestListener) From ccb28d0950148f39b873f7149efb2b468f736807 Mon Sep 17 00:00:00 2001 From: adalpari Date: Mon, 15 Sep 2025 15:15:16 +0200 Subject: [PATCH 06/10] Detect timeout in the stacktrace --- .../ui/reader/views/ReaderPhotoView.java | 32 ++++++++++++++++--- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/views/ReaderPhotoView.java b/WordPress/src/main/java/org/wordpress/android/ui/reader/views/ReaderPhotoView.java index 595a460d2c02..0c0c7b52326c 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/reader/views/ReaderPhotoView.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/views/ReaderPhotoView.java @@ -125,11 +125,19 @@ private void loadImage() { public void onLoadFailed(@Nullable Exception e, @Nullable Object model) { if (e != null) { AppLog.e(AppLog.T.READER, e); - // A timeout could happen but there's no way to know it because the exception here is - // always a GlideException. So let's try to reload the image with a lower res - mImageView.post( - () -> loadFallbackImage() - ); + // Check if the stack trace contains TimeoutError before trying fallback + if (containsTimeoutInStackTrace(e)) { + AppLog.d(AppLog.T.READER, "Timeout detected in stack trace, loading fallback image"); + mImageView.post( + () -> loadFallbackImage() + ); + } else { + boolean lowResNotLoadedYet = isLoading(); + if (lowResNotLoadedYet) { + hideProgress(); + showError(); + } + } } } @@ -140,6 +148,20 @@ public void onResourceReady(@NonNull Drawable resource, @Nullable Object model) }); } + private boolean containsTimeoutInStackTrace(@Nullable Exception e) { + if (e == null) { + return false; + } + + // Convert stack trace to string and check for TimeoutError + java.io.StringWriter sw = new java.io.StringWriter(); + java.io.PrintWriter pw = new java.io.PrintWriter(sw); + e.printStackTrace(pw); + String stackTrace = sw.toString(); + + return stackTrace.contains("TimeoutError"); + } + private void loadFallbackImage() { if (!hasLayout()) { return; From ce865de3f668c9222e30dd5c9b130e527e384f89 Mon Sep 17 00:00:00 2001 From: adalpari Date: Mon, 15 Sep 2025 15:16:52 +0200 Subject: [PATCH 07/10] refactor --- .../wordpress/android/ui/reader/views/ReaderPhotoView.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/views/ReaderPhotoView.java b/WordPress/src/main/java/org/wordpress/android/ui/reader/views/ReaderPhotoView.java index 0c0c7b52326c..d9b2fb24982c 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/reader/views/ReaderPhotoView.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/views/ReaderPhotoView.java @@ -29,6 +29,8 @@ * but adds pinch/zoom and the ability to first load a lo-res version of the image */ public class ReaderPhotoView extends RelativeLayout { + public static final double FALLBACK_RESOLUTION_MULTIPLIER = 0.5; + public interface PhotoViewListener { void onTapPhotoView(); } @@ -166,7 +168,8 @@ private void loadFallbackImage() { if (!hasLayout()) { return; } - String resImageUrl = ReaderUtils.getResizedImageUrl(mImageUrl, (int) (mHiResWidth * 0.5), 0, mIsPrivate, + String resImageUrl = ReaderUtils.getResizedImageUrl(mImageUrl, + (int) (mHiResWidth * FALLBACK_RESOLUTION_MULTIPLIER), 0, mIsPrivate, mIsPrivateAtSite, PhotonUtils.Quality.MEDIUM); mImageManager From d262ae6f8ccc704ab80674fce5a32305d61ce81a Mon Sep 17 00:00:00 2001 From: adalpari Date: Mon, 15 Sep 2025 15:18:06 +0200 Subject: [PATCH 08/10] detekt --- .../main/java/org/wordpress/android/util/image/ImageManager.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/WordPress/src/main/java/org/wordpress/android/util/image/ImageManager.kt b/WordPress/src/main/java/org/wordpress/android/util/image/ImageManager.kt index 0f8d0a4157f4..10338de2b94b 100644 --- a/WordPress/src/main/java/org/wordpress/android/util/image/ImageManager.kt +++ b/WordPress/src/main/java/org/wordpress/android/util/image/ImageManager.kt @@ -41,7 +41,6 @@ import com.bumptech.glide.request.target.ViewTarget import com.bumptech.glide.request.transition.Transition import com.bumptech.glide.signature.ObjectKey import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.time.withTimeout import org.wordpress.android.WordPress import org.wordpress.android.networking.MShot import org.wordpress.android.ui.media.VideoLoader From 648b2b112d15ddde633894d210d21492a97b879e Mon Sep 17 00:00:00 2001 From: adalpari Date: Thu, 18 Sep 2025 11:17:56 +0200 Subject: [PATCH 09/10] Loading the original image --- .../android/ui/reader/views/ReaderPhotoView.java | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/views/ReaderPhotoView.java b/WordPress/src/main/java/org/wordpress/android/ui/reader/views/ReaderPhotoView.java index d9b2fb24982c..be6975bc1759 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/reader/views/ReaderPhotoView.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/views/ReaderPhotoView.java @@ -29,16 +29,11 @@ * but adds pinch/zoom and the ability to first load a lo-res version of the image */ public class ReaderPhotoView extends RelativeLayout { - public static final double FALLBACK_RESOLUTION_MULTIPLIER = 0.5; - public interface PhotoViewListener { void onTapPhotoView(); } private String mImageUrl; - private int mHiResWidth; - private boolean mIsPrivate; - private boolean mIsPrivateAtSite; private PhotoViewListener mPhotoViewListener; private String mLoResImageUrl; @@ -83,9 +78,6 @@ public void setImageUrl(String imageUrl, boolean isPrivateAtSite, PhotoViewListener listener) { mImageUrl = imageUrl; - mHiResWidth = hiResWidth; - mIsPrivate = isPrivate; - mIsPrivateAtSite = isPrivateAtSite; int loResWidth = (int) (hiResWidth * 0.10f); mLoResImageUrl = ReaderUtils @@ -168,12 +160,9 @@ private void loadFallbackImage() { if (!hasLayout()) { return; } - String resImageUrl = ReaderUtils.getResizedImageUrl(mImageUrl, - (int) (mHiResWidth * FALLBACK_RESOLUTION_MULTIPLIER), 0, mIsPrivate, - mIsPrivateAtSite, PhotonUtils.Quality.MEDIUM); - + // Load the original image URL mImageManager - .loadWithResultListener(mImageView, ImageType.IMAGE, resImageUrl, ScaleType.CENTER, mLoResImageUrl, + .loadWithResultListener(mImageView, ImageType.IMAGE, mImageUrl, ScaleType.CENTER, mLoResImageUrl, new RequestListener<>() { @Override public void onLoadFailed(@Nullable Exception e, @Nullable Object model) { From 0248cb3ad84630670793bf745d7d62389888ca61 Mon Sep 17 00:00:00 2001 From: adalpari Date: Thu, 18 Sep 2025 11:22:59 +0200 Subject: [PATCH 10/10] tracking the error --- .../android/ui/reader/views/ReaderPhotoView.java | 11 +++++++++++ .../wordpress/android/analytics/AnalyticsTracker.java | 3 ++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/views/ReaderPhotoView.java b/WordPress/src/main/java/org/wordpress/android/ui/reader/views/ReaderPhotoView.java index be6975bc1759..100ba53723c8 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/reader/views/ReaderPhotoView.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/views/ReaderPhotoView.java @@ -17,6 +17,8 @@ import com.github.chrisbanes.photoview.PhotoViewAttacher; import org.wordpress.android.R; +import org.wordpress.android.analytics.AnalyticsTracker; +import org.wordpress.android.analytics.AnalyticsTracker.Stat; import org.wordpress.android.ui.reader.utils.ReaderUtils; import org.wordpress.android.util.AppLog; import org.wordpress.android.util.PhotonUtils; @@ -24,11 +26,16 @@ import org.wordpress.android.util.image.ImageManager.RequestListener; import org.wordpress.android.util.image.ImageType; +import java.util.HashMap; +import java.util.Map; + /** * used by ReaderPhotoViewerActivity to show full-width images - based on Volley's ImageView * but adds pinch/zoom and the ability to first load a lo-res version of the image */ public class ReaderPhotoView extends RelativeLayout { + private static final String FAILED_IMAGE = "failed_image"; + public interface PhotoViewListener { void onTapPhotoView(); } @@ -125,6 +132,10 @@ public void onLoadFailed(@Nullable Exception e, @Nullable Object model) { mImageView.post( () -> loadFallbackImage() ); + // Track the error to better understand the root of the issue + Map properties = new HashMap<>(); + properties.put(FAILED_IMAGE, mHiResImageUrl); + AnalyticsTracker.track(Stat.IMAGE_LOADING_TIMEOUT, properties); } else { boolean lowResNotLoadedYet = isLoading(); if (lowResNotLoadedYet) { diff --git a/libs/analytics/src/main/java/org/wordpress/android/analytics/AnalyticsTracker.java b/libs/analytics/src/main/java/org/wordpress/android/analytics/AnalyticsTracker.java index 97f1170b357d..c063dd6e0160 100644 --- a/libs/analytics/src/main/java/org/wordpress/android/analytics/AnalyticsTracker.java +++ b/libs/analytics/src/main/java/org/wordpress/android/analytics/AnalyticsTracker.java @@ -1173,7 +1173,8 @@ public enum Stat { BACKGROUND_REST_AUTODISCOVERY_FAILED, WP_ANDROID_APPLICATION_PASSWORD_LOGIN, JP_ANDROID_APPLICATION_PASSWORD_LOGIN, - APPLICATION_PASSWORD_SET_OFF; + APPLICATION_PASSWORD_SET_OFF, + IMAGE_LOADING_TIMEOUT; /* * Please set the event name in the enum only if the new Stat's name in lower case does not match it. * In that case you also need to add the event in the `AnalyticsTrackerNosaraTest.specialNames` map.