From b92961ea41284e76ae4d952d0dd3051054b3962b Mon Sep 17 00:00:00 2001 From: Anton Malinskiy Date: Thu, 14 May 2020 22:28:40 +1000 Subject: [PATCH] PR process setup (#1) * build(workflow): update action-android + upgrade deployment setup * fix(workflow): build.prop is not available on emulator version 26. try other test file * feat(framebuffer): handle protocol version 2 * fix(test): rewrite FileE2ETest pulling --- .github/workflows/ci.yaml | 28 ++-- .../workflows/deploy-sonatype-snapshot.yaml | 22 ++++ .idea/misc.xml | 5 +- .../com/malinskiy/adam/integration/E2ETest.kt | 17 +-- .../malinskiy/adam/integration/FileE2ETest.kt | 32 ++++- .../com/malinskiy/adam/rule/AdbDeviceRule.kt | 30 ++++- .../adam/request/sync/PullFileRequest.kt | 5 +- .../malinskiy/adam/request/sync/RawImage.kt | 121 ++++++++++++++---- .../adam/request/sync/ScreenCaptureRequest.kt | 9 +- .../resources/colorProfiles/DisplayP3.icc | Bin 0 -> 584 bytes src/main/resources/colorProfiles/sRGB.icc | Bin 0 -> 3144 bytes .../adam/request/sync/PullFileRequestTest.kt | 5 +- 12 files changed, 207 insertions(+), 67 deletions(-) create mode 100644 .github/workflows/deploy-sonatype-snapshot.yaml create mode 100644 src/main/resources/colorProfiles/DisplayP3.icc create mode 100644 src/main/resources/colorProfiles/sRGB.icc diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 3955f9c70..db0697406 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -9,7 +9,7 @@ jobs: uses: actions/setup-java@v1 with: java-version: 1.8 - - uses: malinskiy/action-android/install-sdk@release/0.0.4 + - uses: malinskiy/action-android/install-sdk@release/0.0.6 - name: gradle test jacocoTestReport run: ./gradlew test jacocoTestReport - name: Save test output @@ -38,9 +38,10 @@ jobs: uses: actions/setup-java@v1 with: java-version: 1.8 - - uses: malinskiy/action-android/install-sdk@release/0.0.4 + - uses: malinskiy/action-android/install-sdk@release/0.0.6 - name: integration test - uses: malinskiy/action-android/emulator-run-cmd@release/0.0.4 + uses: malinskiy/action-android/emulator-run-cmd@release/0.0.6 + timeout-minutes: 20 with: cmd: ./gradlew integrationTest api: ${{ matrix.api }} @@ -59,22 +60,13 @@ jobs: with: name: integration-test-coverage path: build/reports/jacoco/jacocoIntegrationTestReport/html + - name: Save logcat output + uses: actions/upload-artifact@master + if: failure() + with: + name: logcat + path: artifacts/logcat.log - name: codecov integartion tests run: bash <(curl -s https://codecov.io/bash) -f ./build/reports/jacoco/jacocoIntegrationTestReport/jacocoIntegrationTestReport.xml -F integration env: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} - deploy: - needs: test - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v1 - - name: Set up JDK 1.8 - uses: actions/setup-java@v1 - with: - java-version: 1.8 - - name: deploy-snapshot - run: bash .buildsystem/deploy-sonatype.sh - env: - SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }} - SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }} - GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} diff --git a/.github/workflows/deploy-sonatype-snapshot.yaml b/.github/workflows/deploy-sonatype-snapshot.yaml new file mode 100644 index 000000000..237e7c072 --- /dev/null +++ b/.github/workflows/deploy-sonatype-snapshot.yaml @@ -0,0 +1,22 @@ +name: deploy-sonatype-snapshot +on: + push: + branches: + - master + tags-ignore: + - '*' +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - name: Set up JDK 1.8 + uses: actions/setup-java@v1 + with: + java-version: 1.8 + - name: deploy-snapshot + run: bash .buildsystem/deploy-sonatype.sh + env: + SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }} + SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }} + GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index aecc2806d..c3df5ec6d 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,5 +1,8 @@ - + + + + \ No newline at end of file diff --git a/src/integrationTest/kotlin/com/malinskiy/adam/integration/E2ETest.kt b/src/integrationTest/kotlin/com/malinskiy/adam/integration/E2ETest.kt index fbc82f68b..afeece166 100644 --- a/src/integrationTest/kotlin/com/malinskiy/adam/integration/E2ETest.kt +++ b/src/integrationTest/kotlin/com/malinskiy/adam/integration/E2ETest.kt @@ -27,7 +27,6 @@ import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.runBlocking import org.junit.Rule import org.junit.Test -import java.awt.image.BufferedImage import java.io.File import java.io.IOException import javax.imageio.ImageIO @@ -44,21 +43,9 @@ class E2ETest { val image = adbRule.adb.execute( ScreenCaptureRequest(), adbRule.deviceSerial - ) - - val finalImage = BufferedImage(image.width, image.height, BufferedImage.TYPE_INT_ARGB) - - var index = 0 - val increment = image.bitsPerPixel shr 3 - for (y in 0 until image.height) { - for (x in 0 until image.width) { - val value = image.getARGB(index) - index += increment - finalImage.setRGB(x, y, value) - } - } + ).toBufferedImage() - if (!ImageIO.write(finalImage, "png", File("/tmp/screen.png"))) { + if (!ImageIO.write(image, "png", File("/tmp/screen.png"))) { throw IOException("Failed to find png writer") } } diff --git a/src/integrationTest/kotlin/com/malinskiy/adam/integration/FileE2ETest.kt b/src/integrationTest/kotlin/com/malinskiy/adam/integration/FileE2ETest.kt index 52cf4585c..d0e6104fb 100644 --- a/src/integrationTest/kotlin/com/malinskiy/adam/integration/FileE2ETest.kt +++ b/src/integrationTest/kotlin/com/malinskiy/adam/integration/FileE2ETest.kt @@ -17,6 +17,7 @@ package com.malinskiy.adam.integration import assertk.assertThat +import assertk.assertions.contains import assertk.assertions.isEqualTo import com.malinskiy.adam.extension.md5 import com.malinskiy.adam.request.sync.PullFileRequest @@ -27,6 +28,8 @@ import com.malinskiy.adam.rule.AdbDeviceRule import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.channels.receiveOrNull import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.withTimeout +import org.junit.After import org.junit.Rule import org.junit.Test import java.io.File @@ -88,12 +91,33 @@ class FileE2ETest { } } + @After + fun cleanup() { + runBlocking { + adbRule.adb.execute(ShellCommandRequest("rm /data/local/tmp/testfile"), serial = adbRule.deviceSerial) + } + } + @Test fun testFilePulling() { runBlocking { - val testFile = File("/tmp/build.prop") + val testFile = createTempFile() + + withTimeout(10_000) { + while (true) { + var output = + adbRule.adb.execute(ShellCommandRequest("echo cafebabe > /data/local/tmp/testfile"), serial = adbRule.deviceSerial) + println(output) + output = adbRule.adb.execute(ShellCommandRequest("cat /data/local/tmp/testfile"), serial = adbRule.deviceSerial) + println(output) + if (output.contains("cafebabe")) { + break + } + } + } + val channel = adbRule.adb.execute( - PullFileRequest("/system/build.prop", testFile), + PullFileRequest("/data/local/tmp/testfile", testFile), GlobalScope, adbRule.deviceSerial ) @@ -110,7 +134,7 @@ class FileE2ETest { } println() - val sizeString = adbRule.adb.execute(ShellCommandRequest("ls -ln /system/build.prop"), adbRule.deviceSerial) + val sizeString = adbRule.adb.execute(ShellCommandRequest("ls -ln /data/local/tmp/testfile"), adbRule.deviceSerial) val split = sizeString.split(" ").filter { it != "" } /** @@ -120,6 +144,8 @@ class FileE2ETest { */ val dateIndex = split.indexOfLast { it.matches("[\\d]{4}-[\\d]{2}-[\\d]{2}".toRegex()) } assertThat(split[dateIndex - 1].toLong()).isEqualTo(testFile.length()) + + assertThat(testFile.readText()).contains("cafebabe") } } } \ No newline at end of file diff --git a/src/integrationTest/kotlin/com/malinskiy/adam/rule/AdbDeviceRule.kt b/src/integrationTest/kotlin/com/malinskiy/adam/rule/AdbDeviceRule.kt index c8337e519..742f3cff6 100644 --- a/src/integrationTest/kotlin/com/malinskiy/adam/rule/AdbDeviceRule.kt +++ b/src/integrationTest/kotlin/com/malinskiy/adam/rule/AdbDeviceRule.kt @@ -18,20 +18,48 @@ package com.malinskiy.adam.rule import com.malinskiy.adam.AndroidDebugBridgeClientFactory import com.malinskiy.adam.interactor.StartAdbInteractor +import com.malinskiy.adam.request.devices.Device +import com.malinskiy.adam.request.devices.DeviceState +import com.malinskiy.adam.request.devices.ListDevicesRequest +import com.malinskiy.adam.request.sync.GetSinglePropRequest import kotlinx.coroutines.runBlocking import org.junit.rules.TestRule import org.junit.runner.Description import org.junit.runners.model.Statement +import java.net.ConnectException class AdbDeviceRule : TestRule { val deviceSerial = "emulator-5554" val adb = AndroidDebugBridgeClientFactory().build() override fun apply(base: Statement?, description: Description?): Statement { - return object: Statement() { + return object : Statement() { override fun evaluate() { runBlocking { StartAdbInteractor().execute() + + //Wait for device to be available on adb server + while (true) { + try { + val output = adb.execute(ListDevicesRequest()) + if (output.contains(Device(deviceSerial, DeviceState.DEVICE))) { + break + } + } catch (e: ConnectException) { + continue + } + } + + //Wait for device boot + while (true) { + try { + val completed = adb.execute(GetSinglePropRequest("sys.boot_completed")) + if (completed.isNotEmpty()) break + continue + } catch (e: ConnectException) { + continue + } + } } base?.evaluate() } diff --git a/src/main/kotlin/com/malinskiy/adam/request/sync/PullFileRequest.kt b/src/main/kotlin/com/malinskiy/adam/request/sync/PullFileRequest.kt index 35a3649ed..7c67574cf 100644 --- a/src/main/kotlin/com/malinskiy/adam/request/sync/PullFileRequest.kt +++ b/src/main/kotlin/com/malinskiy/adam/request/sync/PullFileRequest.kt @@ -109,7 +109,10 @@ class PullFileRequest( return currentPosition.toDouble() / totalBytes } header.contentEquals(Const.Message.FAIL) -> { - throw PullFailedException("Failed to pull file $remotePath") + val size = headerBuffer.copyOfRange(4, 8).toInt() + readChannel.readFully(dataBuffer, 0, size) + val errorMessage = String(dataBuffer, 0, size) + throw PullFailedException("Failed to pull file $remotePath: $errorMessage") } else -> { throw UnsupportedSyncProtocolException("Unexpected header message ${String(header, Const.DEFAULT_TRANSPORT_ENCODING)}") diff --git a/src/main/kotlin/com/malinskiy/adam/request/sync/RawImage.kt b/src/main/kotlin/com/malinskiy/adam/request/sync/RawImage.kt index 97102e826..b5d3d0533 100644 --- a/src/main/kotlin/com/malinskiy/adam/request/sync/RawImage.kt +++ b/src/main/kotlin/com/malinskiy/adam/request/sync/RawImage.kt @@ -17,12 +17,20 @@ package com.malinskiy.adam.request.sync import com.malinskiy.adam.exception.UnsupportedImageProtocolException +import java.awt.color.ICC_ColorSpace +import java.awt.color.ICC_Profile import java.awt.image.BufferedImage +import java.awt.image.ColorModel +import java.awt.image.DataBuffer +import java.awt.image.DirectColorModel +import java.io.IOException import java.nio.ByteBuffer + data class RawImage( val version: Int, val bitsPerPixel: Int, + val colorSpace: ColorSpace? = null, val size: Int, val width: Int, val height: Int, @@ -43,26 +51,30 @@ data class RawImage( val g: Int val b: Int val a: Int - if (bitsPerPixel == 16) { - value = buffer[index].toInt() and 0x00FF - value = value or (buffer[index + 1].toInt() shl 8 and 0x0FF00) - // RGB565 to RGB888 - // Multiply by 255/31 to convert from 5 bits (31 max) to 8 bits (255) - r = (value.ushr(11) and 0x1f) * 255 / 31 - g = (value.ushr(5) and 0x3f) * 255 / 63 - b = (value and 0x1f) * 255 / 31 - a = 0xFF // force alpha to opaque if there's no alpha value in the framebuffer. - } else if (bitsPerPixel == 32) { - value = buffer[index].toInt() and 0x00FF - value = value or (buffer[index + 1].toInt() and 0x00FF shl 8) - value = value or (buffer[index + 2].toInt() and 0x00FF shl 16) - value = value or (buffer[index + 3].toInt() and 0x00FF shl 24) - r = value.ushr(redOffset) and getMask(redLength) shl 8 - redLength - g = value.ushr(greenOffset) and getMask(greenLength) shl 8 - greenLength - b = value.ushr(blueOffset) and getMask(blueLength) shl 8 - blueLength - a = value.ushr(alphaOffset) and getMask(alphaLength) shl 8 - alphaLength - } else { - throw UnsupportedOperationException("RawImage.getARGB(int) only works in 16 and 32 bit mode.") + when (bitsPerPixel) { + 16 -> { + value = buffer[index].toInt() and 0x00FF + value = value or (buffer[index + 1].toInt() shl 8 and 0x0FF00) + // RGB565 to RGB888 + // Multiply by 255/31 to convert from 5 bits (31 max) to 8 bits (255) + r = (value.ushr(11) and 0x1f) * 255 / 31 + g = (value.ushr(5) and 0x3f) * 255 / 63 + b = (value and 0x1f) * 255 / 31 + a = 0xFF // force alpha to opaque if there's no alpha value in the framebuffer. + } + 32 -> { + value = buffer[index].toInt() and 0x00FF + value = value or (buffer[index + 1].toInt() and 0x00FF shl 8) + value = value or (buffer[index + 2].toInt() and 0x00FF shl 16) + value = value or (buffer[index + 3].toInt() and 0x00FF shl 24) + r = value.ushr(redOffset) and getMask(redLength) shl 8 - redLength + g = value.ushr(greenOffset) and getMask(greenLength) shl 8 - greenLength + b = value.ushr(blueOffset) and getMask(blueLength) shl 8 - blueLength + a = value.ushr(alphaOffset) and getMask(alphaLength) shl 8 - alphaLength + } + else -> { + throw UnsupportedOperationException("RawImage.getARGB(int) only works in 16 and 32 bit mode.") + } } return a shl 24 or (r shl 16) or (g shl 8) or b @@ -73,18 +85,44 @@ data class RawImage( } fun toBufferedImage(): BufferedImage { - val image = - BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB) + val profileName = getProfileName() + val bufferedImage = when (profileName) { + null -> { + BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB) + } + else -> { + var profile: ICC_Profile? = ICC_Profile.getInstance(ICC_ColorSpace.CS_sRGB) + try { + profile = ICC_Profile.getInstance(javaClass.classLoader.getResourceAsStream("colorProfiles/$profileName")) + } catch (e: IOException) { // Ignore + } + val colorSpace = ICC_ColorSpace(profile) + + val colorModel: ColorModel = + DirectColorModel(colorSpace, 32, 0x00ff0000, 0x0000ff00, 0x000000ff, -0x1000000, false, DataBuffer.TYPE_INT) + val raster = colorModel.createCompatibleWritableRaster(width, height) + + BufferedImage(colorModel, raster, colorModel.isAlphaPremultiplied, null) + } + } - var index = 0 val bytesPerPixel = bitsPerPixel shr 3 for (y in 0 until height) { for (x in 0 until width) { - image.setRGB(x, y, getARGB(index) or -0x1000000) - index += bytesPerPixel + bufferedImage.setRGB(x, y, getARGB((x + y * width) * bytesPerPixel)) } } - return image + + return bufferedImage + } + + private fun getProfileName(): String? { + when (colorSpace) { + ColorSpace.UNKNOWN -> return null + ColorSpace.SRGB -> return "sRGB.icc" + ColorSpace.P3 -> return "DisplayP3.icc" + } + return null } companion object { @@ -130,9 +168,40 @@ data class RawImage( alphaLength = bytes.int, buffer = imageBuffer.moveToByteArray() ) + 2 -> RawImage( + version = version, + bitsPerPixel = bytes.int, + colorSpace = ColorSpace.from(bytes.int), + size = bytes.int, + width = bytes.int, + height = bytes.int, + redOffset = bytes.int, + redLength = bytes.int, + blueOffset = bytes.int, + blueLength = bytes.int, + greenOffset = bytes.int, + greenLength = bytes.int, + alphaOffset = bytes.int, + alphaLength = bytes.int, + buffer = imageBuffer.moveToByteArray() + ) else -> throw UnsupportedImageProtocolException(version) } } } +} + +enum class ColorSpace { + UNKNOWN, + SRGB, + P3; + + companion object { + fun from(value: Int) = when (value) { + 1 -> SRGB + 2 -> P3 + else -> UNKNOWN + } + } } \ No newline at end of file diff --git a/src/main/kotlin/com/malinskiy/adam/request/sync/ScreenCaptureRequest.kt b/src/main/kotlin/com/malinskiy/adam/request/sync/ScreenCaptureRequest.kt index 2a6be4ef9..78f9e001e 100644 --- a/src/main/kotlin/com/malinskiy/adam/request/sync/ScreenCaptureRequest.kt +++ b/src/main/kotlin/com/malinskiy/adam/request/sync/ScreenCaptureRequest.kt @@ -32,6 +32,7 @@ class ScreenCaptureRequest : ComplexRequest() { val protocolVersion = protocolBuffer.order(ByteOrder.LITTLE_ENDIAN).int val headerSize = when (protocolVersion) { 1 -> 12 // bpp, size, width, height, 4*(length, offset) + 2 -> 13 // bpp, colorSpace, size, width, height, 4*(length, offset) 16 -> 3 // compatibility mode: size, width, height. used previously to denote framebuffer depth else -> throw UnsupportedImageProtocolException(protocolVersion) } @@ -42,7 +43,13 @@ class ScreenCaptureRequest : ComplexRequest() { headerBuffer.order(ByteOrder.LITTLE_ENDIAN) headerBuffer.rewind() - val imageSize = headerBuffer.getInt(4) + val imageSize = when (protocolVersion) { + 1 -> headerBuffer.getInt(4) + 2 -> headerBuffer.getInt(8) + 16 -> headerBuffer.getInt(0) + else -> throw UnsupportedImageProtocolException(protocolVersion) + } + val imageBuffer = ByteBuffer.allocate(imageSize) headerBuffer.rewind() readChannel.readFully(imageBuffer) diff --git a/src/main/resources/colorProfiles/DisplayP3.icc b/src/main/resources/colorProfiles/DisplayP3.icc new file mode 100644 index 0000000000000000000000000000000000000000..3f0126bef17ee04818ca9daf668f30452c735acd GIT binary patch literal 584 zcmZ{hKS)AR6vjWbQ2aAQA`T93%Rs|)FrpeXup+RmAZ>a+|3&(Iyk~@STa(e!R6#*Y zOQR6z*w7FZO;NNpC%BZ{&V6t24Rzq2^Sj^q&gI_s0P7McMrhUKhMGwwgzaojuz$b` z2R!h?DN35cJv>tBbjl!e?!Umo_fKKoKfjobg#1Tm;gaP}C&A;-Fqs zQq%_V9C7ZTp>RGW_U89=zTvFW1xfF6E*t!;dkp^6J)4;%d47lmsUj8tYk)YEH|b>) z*Q)z;2R-;p+(mh7gQ|~K0E^g#1_f1!XhOgmV%+)9%z{3XM4cv@P-(V|3K}#QgoH_| zQA99}I8lmRb?R49q}?)Pa!m2F`9I(l=&kGD*5Z2@YDS0Wp?3HV%y$@LcSCKZ1Kh>| zXM3mxXMiymaNkx$Rn$+iQMQ&!z}Ge4$pYh#G_#|XVo|+saVLP$H^zDsfc*p*v>5xo NWNdH+*j|CI{0p~hXy^a{ literal 0 HcmV?d00001 diff --git a/src/main/resources/colorProfiles/sRGB.icc b/src/main/resources/colorProfiles/sRGB.icc new file mode 100644 index 0000000000000000000000000000000000000000..7f9d18d097d1bcccb32e6d5743ac4af593170b6f GIT binary patch literal 3144 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 L{PK7vZD!`*_k{EN literal 0 HcmV?d00001 diff --git a/src/test/kotlin/com/malinskiy/adam/request/sync/PullFileRequestTest.kt b/src/test/kotlin/com/malinskiy/adam/request/sync/PullFileRequestTest.kt index 358d1ff83..f67cb2565 100644 --- a/src/test/kotlin/com/malinskiy/adam/request/sync/PullFileRequestTest.kt +++ b/src/test/kotlin/com/malinskiy/adam/request/sync/PullFileRequestTest.kt @@ -22,6 +22,7 @@ import com.malinskiy.adam.Const import com.malinskiy.adam.exception.PullFailedException import com.malinskiy.adam.exception.UnsupportedSyncProtocolException import com.malinskiy.adam.server.AndroidDebugBridgeServer +import io.ktor.utils.io.writeIntLittleEndian import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.channels.receiveOrNull @@ -160,7 +161,9 @@ class PullFileRequestTest : CoroutineScope { assertThat(recvPath).isEqualTo("/sdcard/testfile") output.respond(Const.Message.FAIL) - output.respondDone() + val message = "lorem ipsum" + output.writeIntLittleEndian(message.length) + output.respondData(message.toByteArray(Const.DEFAULT_TRANSPORT_ENCODING)) } val tempFile = createTempFile()