blob: 6ccd971b45c13766fa99ef2282fdd9df5fb05e96 [file] [log] [blame]
/*
* Copyright (C) 2021 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.appinspection.framework
import android.content.res.Resources
import android.graphics.Bitmap
import android.graphics.Canvas
import android.util.Log
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import com.android.tools.agent.appinspection.util.ThreadUtils
import java.util.Stack
import kotlin.math.roundToInt
fun ViewGroup.getChildren(): List<View> {
ThreadUtils.assertOnMainThread()
return (0 until childCount).map { i -> getChildAt(i) }
}
/**
* Return this node's text value, if it is a kind of node that has one.
*/
fun View.getTextValue(): String? {
if (this !is TextView) return null
return text.toString()
}
/**
* Return a list of this view and all its children in depth-first order
*/
fun View.flatten(): Sequence<View> {
ThreadUtils.assertOnMainThread()
return sequence {
val toProcess = Stack<View>()
toProcess.push(this@flatten)
while (toProcess.isNotEmpty()) {
val curr = toProcess.pop()
yield(curr)
if (curr is ViewGroup) {
toProcess.addAll(curr.getChildren())
}
}
}
}
/**
* Returns true if the view is contained in a layout generated by the platform / system.
*
* <ul>
* <li>DecorView, ViewStub, AndroidComposeView, View will have a null layout
* <li>Layouts from the "android" namespace are from the platform
* <li>AppCompat will typically use an "abc_" prefix for their layout names
* </ul>
*/
fun View.isSystemView(): Boolean {
return try {
val layoutId = sourceLayoutResId
if (layoutId == 0) {
// Programmatically added Views are treated as system views:
return true
}
val namespace = resources.getResourcePackageName(layoutId)
val name = resources.getResourceEntryName(layoutId)
namespace == "android" || name.startsWith("abc_")
} catch (ignored: Resources.NotFoundException) {
false
}
}
/**
* Convert this view into a bitmap.
*
* This method may return null if the app runs out of memory trying to create it.
*/
fun View.takeScreenshot(scale: Float): Bitmap? {
val bitmap = Bitmap.createBitmap(
(width * scale).roundToInt(),
(height * scale).roundToInt(),
Bitmap.Config.RGB_565
)
return try {
val canvas = Canvas(bitmap)
canvas.scale(scale, scale)
ThreadUtils.runOnMainThread { draw(canvas) }.get()
bitmap
} catch (e: OutOfMemoryError) {
Log.w("ViewLayoutInspector", "Out of memory for bitmap")
null
}
}