Skip to content

Commit

Permalink
Merge pull request #39 from Grigory-Rylov/view_dumper
Browse files Browse the repository at this point in the history
View dumper
  • Loading branch information
Grigory-Rylov authored Aug 24, 2023
2 parents 559070f + a250087 commit c34483d
Show file tree
Hide file tree
Showing 49 changed files with 2,104 additions and 280 deletions.
23 changes: 10 additions & 13 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.6")
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( """
Added refresh event by Ctrl + r.<br>
Increased window height.<br>
changeNotes.set(
"""
Fixed storing v2 protocol enable state<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.18.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
@@ -0,0 +1,3 @@
package com.android.layoutinspector

class GerHierarchyException: LayoutParserException()
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 Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,29 +15,26 @@
*/
package com.android.layoutinspector

import com.android.layoutinspector.model.ViewNode
import com.github.grishberg.android.layoutinspector.domain.AbstractViewNode
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: ViewNode?,
val root: AbstractViewNode?,
val previewImage: BufferedImage?,
val data: ByteArray?,
val options: LayoutInspectorCaptureOptions?,
val error: String
val error: String,
) {

companion object {

fun createErrorResult(error: String) = LayoutInspectorResult(
root = 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
@@ -0,0 +1,11 @@
package com.android.layoutinspector

open class LayoutParserException : Exception {
constructor() : super()

constructor(message: String) : super(message)

constructor(e: Throwable) : super(e)

constructor(message: String, t: Throwable) : super(message)
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,20 @@ package com.android.layoutinspector.model

import com.android.layoutinspector.LayoutInspectorCaptureOptions
import com.android.layoutinspector.LayoutInspectorResult
import com.github.grishberg.android.layoutinspector.domain.AbstractViewNode
import java.awt.image.BufferedImage

/**
* Data model for a parsed .li file. Create using methods in [com.android.layoutinspector.parser.LayoutFileDataParser]
*/
data class LayoutFileData(
val bufferedImage: BufferedImage?,
val node: ViewNode?,
val node: AbstractViewNode?,
val options: LayoutInspectorCaptureOptions
) {
companion object {
fun fromLayoutInspectorResult(result: LayoutInspectorResult): LayoutFileData {
return LayoutFileData(result.previewImage, result.root, result.options!!)
}
}
}
}
33 changes: 33 additions & 0 deletions src/main/kotlin/com/android/layoutinspector/model/TreePathUtils.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.android.layoutinspector.model

import com.github.grishberg.android.layoutinspector.domain.AbstractViewNode
import com.google.common.collect.Lists
import javax.swing.tree.TreePath

object TreePathUtils {

/** Finds the path from node to the root. */
@JvmStatic
fun getPath(node: AbstractViewNode): TreePath {
return getPathImpl(node, null)
}

/** Finds the path from node to the parent. */
@JvmStatic
fun getPathFromParent(node: AbstractViewNode, root: AbstractViewNode): TreePath {
return getPathImpl(node, root)
}

private fun getPathImpl(node: AbstractViewNode, root: AbstractViewNode?): TreePath {
var node: AbstractViewNode? = node
val nodes = Lists.newArrayList<Any>()
do {
nodes.add(0, node)
node = node?.parent as AbstractViewNode?
} while (node != null && node !== root)
if (root != null && node === root) {
nodes.add(0, root)
}
return TreePath(nodes.toTypedArray())
}
}
100 changes: 33 additions & 67 deletions src/main/kotlin/com/android/layoutinspector/model/ViewNode.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package com.android.layoutinspector.model

import com.github.grishberg.android.layoutinspector.domain.AbstractViewNode
import com.google.common.collect.Lists
import com.google.common.collect.Maps
import java.awt.Rectangle
Expand All @@ -24,22 +25,21 @@ import java.util.LinkedList
import javax.swing.tree.TreeNode
import javax.swing.tree.TreePath

private const val MAX_TEXT_LENGTH = 20

/**
* Represents an Android View object. Holds properties and a previewBox that contains the display area
* of the object on screen.
* Created by parsing view dumps using [com.android.layoutinspector.parser.ViewNodeParser].
*/
// make parent private because it's the same as the getParent method from TreeNode
data class ViewNode constructor(
private val parent: ViewNode?, val name: String, val hash: String,
private val parent: ViewNode?,
override val name: String,
override val hash: String,
val namedProperties: Map<String, ViewProperty> = Maps.newHashMap(),
val properties: List<ViewProperty> = Lists.newArrayList(),
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),
) :
TreeNode {
) : AbstractViewNode {
// If the force state is set, the preview tries to render/hide the view
// (depending on the parent's state)
enum class ForcedState {
Expand All @@ -54,7 +54,7 @@ data class ViewNode constructor(

// default in case properties are not available
var index: Int = 0
var id: String? = null
override var id: String? = null

var isParentVisible: Boolean = false
private set
Expand All @@ -63,8 +63,21 @@ data class ViewNode constructor(
var forcedState: ForcedState = ForcedState.NONE


val locationOnScreenX: Int
val locationOnScreenY: Int
override val locationOnScreenX: Int
override val locationOnScreenY: Int
override val width: Int
get() = displayInfo.width
override val height: Int
get() = displayInfo.height

override val isVisible: Boolean
get() = displayInfo.isVisible

override val typeAsString: String
get() = calculateTypeAsString()

override val text: String?
get() = namedProperties["text:mText"]?.value

init {
val xProperty = getProperty("layout:getLocationOnScreen_x()")
Expand Down Expand Up @@ -131,32 +144,15 @@ data class ViewNode constructor(
parentVisible = isDrawn
}
for (child in children) {
child.updateNodeDrawn(parentVisible)
isDrawn = isDrawn or (child.isDrawn && child.displayInfo.isVisible)
}
}

fun getFormattedName(): String {
val idPrefix = if (id != null && id != "NO_ID") id else null

val text = getText()
val typeAsString = typeAsString()

if (text != null) {
if (idPrefix != null) {
return "$idPrefix ($typeAsString) - \"$text\""
if (child is ViewNode) {
child.updateNodeDrawn(parentVisible)
isDrawn = isDrawn or (child.isDrawn && child.displayInfo.isVisible)
}
return "$typeAsString - \"$text\""
}
if (idPrefix != null) {
return "$idPrefix ($typeAsString)"
}
return typeAsString
}

override fun toString() = "$name@$hash"

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

Expand All @@ -176,31 +172,27 @@ data class ViewNode constructor(
return true
}

override fun isLeaf(): Boolean {
return childCount == 0
}
override fun isLeaf(): Boolean = childCount == 0

override fun children(): Enumeration<out TreeNode> {
return Collections.enumeration(children)
}

fun typeAsString(): String {
private fun calculateTypeAsString(): String {
val lastDotPost = name.lastIndexOf(".")
if (lastDotPost >= 0) {
return name.substring(lastDotPost + 1)
}
return name
}

fun getElliptizedText(text: String): String {
if (text.length <= MAX_TEXT_LENGTH) {
return text
}
return text.substring(0, MAX_TEXT_LENGTH) + ""
fun replaceChildren(newChildren: List<AbstractViewNode>) {
children.clear()
children.addAll(newChildren.map { child -> child.cloneWithNewParent(this) })
}

fun getText(): String? {
return namedProperties["text:mText"]?.value
override fun cloneWithNewParent(newParent: AbstractViewNode): AbstractViewNode {
throw IllegalStateException("I was not ready for this operation =(")
}

override fun equals(other: Any?): Boolean {
Expand Down Expand Up @@ -235,30 +227,4 @@ data class ViewNode constructor(
return result
}

companion object {
/** Finds the path from node to the root. */
@JvmStatic
fun getPath(node: ViewNode): TreePath {
return getPathImpl(node, null)
}

/** Finds the path from node to the parent. */
@JvmStatic
fun getPathFromParent(node: ViewNode, root: ViewNode): TreePath {
return getPathImpl(node, root)
}

private fun getPathImpl(node: ViewNode, root: ViewNode?): TreePath {
var node: ViewNode? = node
val nodes = Lists.newArrayList<Any>()
do {
nodes.add(0, node)
node = node?.parent
} while (node != null && node !== root)
if (root != null && node === root) {
nodes.add(0, root)
}
return TreePath(nodes.toTypedArray())
}
}
}
Loading

0 comments on commit c34483d

Please sign in to comment.