blob: 176d0427047b3ef22ada78059faad2958a070e26 [file] [log] [blame]
# Copyright 2016 The Vanadium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style
# license that can be found in the LICENSE file.
"""A script to aid debugging the view hierarchy data.
This script helps visualize the view hierarchy by drawing the bounds
of all clickable elements on a screenshot of the UI.
Use this script with an Android device (or emulator) connected to your
machine.
"""
import json
import os
import platform
import socket
import subprocess
from PIL import Image, ImageDraw
from subprocess import call
ADB = None
# The following dimensions are for a Nexus 6P device.
DEVICE_WIDTH = 1440
DEVICE_HEIGHT = 2560
def set_adb_path():
"""Define the ADB path based on operating system."""
try:
global ADB
# For machines with multiple installations of adb, use the last listed
# version of adb.
ADB = subprocess.check_output(['which -a adb'], shell=True).split('\n')[-2]
except subprocess.CalledProcessError:
print 'Could not find adb. Please check your PATH.'
def _clickable_elements_bounds(element):
bounds = []
if element.get("children"):
for child in element["children"]:
bounds += _clickable_elements_bounds(child)
if element["clickable"] and element["visibility"] == "visible":
bounds.append(element["bounds"])
return bounds
def clickable_elements_bounds(view):
root = view["activity"]["root"]
return _clickable_elements_bounds(root)
if __name__ == "__main__":
set_adb_path()
# If multiple phones are connected you would need to specify which phone
# an ADB command is directed at using the -s flag.
call([ADB, "shell", "screencap", "-p", "/sdcard/screen.png"])
call([ADB, "pull", "/sdcard/screen.png"])
call([ADB, "forward", "tcp:1699", "tcp:1699"])
call([ADB, "shell", "dumpsys", "activity", "start-view-server"])
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_address = ("localhost", 1699)
try:
sock.connect(server_address)
message = "d\n" # This is command to dump the view hierarchy.
sock.sendall(message)
response = ""
while True:
data = sock.recv(16)
response += str(data)
# A valid response ends with "RICO_JSON_END".
if "RICO_JSON_END" in response:
break
finally:
sock.close()
view = json.loads(response.split("RICO_JSON_END")[0])
image = Image.open("screen.png")
width, height = image.size
# Resize the image to make it easier to view.
image.thumbnail((width/4, height/4), Image.ANTIALIAS)
width, height = image.size
draw = ImageDraw.Draw(image)
x_factor = width/float(DEVICE_WIDTH)
y_factor = height/float(DEVICE_HEIGHT)
bounds = clickable_elements_bounds(view)
for bound in bounds:
new_bound = [int(bound[0] * x_factor), int(bound[1] * y_factor),
int(bound[2] * x_factor), int(bound[3] * y_factor)]
draw.rectangle(new_bound, outline=(0, 0, 255, 0))
image.save(os.path.join("snapshot.jpg"))
# Open the saved image using appropriate program based on user's OS.
if platform.system() == "Linux":
call(["gnome-open", "snapshot.jpg"])
elif platform.system() == "Darwin":
call(["open", "snapshot.jpg"])