Skip to content

Commit

Permalink
Merge pull request #36 from Grigory-Rylov/fix_giraffe_support
Browse files Browse the repository at this point in the history
Fix giraffe support
  • Loading branch information
Grigory-Rylov authored Aug 17, 2023
2 parents c3bdd08 + 2aef667 commit 0f73577
Show file tree
Hide file tree
Showing 9 changed files with 137 additions and 119 deletions.
4 changes: 3 additions & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,9 @@ dependencies {
implementation ("com.google.code.gson:gson:2.8.6")

implementation ("org.jetbrains.kotlinx:kotlinx-coroutines-swing:1.3.2")
implementation ("org.jetbrains.kotlinx:kotlinx-coroutines-core: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")
Expand Down
6 changes: 3 additions & 3 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,16 @@ studioCompilePath=/Applications/Android Studio Preview.app/Contents

pluginGroup = com.github.grishberg.android
pluginName = android-layout-inspector-plugin
pluginVersion = 23.05.11.0
pluginVersion = 23.08.16.1

# See https://plugins.jetbrains.com/docs/intellij/build-number-ranges.html
# for insight into build numbers and IntelliJ Platform versions.
pluginSinceBuild = 223
pluginUntilBuild = 223.*
pluginUntilBuild = 231.*

# IntelliJ Platform Properties -> https://github.com/JetBrains/gradle-intellij-plugin#intellij-platform-properties
platformType = AI
platformVersion = 2022.3.1.13
platformVersion = 2023.1.1.16

# Plugin Dependencies -> https://plugins.jetbrains.com/docs/intellij/plugin-dependencies.html
# Example: platformPlugins = com.intellij.java, com.jetbrains.php:203.4449.22
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,18 +33,19 @@ object LayoutInspectorBridge {
val V2_MIN_API = 23

@JvmStatic
fun captureView(
suspend fun captureView(
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."
)
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?
try {
logger.d("$TAG parse hierarchy")
Expand All @@ -54,7 +55,7 @@ object LayoutInspectorBridge {
} catch (e: StringIndexOutOfBoundsException) {
return LayoutInspectorResult.createErrorResult("Unexpected error: $e")
} catch (e: IOException) {
return LayoutInspectorResult.createErrorResult( "Unexpected error: $e")
return LayoutInspectorResult.createErrorResult("Unexpected error: $e")
}
if (root == null) {
return LayoutInspectorResult.createErrorResult(
Expand Down Expand Up @@ -95,7 +96,8 @@ object LayoutInspectorBridge {
root = root,
data = bytes.toByteArray(),
previewImage = ImageIO.read(ByteArrayInputStream(preview)),
options= options,
error = "")
options = options,
error = ""
)
}
}
118 changes: 31 additions & 87 deletions src/main/kotlin/com/android/layoutinspector/model/ClientWindow.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,29 +15,26 @@
*/
package com.android.layoutinspector.model

import com.android.ddmlib.ByteBufferUtil
import com.android.ddmlib.Client
import com.android.ddmlib.ClientData
import com.android.ddmlib.DebugViewDumpHandler
import com.android.ddmlib.internal.ClientImpl
import com.android.ddmlib.internal.jdwp.chunkhandler.ChunkHandler
import com.android.ddmlib.internal.jdwp.chunkhandler.HandleViewDebug
import com.android.layoutinspector.LayoutInspectorCaptureOptions
import com.android.layoutinspector.ProtocolVersion
import com.android.layoutinspector.common.AppLogger
import com.google.common.annotations.VisibleForTesting
import com.google.common.collect.Lists
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.first
import java.io.IOException
import java.nio.ByteBuffer
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicReference

/** Represents a root window. */
class ClientWindow(
private val logger: AppLogger,
val title: String,
private val client: Client,
val clientViewInspector: ClientViewInspector = object : ClientViewInspector {}
val clientViewInspector: ClientViewInspector = ModernClientViewInspector()
) {
/**
* Returns the name for the window suitable for displaying on the UI. Returns the class name if
Expand All @@ -55,7 +52,7 @@ class ClientWindow(
}

/** Byte array representing the view hierarchy dump of the window. */
fun loadWindowData(
suspend fun loadWindowData(
options: LayoutInspectorCaptureOptions,
timeout: Long,
unit: TimeUnit
Expand All @@ -71,84 +68,49 @@ class ClientWindow(
)

/** Byte array representing image preview of the provided node. */
fun loadViewImage(node: ViewNode, timeout: Long, unit: TimeUnit): ByteArray? =
suspend fun loadViewImage(node: ViewNode, timeout: Long, unit: TimeUnit): ByteArray? =
clientViewInspector.captureView(logger, client, title, node, timeout, unit)

private class ListViewRootsHandler(
private class ListViewRootsHandlerV2(
private val logger: AppLogger
) : DebugViewDumpHandler(DebugViewDumpHandler.CHUNK_VULW) {
private val myViewRoots = Lists.newCopyOnWriteArrayList<String>()
) : DebugViewDumpHandler() {

private val viewRootsState = MutableStateFlow<List<String>?>(null)

override fun handleViewDebugResult(data: ByteBuffer) {
val viewRoots = mutableListOf<String>()
val nWindows = data.int
for (i in 0 until nWindows) {
repeat(nWindows) {
val len = data.int
myViewRoots.add(ByteBufferUtil.getString(data, len))
}
}

@Throws(IOException::class)
fun getWindows(c: Client, timeout: Long, unit: TimeUnit): List<ClientWindow> {
HandleViewDebug.listViewRoots(c, this)
waitForResult(timeout, unit)
val windows = Lists.newArrayList<ClientWindow>()
for (root in myViewRoots) {
windows.add(ClientWindow(logger, root, c))
viewRoots.add(getString(data, len))
}
return windows
}
}

private class CaptureByteArrayHandler(
private val logger: AppLogger, type: Int
) : DebugViewDumpHandler(type) {
private val mData = AtomicReference<ByteArray>()
override fun handleViewDebugResult(data: ByteBuffer) {
val b = ByteArray(data.remaining())
data.get(b)
mData.set(b)
}

fun getData(timeout: Long, unit: TimeUnit): ByteArray? {
waitForResult(timeout, unit)
return mData.get()
viewRootsState.value = viewRoots
}

suspend fun getWindows(client: Client, timeout: Long, unit: TimeUnit): List<ClientWindow> {
//TODO: check timeouts
client.listViewRoots(this)
val windows = viewRootsState.filterNotNull().first()

private fun handleUnknownChunk(
client: ClientImpl?,
type: Int,
data: ByteBuffer?,
isReply: Boolean,
msgId: Int
) {
if (type == ChunkHandler.CHUNK_FAIL) {
val msg: String
val errorCode: Int = data!!.int
val msgLen: Int = data.int
msg = ByteBufferUtil.getString(data, msgLen)
logger.w("ddms: WARNING: failure code=$errorCode msg=$msg")
} else {
logger.w(
"ddms: WARNING: received unknown chunk " + chunkName(type)
+ ": len=" + data!!.limit() + ", reply=" + isReply
+ ", msgId=0x" + Integer.toHexString(msgId)
)
val result = mutableListOf<ClientWindow>()
for (root in windows) {
result.add(ClientWindow(logger, root, client))
}
logger.w("ddms: client $client, handler $this")
return result
}
}

companion object {
/** Lists all the active window for the current client. */
@Throws(IOException::class)
@JvmStatic
fun getAll(
suspend fun getAllV2(
logger: AppLogger,
client: Client, timeout: Long, unit: TimeUnit
): List<ClientWindow>? {
val cd = client.clientData
return if (cd.hasFeature(ClientData.FEATURE_VIEW_HIERARCHY)) {
ListViewRootsHandler(logger).getWindows(client, timeout, unit)
ListViewRootsHandlerV2(logger).getWindows(client, timeout, unit)
} else null
}

Expand All @@ -167,43 +129,25 @@ class ClientWindow(

@VisibleForTesting
interface ClientViewInspector {
fun dumpViewHierarchy(
suspend fun dumpViewHierarchy(
logger: AppLogger,
client: Client,
title: String,
clientWindowTitle: String,
skipChildren: Boolean,
includeProperties: Boolean,
useV2: Boolean,
timeout: Long,
timeUnit: TimeUnit
): ByteArray? {
val handler = CaptureByteArrayHandler(logger, DebugViewDumpHandler.CHUNK_VURT)
HandleViewDebug.dumpViewHierarchy(
client, title, skipChildren, includeProperties, useV2, handler
)
return try {
handler.getData(timeout, timeUnit)
} catch (e: IOException) {
null
}
}
): ByteArray?

fun captureView(
suspend fun captureView(
logger: AppLogger,
client: Client,
title: String,
clientWindowTitle: String,
node: ViewNode,
timeout: Long,
timeUnit: TimeUnit
): ByteArray? {
val handler = CaptureByteArrayHandler(logger, DebugViewDumpHandler.CHUNK_VUOP)
HandleViewDebug.captureView(client, title, node.toString(), handler)
return try {
handler.getData(timeout, timeUnit)
} catch (e: IOException) {
null
}
}
): ByteArray?
}

override fun toString(): String {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package com.android.layoutinspector.model

import com.android.ddmlib.Client
import com.android.ddmlib.DebugViewDumpHandler
import com.android.layoutinspector.common.AppLogger
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.first
import java.nio.ByteBuffer
import java.util.concurrent.TimeUnit

class ModernClientViewInspector : ClientWindow.ClientViewInspector {
override suspend fun dumpViewHierarchy(
logger: AppLogger,
client: Client,
clientWindowTitle: String,
skipChildren: Boolean,
includeProperties: Boolean,
useV2: Boolean,
timeout: Long,
timeUnit: TimeUnit
): ByteArray? {
val handler = DumpViewHierarchyDebugViewDumpHandler()
client.dumpViewHierarchy(clientWindowTitle, skipChildren, includeProperties, useV2, handler)
return handler.value.filterNotNull().first()
}

private inner class DumpViewHierarchyDebugViewDumpHandler : DebugViewDumpHandler() {
val value = MutableStateFlow<ByteArray?>(null)
override fun handleViewDebugResult(data: ByteBuffer?) {
value.value = data?.array()
}
}

override suspend fun captureView(
logger: AppLogger,
client: Client,
clientWindowTitle: String,
node: ViewNode,
timeout: Long,
timeUnit: TimeUnit
): ByteArray? {
val handler = CaptureViewDebugViewDumpHandler()
client.captureView(clientWindowTitle, node.toString(), handler)
return handler.value.filterNotNull().first()
}

private inner class CaptureViewDebugViewDumpHandler : DebugViewDumpHandler() {
val value = MutableStateFlow<ByteArray?>(null)

override fun handleViewDebugResult(data: ByteBuffer?) {
value.value = data?.array()
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class ClientWindowsProvider(
private val logger: AppLogger
) : ClientWindowsInput {
override suspend fun getClientWindows(options: LayoutRecordOptions): List<ClientWindow> {
return ClientWindow.getAll(
return ClientWindow.getAllV2(
logger,
options.client,
options.timeoutInSeconds.toLong(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,7 @@ class NewLayoutDialog(

try {
val windows =
ClientWindow.getAll(logger, c, settings.clientWindowsTimeout, TimeUnit.SECONDS) ?: emptyList()
ClientWindow.getAllV2(logger, c, settings.clientWindowsTimeout, TimeUnit.SECONDS) ?: emptyList()

if (windows.isNotEmpty()) {
val element = ClientWrapper(c)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package com.github.grishberg.android.layoutinspector.ui.layout

import java.awt.Graphics2D
import java.awt.GraphicsEnvironment
import java.awt.Image
import java.awt.Transparency
import java.awt.image.BufferedImage

Expand All @@ -29,7 +28,7 @@ class ImageHelper {
}

fun copyForClipboard(source: BufferedImage): BufferedImage {
val original: Image = source
val original: BufferedImage = source
val newImage = BufferedImage(
original.getWidth(null), original.getHeight(null), BufferedImage.TYPE_INT_RGB
)
Expand Down
Loading

0 comments on commit 0f73577

Please sign in to comment.