blob: f6ed5cace441b73af6877a028e8bc96ed814fd2a [file] [log] [blame]
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.tools.agent.layoutinspector
import android.content.res.Configuration
import android.graphics.Bitmap
import android.graphics.HardwareRenderer
import android.graphics.Picture
import android.os.Handler
import android.os.Looper
import android.view.AttachInfo
import android.view.View
import android.view.WindowManager
import android.view.inspector.WindowInspector
import com.android.tools.agent.layoutinspector.testing.ResourceEntry
import com.android.tools.agent.layoutinspector.testing.StandardView
import com.android.tools.agent.layoutinspector.testing.StringTable
import com.android.tools.layoutinspector.proto.LayoutInspectorProto
import com.android.tools.profiler.proto.Common
import com.android.tools.transport.AgentRule
import com.google.common.truth.Truth.assertThat
import org.junit.Rule
import org.junit.Test
import org.mockito.ArgumentMatchers.any
import org.mockito.Mockito.`when`
import org.mockito.Mockito.mock
import java.io.ByteArrayOutputStream
import java.lang.management.ManagementFactory
import java.nio.ByteBuffer
import java.util.concurrent.TimeUnit
import java.util.zip.Inflater
class LayoutInspectorServiceTest {
@get:Rule
var agentRule = AgentRule()
private companion object {
init {
System.loadLibrary("jni-test")
}
}
@Test
fun testError() {
LayoutInspectorService.sendErrorMessage("foo")
val event = agentRule.events.poll(5, TimeUnit.SECONDS)
val expected = Common.Event.newBuilder()
// TODO: use ProcessHandle.current().pid() below once we've switched to jdk11
.setPid(ManagementFactory.getRuntimeMXBean().name.substringBefore('@').toInt())
.setGroupId(Common.Event.EventGroupIds.LAYOUT_INSPECTOR_ERROR.number.toLong())
.setKind(Common.Event.Kind.LAYOUT_INSPECTOR)
.setLayoutInspectorEvent(LayoutInspectorProto.LayoutInspectorEvent.newBuilder().setErrorMessage("foo"))
.build()
assertThat(event).isEqualTo(expected)
}
@Test
fun testComponentTreeEvent() {
val picture = Picture()
val pictureBytes = byteArrayOf(1, 2, 3, 4, 5)
picture.setImage(pictureBytes)
val (_, callback) = setUpInspectorService()
val event = onPictureCaptured(callback, picture)
val expectedPid = ManagementFactory.getRuntimeMXBean().name.substringBefore('@').toInt()
assertThat(event.pid).isEqualTo(expectedPid)
assertThat(event.groupId).isEqualTo(1101)
assertThat(event.kind).isEqualTo(Common.Event.Kind.LAYOUT_INSPECTOR)
val tree = event.layoutInspectorEvent.tree
assertThat(tree.payloadType).isEqualTo(LayoutInspectorProto.ComponentTreeEvent.PayloadType.SKP)
val table = StringTable(tree.stringList)
val layout = tree.root
assertThat(layout.drawId).isEqualTo(10)
assertThat(table[layout.viewId]).isEqualTo(ResourceEntry("id", "pck", "linearLayout1"))
assertThat(table[layout.layout]).isEqualTo(ResourceEntry("layout", "pck", "main_activity"))
assertThat(layout.x).isEqualTo(10)
assertThat(layout.y).isEqualTo(50)
assertThat(layout.width).isEqualTo(980)
assertThat(layout.height).isEqualTo(2000)
assertThat(table[layout.className]).isEqualTo("RootLinearLayout")
assertThat(table[layout.packageName]).isEqualTo("android.widget")
assertThat(table[layout.textValue]).isEqualTo("")
assertThat(layout.layoutFlags).isEqualTo(WindowManager.LayoutParams.FLAG_FULLSCREEN
or WindowManager.LayoutParams.FLAG_DIM_BEHIND)
assertThat(layout.subViewCount).isEqualTo(1)
val textView = layout.getSubView(0)
assertThat(textView.drawId).isEqualTo(11)
assertThat(table[textView.viewId]).isEqualTo(ResourceEntry("id", "pck", "textView1"))
assertThat(table[textView.layout]).isEqualTo(ResourceEntry("layout", "pck", "main_activity"))
assertThat(textView.x).isEqualTo(100)
assertThat(textView.y).isEqualTo(200)
assertThat(textView.width).isEqualTo(400)
assertThat(textView.height).isEqualTo(30)
assertThat(table[textView.className]).isEqualTo("TextView")
assertThat(table[textView.packageName]).isEqualTo("android.widget")
assertThat(table[textView.textValue]).isEqualTo("Hello World!")
assertThat(textView.layoutFlags).isEqualTo(0)
assertThat(textView.subViewCount).isEqualTo(0)
assertThat(table[tree.resources.appPackageName]).isEqualTo("pck")
val config = tree.resources.configuration
assertThat(config.fontScale).isEqualTo(1.5f)
assertThat(config.countryCode).isEqualTo(310)
assertThat(config.networkCode).isEqualTo(4)
assertThat(config.screenLayout).isEqualTo(Configuration.SCREENLAYOUT_SIZE_LARGE or
Configuration.SCREENLAYOUT_LONG_NO or
Configuration.SCREENLAYOUT_LAYOUTDIR_RTL)
assertThat(config.colorMode).isEqualTo(Configuration.COLOR_MODE_WIDE_COLOR_GAMUT_NO or
Configuration.COLOR_MODE_HDR_YES)
assertThat(config.touchScreen).isEqualTo(Configuration.TOUCHSCREEN_FINGER)
assertThat(config.keyboard).isEqualTo(Configuration.KEYBOARD_QWERTY)
assertThat(config.keyboardHidden).isEqualTo(Configuration.KEYBOARDHIDDEN_YES)
assertThat(config.hardKeyboardHidden).isEqualTo(Configuration.HARDKEYBOARDHIDDEN_NO)
assertThat(config.navigation).isEqualTo(Configuration.NAVIGATION_WHEEL)
assertThat(config.navigationHidden).isEqualTo(Configuration.NAVIGATIONHIDDEN_YES)
assertThat(config.uiMode).isEqualTo(Configuration.UI_MODE_TYPE_NORMAL or
Configuration.UI_MODE_NIGHT_YES)
assertThat(config.density).isEqualTo(367)
assertThat(config.orientation).isEqualTo(Configuration.ORIENTATION_PORTRAIT)
assertThat(config.screenWidth).isEqualTo(1080)
assertThat(config.screenHeight).isEqualTo(2280)
assertThat(agentRule.payloads[event.layoutInspectorEvent.tree.payloadId])
.isEqualTo(pictureBytes)
}
@Test
fun testUseScreenshotMode() {
val bitmap = mock(Bitmap::class.java)
Bitmap.INSTANCE = bitmap
val bitmapBytes = (1 .. 1_000_000).map { (it % 256).toByte() }.toByteArray()
`when`(bitmap.byteCount).thenReturn(1_000_000)
`when`(bitmap.copyPixelsToBuffer(any()))
.then { invocation ->
invocation.getArgument<ByteBuffer>(0).put(bitmapBytes)
true
}
val (service, callback) = setUpInspectorService()
service.onUseScreenshotModeCommand(true)
val event = onPictureCaptured(callback, Picture())
assertThat(event.groupId).isEqualTo(1101)
assertThat(event.kind).isEqualTo(Common.Event.Kind.LAYOUT_INSPECTOR)
val tree = event.layoutInspectorEvent.tree
assertThat(tree.payloadType)
.isEqualTo(LayoutInspectorProto.ComponentTreeEvent.PayloadType.BITMAP_AS_REQUESTED)
val payload = agentRule.payloads[event.layoutInspectorEvent.tree.payloadId]
val inf = Inflater().also { it.setInput(payload) }
val baos = ByteArrayOutputStream()
val buffer = ByteArray(4096)
var total = 0
while (!inf.finished()) {
val count = inf.inflate(buffer)
if (count <= 0) {
break
}
baos.write(buffer, 0, count)
total += count
}
assertThat(total).isEqualTo(1_000_000)
assertThat(baos.toByteArray()).isEqualTo(bitmapBytes)
}
private fun setUpInspectorService()
: Pair<LayoutInspectorService, HardwareRenderer.PictureCapturedCallback> {
val handler = mock(Handler::class.java)
`when`(handler.looper).thenReturn(Looper.myLooper())
val renderer = HardwareRenderer()
val info = AttachInfo(handler, renderer)
val view: View = StandardView.createLinearLayoutWithTextView()
setField(view, "mAttachInfo", info)
WindowInspector.setGlobalWindowViews(listOf(view))
val service = LayoutInspectorService.instance()
service.startLayoutInspector(view)
val callback = renderer.pictureCaptureCallback!!
return Pair(service, callback)
}
private fun onPictureCaptured(
callback: HardwareRenderer.PictureCapturedCallback,
picture: Picture
): Common.Event {
callback.onPictureCaptured(picture)
val event = agentRule.events.poll(5, TimeUnit.SECONDS)!!
return event
}
private fun setField(instance: Any, fieldName: String, value: Any) {
val field = instance.javaClass.getDeclaredField(fieldName)
field.isAccessible = true
field[instance] = value
}
}