Skip to content

Commit

Permalink
merge dump views children to compose
Browse files Browse the repository at this point in the history
  • Loading branch information
Grigory-Rylov committed Aug 24, 2023
1 parent 6093c21 commit a250087
Show file tree
Hide file tree
Showing 14 changed files with 333 additions and 75 deletions.
21 changes: 9 additions & 12 deletions build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
import org.jetbrains.changelog.markdownToHTML
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile


plugins {
// Java support
id("java")
Expand Down Expand Up @@ -39,17 +35,17 @@ dependencies {
implementation("io.rsocket.broker:rsocket-broker-frames:0.3.0")


implementation ("org.jooq:joor-java-8:0.9.7")
implementation ("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
implementation ("com.google.code.gson:gson:2.8.9")
implementation("org.jooq:joor-java-8:0.9.7")
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
implementation("com.google.code.gson:gson:2.8.9")

implementation ("org.jetbrains.kotlinx:kotlinx-coroutines-swing:1.3.2")
implementation ("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.1")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-swing:1.3.2")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.1")

implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.1")


testImplementation ("junit:junit:4.12")
testImplementation("junit:junit:4.12")
}


Expand All @@ -60,9 +56,10 @@ tasks {
sinceBuild.set(properties("pluginSinceBuild"))
untilBuild.set(properties("pluginUntilBuild"))

changeNotes.set( """
changeNotes.set(
"""
Fixed storing v2 protocol enable state<br>
Added uiautomator dump mode for getting layouts from dumps. Allows to see compose borders.<br>
Added uiautomator dump mode for getting ComposeView's children layouts from dumps. Allows to see compose borders.<br>
"""
)
}
Expand Down
4 changes: 2 additions & 2 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ studioCompilePath=/Applications/Android Studio Preview.app/Contents

pluginGroup = com.github.grishberg.android
pluginName = android-layout-inspector-plugin
pluginVersion = 23.08.23.0
pluginVersion = 23.08.24.0

# See https://plugins.jetbrains.com/docs/intellij/build-number-ranges.html
# for insight into build numbers and IntelliJ Platform versions.
Expand All @@ -31,4 +31,4 @@ gradleVersion = 7.4
# Opt-out flag for bundling Kotlin standard library.
# See https://plugins.jetbrains.com/docs/intellij/kotlin.html#kotlin-standard-library for details.
# suppress inspection "UnusedProperty"
kotlin.stdlib.default.dependency = false
kotlin.stdlib.default.dependency = false
Original file line number Diff line number Diff line change
Expand Up @@ -29,24 +29,18 @@ import javax.imageio.ImageIO
private const val TAG = "LayoutInspectorBridge"

object LayoutInspectorBridge {
@JvmStatic
val V2_MIN_API = 23

@JvmStatic val V2_MIN_API = 23

@JvmStatic
suspend fun captureView(
logger: AppLogger,
window: ClientWindow,
options: LayoutInspectorCaptureOptions,
timeoutInSeconds: Long
logger: AppLogger, window: ClientWindow, options: LayoutInspectorCaptureOptions, timeoutInSeconds: Long
): LayoutInspectorResult {
val hierarchy =
window.loadWindowData(options, timeoutInSeconds, TimeUnit.SECONDS)
?: return LayoutInspectorResult.createErrorResult(
"There was a timeout error capturing the layout data from the device.\n" +
"The device may be too slow, the captured view may be too complex, or the view may contain animations.\n\n" +
"Please retry with a simplified view and ensure the device is responsive."
)
var root: ViewNode?
val hierarchy = window.loadWindowData(options, timeoutInSeconds, TimeUnit.SECONDS)
?: return LayoutInspectorResult.createErrorResult(
"There was a timeout error capturing the layout data from the device.\n" + "The device may be too slow, the captured view may be too complex, or the view may contain animations.\n\n" + "Please retry with a simplified view and ensure the device is responsive."
)
val root: ViewNode?
try {
logger.d("$TAG parse hierarchy")
root = ViewNodeParser.parse(hierarchy, options.version)
Expand All @@ -65,9 +59,7 @@ object LayoutInspectorBridge {
// Get the preview of the root node
logger.d("$TAG Get the preview of the root node")
val preview = window.loadViewImage(
root,
timeoutInSeconds,
TimeUnit.SECONDS
root, timeoutInSeconds, TimeUnit.SECONDS
) ?: return LayoutInspectorResult.createErrorResult("Unable to obtain preview image")
logger.d("$TAG preview downloaded")
val bytes = ByteArrayOutputStream(4096)
Expand All @@ -94,7 +86,6 @@ object LayoutInspectorBridge {
}
return LayoutInspectorResult(
root = root,
dumpViewRoot = null,
data = bytes.toByteArray(),
previewImage = ImageIO.read(ByteArrayInputStream(preview)),
options = options,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,33 +15,26 @@
*/
package com.android.layoutinspector

import com.android.layoutinspector.model.ViewNode
import com.github.grishberg.android.layoutinspector.domain.AbstractViewNode
import com.github.grishberg.android.layoutinspector.domain.DumpViewNode
import java.awt.image.BufferedImage


/**
* Represents result of a capture
* Success: data is not null, and error is the empty string
* Error: data is null, and error a non empty error message
*/
class LayoutInspectorResult(
val root: AbstractViewNode?,
var dumpViewRoot: AbstractViewNode?,
val previewImage: BufferedImage?,
val data: ByteArray?,
val options: LayoutInspectorCaptureOptions?,
val error: String,
) {

companion object {

fun createErrorResult(error: String) = LayoutInspectorResult(
root = null,
dumpViewRoot = null,
previewImage = null,
data = null,
options = null,
error = error
root = null, previewImage = null, data = null, options = null, error = error
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,11 @@ import java.awt.image.BufferedImage
data class LayoutFileData(
val bufferedImage: BufferedImage?,
val node: AbstractViewNode?,
val dumpNode: AbstractViewNode?,
val options: LayoutInspectorCaptureOptions
) {
companion object {
fun fromLayoutInspectorResult(result: LayoutInspectorResult): LayoutFileData {
return LayoutFileData(result.previewImage, result.root, result.dumpViewRoot, result.options!!)
return LayoutFileData(result.previewImage, result.root, result.options!!)
}
}
}
19 changes: 15 additions & 4 deletions src/main/kotlin/com/android/layoutinspector/model/ViewNode.kt
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ data class ViewNode constructor(
override val hash: String,
val namedProperties: Map<String, ViewProperty> = Maps.newHashMap(),
val properties: List<ViewProperty> = Lists.newArrayList(),
override val children: MutableList<ViewNode> = Lists.newArrayList(),
override val children: MutableList<AbstractViewNode> = Lists.newArrayList(),
var displayInfo: DisplayInfo = DisplayInfo(false, false, 0, 0, 0, 0, 0, 0, false, 0f, 0f, 0f, 0f, null),
) : AbstractViewNode {
// If the force state is set, the preview tries to render/hide the view
Expand Down Expand Up @@ -144,13 +144,15 @@ data class ViewNode constructor(
parentVisible = isDrawn
}
for (child in children) {
child.updateNodeDrawn(parentVisible)
isDrawn = isDrawn or (child.isDrawn && child.displayInfo.isVisible)
if (child is ViewNode) {
child.updateNodeDrawn(parentVisible)
isDrawn = isDrawn or (child.isDrawn && child.displayInfo.isVisible)
}
}
}
override fun toString() = "$name@$hash"

override fun getChildAt(childIndex: Int): ViewNode {
override fun getChildAt(childIndex: Int): AbstractViewNode {
return children[childIndex]
}

Expand Down Expand Up @@ -184,6 +186,15 @@ data class ViewNode constructor(
return name
}

fun replaceChildren(newChildren: List<AbstractViewNode>) {
children.clear()
children.addAll(newChildren.map { child -> child.cloneWithNewParent(this) })
}

override fun cloneWithNewParent(newParent: AbstractViewNode): AbstractViewNode {
throw IllegalStateException("I was not ready for this operation =(")
}

override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
* limitations under the License.
*/
package com.android.layoutinspector.parser

import com.android.layoutinspector.LayoutInspectorCaptureOptions
import com.android.layoutinspector.model.LayoutFileData
import com.android.layoutinspector.model.ViewNode
Expand All @@ -24,23 +25,25 @@ import java.io.IOException
import java.io.ObjectInputStream
import java.nio.file.Files
import javax.imageio.ImageIO

object LayoutFileDataParser {

/**
* List of [ViewProperty] to be skipped since the framework won't correctly report their data.
* See ag/64673340
*/
@JvmStatic
val SKIPPED_PROPERTIES = listOf("bg_", "fg_")
@JvmStatic val SKIPPED_PROPERTIES = listOf("bg_", "fg_")

@Throws(IOException::class)
@JvmStatic
fun parseFromFile(file: File): LayoutFileData {
return parseFromBytes(Files.readAllBytes(file.toPath()))
}

@Throws(IOException::class)
@JvmStatic
fun parseFromBytes(
bytes: ByteArray,
skippedProperties: Collection<String> = SKIPPED_PROPERTIES
bytes: ByteArray, skippedProperties: Collection<String> = SKIPPED_PROPERTIES
): LayoutFileData {
val bufferedImage: BufferedImage?
var node: ViewNode? = null
Expand All @@ -61,6 +64,6 @@ object LayoutFileDataParser {
input.readFully(previewBytes)
}
bufferedImage = ImageIO.read(ByteArrayInputStream(previewBytes))
return LayoutFileData(bufferedImage, node, dumpNode = null, options)
return LayoutFileData(bufferedImage, node, options)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ package com.github.grishberg.android.layoutinspector.domain
import javax.swing.tree.TreeNode

interface AbstractViewNode : TreeNode {
val children : List<AbstractViewNode>

val children: List<AbstractViewNode>
val id: String?
val name: String
val locationOnScreenX: Int
Expand All @@ -14,4 +15,6 @@ interface AbstractViewNode : TreeNode {
val hash: String
val typeAsString: String
val text: String?

fun cloneWithNewParent(newParent: AbstractViewNode): AbstractViewNode
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import java.util.Enumeration
import javax.swing.tree.TreeNode

data class DumpViewNode(
val parent: DumpViewNode?,
val parent: AbstractViewNode?,
val pkg: String,
override val name: String,
override val id: String?,
Expand Down Expand Up @@ -36,7 +36,7 @@ data class DumpViewNode(
}
}

fun addChild(child: DumpViewNode) {
fun addChild(child: AbstractViewNode) {
children.add(child)
}

Expand All @@ -53,4 +53,14 @@ data class DumpViewNode(
override fun isLeaf(): Boolean = childCount == 0

override fun children(): Enumeration<out TreeNode> = Collections.enumeration(children)

override fun cloneWithNewParent(newParent: AbstractViewNode): AbstractViewNode {
val cloned = DumpViewNode(newParent, pkg, name, id, globalLeft, globalTop, globalRight, globalBottom, text)

for(child in children) {
cloned.addChild(child.cloneWithNewParent(cloned))
}

return cloned
}
}
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
package com.github.grishberg.android.layoutinspector.process

import com.github.grishberg.android.layoutinspector.domain.AbstractViewNode
import com.github.grishberg.android.layoutinspector.domain.DumpViewNode
import org.xml.sax.Attributes
import org.xml.sax.helpers.DefaultHandler
import java.io.ByteArrayInputStream
import java.io.InputStream
import java.nio.charset.StandardCharsets
import javax.xml.parsers.SAXParser
import javax.xml.parsers.SAXParserFactory
import org.xml.sax.Attributes
import org.xml.sax.helpers.DefaultHandler

class HierarchyDumpParser {

private val BOUNDS_PATTERN = "\\[(\\d+),(\\d+)\\]\\[(\\d+),(\\d+)\\]".toRegex()
fun parseDump(viewDump: String): AbstractViewNode? {

fun parseDump(viewDump: String): DumpViewNode? {
val factory = SAXParserFactory.newInstance()
val saxParser: SAXParser = factory.newSAXParser()
val handler = ViewDumpHandler()
Expand All @@ -24,6 +24,7 @@ class HierarchyDumpParser {
}

private inner class ViewDumpHandler : DefaultHandler() {

var state: State? = null

var rootNode: DumpViewNode? = null
Expand Down Expand Up @@ -51,6 +52,9 @@ class HierarchyDumpParser {

if (qName == "node") {
rootNode?.parent?.let {
if (it !is DumpViewNode) {
throw IllegalStateException()
}
rootNode = it
}
}
Expand All @@ -62,6 +66,7 @@ class HierarchyDumpParser {
}

private interface State {

fun processAttributes(attributes: Attributes)

fun characters(ch: CharArray, start: Int, length: Int)
Expand Down Expand Up @@ -90,8 +95,15 @@ class HierarchyDumpParser {
val globalBounds = attributes.getValue("bounds")
val rectBounds = parseBounds(globalBounds)
newNode = DumpViewNode(
parent = parentNode, pkg = pkg, name = className, id = parseId(id),
rectBounds.left, rectBounds.top, rectBounds.right, rectBounds.bottom, attributes.getValue("text")
parent = parentNode,
pkg = pkg,
name = className,
id = parseId(id),
rectBounds.left,
rectBounds.top,
rectBounds.right,
rectBounds.bottom,
attributes.getValue("text")
)
parentNode?.addChild(newNode)
}
Expand All @@ -118,13 +130,9 @@ class HierarchyDumpParser {
)
}

override fun characters(ch: CharArray, start: Int, length: Int) {
println(ch)
}
override fun characters(ch: CharArray, start: Int, length: Int) = Unit

override fun endElement(uri: String, localName: String, qName: String) {
println(qName)
}
override fun endElement(uri: String, localName: String, qName: String) = Unit

fun createNode(): DumpViewNode {
return newNode
Expand Down
Loading

0 comments on commit a250087

Please sign in to comment.