Skip to content

Commit 71585ff

Browse files
Barney Mossmeta-codesync[bot]
authored andcommitted
Show ImageSource type and per-source URLs in the Android image debug overlay Info tab
Reviewed By: oprisnik Differential Revision: D110602596 fbshipit-source-id: 3efd10f100a334b18ea07750a1423c6f7747a627
1 parent f69d9b4 commit 71585ff

5 files changed

Lines changed: 165 additions & 157 deletions

File tree

vito/tools/liveeditor/build.gradle

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
import com.facebook.fresco.buildsrc.Deps
99
import com.facebook.fresco.buildsrc.GradleDeps
10+
import com.facebook.fresco.buildsrc.TestDeps
1011

1112
apply plugin: 'com.android.library'
1213
apply plugin: 'kotlin-android'
@@ -34,6 +35,13 @@ dependencies {
3435
implementation project(':vito:view')
3536

3637
implementation Deps.AndroidX.core
38+
39+
testImplementation TestDeps.junit
40+
testImplementation TestDeps.assertjCore
41+
testImplementation(TestDeps.robolectric) {
42+
exclude group: 'commons-logging', module: 'commons-logging'
43+
exclude group: 'org.apache.httpcomponents', module: 'httpclient'
44+
}
3745
}
3846

3947
android {

vito/tools/liveeditor/src/main/java/com/facebook/fresco/vito/tools/liveeditor/ImageSourceParser.kt

Lines changed: 0 additions & 149 deletions
This file was deleted.

vito/tools/liveeditor/src/main/java/com/facebook/fresco/vito/tools/liveeditor/ImageSourceSyntaxException.kt

Lines changed: 0 additions & 5 deletions
This file was deleted.

vito/tools/liveeditor/src/main/java/com/facebook/fresco/vito/tools/liveeditor/LiveEditorUiUtils.kt

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,15 @@ import com.facebook.fresco.vito.core.impl.debug.DebugOverlayImageOriginColor
2727
import com.facebook.fresco.vito.core.impl.debug.StringDebugDataProvider
2828
import com.facebook.fresco.vito.core.impl.obtainExtras
2929
import com.facebook.fresco.vito.options.ImageOptions
30+
import com.facebook.fresco.vito.source.BitmapImageSource
31+
import com.facebook.fresco.vito.source.ColorImageSource
32+
import com.facebook.fresco.vito.source.DrawableImageSource
33+
import com.facebook.fresco.vito.source.DrawableResImageSource
34+
import com.facebook.fresco.vito.source.EmptyImageSource
35+
import com.facebook.fresco.vito.source.FirstAvailableImageSource
36+
import com.facebook.fresco.vito.source.ImageSource
37+
import com.facebook.fresco.vito.source.IncreasingQualityImageSource
38+
import com.facebook.fresco.vito.source.UriImageSource
3039

3140
class LiveEditorUiUtils(
3241
var liveEditor: ImageLiveEditor?,
@@ -189,9 +198,8 @@ class LiveEditorUiUtils(
189198
// 0. Cache source indicator (color-coded emoji)
190199
addView(createCacheSourceIndicator(context))
191200

192-
// 1. ImageSource info
193-
var info: List<Pair<String, String>> =
194-
ImageSourceParser.convertSourceToKeyValue(liveEditor?.getSource().toString())
201+
// 1. ImageSource info — typed + recursive (source type + per-source URLs).
202+
var info: List<Pair<String, String>> = sourceInfoRows(liveEditor?.getSource())
195203

196204
// 2. Debug provider data
197205
liveEditor?.let { liveEditorNonNull ->
@@ -380,5 +388,38 @@ class LiveEditorUiUtils(
380388
)
381389

382390
internal fun Int.dpToPx(context: Context): Int = dpToPxF(context).toInt()
391+
392+
/**
393+
* Flattens an [ImageSource] into Info-tab rows: a "Source type" row (top-level
394+
* [ImageSource.getClassNameString]) followed by one row per constituent source. Composites are
395+
* unwrapped recursively — [IncreasingQualityImageSource] into low-res/high-res rows,
396+
* [FirstAvailableImageSource] into indexed rows. Reads typed accessors directly instead of the
397+
* fragile toString parse (URLs contain `=`/`,` which that parser shredded). Custom or future
398+
* subtypes not handled above fall back to a [Object.toString] row so nothing is silently
399+
* dropped during debugging. Empty for null.
400+
*/
401+
internal fun sourceInfoRows(source: ImageSource?): List<Pair<String, String>> {
402+
if (source == null) return emptyList()
403+
return listOf("Source type" to source.getClassNameString()) + sourceUrlRows(source, "")
404+
}
405+
406+
private fun sourceUrlRows(source: ImageSource, suffix: String): List<Pair<String, String>> =
407+
when (source) {
408+
is IncreasingQualityImageSource ->
409+
sourceUrlRows(source.lowResSource, "$suffix (low-res)") +
410+
sourceUrlRows(source.highResSource, "$suffix (high-res)")
411+
is FirstAvailableImageSource ->
412+
source.imageSources.flatMapIndexed { i, inner ->
413+
sourceUrlRows(inner, "$suffix (${i + 1}/${source.imageSources.size})")
414+
}
415+
is UriImageSource -> listOf("Image URL$suffix" to source.imageUri.toString())
416+
is BitmapImageSource ->
417+
listOf("Image (bitmap)$suffix" to "${source.bitmap.width}x${source.bitmap.height}")
418+
is ColorImageSource -> listOf("Image (color)$suffix" to "#%08X".format(source.color))
419+
is DrawableResImageSource -> listOf("Image (resource)$suffix" to "resId=${source.resId}")
420+
is DrawableImageSource -> listOf("Image (drawable)$suffix" to source.drawable.toString())
421+
is EmptyImageSource -> emptyList()
422+
else -> listOf("Image (source)$suffix" to source.toString())
423+
}
383424
}
384425
}
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
package com.facebook.fresco.vito.tools.liveeditor
9+
10+
import android.graphics.Bitmap
11+
import android.net.Uri
12+
import com.facebook.fresco.vito.source.ImageSource
13+
import com.facebook.fresco.vito.source.ImageSourceProvider
14+
import com.facebook.fresco.vito.tools.liveeditor.LiveEditorUiUtils.Companion.sourceInfoRows
15+
import org.assertj.core.api.Assertions.assertThat
16+
import org.junit.Test
17+
import org.junit.runner.RunWith
18+
import org.robolectric.RobolectricTestRunner
19+
20+
@RunWith(RobolectricTestRunner::class)
21+
class LiveEditorUiUtilsTest {
22+
23+
@Test
24+
fun testSourceInfoRows_whenSingleUriSource_thenReturnsTypeAndUrl() {
25+
val url = "https://scontent.example/x.jpg?a=1&_nc_ohc=abc,def"
26+
val source = ImageSourceProvider.forUri(Uri.parse(url))
27+
assertThat(sourceInfoRows(source))
28+
.containsExactly("Source type" to "SingleImageSource", "Image URL" to url)
29+
}
30+
31+
@Test
32+
fun testSourceInfoRows_whenIncreasingQuality_thenReturnsLowAndHighRows() {
33+
val low = "https://scontent.example/low.jpg?a=1"
34+
val high = "https://scontent.example/high.jpg?b=2,3"
35+
val source = ImageSourceProvider.increasingQuality(Uri.parse(low), Uri.parse(high))
36+
assertThat(sourceInfoRows(source))
37+
.containsExactly(
38+
"Source type" to "IncreasingQualityImageSource",
39+
"Image URL (low-res)" to low,
40+
"Image URL (high-res)" to high,
41+
)
42+
}
43+
44+
@Test
45+
fun testSourceInfoRows_whenFirstAvailable_thenReturnsIndexedRows() {
46+
val a = "https://scontent.example/a.jpg"
47+
val b = "https://scontent.example/b.jpg?x=1,2"
48+
val source =
49+
ImageSourceProvider.firstAvailable(
50+
ImageSourceProvider.forUri(Uri.parse(a)),
51+
ImageSourceProvider.forUri(Uri.parse(b)),
52+
)
53+
assertThat(sourceInfoRows(source))
54+
.containsExactly(
55+
"Source type" to "FirstAvailableImageSource",
56+
"Image URL (1/2)" to a,
57+
"Image URL (2/2)" to b,
58+
)
59+
}
60+
61+
@Test
62+
fun testSourceInfoRows_whenEmptySource_thenReturnsSingleTypeRow() {
63+
val rows = sourceInfoRows(ImageSourceProvider.emptySource())
64+
assertThat(rows).hasSize(1)
65+
assertThat(rows[0].first).isEqualTo("Source type")
66+
}
67+
68+
@Test
69+
fun testSourceInfoRows_whenNull_thenReturnsEmpty() {
70+
assertThat(sourceInfoRows(null)).isEmpty()
71+
}
72+
73+
@Test
74+
fun testSourceInfoRows_whenNestedComposite_thenSuffixesAccumulateOuterFirst() {
75+
val a = "https://scontent.example/a.jpg"
76+
val low = "https://scontent.example/low.jpg?a=1"
77+
val high = "https://scontent.example/high.jpg?b=2,3"
78+
val source =
79+
ImageSourceProvider.firstAvailable(
80+
ImageSourceProvider.forUri(Uri.parse(a)),
81+
ImageSourceProvider.increasingQuality(Uri.parse(low), Uri.parse(high)),
82+
)
83+
assertThat(sourceInfoRows(source))
84+
.containsExactly(
85+
"Source type" to "FirstAvailableImageSource",
86+
"Image URL (1/2)" to a,
87+
"Image URL (2/2) (low-res)" to low,
88+
"Image URL (2/2) (high-res)" to high,
89+
)
90+
}
91+
92+
@Test
93+
fun testSourceInfoRows_whenBitmapLeaf_thenReturnsBitmapDimensions() {
94+
val source = ImageSourceProvider.bitmap(Bitmap.createBitmap(4, 2, Bitmap.Config.ARGB_8888))
95+
assertThat(sourceInfoRows(source))
96+
.containsExactly("Source type" to "BitmapImageSource", "Image (bitmap)" to "4x2")
97+
}
98+
99+
@Test
100+
fun testSourceInfoRows_whenUnknownSource_thenFallsBackToToString() {
101+
val source =
102+
object : ImageSource {
103+
override fun getClassNameString(): String = "CustomTestImageSource"
104+
105+
override fun toString(): String = "CustomTestImageSource(uri=custom://x)"
106+
}
107+
assertThat(sourceInfoRows(source))
108+
.containsExactly(
109+
"Source type" to "CustomTestImageSource",
110+
"Image (source)" to "CustomTestImageSource(uri=custom://x)",
111+
)
112+
}
113+
}

0 commit comments

Comments
 (0)