Merge "Merge Android 12L"
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -5,4 +5,6 @@
|
||||
Thumbs.db
|
||||
*.iml
|
||||
.idea/
|
||||
*.yarn/
|
||||
.yarnrc
|
||||
gen/
|
||||
|
||||
@@ -96,6 +96,9 @@ public class DefaultActivity extends Activity {
|
||||
Settings.Global.putString(getContentResolver(), Settings.Global.DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT, "1");
|
||||
Settings.Global.putString(getContentResolver(), Settings.Global.DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES, "1");
|
||||
Settings.Global.putString(getContentResolver(), Settings.Global.DEVELOPMENT_WM_DISPLAY_SETTINGS_PATH, "vendor/etc/display_settings_freeform.xml");
|
||||
} else if ("resizable".equals(displaySettingsName)) {
|
||||
// Enable auto rotate for resizable AVD
|
||||
Settings.System.putString(getContentResolver(), Settings.System.ACCELEROMETER_ROTATION, "1");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -96,8 +96,17 @@ public class SoftKeyboard extends InputMethodService
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new context object whose resources are adjusted to match the metrics of the display
|
||||
* which is managed by WindowManager.
|
||||
* Returns the context object whose resources are adjusted to match the metrics of the display.
|
||||
*
|
||||
* Note that before {@link android.os.Build.VERSION_CODES#KITKAT}, there is no way to support
|
||||
* multi-display scenarios, so the context object will just return the IME context itself.
|
||||
*
|
||||
* With initiating multi-display APIs from {@link android.os.Build.VERSION_CODES#KITKAT}, the
|
||||
* context object has to return with re-creating the display context according the metrics
|
||||
* of the display in runtime.
|
||||
*
|
||||
* Starts from {@link android.os.Build.VERSION_CODES#S_V2}, the returning context object has
|
||||
* became to IME context self since it ends up capable of updating its resources internally.
|
||||
*
|
||||
* @see {@link Context#createDisplayContext(Display)}
|
||||
*/
|
||||
@@ -106,8 +115,10 @@ public class SoftKeyboard extends InputMethodService
|
||||
// createDisplayContext is not available.
|
||||
return this;
|
||||
}
|
||||
// TODO (b/133825283): Non-activity components Resources / DisplayMetrics update when
|
||||
// moving to external display.
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S_V2) {
|
||||
// IME context sources is now managed by WindowProviderService from Android 12L.
|
||||
return this;
|
||||
}
|
||||
// An issue in Q that non-activity components Resources / DisplayMetrics in
|
||||
// Context doesn't well updated when the IME window moving to external display.
|
||||
// Currently we do a workaround is to create new display context directly and re-init
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
Pkg.UserSrc=false
|
||||
Pkg.Revision=${PLATFORM_SDK_VERSION}.0.1
|
||||
#Pkg.Revision=31.0.0 rc5
|
||||
Pkg.Revision=${PLATFORM_SDK_VERSION}.0.0
|
||||
#Pkg.Revision=32.0.0 rc2
|
||||
|
||||
@@ -55,6 +55,14 @@ WINSCOPE_TOKEN_HEADER = "Winscope-Token"
|
||||
# Location to save the proxy security token
|
||||
WINSCOPE_TOKEN_LOCATION = os.path.expanduser('~/.config/winscope/.token')
|
||||
|
||||
# Winscope traces extensions
|
||||
WINSCOPE_EXT = ".winscope"
|
||||
WINSCOPE_EXT_LEGACY = ".pb"
|
||||
WINSCOPE_EXTS = [WINSCOPE_EXT, WINSCOPE_EXT_LEGACY]
|
||||
|
||||
# Winscope traces directory
|
||||
WINSCOPE_DIR = "/data/misc/wmtrace/"
|
||||
|
||||
# Max interval between the client keep-alive requests in seconds
|
||||
KEEP_ALIVE_INTERVAL_S = 5
|
||||
|
||||
@@ -85,12 +93,29 @@ class FileMatcher:
|
||||
matchingFiles = call_adb(
|
||||
f"shell su root find {self.path} -name {self.matcher}", device_id)
|
||||
|
||||
log.debug("Found file %s", matchingFiles.split('\n')[:-1])
|
||||
return matchingFiles.split('\n')[:-1]
|
||||
|
||||
def get_filetype(self):
|
||||
return self.type
|
||||
|
||||
|
||||
class WinscopeFileMatcher(FileMatcher):
|
||||
def __init__(self, path, matcher, filetype) -> None:
|
||||
self.path = path
|
||||
self.internal_matchers = list(map(lambda ext: FileMatcher(path, f'{matcher}{ext}', filetype),
|
||||
WINSCOPE_EXTS))
|
||||
self.type = filetype
|
||||
|
||||
def get_filepaths(self, device_id):
|
||||
for matcher in self.internal_matchers:
|
||||
files = matcher.get_filepaths(device_id)
|
||||
if len(files) > 0:
|
||||
return files
|
||||
log.debug("No files found")
|
||||
return []
|
||||
|
||||
|
||||
class TraceTarget:
|
||||
"""Defines a single parameter to trace.
|
||||
|
||||
@@ -111,46 +136,50 @@ class TraceTarget:
|
||||
# Order of files matters as they will be expected in that order and decoded in that order
|
||||
TRACE_TARGETS = {
|
||||
"window_trace": TraceTarget(
|
||||
File("/data/misc/wmtrace/wm_trace.pb", "window_trace"),
|
||||
WinscopeFileMatcher(WINSCOPE_DIR, "wm_trace", "window_trace"),
|
||||
'su root cmd window tracing start\necho "WM trace started."',
|
||||
'su root cmd window tracing stop >/dev/null 2>&1'
|
||||
),
|
||||
"accessibility_trace": TraceTarget(
|
||||
WinscopeFileMatcher("/data/misc/a11ytrace", "a11y_trace", "accessibility_trace"),
|
||||
'su root cmd accessibility start-trace\necho "Accessibility trace started."',
|
||||
'su root cmd accessibility stop-trace >/dev/null 2>&1'
|
||||
),
|
||||
"layers_trace": TraceTarget(
|
||||
File("/data/misc/wmtrace/layers_trace.pb", "layers_trace"),
|
||||
WinscopeFileMatcher(WINSCOPE_DIR, "layers_trace", "layers_trace"),
|
||||
'su root service call SurfaceFlinger 1025 i32 1\necho "SF trace started."',
|
||||
'su root service call SurfaceFlinger 1025 i32 0 >/dev/null 2>&1'
|
||||
),
|
||||
"screen_recording": TraceTarget(
|
||||
File("/data/local/tmp/screen.winscope.mp4", "screen_recording"),
|
||||
'screenrecord --bit-rate 8M /data/local/tmp/screen.winscope.mp4 >/dev/null 2>&1 &\necho "ScreenRecorder started."',
|
||||
File(f'/data/local/tmp/screen.mp4', "screen_recording"),
|
||||
f'screenrecord --bit-rate 8M /data/local/tmp/screen.mp4 >/dev/null 2>&1 &\necho "ScreenRecorder started."',
|
||||
'pkill -l SIGINT screenrecord >/dev/null 2>&1'
|
||||
),
|
||||
"transaction": TraceTarget(
|
||||
[
|
||||
File("/data/misc/wmtrace/transaction_trace.pb", "transactions"),
|
||||
FileMatcher("/data/misc/wmtrace/", "transaction_merges_*.pb",
|
||||
"transaction_merges"),
|
||||
WinscopeFileMatcher(WINSCOPE_DIR, "transaction_trace", "transactions"),
|
||||
FileMatcher(WINSCOPE_DIR, f'transaction_merges_*', "transaction_merges"),
|
||||
],
|
||||
'su root service call SurfaceFlinger 1020 i32 1\necho "SF transactions recording started."',
|
||||
'su root service call SurfaceFlinger 1020 i32 0 >/dev/null 2>&1'
|
||||
),
|
||||
"proto_log": TraceTarget(
|
||||
File("/data/misc/wmtrace/wm_log.pb", "proto_log"),
|
||||
WinscopeFileMatcher(WINSCOPE_DIR, "wm_log", "proto_log"),
|
||||
'su root cmd window logging start\necho "WM logging started."',
|
||||
'su root cmd window logging stop >/dev/null 2>&1'
|
||||
),
|
||||
"ime_trace_clients": TraceTarget(
|
||||
File("/data/misc/wmtrace/ime_trace_clients.pb", "ime_trace_clients"),
|
||||
WinscopeFileMatcher(WINSCOPE_DIR, "ime_trace_clients", "ime_trace_clients"),
|
||||
'su root ime tracing start\necho "Clients IME trace started."',
|
||||
'su root ime tracing stop >/dev/null 2>&1'
|
||||
),
|
||||
"ime_trace_service": TraceTarget(
|
||||
File("/data/misc/wmtrace/ime_trace_service.pb", "ime_trace_service"),
|
||||
WinscopeFileMatcher(WINSCOPE_DIR, "ime_trace_service", "ime_trace_service"),
|
||||
'su root ime tracing start\necho "Service IME trace started."',
|
||||
'su root ime tracing stop >/dev/null 2>&1'
|
||||
),
|
||||
"ime_trace_managerservice": TraceTarget(
|
||||
File("/data/misc/wmtrace/ime_trace_managerservice.pb", "ime_trace_managerservice"),
|
||||
WinscopeFileMatcher(WINSCOPE_DIR, "ime_trace_managerservice", "ime_trace_managerservice"),
|
||||
'su root ime tracing start\necho "ManagerService IME trace started."',
|
||||
'su root ime tracing stop >/dev/null 2>&1'
|
||||
),
|
||||
@@ -174,6 +203,52 @@ class SurfaceFlingerTraceConfig:
|
||||
def command(self) -> str:
|
||||
return f'su root service call SurfaceFlinger 1033 i32 {self.flags}'
|
||||
|
||||
class SurfaceFlingerTraceSelectedConfig:
|
||||
"""Handles optional selected configuration for surfaceflinger traces.
|
||||
"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
# defaults set for all configs
|
||||
self.selectedConfigs = {
|
||||
"sfbuffersize": "16000"
|
||||
}
|
||||
|
||||
def add(self, configType, configValue) -> None:
|
||||
self.selectedConfigs[configType] = configValue
|
||||
|
||||
def is_valid(self, configType) -> bool:
|
||||
return configType in CONFIG_SF_SELECTION
|
||||
|
||||
def setBufferSize(self) -> str:
|
||||
return f'su root service call SurfaceFlinger 1029 i32 {self.selectedConfigs["sfbuffersize"]}'
|
||||
|
||||
class WindowManagerTraceSelectedConfig:
|
||||
"""Handles optional selected configuration for windowmanager traces.
|
||||
"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
# defaults set for all configs
|
||||
self.selectedConfigs = {
|
||||
"wmbuffersize": "16000",
|
||||
"tracinglevel": "all",
|
||||
"tracingtype": "frame",
|
||||
}
|
||||
|
||||
def add(self, configType, configValue) -> None:
|
||||
self.selectedConfigs[configType] = configValue
|
||||
|
||||
def is_valid(self, configType) -> bool:
|
||||
return configType in CONFIG_WM_SELECTION
|
||||
|
||||
def setBufferSize(self) -> str:
|
||||
return f'su root cmd window tracing size {self.selectedConfigs["wmbuffersize"]}'
|
||||
|
||||
def setTracingLevel(self) -> str:
|
||||
return f'su root cmd window tracing level {self.selectedConfigs["tracinglevel"]}'
|
||||
|
||||
def setTracingType(self) -> str:
|
||||
return f'su root cmd window tracing {self.selectedConfigs["tracingtype"]}'
|
||||
|
||||
|
||||
CONFIG_FLAG = {
|
||||
"composition": 1 << 2,
|
||||
@@ -181,6 +256,17 @@ CONFIG_FLAG = {
|
||||
"hwc": 1 << 4
|
||||
}
|
||||
|
||||
#Keep up to date with options in DataAdb.vue
|
||||
CONFIG_SF_SELECTION = [
|
||||
"sfbuffersize",
|
||||
]
|
||||
|
||||
#Keep up to date with options in DataAdb.vue
|
||||
CONFIG_WM_SELECTION = [
|
||||
"wmbuffersize",
|
||||
"tracingtype",
|
||||
"tracinglevel",
|
||||
]
|
||||
|
||||
class DumpTarget:
|
||||
"""Defines a single parameter to trace.
|
||||
@@ -199,12 +285,12 @@ class DumpTarget:
|
||||
|
||||
DUMP_TARGETS = {
|
||||
"window_dump": DumpTarget(
|
||||
File("/data/local/tmp/wm_dump.pb", "window_dump"),
|
||||
'su root dumpsys window --proto > /data/local/tmp/wm_dump.pb'
|
||||
File(f'/data/local/tmp/wm_dump{WINSCOPE_EXT}', "window_dump"),
|
||||
f'su root dumpsys window --proto > /data/local/tmp/wm_dump{WINSCOPE_EXT}'
|
||||
),
|
||||
"layers_dump": DumpTarget(
|
||||
File("/data/local/tmp/sf_dump.pb", "layers_dump"),
|
||||
'su root dumpsys SurfaceFlinger --proto > /data/local/tmp/sf_dump.pb'
|
||||
File(f'/data/local/tmp/sf_dump{WINSCOPE_EXT}', "layers_dump"),
|
||||
f'su root dumpsys SurfaceFlinger --proto > /data/local/tmp/sf_dump{WINSCOPE_EXT}'
|
||||
)
|
||||
}
|
||||
|
||||
@@ -420,6 +506,9 @@ class FetchFilesEndpoint(DeviceRequestEndpoint):
|
||||
buf = base64.encodebytes(tmp.read()).decode("utf-8")
|
||||
file_buffers[file_type].append(buf)
|
||||
|
||||
if (len(file_buffers) == 0):
|
||||
log.error("Proxy didn't find any file to fetch")
|
||||
|
||||
# server.send_header('X-Content-Type-Options', 'nosniff')
|
||||
# add_standard_headers(server)
|
||||
j = json.dumps(file_buffers)
|
||||
@@ -578,6 +667,18 @@ class EndTrace(DeviceRequestEndpoint):
|
||||
"utf-8"))
|
||||
|
||||
|
||||
def execute_command(server, device_id, shell, configType, configValue):
|
||||
process = subprocess.Popen(shell, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
|
||||
stdin=subprocess.PIPE, start_new_session=True)
|
||||
log.debug(f"Changing trace config on device {device_id} {configType}:{configValue}")
|
||||
out, err = process.communicate(configValue.encode('utf-8'))
|
||||
if process.returncode != 0:
|
||||
raise AdbError(
|
||||
f"Error executing command:\n {configValue}\n\n### OUTPUT ###{out.decode('utf-8')}\n{err.decode('utf-8')}")
|
||||
log.debug(f"Changing trace config finished on device {device_id}")
|
||||
server.respond(HTTPStatus.OK, b'', "text/plain")
|
||||
|
||||
|
||||
class ConfigTrace(DeviceRequestEndpoint):
|
||||
def process_with_device(self, server, path, device_id):
|
||||
try:
|
||||
@@ -598,15 +699,50 @@ class ConfigTrace(DeviceRequestEndpoint):
|
||||
command = config.command()
|
||||
shell = ['adb', '-s', device_id, 'shell']
|
||||
log.debug(f"Starting shell {' '.join(shell)}")
|
||||
process = subprocess.Popen(shell, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
|
||||
stdin=subprocess.PIPE, start_new_session=True)
|
||||
log.debug(f"Changing trace config on device {device_id} cmd:{command}")
|
||||
out, err = process.communicate(command.encode('utf-8'))
|
||||
if process.returncode != 0:
|
||||
raise AdbError(
|
||||
f"Error executing command:\n {command}\n\n### OUTPUT ###{out.decode('utf-8')}\n{err.decode('utf-8')}")
|
||||
log.debug(f"Changing trace config finished on device {device_id}")
|
||||
server.respond(HTTPStatus.OK, b'', "text/plain")
|
||||
execute_command(server, device_id, shell, "sf buffer size", command)
|
||||
|
||||
|
||||
def add_selected_request_to_config(self, server, device_id, config):
|
||||
try:
|
||||
requested_configs = self.get_request(server)
|
||||
for requested_config in requested_configs:
|
||||
if config.is_valid(requested_config):
|
||||
config.add(requested_config, requested_configs[requested_config])
|
||||
else:
|
||||
raise BadRequest(
|
||||
f"Unsupported config {requested_config}\n")
|
||||
except KeyError as err:
|
||||
raise BadRequest("Unsupported trace target\n" + str(err))
|
||||
if device_id in TRACE_THREADS:
|
||||
BadRequest(f"Trace in progress for {device_id}")
|
||||
if not check_root(device_id):
|
||||
raise AdbError(
|
||||
f"Unable to acquire root privileges on the device - check the output of 'adb -s {device_id} shell su root id'")
|
||||
return config
|
||||
|
||||
|
||||
class SurfaceFlingerSelectedConfigTrace(DeviceRequestEndpoint):
|
||||
def process_with_device(self, server, path, device_id):
|
||||
config = SurfaceFlingerTraceSelectedConfig()
|
||||
config = add_selected_request_to_config(self, server, device_id, config)
|
||||
setBufferSize = config.setBufferSize()
|
||||
shell = ['adb', '-s', device_id, 'shell']
|
||||
log.debug(f"Starting shell {' '.join(shell)}")
|
||||
execute_command(server, device_id, shell, "sf buffer size", setBufferSize)
|
||||
|
||||
|
||||
class WindowManagerSelectedConfigTrace(DeviceRequestEndpoint):
|
||||
def process_with_device(self, server, path, device_id):
|
||||
config = WindowManagerTraceSelectedConfig()
|
||||
config = add_selected_request_to_config(self, server, device_id, config)
|
||||
setBufferSize = config.setBufferSize()
|
||||
setTracingType = config.setTracingType()
|
||||
setTracingLevel = config.setTracingLevel()
|
||||
shell = ['adb', '-s', device_id, 'shell']
|
||||
log.debug(f"Starting shell {' '.join(shell)}")
|
||||
execute_command(server, device_id, shell, "wm buffer size", setBufferSize)
|
||||
execute_command(server, device_id, shell, "tracing type", setTracingType)
|
||||
execute_command(server, device_id, shell, "tracing level", setTracingLevel)
|
||||
|
||||
|
||||
class StatusEndpoint(DeviceRequestEndpoint):
|
||||
@@ -659,6 +795,10 @@ class ADBWinscopeProxy(BaseHTTPRequestHandler):
|
||||
self.router.register_endpoint(RequestType.POST, "dump", DumpEndpoint())
|
||||
self.router.register_endpoint(
|
||||
RequestType.POST, "configtrace", ConfigTrace())
|
||||
self.router.register_endpoint(
|
||||
RequestType.POST, "selectedsfconfigtrace", SurfaceFlingerSelectedConfigTrace())
|
||||
self.router.register_endpoint(
|
||||
RequestType.POST, "selectedwmconfigtrace", WindowManagerSelectedConfigTrace())
|
||||
super().__init__(request, client_address, server)
|
||||
|
||||
def respond(self, code: int, data: bytes, mime: str) -> None:
|
||||
|
||||
@@ -5,60 +5,61 @@
|
||||
"author": "Adrian Roos <roosa@google.com>",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "cross-env NODE_ENV=development webpack-dev-server --open --hot",
|
||||
"build": "cross-env NODE_ENV=production webpack --progress --hide-modules",
|
||||
"dev": "cross-env NODE_ENV=development webpack serve --open --hot",
|
||||
"build": "cross-env NODE_ENV=production webpack --progress",
|
||||
"test": "webpack --config webpack.spec.config.js && jasmine dist/bundleSpec.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"cross-env": "^7.0.2",
|
||||
"jszip": "^3.5.0",
|
||||
"kotlin": "^1.3.72",
|
||||
"cross-env": "^7.0.3",
|
||||
"jszip": "^3.6.0",
|
||||
"kotlin": "^1.5.21",
|
||||
"lodash.clonedeep": "^4.5.0",
|
||||
"ts-loader": "^8.0.3",
|
||||
"typescript": "^4.0.2",
|
||||
"vue": "^2.3.3",
|
||||
"vue-context": "^5.2.0",
|
||||
"vue-material": "^1.0.0-beta-11",
|
||||
"vuex": "^3.4.0"
|
||||
"ts-loader": "^8.3.0",
|
||||
"typescript": "^4.3.5",
|
||||
"vue": "^2.6.14",
|
||||
"vue-context": "^6.0.0",
|
||||
"vue-gtag": "^1.16.1",
|
||||
"vue-material": "^1.0.0-beta-15",
|
||||
"vuex": "^3.6.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.10.5",
|
||||
"@babel/polyfill": "^7.10.4",
|
||||
"@babel/preset-env": "^7.10.4",
|
||||
"@babel/register": "^7.10.5",
|
||||
"@babel/core": "^7.14.6",
|
||||
"@babel/polyfill": "^7.12.1",
|
||||
"@babel/preset-env": "^7.14.7",
|
||||
"@babel/register": "^7.14.5",
|
||||
"@jetbrains/kotlin-webpack-plugin": "^3.0.2",
|
||||
"@testing-library/vue": "^5.1.0",
|
||||
"@types/lodash": "^4.14.158",
|
||||
"babel-loader": "^8.1.0",
|
||||
"compression-webpack-plugin": "^4.0.0",
|
||||
"cross-env": "^7.0.2",
|
||||
"css-loader": "^3.6.0",
|
||||
"eslint": "^7.1.0",
|
||||
"@testing-library/vue": "^5.8.1",
|
||||
"@types/lodash": "^4.14.171",
|
||||
"babel-loader": "^8.2.2",
|
||||
"compression-webpack-plugin": "^6.1.1",
|
||||
"cross-env": "^7.0.3",
|
||||
"css-loader": "^5.2.7",
|
||||
"eslint": "^7.30.0",
|
||||
"eslint-config-google": "^0.14.0",
|
||||
"eslint-plugin-vue": "^6.2.2",
|
||||
"file-loader": "^6.0.0",
|
||||
"eslint-plugin-vue": "^7.13.0",
|
||||
"file-loader": "^6.2.0",
|
||||
"friendly-errors-webpack-plugin": "^1.7.0",
|
||||
"html-webpack-inline-source-plugin": "^0.0.10",
|
||||
"html-webpack-plugin": "3.2.0",
|
||||
"husky": "^4.2.5",
|
||||
"jasmine": "^3.5.0",
|
||||
"lint-staged": ">=10",
|
||||
"html-webpack-inline-source-plugin": "^1.0.0-beta.2",
|
||||
"html-webpack-plugin": "4.5.2",
|
||||
"husky": "^7.0.0",
|
||||
"jasmine": "^3.8.0",
|
||||
"lint-staged": "^11.0.1",
|
||||
"loader-utils": "^2.0.0",
|
||||
"mini-css-extract-plugin": "^0.9.0",
|
||||
"mini-css-extract-plugin": "^1.6.2",
|
||||
"optimize-css-assets-webpack-plugin": "^5.0.3",
|
||||
"protobufjs": "^6.10.0",
|
||||
"source-map-loader": "^1.0.1",
|
||||
"style-loader": "^1.2.1",
|
||||
"ts-loader": "^8.0.1",
|
||||
"typescript": "^3.9.7",
|
||||
"protobufjs": "^6.11.2",
|
||||
"source-map-loader": "^1.1.3",
|
||||
"style-loader": "^2.0.0",
|
||||
"ts-loader": "^8.3.0",
|
||||
"typescript": "^4.3.5",
|
||||
"uglifyjs-webpack-plugin": "^2.2.0",
|
||||
"vue-loader": "^15.9.3",
|
||||
"vue-style-loader": "^4.1.2",
|
||||
"vue-template-compiler": "^2.6.11",
|
||||
"webpack": "^4.43.0",
|
||||
"webpack-cli": "^3.3.12",
|
||||
"webpack-dev-server": "^3.11.0",
|
||||
"webpack-merge": "^5.0.9"
|
||||
"vue-loader": "^15.9.2",
|
||||
"vue-style-loader": "^4.1.3",
|
||||
"vue-template-compiler": "^2.6.14",
|
||||
"webpack": "^4.46.0",
|
||||
"webpack-cli": "^4.7.2",
|
||||
"webpack-dev-server": "^3.11.2",
|
||||
"webpack-merge": "^5.8.0"
|
||||
},
|
||||
"husky": {
|
||||
"hooks": {
|
||||
|
||||
@@ -1,10 +1,24 @@
|
||||
import { DiffGenerator, DiffType } from "../src/utils/diff.js";
|
||||
import { Node, DiffNode, toPlainObject } from "./utils/tree.js";
|
||||
|
||||
const treeOne = new Node({ id: 1 }, [
|
||||
new Node({ id: 2 }, []),
|
||||
new Node({ id: 3 }, []),
|
||||
new Node({ id: 4 }, []),
|
||||
]);
|
||||
const treeTwo = new Node({ id: 1 }, [
|
||||
new Node({ id: 2 }, []),
|
||||
new Node({ id: 3 }, [
|
||||
new Node({ id: 5 }, []),
|
||||
]),
|
||||
new Node({ id: 4 }, []),
|
||||
]);
|
||||
|
||||
function checkDiffTreeWithNoModifiedCheck(oldTree, newTree, expectedDiffTree) {
|
||||
const diffTree = new DiffGenerator(newTree)
|
||||
.compareWith(oldTree)
|
||||
.withUniqueNodeId(node => node.id)
|
||||
.withModifiedCheck(() => false)
|
||||
.generateDiffTree();
|
||||
|
||||
expect(diffTree).toEqual(expectedDiffTree);
|
||||
@@ -12,25 +26,8 @@ function checkDiffTreeWithNoModifiedCheck(oldTree, newTree, expectedDiffTree) {
|
||||
|
||||
describe("DiffGenerator", () => {
|
||||
it("can generate a simple add diff", () => {
|
||||
const oldTree = new Node({ id: 1 }, [
|
||||
new Node({ id: 2 }, []),
|
||||
new Node({ id: 3 }, []),
|
||||
new Node({ id: 4 }, []),
|
||||
]);
|
||||
|
||||
const newTree = new Node({ id: 1 }, [
|
||||
new Node({ id: 2 }, []),
|
||||
new Node({ id: 3 }, [
|
||||
new Node({ id: 5 }, []),
|
||||
]),
|
||||
new Node({ id: 4 }, []),
|
||||
]);
|
||||
|
||||
const diffTree = new DiffGenerator(newTree)
|
||||
.compareWith(oldTree)
|
||||
.withUniqueNodeId(node => node.id)
|
||||
.withModifiedCheck(() => false)
|
||||
.generateDiffTree();
|
||||
const oldTree = treeOne;
|
||||
const newTree = treeTwo;
|
||||
|
||||
const expectedDiffTree = toPlainObject(
|
||||
new DiffNode({ id: 1 }, DiffType.NONE, [
|
||||
@@ -46,19 +43,8 @@ describe("DiffGenerator", () => {
|
||||
});
|
||||
|
||||
it("can generate a simple delete diff", () => {
|
||||
const oldTree = new Node({ id: 1 }, [
|
||||
new Node({ id: 2 }, []),
|
||||
new Node({ id: 3 }, [
|
||||
new Node({ id: 5 }, []),
|
||||
]),
|
||||
new Node({ id: 4 }, []),
|
||||
]);
|
||||
|
||||
const newTree = new Node({ id: 1 }, [
|
||||
new Node({ id: 2 }, []),
|
||||
new Node({ id: 3 }, []),
|
||||
new Node({ id: 4 }, []),
|
||||
]);
|
||||
const oldTree = treeTwo;
|
||||
const newTree = treeOne;
|
||||
|
||||
const expectedDiffTree = toPlainObject(
|
||||
new DiffNode({ id: 1 }, DiffType.NONE, [
|
||||
@@ -74,13 +60,7 @@ describe("DiffGenerator", () => {
|
||||
});
|
||||
|
||||
it("can generate a simple move diff", () => {
|
||||
const oldTree = new Node({ id: 1 }, [
|
||||
new Node({ id: 2 }, []),
|
||||
new Node({ id: 3 }, [
|
||||
new Node({ id: 5 }, []),
|
||||
]),
|
||||
new Node({ id: 4 }, []),
|
||||
]);
|
||||
const oldTree = treeTwo;
|
||||
|
||||
const newTree = new Node({ id: 1 }, [
|
||||
new Node({ id: 2 }, []),
|
||||
@@ -264,4 +244,4 @@ describe("DiffGenerator", () => {
|
||||
|
||||
checkDiffTreeWithNoModifiedCheck(oldTree, newTree, expectedDiffTree);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,32 +1,6 @@
|
||||
import { DiffType } from "../src/utils/diff.js";
|
||||
import { ObjectTransformer } from "../src/transform.js";
|
||||
import { Node, DiffNode, toPlainObject } from "./utils/tree.js";
|
||||
|
||||
class ObjNode extends Node {
|
||||
constructor(name, children, combined) {
|
||||
const nodeDef = {
|
||||
kind: '',
|
||||
name: name,
|
||||
};
|
||||
if (combined) {
|
||||
nodeDef.combined = true;
|
||||
}
|
||||
super(nodeDef, children);
|
||||
}
|
||||
}
|
||||
|
||||
class ObjDiffNode extends DiffNode {
|
||||
constructor(name, diffType, children, combined) {
|
||||
const nodeDef = {
|
||||
kind: '',
|
||||
name: name,
|
||||
};
|
||||
if (combined) {
|
||||
nodeDef.combined = true;
|
||||
}
|
||||
super(nodeDef, diffType, children);
|
||||
}
|
||||
}
|
||||
import { ObjNode, ObjDiffNode, toPlainObject } from "./utils/tree.js";
|
||||
|
||||
describe("ObjectTransformer", () => {
|
||||
it("can transform a simple object", () => {
|
||||
@@ -46,19 +20,19 @@ describe("ObjectTransformer", () => {
|
||||
const expectedTransformedObj = toPlainObject(
|
||||
new ObjNode('root', [
|
||||
new ObjNode('obj', [
|
||||
new ObjNode('string: string', [], true),
|
||||
new ObjNode('number: 3', [], true),
|
||||
]),
|
||||
new ObjNode('string: string', [], true, 'root.obj.string'),
|
||||
new ObjNode('number: 3', [], true, 'root.obj.number'),
|
||||
], undefined, 'root.obj'),
|
||||
new ObjNode('array', [
|
||||
new ObjNode('0', [
|
||||
new ObjNode('nested: item', [], true),
|
||||
]),
|
||||
new ObjNode("1: two", [], true),
|
||||
]),
|
||||
])
|
||||
new ObjNode('nested: item', [], true, 'root.array.0.nested'),
|
||||
], undefined, 'root.array.0'),
|
||||
new ObjNode("1: two", [], true, 'root.array.1'),
|
||||
], undefined, 'root.array'),
|
||||
], undefined, 'root')
|
||||
);
|
||||
|
||||
const transformedObj = new ObjectTransformer(obj, 'root')
|
||||
const transformedObj = new ObjectTransformer(obj, 'root', 'root')
|
||||
.setOptions({ formatter: () => { } })
|
||||
.transform();
|
||||
|
||||
@@ -75,12 +49,12 @@ describe("ObjectTransformer", () => {
|
||||
const expectedTransformedObj = toPlainObject(
|
||||
new ObjNode('root', [
|
||||
new ObjNode('obj', [
|
||||
new ObjNode('null: null', [], true),
|
||||
]),
|
||||
])
|
||||
new ObjNode('null: null', [], true, 'root.obj.null'),
|
||||
], undefined, 'root.obj'),
|
||||
], undefined, 'root')
|
||||
);
|
||||
|
||||
const transformedObj = new ObjectTransformer(obj, 'root')
|
||||
const transformedObj = new ObjectTransformer(obj, 'root', 'root')
|
||||
.setOptions({ formatter: () => { } })
|
||||
.transform();
|
||||
|
||||
@@ -106,14 +80,14 @@ describe("ObjectTransformer", () => {
|
||||
const expectedTransformedObj = toPlainObject(
|
||||
new ObjDiffNode('root', DiffType.NONE, [
|
||||
new ObjDiffNode('a', DiffType.NONE, [
|
||||
new ObjDiffNode('b: 1', DiffType.NONE, [], true),
|
||||
new ObjDiffNode('d: 3', DiffType.ADDED, [], true),
|
||||
]),
|
||||
new ObjDiffNode('c: 2', DiffType.NONE, [], true),
|
||||
])
|
||||
new ObjDiffNode('b: 1', DiffType.NONE, [], true, 'root.a.b'),
|
||||
new ObjDiffNode('d: 3', DiffType.ADDED, [], true, 'root.a.d'),
|
||||
], false, 'root.a'),
|
||||
new ObjDiffNode('c: 2', DiffType.NONE, [], true, 'root.c'),
|
||||
], false, 'root')
|
||||
);
|
||||
|
||||
const transformedObj = new ObjectTransformer(newObj, 'root')
|
||||
const transformedObj = new ObjectTransformer(newObj, 'root', 'root')
|
||||
.setOptions({ formatter: () => { } })
|
||||
.withDiff(oldObj)
|
||||
.transform();
|
||||
@@ -133,13 +107,13 @@ describe("ObjectTransformer", () => {
|
||||
const expectedTransformedObj = toPlainObject(
|
||||
new ObjDiffNode('root', DiffType.NONE, [
|
||||
new ObjDiffNode('a', DiffType.NONE, [
|
||||
new ObjDiffNode('1', DiffType.ADDED, []),
|
||||
new ObjDiffNode('null', DiffType.DELETED, []),
|
||||
]),
|
||||
])
|
||||
new ObjDiffNode('1', DiffType.ADDED, [], false, 'root.a.1'),
|
||||
new ObjDiffNode('null', DiffType.DELETED, [], false, 'root.a.null'),
|
||||
], false, 'root.a'),
|
||||
], false, 'root')
|
||||
);
|
||||
|
||||
const transformedObj = new ObjectTransformer(newObj, 'root')
|
||||
const transformedObj = new ObjectTransformer(newObj, 'root', 'root')
|
||||
.setOptions({ formatter: () => { } })
|
||||
.withDiff(oldObj)
|
||||
.transform();
|
||||
@@ -166,19 +140,19 @@ describe("ObjectTransformer", () => {
|
||||
new ObjDiffNode('root', DiffType.NONE, [
|
||||
new ObjDiffNode('a', DiffType.NONE, [
|
||||
new ObjDiffNode('b', DiffType.NONE, [
|
||||
new ObjDiffNode('1', DiffType.ADDED, []),
|
||||
new ObjDiffNode('null', DiffType.DELETED, []),
|
||||
]),
|
||||
]),
|
||||
new ObjDiffNode('c: 2', DiffType.NONE, [], true),
|
||||
])
|
||||
new ObjDiffNode('1', DiffType.ADDED, [], false, 'root.a.b.1'),
|
||||
new ObjDiffNode('null', DiffType.DELETED, [], false, 'root.a.b.null'),
|
||||
], false, 'root.a.b'),
|
||||
], false, 'root.a'),
|
||||
new ObjDiffNode('c: 2', DiffType.NONE, [], true, 'root.c'),
|
||||
], false, 'root')
|
||||
);
|
||||
|
||||
const transformedObj = new ObjectTransformer(newObj, 'root')
|
||||
const transformedObj = new ObjectTransformer(newObj, 'root', 'root')
|
||||
.setOptions({ formatter: () => { } })
|
||||
.withDiff(oldObj)
|
||||
.transform();
|
||||
|
||||
expect(transformedObj).toEqual(expectedTransformedObj);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,25 +1,28 @@
|
||||
import { detectAndDecode, decodeAndTransformProto, FILE_TYPES } from '../src/decode';
|
||||
import { decodeAndTransformProto, FILE_TYPES, FILE_DECODERS } from '../src/decode';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
const layers_traces = [
|
||||
require('./traces/layers_trace/layers_trace_emptyregion.pb'),
|
||||
require('./traces/layers_trace/layers_trace_invalid_layer_visibility.pb'),
|
||||
require('./traces/layers_trace/layers_trace_orphanlayers.pb'),
|
||||
require('./traces/layers_trace/layers_trace_root.pb'),
|
||||
require('./traces/layers_trace/layers_trace_root_aosp.pb'),
|
||||
];
|
||||
import { expectedEntries, expectedLayers, layers_traces } from './traces/ExpectedTraces';
|
||||
|
||||
describe("Proto Transformations", () => {
|
||||
it("can transform surface flinger traces", () => {
|
||||
for (const trace of layers_traces) {
|
||||
fs.readFileSync(path.resolve(__dirname, trace));
|
||||
const traceBuffer = fs.readFileSync(path.resolve(__dirname, trace));
|
||||
for (var i = 0; i < layers_traces.length; i++) {
|
||||
const trace = layers_traces[i];
|
||||
const buffer = new Uint8Array(fs.readFileSync(path.resolve(__dirname, trace)));
|
||||
const data = decodeAndTransformProto(buffer, FILE_DECODERS[FILE_TYPES.SURFACE_FLINGER_TRACE].decoderParams, true);
|
||||
|
||||
const buffer = new Uint8Array(traceBuffer);
|
||||
const data = decodeAndTransformProto(buffer, FILE_TYPES.layers_trace, true);
|
||||
// use final entry as this determines if there was any error in previous entry parsing
|
||||
const transformedEntry = data.entries[data.entries.length-1];
|
||||
const expectedEntry = expectedEntries[i];
|
||||
for (const property in expectedEntry) {
|
||||
expect(transformedEntry[property]).toEqual(expectedEntry[property]);
|
||||
}
|
||||
|
||||
expect(true).toBe(true);
|
||||
// check final flattened layer
|
||||
const transformedLayer = transformedEntry.flattenedLayers[transformedEntry.flattenedLayers.length-1];
|
||||
const expectedLayer = expectedLayers[i];
|
||||
for (const property in expectedLayer) {
|
||||
expect(transformedLayer[property]).toEqual(expectedLayer[property]);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
71
tools/winscope/spec/TagErrorSpec.js
Normal file
71
tools/winscope/spec/TagErrorSpec.js
Normal file
@@ -0,0 +1,71 @@
|
||||
import { decodeAndTransformProto, FILE_TYPES, FILE_DECODERS } from '../src/decode';
|
||||
import Tag from '../src/flickerlib/tags/Tag';
|
||||
import Error from '../src/flickerlib/errors/Error';
|
||||
import { TaggingEngine } from '../src/flickerlib/common.js';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
const tagTrace = '../spec/traces/tag_trace.winscope';
|
||||
const errorTrace = '../spec/traces/error_trace.winscope';
|
||||
|
||||
describe("Tag Transformation", () => {
|
||||
it("can transform tag traces", () => {
|
||||
const buffer = new Uint8Array(fs.readFileSync(path.resolve(__dirname, tagTrace)));
|
||||
|
||||
const data = decodeAndTransformProto(buffer, FILE_DECODERS[FILE_TYPES.TAG_TRACE].decoderParams, true);
|
||||
|
||||
expect(data.entries[0].timestamp.toString()).toEqual('159979677861');
|
||||
expect(data.entries[1].timestamp.toString()).toEqual('161268519083');
|
||||
expect(data.entries[2].timestamp.toString()).toEqual('161126718496');
|
||||
expect(data.entries[3].timestamp.toString()).toEqual('161613497398');
|
||||
expect(data.entries[4].timestamp.toString()).toEqual('161227062777');
|
||||
expect(data.entries[5].timestamp.toString()).toEqual('161268519083');
|
||||
expect(data.entries[6].timestamp.toString()).toEqual('161825945076');
|
||||
expect(data.entries[7].timestamp.toString()).toEqual('162261072567');
|
||||
|
||||
expect(data.entries[0].tags).toEqual([new Tag(12345,"PIP_ENTER",true,1,"",0)]);
|
||||
expect(data.entries[1].tags).toEqual([new Tag(12345,"PIP_ENTER",false,2,"",2)]);
|
||||
expect(data.entries[2].tags).toEqual([new Tag(67890,"ROTATION",true,3,"",3)]);
|
||||
expect(data.entries[3].tags).toEqual([new Tag(67890,"ROTATION",false,4,"",4)]);
|
||||
expect(data.entries[4].tags).toEqual([new Tag(9876,"PIP_EXIT",true,5,"",5)]);
|
||||
expect(data.entries[5].tags).toEqual([new Tag(9876,"PIP_EXIT",false,6,"",6)]);
|
||||
expect(data.entries[6].tags).toEqual([new Tag(54321,"IME_APPEAR",true,7,"",7)]);
|
||||
expect(data.entries[7].tags).toEqual([new Tag(54321,"IME_APPEAR",false,8,"",8)]);
|
||||
})
|
||||
});
|
||||
|
||||
describe("Detect Tag", () => {
|
||||
it("can detect tags", () => {
|
||||
const wmFile = '../spec/traces/regular_rotation_in_last_state_wm_trace.winscope'
|
||||
const layersFile = '../spec/traces/regular_rotation_in_last_state_layers_trace.winscope'
|
||||
const wmBuffer = new Uint8Array(fs.readFileSync(path.resolve(__dirname, wmFile)));
|
||||
const layersBuffer = new Uint8Array(fs.readFileSync(path.resolve(__dirname, layersFile)));
|
||||
|
||||
const wmTrace = decodeAndTransformProto(wmBuffer, FILE_DECODERS[FILE_TYPES.WINDOW_MANAGER_TRACE].decoderParams, true);
|
||||
const layersTrace = decodeAndTransformProto(layersBuffer, FILE_DECODERS[FILE_TYPES.SURFACE_FLINGER_TRACE].decoderParams, true);
|
||||
|
||||
const engine = new TaggingEngine(wmTrace, layersTrace, (text) => { console.log(text) });
|
||||
const tagTrace = engine.run();
|
||||
expect(tagTrace.size).toEqual(4);
|
||||
expect(tagTrace.entries[0].timestamp.toString()).toEqual('280186737540384');
|
||||
expect(tagTrace.entries[1].timestamp.toString()).toEqual('280187243649340');
|
||||
expect(tagTrace.entries[2].timestamp.toString()).toEqual('280188522078113');
|
||||
expect(tagTrace.entries[3].timestamp.toString()).toEqual('280189020672174');
|
||||
})
|
||||
});
|
||||
|
||||
describe("Error Transformation", () => {
|
||||
it("can transform error traces", () => {
|
||||
const buffer = new Uint8Array(fs.readFileSync(path.resolve(__dirname, errorTrace)));
|
||||
|
||||
const data = decodeAndTransformProto(buffer, FILE_DECODERS[FILE_TYPES.ERROR_TRACE].decoderParams, true);
|
||||
|
||||
expect(data.entries[0].timestamp.toString()).toEqual('161401263106');
|
||||
expect(data.entries[1].timestamp.toString()).toEqual('161126718496');
|
||||
expect(data.entries[2].timestamp.toString()).toEqual('162261072567');
|
||||
|
||||
expect(data.entries[0].errors).toEqual([new Error("","",33,"",33)]);
|
||||
expect(data.entries[1].errors).toEqual([new Error("","",66,"",66)]);
|
||||
expect(data.entries[2].errors).toEqual([new Error("","",99,"",99)]);
|
||||
})
|
||||
});
|
||||
492
tools/winscope/spec/traces/ExpectedTraces.js
Normal file
492
tools/winscope/spec/traces/ExpectedTraces.js
Normal file
@@ -0,0 +1,492 @@
|
||||
import { Buffer, RectF, Transform, Matrix, Color, Rect, Region } from '../../src/flickerlib/common.js';
|
||||
import { VISIBLE_CHIP } from '../../src/flickerlib/treeview/Chips';
|
||||
|
||||
const standardTransform = new Transform(0, new Matrix(1, 0, 0, 0, 1, 0));
|
||||
const standardRect = new Rect(0, 0, 0, 0);
|
||||
const standardColor = new Color(0, 0, 0, 1);
|
||||
const standardCrop = new Rect(0, 0, -1, -1);
|
||||
|
||||
const expectedEmptyRegionLayer = {
|
||||
backgroundBlurRadius: 0,
|
||||
chips: [],
|
||||
cornerRadius: 0,
|
||||
effectiveScalingMode: 0,
|
||||
hwcCompositionType: "INVALID",
|
||||
id: 580,
|
||||
isOpaque: false,
|
||||
isRelativeOf: false,
|
||||
kind: "580",
|
||||
name: "SurfaceView - com.android.chrome/com.google.android.apps.chrome.Main#0",
|
||||
shadowRadius: 0,
|
||||
shortName: "SurfaceView - com.android.(...).Main#0",
|
||||
type: "BufferLayer",
|
||||
z: -1,
|
||||
zOrderRelativeOf: null,
|
||||
parentId: 579,
|
||||
activeBuffer: new Buffer(1440, 2614, 1472, 1),
|
||||
bufferTransform: standardTransform,
|
||||
color: new Color(0, 0, 0, 0.0069580078125),
|
||||
crop: standardCrop,
|
||||
hwcFrame: standardRect,
|
||||
screenBounds: new RectF(37, 43, 146, 152),
|
||||
transform: new Transform(0, new Matrix(1, 0, 37.37078094482422, 0, 1, -3.5995326042175293)),
|
||||
visibleRegion: new Region([new Rect(37, 43, 146, 152)]),
|
||||
};
|
||||
const emptyRegionProto = {
|
||||
2: "\nparent=0\ntype=BufferLayer\nname=Display Root#0",
|
||||
3: "\nparent=0\ntype=BufferLayer\nname=Display Overlays#0",
|
||||
4: "\nparent=2\ntype=BufferLayer\nname=mBelowAppWindowsContainers#0",
|
||||
5: "\nparent=2\ntype=BufferLayer\nname=com.android.server.wm.DisplayContent$TaskStackContainers@193aa46#0",
|
||||
6: "\nparent=5\ntype=BufferLayer\nname=animationLayer#0",
|
||||
7: "\nparent=5\ntype=BufferLayer\nname=splitScreenDividerAnchor#0",
|
||||
8: "\nparent=2\ntype=BufferLayer\nname=mAboveAppWindowsContainers#0",
|
||||
9: "\nparent=2\ntype=BufferLayer\nname=mImeWindowsContainers#0",
|
||||
10: "\nparent=5\ntype=BufferLayer\nname=Stack=0#0",
|
||||
11: "\nparent=10\ntype=ColorLayer\nname=animation background stackId=0#0",
|
||||
12: "\nparent=9\ntype=BufferLayer\nname=WindowToken{f81e7fc android.os.Binder@7c880ef}#0",
|
||||
13: "\nparent=4\ntype=BufferLayer\nname=WallpaperWindowToken{3756850 token=android.os.Binder@25b3e13}#0",
|
||||
18: "\nparent=13\ntype=BufferLayer\nname=fd46a8e com.breel.wallpapers.dioramas.lagos.LagosWallpaperService#0",
|
||||
19: "\nparent=18\ntype=BufferLayer\nname=com.breel.wallpapers.dioramas.lagos.LagosWallpaperService#0",
|
||||
20: "\nparent=8\ntype=BufferLayer\nname=WindowToken{fc1aa98 android.os.BinderProxy@3517c7b}#0",
|
||||
21: "\nparent=20\ntype=BufferLayer\nname=10022f1 DockedStackDivider#0",
|
||||
22: "\nparent=8\ntype=BufferLayer\nname=WindowToken{49a6772 android.os.BinderProxy@7ba1c7d}#0",
|
||||
23: "\nparent=22\ntype=BufferLayer\nname=56ef7c3 AssistPreviewPanel#0",
|
||||
24: "\nparent=8\ntype=BufferLayer\nname=WindowToken{35f7d5c android.os.BinderProxy@8b38fcf}#0",
|
||||
25: "\nparent=24\ntype=BufferLayer\nname=9029865 NavigationBar#0",
|
||||
26: "\nparent=8\ntype=BufferLayer\nname=WindowToken{a9a69ab android.os.BinderProxy@f64ffa}#0",
|
||||
27: "\nparent=26\ntype=BufferLayer\nname=5334808 StatusBar#0",
|
||||
28: "\nparent=8\ntype=BufferLayer\nname=WindowToken{a63ca37 android.os.BinderProxy@435eb36}#0",
|
||||
29: "\nparent=28\ntype=BufferLayer\nname=1a40ba4 ScreenDecorOverlay#0",
|
||||
30: "\nparent=8\ntype=BufferLayer\nname=WindowToken{4ed84c2 android.os.BinderProxy@33d1d0d}#0",
|
||||
31: "\nparent=30\ntype=BufferLayer\nname=7a0d2d3 ScreenDecorOverlayBottom#0",
|
||||
32: "\nparent=25\ntype=BufferLayer\nname=NavigationBar#0",
|
||||
33: "\nparent=27\ntype=BufferLayer\nname=StatusBar#0",
|
||||
34: "\nparent=29\ntype=BufferLayer\nname=ScreenDecorOverlay#0",
|
||||
35: "\nparent=31\ntype=BufferLayer\nname=ScreenDecorOverlayBottom#0",
|
||||
36: "\nparent=10\ntype=BufferLayer\nname=Task=239#0",
|
||||
37: "\nparent=632\ntype=BufferLayer\nname=AppWindowToken{188ce21 token=Token{824488 ActivityRecord{b0d882b u0 com.google.android.apps.nexuslauncher/.NexusLauncherActivity t239}}}#0",
|
||||
38: "\nparent=37\ntype=BufferLayer\nname=9f6e33d com.google.android.apps.nexuslauncher/com.google.android.apps.nexuslauncher.NexusLauncherActivity#0",
|
||||
44: "\nparent=37\ntype=BufferLayer\nname=81a00fc com.google.android.apps.nexuslauncher/com.google.android.apps.nexuslauncher.NexusLauncherActivity#0",
|
||||
88: "\nparent=5\ntype=BufferLayer\nname=Stack=2#0",
|
||||
89: "\nparent=88\ntype=ColorLayer\nname=animation background stackId=2#0",
|
||||
90: "\nparent=88\ntype=BufferLayer\nname=Task=241#0",
|
||||
91: "\nparent=633\ntype=BufferLayer\nname=AppWindowToken{a9f5144 token=Token{f102257 ActivityRecord{3a0fd6 u0 com.android.chrome/com.google.android.apps.chrome.Main t241}}}#0",
|
||||
96: "\nparent=91\ntype=BufferLayer\nname=87e310e com.android.chrome/com.google.android.apps.chrome.Main#0",
|
||||
574: "\nparent=8\ntype=BufferLayer\nname=WindowToken{37eed7d android.os.Binder@6e217d4}#0",
|
||||
579: "\nparent=96\ntype=BufferLayer\nname=com.android.chrome/com.google.android.apps.chrome.Main#0",
|
||||
580: "\nparent=579\ntype=BufferLayer\nname=SurfaceView - com.android.chrome/com.google.android.apps.chrome.Main#0",
|
||||
581: "\nparent=579\ntype=ColorLayer\nname=Background for -SurfaceView - com.android.chrome/com.google.android.apps.chrome.Main#0",
|
||||
583: "\nparent=44\ntype=BufferLayer\nname=com.google.android.apps.nexuslauncher/com.google.android.apps.nexuslauncher.NexusLauncherActivity#0",
|
||||
629: "\nparent=38\ntype=BufferLayer\nname=com.google.android.apps.nexuslauncher/com.google.android.apps.nexuslauncher.NexusLauncherActivity#2",
|
||||
632: "\nparent=6\ntype=BufferLayer\nname=Surface(name=AppWindowToken{188ce21 token=Token{824488 ActivityRecord{b0d882b u0 com.google.android.apps.nexuslauncher/.NexusLauncherActivity t239}}})/@0x90c9c46 - animation-leash#1",
|
||||
633: "\nparent=6\ntype=BufferLayer\nname=Surface(name=AppWindowToken{a9f5144 token=Token{f102257 ActivityRecord{3a0fd6 u0 com.android.chrome/com.google.android.apps.chrome.Main t241}}})/@0xd9b9374 - animation-leash#1"
|
||||
};
|
||||
const expectedEmptyRegion = {
|
||||
chips: [],
|
||||
proto: emptyRegionProto,
|
||||
hwcBlob: "",
|
||||
isVisible: true,
|
||||
kind: "entry",
|
||||
rects: [],
|
||||
shortName: "0d0h38m28s521ms",
|
||||
timestampMs: "2308521813510",
|
||||
where: "",
|
||||
name: "0d0h38m28s521ms",
|
||||
stableId: "LayerTraceEntry",
|
||||
visibleLayers: [],
|
||||
};
|
||||
|
||||
const expectedInvalidLayerVisibilityLayer = {
|
||||
backgroundBlurRadius: 0,
|
||||
chips: [],
|
||||
cornerRadius: 0,
|
||||
effectiveScalingMode: 0,
|
||||
hwcCompositionType: "INVALID",
|
||||
id: 1536,
|
||||
isOpaque: false,
|
||||
isRelativeOf: false,
|
||||
kind: "1536",
|
||||
name: "com.google.android.apps.nexuslauncher/com.google.android.apps.nexuslauncher.NexusLauncherActivity#2",
|
||||
shadowRadius: 0,
|
||||
shortName: "com.google.(...).NexusLauncherActivity#2",
|
||||
type: "BufferLayer",
|
||||
z: 0,
|
||||
zOrderRelativeOf: null,
|
||||
parentId: 1535,
|
||||
stableId: "BufferLayer 1536 com.google.android.apps.nexuslauncher/com.google.android.apps.nexuslauncher.NexusLauncherActivity#2",
|
||||
activeBuffer: new Buffer(1440, 2880, 1472, 1),
|
||||
bufferTransform: standardTransform,
|
||||
color: new Color(-1, -1, -1, 0),
|
||||
hwcFrame: standardRect,
|
||||
transform: standardTransform,
|
||||
visibleRegion: new Region([standardRect]),
|
||||
};
|
||||
const invalidLayerVisibilityProto = {
|
||||
2: "\nparent=0\ntype=BufferLayer\nname=Display Root#0",
|
||||
3: "\nparent=0\ntype=BufferLayer\nname=Display Overlays#0",
|
||||
4: "\nparent=2\ntype=BufferLayer\nname=mBelowAppWindowsContainers#0",
|
||||
5: "\nparent=2\ntype=BufferLayer\nname=com.android.server.wm.DisplayContent$TaskStackContainers@4270eb4#0",
|
||||
6: "\nparent=5\ntype=BufferLayer\nname=animationLayer#0",
|
||||
7: "\nparent=5\ntype=BufferLayer\nname=boostedAnimationLayer#0",
|
||||
8: "\nparent=5\ntype=BufferLayer\nname=homeAnimationLayer#0",
|
||||
9: "\nparent=5\ntype=BufferLayer\nname=splitScreenDividerAnchor#0",
|
||||
10: "\nparent=2\ntype=BufferLayer\nname=mAboveAppWindowsContainers#0",
|
||||
11: "\nparent=2\ntype=BufferLayer\nname=mImeWindowsContainers#0",
|
||||
12: "\nparent=5\ntype=BufferLayer\nname=Stack=0#0",
|
||||
13: "\nparent=12\ntype=ColorLayer\nname=animation background stackId=0#0",
|
||||
14: "\nparent=11\ntype=BufferLayer\nname=WindowToken{268fcff android.os.Binder@6688c1e}#0",
|
||||
15: "\nparent=4\ntype=BufferLayer\nname=WallpaperWindowToken{6572e20 token=android.os.Binder@9543923}#0",
|
||||
20: "\nparent=15\ntype=BufferLayer\nname=5e2e96f com.breel.wallpapers.dioramas.lagos.LagosWallpaperService#0",
|
||||
21: "\nparent=20\ntype=BufferLayer\nname=com.breel.wallpapers.dioramas.lagos.LagosWallpaperService#0",
|
||||
26: "\nparent=10\ntype=BufferLayer\nname=WindowToken{68e3f31 android.os.BinderProxy@f018fd8}#0",
|
||||
27: "\nparent=26\ntype=BufferLayer\nname=2b80616 NavigationBar#0",
|
||||
28: "\nparent=10\ntype=BufferLayer\nname=WindowToken{4e20cae android.os.BinderProxy@9086129}#0",
|
||||
29: "\nparent=28\ntype=BufferLayer\nname=b09a4f StatusBar#0",
|
||||
30: "\nparent=3\ntype=BufferLayer\nname=WindowToken{501e3b8 android.os.BinderProxy@238661b}#0",
|
||||
31: "\nparent=30\ntype=BufferLayer\nname=d803591 ScreenDecorOverlay#0",
|
||||
32: "\nparent=3\ntype=BufferLayer\nname=WindowToken{56d48f7 android.os.BinderProxy@f0f2cf6}#0",
|
||||
33: "\nparent=32\ntype=BufferLayer\nname=1cd8364 ScreenDecorOverlayBottom#0",
|
||||
35: "\nparent=29\ntype=BufferLayer\nname=StatusBar#0",
|
||||
36: "\nparent=31\ntype=BufferLayer\nname=ScreenDecorOverlay#0",
|
||||
37: "\nparent=33\ntype=BufferLayer\nname=ScreenDecorOverlayBottom#0",
|
||||
38: "\nparent=12\ntype=BufferLayer\nname=Task=2#0",
|
||||
39: "\nparent=38\ntype=BufferLayer\nname=AppWindowToken{215b919 token=Token{104a060 ActivityRecord{7e30c63 u0 com.google.android.apps.nexuslauncher/.NexusLauncherActivity t2}}}#0",
|
||||
821: "\nparent=14\ntype=BufferLayer\nname=5c937c8 InputMethod#0",
|
||||
1078: "\nparent=10\ntype=BufferLayer\nname=WindowToken{7dc6283 android.os.BinderProxy@f83c532}#0",
|
||||
1079: "\nparent=1078\ntype=BufferLayer\nname=32c0c00 AssistPreviewPanel#0",
|
||||
1080: "\nparent=10\ntype=BufferLayer\nname=WindowToken{9f8a3df android.os.BinderProxy@825027e}#0",
|
||||
1081: "\nparent=1080\ntype=BufferLayer\nname=26d9efb DockedStackDivider#0",
|
||||
1403: "\nparent=10\ntype=BufferLayer\nname=WindowToken{dedcfff android.os.Binder@a80cb1e}#0",
|
||||
1447: "\nparent=39\ntype=BufferLayer\nname=39ca531 com.google.android.apps.nexuslauncher/com.google.android.apps.nexuslauncher.NexusLauncherActivity#0",
|
||||
1454: "\nparent=27\ntype=BufferLayer\nname=NavigationBar#0",
|
||||
1502: "\nparent=10\ntype=BufferLayer\nname=WindowToken{3ea357b android.os.Binder@6d9a90a}#0",
|
||||
1505: "\nparent=1518\ntype=BufferLayer\nname=Task=623#0",
|
||||
1506: "\nparent=1505\ntype=BufferLayer\nname=AppWindowToken{6deed44 token=Token{45cae57 ActivityRecord{7f14bd6 u0 com.android.server.wm.flicker.testapp/.SimpleActivity t623}}}#0",
|
||||
1518: "\nparent=5\ntype=BufferLayer\nname=Stack=51#0",
|
||||
1519: "\nparent=1518\ntype=ColorLayer\nname=animation background stackId=51#0",
|
||||
1521: "\nparent=1506\ntype=BufferLayer\nname=496d52e com.android.server.wm.flicker.testapp/com.android.server.wm.flicker.testapp.SimpleActivity#0",
|
||||
1534: "\nparent=1447\ntype=BufferLayer\nname=com.google.android.apps.nexuslauncher/com.google.android.apps.nexuslauncher.NexusLauncherActivity#0",
|
||||
1535: "\nparent=39\ntype=BufferLayer\nname=e280197 com.google.android.apps.nexuslauncher/com.google.android.apps.nexuslauncher.NexusLauncherActivity#0",
|
||||
1536: "\nparent=1535\ntype=BufferLayer\nname=com.google.android.apps.nexuslauncher/com.google.android.apps.nexuslauncher.NexusLauncherActivity#2",
|
||||
};
|
||||
const expectedInvalidLayerVisibility = {
|
||||
chips: [],
|
||||
proto: invalidLayerVisibilityProto,
|
||||
hwcBlob: "",
|
||||
isVisible: true,
|
||||
kind: "entry",
|
||||
rects: [],
|
||||
shortName: "2d22h13m17s233ms",
|
||||
timestampMs: "252797233543024",
|
||||
where: "",
|
||||
name: "2d22h13m17s233ms",
|
||||
stableId: "LayerTraceEntry",
|
||||
visibleLayers: [],
|
||||
};
|
||||
|
||||
const expectedOrphanLayersLayer = {
|
||||
backgroundBlurRadius: 0,
|
||||
chips: [],
|
||||
cornerRadius: 0,
|
||||
effectiveScalingMode: 0,
|
||||
hwcCompositionType: "INVALID",
|
||||
id: 1012,
|
||||
isOpaque: true,
|
||||
isRelativeOf: false,
|
||||
kind: "1012",
|
||||
name: "SurfaceView - com.android.chrome/com.google.android.apps.chrome.Main#0",
|
||||
shadowRadius: 0,
|
||||
shortName: "SurfaceView - com.android.(...).Main#0",
|
||||
type: "BufferLayer",
|
||||
z: -1,
|
||||
zOrderRelativeOf: null,
|
||||
parentId: 1011,
|
||||
stableId: "BufferLayer 1012 SurfaceView - com.android.chrome/com.google.android.apps.chrome.Main#0",
|
||||
activeBuffer: new Buffer(1440, 2614, 1472, 1),
|
||||
bufferTransform: standardTransform,
|
||||
color: standardColor,
|
||||
crop: standardCrop,
|
||||
hwcFrame: standardRect,
|
||||
screenBounds: new RectF(0, 98, 1440, 2712),
|
||||
transform: new Transform(0, new Matrix(1, 0, 0, 0, 1, 98)),
|
||||
visibleRegion: new Region([new Rect(0, 98, 1440, 2712)]),
|
||||
};
|
||||
const expectedOrphanLayersProto = {
|
||||
2: "\nparent=0\ntype=BufferLayer\nname=Display Root#0",
|
||||
3: "\nparent=0\ntype=BufferLayer\nname=Display Overlays#0",
|
||||
4: "\nparent=2\ntype=BufferLayer\nname=mBelowAppWindowsContainers#0",
|
||||
5: "\nparent=2\ntype=BufferLayer\nname=com.android.server.wm.DisplayContent$TaskStackContainers@e7dd520#0",
|
||||
6: "\nparent=5\ntype=BufferLayer\nname=animationLayer#0",
|
||||
7: "\nparent=5\ntype=BufferLayer\nname=splitScreenDividerAnchor#0",
|
||||
8: "\nparent=2\ntype=BufferLayer\nname=mAboveAppWindowsContainers#0",
|
||||
9: "\nparent=2\ntype=BufferLayer\nname=mImeWindowsContainers#0",
|
||||
10: "\nparent=5\ntype=BufferLayer\nname=Stack=0#0",
|
||||
11: "\nparent=10\ntype=ColorLayer\nname=animation background stackId=0#0",
|
||||
12: "\nparent=9\ntype=BufferLayer\nname=WindowToken{1350b6f android.os.Binder@d1b0e4e}#0",
|
||||
13: "\nparent=4\ntype=BufferLayer\nname=WallpaperWindowToken{4537182 token=android.os.Binder@d87c4cd}#0",
|
||||
18: "\nparent=13\ntype=BufferLayer\nname=8d26107 com.breel.wallpapers.dioramas.lagos.LagosWallpaperService#0",
|
||||
19: "\nparent=18\ntype=BufferLayer\nname=com.breel.wallpapers.dioramas.lagos.LagosWallpaperService#0",
|
||||
20: "\nparent=8\ntype=BufferLayer\nname=WindowToken{fba948d android.os.BinderProxy@756b124}#0",
|
||||
21: "\nparent=20\ntype=BufferLayer\nname=dc26642 DockedStackDivider#0",
|
||||
22: "\nparent=8\ntype=BufferLayer\nname=WindowToken{45663b4 android.os.BinderProxy@5273887}#0",
|
||||
23: "\nparent=22\ntype=BufferLayer\nname=c617bdd AssistPreviewPanel#0",
|
||||
24: "\nparent=8\ntype=BufferLayer\nname=WindowToken{ef90888 android.os.BinderProxy@9d4dc2b}#0",
|
||||
25: "\nparent=24\ntype=BufferLayer\nname=1d24221 NavigationBar#0",
|
||||
26: "\nparent=8\ntype=BufferLayer\nname=WindowToken{6b1dca9 android.os.BinderProxy@a53d830}#0",
|
||||
27: "\nparent=26\ntype=BufferLayer\nname=eaca22e StatusBar#0",
|
||||
28: "\nparent=8\ntype=BufferLayer\nname=WindowToken{72e584c android.os.BinderProxy@3ba407f}#0",
|
||||
29: "\nparent=28\ntype=BufferLayer\nname=46af095 ScreenDecorOverlay#0",
|
||||
30: "\nparent=8\ntype=BufferLayer\nname=WindowToken{bc659b android.os.BinderProxy@f1405aa}#0",
|
||||
31: "\nparent=30\ntype=BufferLayer\nname=80ead38 ScreenDecorOverlayBottom#0",
|
||||
33: "\nparent=27\ntype=BufferLayer\nname=StatusBar#0",
|
||||
34: "\nparent=29\ntype=BufferLayer\nname=ScreenDecorOverlay#0",
|
||||
35: "\nparent=31\ntype=BufferLayer\nname=ScreenDecorOverlayBottom#0",
|
||||
36: "\nparent=10\ntype=BufferLayer\nname=Task=2#0",
|
||||
37: "\nparent=36\ntype=BufferLayer\nname=AppWindowToken{5162f77 token=Token{ac99d76 ActivityRecord{7749795 u0 com.google.android.apps.nexuslauncher/.NexusLauncherActivity t2}}}#0",
|
||||
38: "\nparent=37\ntype=BufferLayer\nname=2c19e73 com.google.android.apps.nexuslauncher/com.google.android.apps.nexuslauncher.NexusLauncherActivity#0",
|
||||
41: "\nparent=25\ntype=BufferLayer\nname=NavigationBar#1",
|
||||
43: "\nparent=37\ntype=BufferLayer\nname=2f0c80b com.google.android.apps.nexuslauncher/com.google.android.apps.nexuslauncher.NexusLauncherActivity#0",
|
||||
46: "\nparent=5\ntype=BufferLayer\nname=Stack=1#0",
|
||||
47: "\nparent=46\ntype=ColorLayer\nname=animation background stackId=1#0",
|
||||
48: "\nparent=46\ntype=BufferLayer\nname=Task=89#0",
|
||||
49: "\nparent=48\ntype=BufferLayer\nname=AppWindowToken{1d514da token=Token{d36fe85 ActivityRecord{e0ec0ef u0 com.android.chrome/com.google.android.apps.chrome.Main t89}}}#0",
|
||||
54: "\nparent=49\ntype=BufferLayer\nname=8ae6e06 com.android.chrome/com.google.android.apps.chrome.Main#0",
|
||||
607: "\nparent=5\ntype=BufferLayer\nname=Stack=9#0",
|
||||
608: "\nparent=607\ntype=ColorLayer\nname=animation background stackId=9#0",
|
||||
609: "\nparent=607\ntype=BufferLayer\nname=Task=97#0",
|
||||
615: "\nparent=609\ntype=BufferLayer\nname=AppWindowToken{28730c9 token=Token{4d768d0 ActivityRecord{faf093 u0 com.google.android.gm/.welcome.WelcomeTourActivity t97}}}#0",
|
||||
616: "\nparent=615\ntype=BufferLayer\nname=44e6e5c com.google.android.gm/com.google.android.gm.welcome.WelcomeTourActivity#0",
|
||||
679: "\nparent=12\ntype=BufferLayer\nname=2d0b1e4 InputMethod#0",
|
||||
993: "\nparent=8\ntype=BufferLayer\nname=WindowToken{e425e58 android.os.Binder@6d9a73b}#0",
|
||||
1011: "\nparent=54\ntype=BufferLayer\nname=com.android.chrome/com.google.android.apps.chrome.Main#0",
|
||||
1012: "\nparent=1011\ntype=BufferLayer\nname=SurfaceView - com.android.chrome/com.google.android.apps.chrome.Main#0",
|
||||
1013: "\nparent=1011\ntype=ColorLayer\nname=Background for -SurfaceView - com.android.chrome/com.google.android.apps.chrome.Main#0",
|
||||
};
|
||||
const expectedOrphanLayers = {
|
||||
chips: [],
|
||||
proto: expectedOrphanLayersProto,
|
||||
hwcBlob: "",
|
||||
isVisible: true,
|
||||
kind: "entry",
|
||||
rects: [],
|
||||
shortName: "3d23h30m9s820ms",
|
||||
timestampMs: "343809820196384",
|
||||
where: "",
|
||||
name: "3d23h30m9s820ms",
|
||||
stableId: "LayerTraceEntry",
|
||||
visibleLayers: [],
|
||||
};
|
||||
|
||||
const expectedRootLayer = {
|
||||
backgroundBlurRadius: 0,
|
||||
cornerRadius: 0,
|
||||
effectiveScalingMode: 0,
|
||||
hwcCompositionType: "INVALID",
|
||||
id: 12545,
|
||||
isOpaque: true,
|
||||
isRelativeOf: false,
|
||||
kind: "12545",
|
||||
name: "com.android.server.wm.flicker.testapp/com.android.server.wm.flicker.testapp.SimpleActivity#0",
|
||||
shadowRadius: 0,
|
||||
shortName: "com.android.(...).SimpleActivity#0",
|
||||
type: "BufferQueueLayer",
|
||||
z: 0,
|
||||
zOrderRelativeOf: null,
|
||||
parentId: 12541,
|
||||
stableId: "BufferQueueLayer 12545 com.android.server.wm.flicker.testapp/com.android.server.wm.flicker.testapp.SimpleActivity#0",
|
||||
activeBuffer: new Buffer(1440, 2960, 1472, 1),
|
||||
chips: [VISIBLE_CHIP],
|
||||
bufferTransform: standardTransform,
|
||||
color: standardColor,
|
||||
crop: new Rect(0, 0, 1440, 2960),
|
||||
hwcFrame: standardRect,
|
||||
screenBounds: new RectF(0, 0, 1440, 2960),
|
||||
sourceBounds: new RectF(0, 0, 1440, 2960),
|
||||
transform: standardTransform,
|
||||
visibleRegion: new Region([new Rect(0, 0, 1440, 2960)]),
|
||||
};
|
||||
const expectedRootProto = {
|
||||
2: "\nparent=-1\ntype=ContainerLayer\nname=Root#0",
|
||||
3: "\nparent=2\ntype=ContainerLayer\nname=mWindowContainers#0",
|
||||
4: "\nparent=2\ntype=ContainerLayer\nname=mOverlayContainers#0",
|
||||
5: "\nparent=3\ntype=ContainerLayer\nname=mBelowAppWindowsContainers#0",
|
||||
6: "\nparent=3\ntype=ContainerLayer\nname=com.android.server.wm.DisplayContent$TaskContainers@708b672#0",
|
||||
7: "\nparent=6\ntype=ContainerLayer\nname=animationLayer#0",
|
||||
8: "\nparent=6\ntype=ContainerLayer\nname=boostedAnimationLayer#0",
|
||||
9: "\nparent=6\ntype=ContainerLayer\nname=homeAnimationLayer#0",
|
||||
10: "\nparent=6\ntype=ContainerLayer\nname=splitScreenDividerAnchor#0",
|
||||
11: "\nparent=3\ntype=ContainerLayer\nname=mAboveAppWindowsContainers#0",
|
||||
12: "\nparent=3\ntype=ContainerLayer\nname=ImeContainer#0",
|
||||
13: "\nparent=6\ntype=ContainerLayer\nname=Task=1#0",
|
||||
18: "\nparent=5\ntype=ContainerLayer\nname=WallpaperWindowToken{4c3f8ef token=android.os.Binder@a0341ce}#0",
|
||||
19: "\nparent=18\ntype=ContainerLayer\nname=aa9ba7e com.breel.wallpapers18.soundviz.wallpaper.variations.SoundVizWallpaperV2#0",
|
||||
20: "\nparent=19\ntype=BufferQueueLayer\nname=com.breel.wallpapers18.soundviz.wallpaper.variations.SoundVizWallpaperV2#0",
|
||||
23: "\nparent=11\ntype=ContainerLayer\nname=WindowToken{2e98b86 android.os.BinderProxy@6e5dbc8}#0",
|
||||
24: "\nparent=23\ntype=ContainerLayer\nname=5976c47 NavigationBar0#0",
|
||||
25: "\nparent=11\ntype=ContainerLayer\nname=WindowToken{525aa4 android.os.BinderProxy@df1e236}#0",
|
||||
26: "\nparent=25\ntype=ContainerLayer\nname=986c00d NotificationShade#0",
|
||||
27: "\nparent=11\ntype=ContainerLayer\nname=WindowToken{7ec5009 android.os.BinderProxy@de2add3}#0",
|
||||
28: "\nparent=27\ntype=ContainerLayer\nname=3a0542f StatusBar#0",
|
||||
31: "\nparent=-1\ntype=ContainerLayer\nname=WindowToken{eef604c android.os.BinderProxy@d3a687f}#0",
|
||||
32: "\nparent=31\ntype=ContainerLayer\nname=20b5895 ScreenDecorOverlay#0",
|
||||
33: "\nparent=-1\ntype=ContainerLayer\nname=WindowToken{4846f6f android.os.BinderProxy@39824e}#0",
|
||||
34: "\nparent=33\ntype=ContainerLayer\nname=1d714 ScreenDecorOverlayBottom#0",
|
||||
36: "\nparent=32\ntype=BufferQueueLayer\nname=ScreenDecorOverlay#0",
|
||||
38: "\nparent=34\ntype=BufferQueueLayer\nname=ScreenDecorOverlayBottom#0",
|
||||
40: "\nparent=28\ntype=BufferQueueLayer\nname=StatusBar#0",
|
||||
43: "\nparent=12\ntype=ContainerLayer\nname=WindowToken{fa12db9 android.os.Binder@4b88380}#0",
|
||||
46: "\nparent=13\ntype=ContainerLayer\nname=Task=4#0",
|
||||
47: "\nparent=46\ntype=ContainerLayer\nname=ActivityRecord{99bbfb0 u0 com.google.android.apps.nexuslauncher/.NexusLauncherActivity#0",
|
||||
54: "\nparent=24\ntype=BufferQueueLayer\nname=NavigationBar0#0",
|
||||
71: "\nparent=43\ntype=ContainerLayer\nname=e8f94d2 InputMethod#0",
|
||||
11499: "\nparent=47\ntype=ContainerLayer\nname=6737b79 com.google.android.apps.nexuslauncher/com.google.android.apps.nexuslauncher.NexusLauncherActivity#0",
|
||||
11501: "\nparent=-1\ntype=ContainerLayer\nname=Input Consumer recents_animation_input_consumer#2",
|
||||
11759: "\nparent=6\ntype=ContainerLayer\nname=Task=873#0",
|
||||
11760: "\nparent=11759\ntype=ContainerLayer\nname=Task=874#0",
|
||||
11761: "\nparent=11760\ntype=ContainerLayer\nname=ActivityRecord{7398002 u0 com.android.server.wm.flicker.testapp/.ImeActivityAutoFocus#0",
|
||||
11785: "\nparent=11761\ntype=ColorLayer\nname=Letterbox - right#0",
|
||||
12131: "\nparent=11\ntype=ContainerLayer\nname=WindowToken{bbffcfd android.os.Binder@547b554}#0",
|
||||
12379: "\nparent=47\ntype=ContainerLayer\nname=3f8f098 com.google.android.apps.nexuslauncher/com.google.android.apps.nexuslauncher.NexusLauncherActivity#0",
|
||||
12412: "\nparent=11761\ntype=ContainerLayer\nname=edca7c6 com.android.server.wm.flicker.testapp/com.android.server.wm.flicker.testapp.ImeActivityAutoFocus#0",
|
||||
12448: "\nparent=2147483645\ntype=ContainerLayer\nname=Surface(name=ActivityRecord{99bbfb0 u0 com.google.android.apps.nexuslauncher/.NexusLauncherActivity)/@0x2c3972c - animation-leash#0",
|
||||
12449: "\nparent=2147483645\ntype=ContainerLayer\nname=Surface(name=ActivityRecord{fc16c94 u0 com.android.server.wm.flicker.testapp/.ImeActivity)/@0x7049863 - animation-leash#0",
|
||||
12485: "\nparent=6\ntype=ContainerLayer\nname=Task=908#0",
|
||||
12486: "\nparent=12485\ntype=ContainerLayer\nname=Task=909#0",
|
||||
12487: "\nparent=12486\ntype=ContainerLayer\nname=ActivityRecord{4b3c5cb u0 com.android.server.wm.flicker.testapp/.ImeActivity#0",
|
||||
12500: "\nparent=2147483645\ntype=ContainerLayer\nname=Surface(name=ActivityRecord{99bbfb0 u0 com.google.android.apps.nexuslauncher/.NexusLauncherActivity)/@0x2c3972c - animation-leash#0",
|
||||
12501: "\nparent=2147483645\ntype=ContainerLayer\nname=Surface(name=ActivityRecord{4b3c5cb u0 com.android.server.wm.flicker.testapp/.ImeActivity)/@0x4ad47a1 - animation-leash#0",
|
||||
12502: "\nparent=2147483645\ntype=ContainerLayer\nname=Surface(name=WallpaperWindowToken{4c3f8ef token=android.os.Binder@a0341ce})/@0xcde5e65 - animation-leash#0",
|
||||
12511: "\nparent=12487\ntype=ColorLayer\nname=Letterbox - right#1",
|
||||
12514: "\nparent=12487\ntype=ContainerLayer\nname=debe1ed com.android.server.wm.flicker.testapp/com.android.server.wm.flicker.testapp.ImeActivity#0",
|
||||
12526: "\nparent=11\ntype=ContainerLayer\nname=WindowToken{6b7d663 android.os.BinderProxy@391f21d}#0",
|
||||
12527: "\nparent=12526\ntype=ContainerLayer\nname=32aa260 AssistPreviewPanel#0",
|
||||
12529: "\nparent=11\ntype=ContainerLayer\nname=WindowToken{31f7489 android.os.BinderProxy@67b1e53}#0",
|
||||
12530: "\nparent=12529\ntype=ContainerLayer\nname=cbb28bc DockedStackDivider#0",
|
||||
12536: "\nparent=6\ntype=ContainerLayer\nname=Task=910#0",
|
||||
12537: "\nparent=12536\ntype=ContainerLayer\nname=Task=911#0",
|
||||
12538: "\nparent=12537\ntype=ContainerLayer\nname=ActivityRecord{d3b8a44 u0 com.android.server.wm.flicker.testapp/.SimpleActivity#0",
|
||||
12541: "\nparent=12538\ntype=ContainerLayer\nname=a3583c5 com.android.server.wm.flicker.testapp/com.android.server.wm.flicker.testapp.SimpleActivity#0",
|
||||
12545: "\nparent=12541\ntype=BufferQueueLayer\nname=com.android.server.wm.flicker.testapp/com.android.server.wm.flicker.testapp.SimpleActivity#0",
|
||||
2147483645: "\nparent=-1\ntype=\nname=Offscreen Root",
|
||||
};
|
||||
const expectedRoot = {
|
||||
chips: [],
|
||||
proto: expectedRootProto,
|
||||
hwcBlob: "",
|
||||
isVisible: true,
|
||||
kind: "entry",
|
||||
shortName: "0d1h46m19s146ms",
|
||||
timestampMs: "6379146308030",
|
||||
where: "",
|
||||
name: "0d1h46m19s146ms",
|
||||
stableId: "LayerTraceEntry",
|
||||
};
|
||||
|
||||
const expectedRootAospLayer = {
|
||||
backgroundBlurRadius: 0,
|
||||
cornerRadius: 0,
|
||||
effectiveScalingMode: 0,
|
||||
hwcCompositionType: "INVALID",
|
||||
id: 876,
|
||||
isOpaque: false,
|
||||
isRelativeOf: false,
|
||||
kind: "876",
|
||||
name: "com.android.launcher3/com.android.launcher3.Launcher#0",
|
||||
shadowRadius: 0,
|
||||
shortName: "com.android.(...).Launcher#0",
|
||||
type: "BufferLayer",
|
||||
z: 0,
|
||||
zOrderRelativeOf: null,
|
||||
parentId: 41,
|
||||
activeBuffer: new Buffer(1440, 2880, 1472, 1),
|
||||
bufferTransform: standardTransform,
|
||||
chips: [VISIBLE_CHIP],
|
||||
color: standardColor,
|
||||
crop: new Rect(0, 0, 1440, 2880),
|
||||
hwcFrame: standardRect,
|
||||
screenBounds: new RectF(0, 0, 1440, 2880),
|
||||
sourceBounds: new RectF(0, 0, 1440, 2880),
|
||||
transform: standardTransform,
|
||||
visibleRegion: new Region([new Rect(0, 0, 1440, 2880)]),
|
||||
};
|
||||
const expectedRootAospProto = {
|
||||
2: "\nparent=-1\ntype=ContainerLayer\nname=Display Root#0",
|
||||
3: "\nparent=-1\ntype=ContainerLayer\nname=Display Overlays#0",
|
||||
4: "\nparent=2\ntype=ContainerLayer\nname=mBelowAppWindowsContainers#0",
|
||||
5: "\nparent=2\ntype=ContainerLayer\nname=com.android.server.wm.DisplayContent$TaskStackContainers@d8077b3#0",
|
||||
6: "\nparent=5\ntype=ContainerLayer\nname=animationLayer#0",
|
||||
7: "\nparent=5\ntype=ContainerLayer\nname=boostedAnimationLayer#0",
|
||||
8: "\nparent=5\ntype=ContainerLayer\nname=homeAnimationLayer#0",
|
||||
9: "\nparent=5\ntype=ContainerLayer\nname=splitScreenDividerAnchor#0",
|
||||
10: "\nparent=2\ntype=ContainerLayer\nname=mAboveAppWindowsContainers#0",
|
||||
11: "\nparent=2\ntype=ContainerLayer\nname=mImeWindowsContainers#0",
|
||||
12: "\nparent=5\ntype=ContainerLayer\nname=Stack=0#0",
|
||||
13: "\nparent=12\ntype=ColorLayer\nname=animation background stackId=0#0",
|
||||
18: "\nparent=4\ntype=ContainerLayer\nname=WallpaperWindowToken{5a7eaca token=android.os.Binder@438b635}#0",
|
||||
23: "\nparent=10\ntype=ContainerLayer\nname=WindowToken{d19e48 android.os.BinderProxy@560ac3a}#0",
|
||||
24: "\nparent=23\ntype=ContainerLayer\nname=b2a84e1 NavigationBar0#0",
|
||||
25: "\nparent=10\ntype=ContainerLayer\nname=WindowToken{74d6851 android.os.BinderProxy@8b22adb}#0",
|
||||
26: "\nparent=25\ntype=ContainerLayer\nname=16448b6 StatusBar#0",
|
||||
27: "\nparent=-1\ntype=ContainerLayer\nname=WindowToken{624863c android.os.BinderProxy@975b02f}#0",
|
||||
28: "\nparent=27\ntype=ContainerLayer\nname=cdb9fc5 ScreenDecorOverlay#0",
|
||||
29: "\nparent=-1\ntype=ContainerLayer\nname=WindowToken{cb7204b android.os.BinderProxy@b8f3d1a}#0",
|
||||
30: "\nparent=29\ntype=ContainerLayer\nname=ad1ca28 ScreenDecorOverlayBottom#0",
|
||||
31: "\nparent=28\ntype=BufferLayer\nname=ScreenDecorOverlay#0",
|
||||
32: "\nparent=30\ntype=BufferLayer\nname=ScreenDecorOverlayBottom#0",
|
||||
33: "\nparent=18\ntype=ContainerLayer\nname=4f4b23b com.android.systemui.ImageWallpaper#0",
|
||||
34: "\nparent=33\ntype=BufferLayer\nname=com.android.systemui.ImageWallpaper#0",
|
||||
36: "\nparent=26\ntype=BufferLayer\nname=StatusBar#0",
|
||||
37: "\nparent=12\ntype=ContainerLayer\nname=Task=144#0",
|
||||
38: "\nparent=37\ntype=ContainerLayer\nname=AppWindowToken{54e2de0 token=Token{f4c5fe3 ActivityRecord{6a9dc12 u0 com.android.launcher3/.Launcher t144}}}#0",
|
||||
40: "\nparent=-1\ntype=ContainerLayer\nname=Input Consumer recents_animation_input_consumer#1",
|
||||
41: "\nparent=38\ntype=ContainerLayer\nname=418b5c0 com.android.launcher3/com.android.launcher3.Launcher#0",
|
||||
45: "\nparent=11\ntype=ContainerLayer\nname=WindowToken{9158878 android.os.Binder@4f4a5db}#0",
|
||||
46: "\nparent=24\ntype=BufferLayer\nname=NavigationBar0#0",
|
||||
731: "\nparent=10\ntype=ContainerLayer\nname=WindowToken{c0ebbde android.os.BinderProxy@1af0e60}#0",
|
||||
732: "\nparent=731\ntype=ContainerLayer\nname=b37d1bf AssistPreviewPanel#0",
|
||||
733: "\nparent=10\ntype=ContainerLayer\nname=WindowToken{dc6b7ea android.os.BinderProxy@166b08c}#0",
|
||||
734: "\nparent=733\ntype=ContainerLayer\nname=2a1cadb DockedStackDivider#0",
|
||||
862: "\nparent=10\ntype=ContainerLayer\nname=WindowToken{f63efe6 android.os.Binder@d536e41}#0",
|
||||
865: "\nparent=887\ntype=ContainerLayer\nname=Task=170#0",
|
||||
866: "\nparent=865\ntype=ContainerLayer\nname=AppWindowToken{c829d40 token=Token{59970c3 ActivityRecord{36f2472 u0 com.android.server.wm.flicker.testapp/.PipActivity t170}}}#0",
|
||||
871: "\nparent=866\ntype=ContainerLayer\nname=8153ff7 com.android.server.wm.flicker.testapp/com.android.server.wm.flicker.testapp.PipActivity#0",
|
||||
876: "\nparent=41\ntype=BufferLayer\nname=com.android.launcher3/com.android.launcher3.Launcher#0",
|
||||
887: "\nparent=5\ntype=ContainerLayer\nname=Stack=78#0",
|
||||
888: "\nparent=887\ntype=ColorLayer\nname=animation background stackId=78#0",
|
||||
};
|
||||
const expectedRootAosp = {
|
||||
chips: [],
|
||||
proto: expectedRootAospProto,
|
||||
hwcBlob: "",
|
||||
isVisible: true,
|
||||
kind: "entry",
|
||||
shortName: "0d1h3m1s911ms",
|
||||
timestampMs: "3781911657318",
|
||||
where: "",
|
||||
name: "0d1h3m1s911ms",
|
||||
stableId: "LayerTraceEntry",
|
||||
};
|
||||
|
||||
const expectedEntries = [
|
||||
expectedEmptyRegion,
|
||||
expectedInvalidLayerVisibility,
|
||||
expectedOrphanLayers,
|
||||
expectedRoot,
|
||||
expectedRootAosp
|
||||
];
|
||||
const expectedLayers = [
|
||||
expectedEmptyRegionLayer,
|
||||
expectedInvalidLayerVisibilityLayer,
|
||||
expectedOrphanLayersLayer,
|
||||
expectedRootLayer,
|
||||
expectedRootAospLayer
|
||||
];
|
||||
const layers_traces = [
|
||||
'../spec/traces/layers_trace/layers_trace_emptyregion.pb',
|
||||
'../spec/traces/layers_trace/layers_trace_invalid_layer_visibility.pb',
|
||||
'../spec/traces/layers_trace/layers_trace_orphanlayers.pb',
|
||||
'../spec/traces/layers_trace/layers_trace_root.pb',
|
||||
'../spec/traces/layers_trace/layers_trace_root_aosp.pb',
|
||||
];
|
||||
|
||||
export { expectedEntries, expectedLayers, layers_traces };
|
||||
BIN
tools/winscope/spec/traces/error_trace.winscope
Normal file
BIN
tools/winscope/spec/traces/error_trace.winscope
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
tools/winscope/spec/traces/tag_trace.winscope
Normal file
BIN
tools/winscope/spec/traces/tag_trace.winscope
Normal file
Binary file not shown.
@@ -9,6 +9,31 @@ class DiffNode extends Node {
|
||||
constructor(nodeDef, diffType, children) {
|
||||
super(nodeDef, children);
|
||||
this.diff = { type: diffType };
|
||||
this.name = undefined;
|
||||
this.stableId = undefined;
|
||||
this.kind = undefined;
|
||||
this.shortName = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
class ObjNode extends Node {
|
||||
constructor(name, children, combined, stableId) {
|
||||
const nodeDef = {
|
||||
kind: '',
|
||||
name: name,
|
||||
stableId: stableId,
|
||||
};
|
||||
if (combined) {
|
||||
nodeDef.combined = true;
|
||||
}
|
||||
super(nodeDef, children);
|
||||
}
|
||||
}
|
||||
|
||||
class ObjDiffNode extends ObjNode {
|
||||
constructor(name, diffType, children, combined, stableId) {
|
||||
super(name, children, combined, stableId);
|
||||
this.diff = { type: diffType };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,4 +55,4 @@ function toPlainObject(theClass) {
|
||||
}
|
||||
}
|
||||
|
||||
export { Node, DiffNode, toPlainObject };
|
||||
export { Node, DiffNode, ObjNode, ObjDiffNode, toPlainObject };
|
||||
|
||||
41
tools/winscope/src/AccessibilityTraceView.vue
Normal file
41
tools/winscope/src/AccessibilityTraceView.vue
Normal file
@@ -0,0 +1,41 @@
|
||||
<!-- 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.
|
||||
-->
|
||||
|
||||
<template>
|
||||
<TraceView
|
||||
:store="store"
|
||||
:file="file"
|
||||
:summarizer="summarizer"
|
||||
:presentTags="[]"
|
||||
:presentErrors="[]"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import TraceView from "@/TraceView.vue"
|
||||
|
||||
export default {
|
||||
name: "AccessibilityTraceView",
|
||||
props: ["store", "file"],
|
||||
components: {
|
||||
TraceView
|
||||
},
|
||||
methods: {
|
||||
summarizer(item) {
|
||||
return null;
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -19,6 +19,11 @@
|
||||
<h1 class="md-title" style="flex: 1">{{title}}</h1>
|
||||
<md-button
|
||||
class="md-primary md-theme-default download-all-btn"
|
||||
@click="generateTags()"
|
||||
v-if="dataLoaded && canGenerateTags"
|
||||
>Generate Tags</md-button>
|
||||
<md-button
|
||||
class="md-primary md-theme-default"
|
||||
@click="downloadAsZip(files)"
|
||||
v-if="dataLoaded"
|
||||
>Download All</md-button>
|
||||
@@ -36,7 +41,7 @@
|
||||
<dataadb class="adbinput" ref="adb" :store="store"
|
||||
@dataReady="onDataReady" @statusChange="setStatus" />
|
||||
</div>
|
||||
<div class="input">
|
||||
<div class="input" @dragover.prevent @drop.prevent>
|
||||
<datainput class="fileinput" ref="input" :store="store"
|
||||
@dataReady="onDataReady" @statusChange="setStatus" />
|
||||
</div>
|
||||
@@ -52,13 +57,19 @@
|
||||
:ref="file.type"
|
||||
:store="store"
|
||||
:file="file"
|
||||
:presentTags="Object.freeze(presentTags)"
|
||||
:presentErrors="Object.freeze(presentErrors)"
|
||||
:dataViewFiles="dataViewFiles"
|
||||
@click="onDataViewFocus(file)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<overlay
|
||||
:presentTags="Object.freeze(presentTags)"
|
||||
:presentErrors="Object.freeze(presentErrors)"
|
||||
:store="store"
|
||||
:ref="overlayRef"
|
||||
:searchTypes="searchTypes"
|
||||
v-if="dataLoaded"
|
||||
v-on:bottom-nav-height-change="handleBottomNavHeightChange"
|
||||
/>
|
||||
@@ -77,7 +88,10 @@ import FileType from './mixins/FileType.js';
|
||||
import SaveAsZip from './mixins/SaveAsZip';
|
||||
import FocusedDataViewFinder from './mixins/FocusedDataViewFinder';
|
||||
import {DIRECTION} from './utils/utils';
|
||||
import {NAVIGATION_STYLE} from './utils/consts';
|
||||
import Searchbar from './Searchbar.vue';
|
||||
import {NAVIGATION_STYLE, SEARCH_TYPE} from './utils/consts';
|
||||
import {TRACE_TYPES, FILE_TYPES, dataFile} from './decode.js';
|
||||
import { TaggingEngine } from './flickerlib/common';
|
||||
|
||||
const APP_NAME = 'Winscope';
|
||||
|
||||
@@ -97,11 +111,19 @@ export default {
|
||||
simplifyNames: true,
|
||||
displayDefaults: true,
|
||||
navigationStyle: NAVIGATION_STYLE.GLOBAL,
|
||||
flickerTraceView: false,
|
||||
showFileTypes: [],
|
||||
isInputMode: false,
|
||||
}),
|
||||
overlayRef: 'overlay',
|
||||
mainContentStyle: {
|
||||
'padding-bottom': `${CONTENT_BOTTOM_PADDING}px`,
|
||||
},
|
||||
tagFile: null,
|
||||
presentTags: [],
|
||||
presentErrors: [],
|
||||
searchTypes: [SEARCH_TYPE.TIMESTAMP],
|
||||
hasTagOrErrorTraces: false,
|
||||
};
|
||||
},
|
||||
created() {
|
||||
@@ -113,9 +135,75 @@ export default {
|
||||
window.removeEventListener('keydown', this.onKeyDown);
|
||||
window.removeEventListener('scroll', this.onScroll);
|
||||
},
|
||||
|
||||
methods: {
|
||||
/** Get states from either tag files or error files */
|
||||
getUpdatedStates(files) {
|
||||
var states = [];
|
||||
for (const file of files) {
|
||||
states.push(...file.data);
|
||||
}
|
||||
return states;
|
||||
},
|
||||
/** Get tags from all uploaded tag files*/
|
||||
getUpdatedTags() {
|
||||
if (this.tagFile === null) return [];
|
||||
const tagStates = this.getUpdatedStates([this.tagFile]);
|
||||
var tags = [];
|
||||
tagStates.forEach(tagState => {
|
||||
tagState.tags.forEach(tag => {
|
||||
tag.timestamp = Number(tagState.timestamp);
|
||||
// tags generated on frontend have transition.name due to kotlin enum
|
||||
tag.transition = tag.transition.name ?? tag.transition;
|
||||
tags.push(tag);
|
||||
});
|
||||
});
|
||||
return tags;
|
||||
},
|
||||
/** Get tags from all uploaded error files*/
|
||||
getUpdatedErrors() {
|
||||
var errorStates = this.getUpdatedStates(this.errorFiles);
|
||||
var errors = [];
|
||||
//TODO (b/196201487) add check if errors empty
|
||||
errorStates.forEach(errorState => {
|
||||
errorState.errors.forEach(error => {
|
||||
error.timestamp = Number(errorState.timestamp);
|
||||
errors.push(error);
|
||||
});
|
||||
});
|
||||
return errors;
|
||||
},
|
||||
/** Set flicker mode check for if there are tag/error traces uploaded*/
|
||||
updateHasTagOrErrorTraces() {
|
||||
return this.hasTagTrace() || this.hasErrorTrace();
|
||||
},
|
||||
hasTagTrace() {
|
||||
return this.tagFile !== null;
|
||||
},
|
||||
hasErrorTrace() {
|
||||
return this.errorFiles.length > 0;
|
||||
},
|
||||
/** Activate flicker search tab if tags/errors uploaded*/
|
||||
updateSearchTypes() {
|
||||
this.searchTypes = [SEARCH_TYPE.TIMESTAMP];
|
||||
if (this.hasTagTrace()) {
|
||||
this.searchTypes.push(SEARCH_TYPE.TRANSITIONS);
|
||||
}
|
||||
if (this.hasErrorTrace()) {
|
||||
this.searchTypes.push(SEARCH_TYPE.ERRORS);
|
||||
}
|
||||
},
|
||||
/** Filter data view files by current show settings*/
|
||||
updateShowFileTypes() {
|
||||
this.store.showFileTypes = this.dataViewFiles
|
||||
.filter((file) => file.show)
|
||||
.map(file => file.type);
|
||||
},
|
||||
clear() {
|
||||
this.store.showFileTypes = [];
|
||||
this.tagFile = null;
|
||||
this.$store.commit('clearFiles');
|
||||
this.buttonClicked("Clear")
|
||||
},
|
||||
onDataViewFocus(file) {
|
||||
this.$store.commit('setActiveFile', file);
|
||||
@@ -123,6 +211,7 @@ export default {
|
||||
},
|
||||
onKeyDown(event) {
|
||||
event = event || window.event;
|
||||
if (this.store.isInputMode) return false;
|
||||
if (event.keyCode == 37 /* left */ ) {
|
||||
this.$store.dispatch('advanceTimeline', DIRECTION.BACKWARD);
|
||||
} else if (event.keyCode == 39 /* right */ ) {
|
||||
@@ -139,7 +228,14 @@ export default {
|
||||
},
|
||||
onDataReady(files) {
|
||||
this.$store.dispatch('setFiles', files);
|
||||
|
||||
this.tagFile = this.tagFiles[0] ?? null;
|
||||
this.hasTagOrErrorTraces = this.updateHasTagOrErrorTraces();
|
||||
this.presentTags = this.getUpdatedTags();
|
||||
this.presentErrors = this.getUpdatedErrors();
|
||||
this.updateSearchTypes();
|
||||
this.updateFocusedView();
|
||||
this.updateShowFileTypes();
|
||||
},
|
||||
setStatus(status) {
|
||||
if (status) {
|
||||
@@ -155,10 +251,45 @@ export default {
|
||||
`${ CONTENT_BOTTOM_PADDING + newHeight }px`,
|
||||
);
|
||||
},
|
||||
generateTags() {
|
||||
// generate tag file
|
||||
this.buttonClicked("Generate Tags");
|
||||
const engine = new TaggingEngine(
|
||||
this.$store.getters.tagGenerationWmTrace,
|
||||
this.$store.getters.tagGenerationSfTrace,
|
||||
(text) => { console.log(text) }
|
||||
);
|
||||
const tagTrace = engine.run();
|
||||
const tagFile = this.generateTagFile(tagTrace);
|
||||
|
||||
// update tag trace in set files, update flicker mode
|
||||
this.tagFile = tagFile;
|
||||
this.hasTagOrErrorTraces = this.updateHasTagOrErrorTraces();
|
||||
this.presentTags = this.getUpdatedTags();
|
||||
this.presentErrors = this.getUpdatedErrors();
|
||||
this.updateSearchTypes();
|
||||
},
|
||||
|
||||
generateTagFile(tagTrace) {
|
||||
const data = tagTrace.entries;
|
||||
const blobUrl = URL.createObjectURL(new Blob([], {type: undefined}));
|
||||
return dataFile(
|
||||
"GeneratedTagTrace.winscope",
|
||||
data.map((x) => x.timestamp),
|
||||
data,
|
||||
blobUrl,
|
||||
FILE_TYPES.TAG_TRACE
|
||||
);
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
files() {
|
||||
return this.$store.getters.sortedFiles;
|
||||
return this.$store.getters.sortedFiles.map(file => {
|
||||
if (this.hasDataView(file)) {
|
||||
file.show = true;
|
||||
}
|
||||
return file;
|
||||
});
|
||||
},
|
||||
prettyDump() {
|
||||
return JSON.stringify(this.dump, null, 2);
|
||||
@@ -174,7 +305,21 @@ export default {
|
||||
return this.activeDataView;
|
||||
},
|
||||
dataViewFiles() {
|
||||
return this.files.filter((f) => this.hasDataView(f));
|
||||
return this.files.filter((file) => this.hasDataView(file));
|
||||
},
|
||||
tagFiles() {
|
||||
return this.$store.getters.tagFiles;
|
||||
},
|
||||
errorFiles() {
|
||||
return this.$store.getters.errorFiles;
|
||||
},
|
||||
timelineFiles() {
|
||||
return this.$store.getters.timelineFiles;
|
||||
},
|
||||
canGenerateTags() {
|
||||
const fileTypes = this.dataViewFiles.map((file) => file.type);
|
||||
return fileTypes.includes(TRACE_TYPES.WINDOW_MANAGER)
|
||||
&& fileTypes.includes(TRACE_TYPES.SURFACE_FLINGER);
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
@@ -187,6 +332,7 @@ export default {
|
||||
dataview: DataView,
|
||||
datainput: DataInput,
|
||||
dataadb: DataAdb,
|
||||
searchbar: Searchbar,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -194,7 +340,7 @@ export default {
|
||||
@import url('https://fonts.googleapis.com/css2?family=Open+Sans:wght@600&display=swap');
|
||||
|
||||
#app .md-app-container {
|
||||
/* Get rid of tranforms which prevent fixed position from being used */
|
||||
/* Get rid of transforms which prevent fixed position from being used */
|
||||
transform: none!important;
|
||||
min-height: 100vh;
|
||||
}
|
||||
@@ -240,20 +386,10 @@ export default {
|
||||
margin-top: 1em
|
||||
}
|
||||
|
||||
h1,
|
||||
h2 {
|
||||
h1 {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #42b983;
|
||||
}
|
||||
|
||||
.data-inputs {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
@@ -279,4 +415,18 @@ a {
|
||||
.data-view-container {
|
||||
padding: 25px 20px 0 20px;
|
||||
}
|
||||
</style>
|
||||
|
||||
.snackbar-break-words {
|
||||
/* These are technically the same, but use both */
|
||||
overflow-wrap: break-word;
|
||||
word-wrap: break-word;
|
||||
-ms-word-break: break-all;
|
||||
word-break: break-word;
|
||||
/* Adds a hyphen where the word breaks, if supported (No Blink) */
|
||||
-ms-hyphens: auto;
|
||||
-moz-hyphens: auto;
|
||||
-webkit-hyphens: auto;
|
||||
hyphens: auto;
|
||||
padding: 10px 10px 10px 10px;
|
||||
}
|
||||
</style>
|
||||
@@ -31,7 +31,7 @@
|
||||
<p>Or get it from the AOSP repository.</p>
|
||||
</div>
|
||||
<div class="md-layout">
|
||||
<md-button class="md-accent" :href="downloadProxyUrl">Download from AOSP</md-button>
|
||||
<md-button class="md-accent" :href="downloadProxyUrl" @click="buttonClicked(`Download from AOSP`)">Download from AOSP</md-button>
|
||||
<md-button class="md-accent" @click="restart">Retry</md-button>
|
||||
</div>
|
||||
</md-card-content>
|
||||
@@ -86,10 +86,29 @@
|
||||
<div class="selection">
|
||||
<md-checkbox class="md-primary" v-for="traceKey in Object.keys(TRACES)" :key="traceKey" v-model="adbStore[traceKey]">{{TRACES[traceKey].name}}</md-checkbox>
|
||||
</div>
|
||||
<div class="trace-config" v-for="traceKey in Object.keys(TRACE_CONFIG)" :key="traceKey">
|
||||
<h4>{{TRACES[traceKey].name}} config</h4>
|
||||
<div class="trace-config">
|
||||
<h4>Surface Flinger config</h4>
|
||||
<div class="selection">
|
||||
<md-checkbox class="md-primary" v-for="config in TRACE_CONFIG[traceKey]" :key="config" v-model="adbStore[config]">{{config}}</md-checkbox>
|
||||
<md-checkbox class="md-primary" v-for="config in TRACE_CONFIG['layers_trace']" :key="config" v-model="adbStore[config]">{{config}}</md-checkbox>
|
||||
<div class="selection">
|
||||
<md-field class="config-selection" v-for="selectConfig in Object.keys(SF_SELECTED_CONFIG)" :key="selectConfig">
|
||||
<md-select v-model="SF_SELECTED_CONFIG_VALUES[selectConfig]" :placeholder="selectConfig">
|
||||
<md-option value="">{{selectConfig}}</md-option>
|
||||
<md-option v-for="option in SF_SELECTED_CONFIG[selectConfig]" :key="option" :value="option">{{ option }}</md-option>
|
||||
</md-select>
|
||||
</md-field>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="trace-config">
|
||||
<h4>Window Manager config</h4>
|
||||
<div class="selection">
|
||||
<md-field class="config-selection" v-for="selectConfig in Object.keys(WM_SELECTED_CONFIG)" :key="selectConfig">
|
||||
<md-select v-model="WM_SELECTED_CONFIG_VALUES[selectConfig]" :placeholder="selectConfig">
|
||||
<md-option value="">{{selectConfig}}</md-option>
|
||||
<md-option v-for="option in WM_SELECTED_CONFIG[selectConfig]" :key="option" :value="option">{{ option }}</md-option>
|
||||
</md-select>
|
||||
</md-field>
|
||||
</div>
|
||||
</div>
|
||||
<md-button class="md-primary trace-btn" @click="startTrace">Start trace</md-button>
|
||||
@@ -149,6 +168,8 @@ const PROXY_ENDPOINTS = {
|
||||
START_TRACE: '/start/',
|
||||
END_TRACE: '/end/',
|
||||
CONFIG_TRACE: '/configtrace/',
|
||||
SELECTED_WM_CONFIG_TRACE: '/selectedwmconfigtrace/',
|
||||
SELECTED_SF_CONFIG_TRACE: '/selectedsfconfigtrace/',
|
||||
DUMP: '/dump/',
|
||||
FETCH: '/fetch/',
|
||||
STATUS: '/status/',
|
||||
@@ -158,6 +179,9 @@ const TRACES = {
|
||||
'window_trace': {
|
||||
name: 'Window Manager',
|
||||
},
|
||||
'accessibility_trace': {
|
||||
name: 'Accessibility',
|
||||
},
|
||||
'layers_trace': {
|
||||
name: 'Surface Flinger',
|
||||
},
|
||||
@@ -189,6 +213,33 @@ const TRACE_CONFIG = {
|
||||
],
|
||||
};
|
||||
|
||||
const SF_SELECTED_CONFIG = {
|
||||
'sfbuffersize': [
|
||||
'4000',
|
||||
'8000',
|
||||
'16000',
|
||||
'32000',
|
||||
],
|
||||
};
|
||||
|
||||
const WM_SELECTED_CONFIG = {
|
||||
'wmbuffersize': [
|
||||
'4000',
|
||||
'8000',
|
||||
'16000',
|
||||
'32000',
|
||||
],
|
||||
'tracingtype': [
|
||||
'frame',
|
||||
'transaction',
|
||||
],
|
||||
'tracinglevel': [
|
||||
'all',
|
||||
'trim',
|
||||
'critical',
|
||||
],
|
||||
};
|
||||
|
||||
const DUMPS = {
|
||||
'window_dump': {
|
||||
name: 'Window Manager',
|
||||
@@ -200,6 +251,7 @@ const DUMPS = {
|
||||
|
||||
const proxyFileTypeAdapter = {
|
||||
'window_trace': FILE_TYPES.WINDOW_MANAGER_TRACE,
|
||||
'accessibility_trace': FILE_TYPES.ACCESSIBILITY_TRACE,
|
||||
'layers_trace': FILE_TYPES.SURFACE_FLINGER_TRACE,
|
||||
'wl_trace': FILE_TYPES.WAYLAND_TRACE,
|
||||
'layers_dump': FILE_TYPES.SURFACE_FLINGER_DUMP,
|
||||
@@ -224,6 +276,10 @@ export default {
|
||||
STATES,
|
||||
TRACES,
|
||||
TRACE_CONFIG,
|
||||
SF_SELECTED_CONFIG,
|
||||
WM_SELECTED_CONFIG,
|
||||
SF_SELECTED_CONFIG_VALUES: {},
|
||||
WM_SELECTED_CONFIG_VALUES: {},
|
||||
DUMPS,
|
||||
FILE_DECODERS,
|
||||
WINSCOPE_PROXY_VERSION,
|
||||
@@ -288,7 +344,7 @@ export default {
|
||||
this.keep_alive_worker = null;
|
||||
return;
|
||||
}
|
||||
this.callProxy('GET', PROXY_ENDPOINTS.STATUS + this.deviceId() + '/', this, function(request, view) {
|
||||
this.callProxy('GET', `${PROXY_ENDPOINTS.STATUS}${this.deviceId()}/`, this, function(request, view) {
|
||||
if (request.responseText !== 'True') {
|
||||
view.endTrace();
|
||||
} else if (view.keep_alive_worker === null) {
|
||||
@@ -299,37 +355,47 @@ export default {
|
||||
startTrace() {
|
||||
const requested = this.toTrace();
|
||||
const requestedConfig = this.toTraceConfig();
|
||||
const requestedSelectedSfConfig = this.toSelectedSfTraceConfig();
|
||||
const requestedSelectedWmConfig = this.toSelectedWmTraceConfig();
|
||||
if (requested.length < 1) {
|
||||
this.errorText = 'No targets selected';
|
||||
this.status = STATES.ERROR;
|
||||
this.newEventOccurred("No targets selected");
|
||||
return;
|
||||
}
|
||||
this.callProxy('POST', PROXY_ENDPOINTS.CONFIG_TRACE + this.deviceId() + '/', this, null, null, requestedConfig);
|
||||
|
||||
this.newEventOccurred("Start Trace");
|
||||
this.callProxy('POST', `${PROXY_ENDPOINTS.CONFIG_TRACE}${this.deviceId()}/`, this, null, null, requestedConfig);
|
||||
this.callProxy('POST', `${PROXY_ENDPOINTS.SELECTED_SF_CONFIG_TRACE}${this.deviceId()}/`, this, null, null, requestedSelectedSfConfig);
|
||||
this.callProxy('POST', `${PROXY_ENDPOINTS.SELECTED_WM_CONFIG_TRACE}${this.deviceId()}/`, this, null, null, requestedSelectedWmConfig);
|
||||
this.status = STATES.END_TRACE;
|
||||
this.callProxy('POST', PROXY_ENDPOINTS.START_TRACE + this.deviceId() + '/', this, function(request, view) {
|
||||
this.callProxy('POST', `${PROXY_ENDPOINTS.START_TRACE}${this.deviceId()}/`, this, function(request, view) {
|
||||
view.keepAliveTrace();
|
||||
}, null, requested);
|
||||
},
|
||||
dumpState() {
|
||||
this.buttonClicked("Dump State");
|
||||
const requested = this.toDump();
|
||||
if (requested.length < 1) {
|
||||
this.errorText = 'No targets selected';
|
||||
this.status = STATES.ERROR;
|
||||
this.newEventOccurred("No targets selected");
|
||||
return;
|
||||
}
|
||||
this.status = STATES.LOAD_DATA;
|
||||
this.callProxy('POST', PROXY_ENDPOINTS.DUMP + this.deviceId() + '/', this, function(request, view) {
|
||||
this.callProxy('POST', `${PROXY_ENDPOINTS.DUMP}${this.deviceId()}/`, this, function(request, view) {
|
||||
view.loadFile(requested, 0);
|
||||
}, null, requested);
|
||||
},
|
||||
endTrace() {
|
||||
this.status = STATES.LOAD_DATA;
|
||||
this.callProxy('POST', PROXY_ENDPOINTS.END_TRACE + this.deviceId() + '/', this, function(request, view) {
|
||||
this.callProxy('POST', `${PROXY_ENDPOINTS.END_TRACE}${this.deviceId()}/`, this, function(request, view) {
|
||||
view.loadFile(view.toTrace(), 0);
|
||||
});
|
||||
this.newEventOccurred("Ended Trace");
|
||||
},
|
||||
loadFile(files, idx) {
|
||||
this.callProxy('GET', PROXY_ENDPOINTS.FETCH + this.deviceId() + '/' + files[idx] + '/', this, function(request, view) {
|
||||
this.callProxy('GET', `${PROXY_ENDPOINTS.FETCH}${this.deviceId()}/${files[idx]}/`, this, function(request, view) {
|
||||
try {
|
||||
const enc = new TextDecoder('utf-8');
|
||||
const resp = enc.decode(request.response);
|
||||
@@ -371,6 +437,24 @@ export default {
|
||||
.flatMap((file) => TRACE_CONFIG[file])
|
||||
.filter((config) => this.adbStore[config]);
|
||||
},
|
||||
toSelectedSfTraceConfig() {
|
||||
const requestedSelectedConfig = {};
|
||||
for (const config in this.SF_SELECTED_CONFIG_VALUES) {
|
||||
if (this.SF_SELECTED_CONFIG_VALUES[config] !== "") {
|
||||
requestedSelectedConfig[config] = this.SF_SELECTED_CONFIG_VALUES[config];
|
||||
}
|
||||
}
|
||||
return requestedSelectedConfig;
|
||||
},
|
||||
toSelectedWmTraceConfig() {
|
||||
const requestedSelectedConfig = {};
|
||||
for (const config in this.WM_SELECTED_CONFIG_VALUES) {
|
||||
if (this.WM_SELECTED_CONFIG_VALUES[config] !== "") {
|
||||
requestedSelectedConfig[config] = this.WM_SELECTED_CONFIG_VALUES[config];
|
||||
}
|
||||
}
|
||||
return requestedSelectedConfig;
|
||||
},
|
||||
toDump() {
|
||||
return Object.keys(DUMPS)
|
||||
.filter((dumpKey) => this.adbStore[dumpKey]);
|
||||
@@ -384,9 +468,11 @@ export default {
|
||||
return this.selectedDevice;
|
||||
},
|
||||
restart() {
|
||||
this.buttonClicked("Connect / Retry");
|
||||
this.status = STATES.CONNECTING;
|
||||
},
|
||||
resetLastDevice() {
|
||||
this.buttonClicked("Change Device");
|
||||
this.adbStore.lastDevice = '';
|
||||
this.restart();
|
||||
},
|
||||
@@ -448,6 +534,12 @@ export default {
|
||||
|
||||
</script>
|
||||
<style scoped>
|
||||
.config-selection {
|
||||
width: 150px;
|
||||
display: inline-flex;
|
||||
margin-left: 5px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
.device-choice {
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
@@ -13,40 +13,47 @@
|
||||
limitations under the License.
|
||||
-->
|
||||
<template>
|
||||
<div @dragleave="fileDragOut" @dragover="fileDragIn" @drop="handleFileDrop">
|
||||
<flat-card style="min-width: 50em">
|
||||
<md-card-header>
|
||||
<div class="md-title">Open files</div>
|
||||
</md-card-header>
|
||||
<md-card-content>
|
||||
<md-list>
|
||||
<md-list-item v-for="file in dataFiles" v-bind:key="file.filename">
|
||||
<md-icon>{{FILE_ICONS[file.type]}}</md-icon>
|
||||
<span class="md-list-item-text">{{file.filename}} ({{file.type}})
|
||||
</span>
|
||||
<md-button
|
||||
class="md-icon-button md-accent"
|
||||
@click="onRemoveFile(file.type)"
|
||||
>
|
||||
<md-icon>close</md-icon>
|
||||
</md-button>
|
||||
</md-list-item>
|
||||
</md-list>
|
||||
<md-progress-spinner
|
||||
:md-diameter="30"
|
||||
:md-stroke="3"
|
||||
md-mode="indeterminate"
|
||||
v-show="loadingFiles"
|
||||
/>
|
||||
<div>
|
||||
<md-checkbox v-model="store.displayDefaults" class="md-primary">
|
||||
Show default properties
|
||||
<md-tooltip md-direction="bottom">
|
||||
If checked, shows the value of all properties.
|
||||
Otherwise, hides all properties whose value is the default for its
|
||||
data type.
|
||||
</md-tooltip>
|
||||
</md-checkbox>
|
||||
</div>
|
||||
<div class="dropbox">
|
||||
<md-list style="background: none">
|
||||
<md-list-item v-for="file in dataFiles" v-bind:key="file.filename">
|
||||
<md-icon>{{FILE_ICONS[file.type]}}</md-icon>
|
||||
<span class="md-list-item-text">{{file.filename}} ({{file.type}})
|
||||
</span>
|
||||
<md-button
|
||||
class="md-icon-button md-accent"
|
||||
@click="onRemoveFile(file.type)"
|
||||
>
|
||||
<md-icon>close</md-icon>
|
||||
</md-button>
|
||||
</md-list-item>
|
||||
</md-list>
|
||||
<md-progress-spinner
|
||||
:md-diameter="30"
|
||||
:md-stroke="3"
|
||||
md-mode="indeterminate"
|
||||
v-show="loadingFiles"
|
||||
class="progress-spinner"
|
||||
/>
|
||||
<input
|
||||
type="file"
|
||||
@change="onLoadFile"
|
||||
v-on:drop="handleFileDrop"
|
||||
ref="fileUpload"
|
||||
id="dropzone"
|
||||
v-show="false"
|
||||
multiple
|
||||
/>
|
||||
<p v-if="!dataReady">
|
||||
Drag your <b>.winscope</b> or <b>.zip</b> file(s) here to begin
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="md-layout">
|
||||
<div class="md-layout-item md-small-size-100">
|
||||
<md-field>
|
||||
@@ -62,13 +69,6 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="md-layout">
|
||||
<input
|
||||
type="file"
|
||||
@change="onLoadFile"
|
||||
ref="fileUpload"
|
||||
v-show="false"
|
||||
:multiple="fileType === 'auto'"
|
||||
/>
|
||||
<md-button
|
||||
class="md-primary md-theme-default"
|
||||
@click="$refs.fileUpload.click()"
|
||||
@@ -100,7 +100,7 @@
|
||||
:md-active.sync="showSnackbar"
|
||||
md-persistent
|
||||
>
|
||||
<span style="white-space: pre-line;">{{ snackbarText }}</span>
|
||||
<p class="snackbar-break-words">{{ snackbarText }}</p>
|
||||
<div @click="hideSnackbarMessage()">
|
||||
<md-button class="md-icon-button">
|
||||
<md-icon style="color: white">close</md-icon>
|
||||
@@ -108,6 +108,7 @@
|
||||
</div>
|
||||
</md-snackbar>
|
||||
</flat-card>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import FlatCard from './components/FlatCard.vue';
|
||||
@@ -145,12 +146,13 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
showSnackbarMessage(message, duration) {
|
||||
this.snackbarText = message;
|
||||
this.snackbarText = '\n' + message + '\n';
|
||||
this.snackbarDuration = duration;
|
||||
this.showSnackbar = true;
|
||||
},
|
||||
hideSnackbarMessage() {
|
||||
this.showSnackbar = false;
|
||||
this.buttonClicked("Hide Snackbar Message")
|
||||
},
|
||||
getFetchFilesLoadingAnimation() {
|
||||
let frame = 0;
|
||||
@@ -234,8 +236,24 @@ export default {
|
||||
});
|
||||
}
|
||||
},
|
||||
fileDragIn(e) {
|
||||
e.preventDefault();
|
||||
},
|
||||
fileDragOut(e) {
|
||||
e.preventDefault();
|
||||
},
|
||||
handleFileDrop(e) {
|
||||
e.preventDefault();
|
||||
let droppedFiles = e.dataTransfer.files;
|
||||
if(!droppedFiles) return;
|
||||
// Record analytics event
|
||||
this.draggedAndDropped(droppedFiles);
|
||||
|
||||
this.processFiles(droppedFiles);
|
||||
},
|
||||
onLoadFile(e) {
|
||||
const files = event.target.files || event.dataTransfer.files;
|
||||
this.uploadedFileThroughFilesystem(files);
|
||||
this.processFiles(files);
|
||||
},
|
||||
async processFiles(files) {
|
||||
@@ -531,3 +549,29 @@ export default {
|
||||
};
|
||||
|
||||
</script>
|
||||
<style>
|
||||
.dropbox:hover {
|
||||
background: rgb(224, 224, 224);
|
||||
}
|
||||
|
||||
.dropbox p {
|
||||
font-size: 1.2em;
|
||||
text-align: center;
|
||||
padding: 50px 10px;
|
||||
}
|
||||
|
||||
.dropbox {
|
||||
outline: 2px dashed #448aff; /* the dash box */
|
||||
outline-offset: -10px;
|
||||
background: white;
|
||||
color: #448aff;
|
||||
padding: 10px 10px 10px 10px;
|
||||
min-height: 200px; /* minimum height */
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.progress-spinner {
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
@@ -18,8 +18,13 @@
|
||||
<md-card-header>
|
||||
<md-card-header-text>
|
||||
<div class="md-title">
|
||||
<button class="toggle-view-button" @click="toggleView">
|
||||
<i aria-hidden="true" class="md-icon md-theme-default material-icons">
|
||||
{{ isShowFileType(file.type) ? "expand_more" : "chevron_right" }}
|
||||
</i>
|
||||
</button>
|
||||
<md-icon>{{ TRACE_ICONS[file.type] }}</md-icon>
|
||||
{{file.type}}
|
||||
{{ file.type }}
|
||||
</div>
|
||||
</md-card-header-text>
|
||||
<md-button
|
||||
@@ -30,37 +35,48 @@
|
||||
<md-icon>save_alt</md-icon>
|
||||
</md-button>
|
||||
</md-card-header>
|
||||
|
||||
<WindowManagerTraceView
|
||||
v-if="showInWindowManagerTraceView(file)"
|
||||
<AccessibilityTraceView
|
||||
v-if="showInAccessibilityTraceView(file) && isShowFileType(file.type)"
|
||||
:store="store"
|
||||
:file="file"
|
||||
ref="view"
|
||||
/>
|
||||
<WindowManagerTraceView
|
||||
v-if="showInWindowManagerTraceView(file) && isShowFileType(file.type)"
|
||||
:store="store"
|
||||
:file="file"
|
||||
:presentTags="presentTags"
|
||||
:presentErrors="presentErrors"
|
||||
ref="view"
|
||||
/>
|
||||
<SurfaceFlingerTraceView
|
||||
v-else-if="showInSurfaceFlingerTraceView(file)"
|
||||
v-else-if="showInSurfaceFlingerTraceView(file) && isShowFileType(file.type)"
|
||||
:store="store"
|
||||
:file="file"
|
||||
:presentTags="presentTags"
|
||||
:presentErrors="presentErrors"
|
||||
ref="view"
|
||||
/>
|
||||
<transactionsview
|
||||
v-else-if="isTransactions(file)"
|
||||
v-else-if="isTransactions(file) && isShowFileType(file.type)"
|
||||
:trace="file"
|
||||
ref="view"
|
||||
/>
|
||||
<logview
|
||||
v-else-if="isLog(file)"
|
||||
v-else-if="isLog(file) && isShowFileType(file.type)"
|
||||
:file="file"
|
||||
ref="view"
|
||||
/>
|
||||
<traceview
|
||||
v-else-if="showInTraceView(file)"
|
||||
v-else-if="showInTraceView(file) && isShowFileType(file.type)"
|
||||
:store="store"
|
||||
:file="file"
|
||||
:presentTags="[]"
|
||||
:presentErrors="[]"
|
||||
ref="view"
|
||||
/>
|
||||
<div v-else>
|
||||
<h1 class="bad">Unrecognized DataType</h1>
|
||||
<h1 v-if="isShowFileType(file.type)" class="bad">Unrecognized DataType</h1>
|
||||
</div>
|
||||
|
||||
</flat-card>
|
||||
@@ -68,6 +84,7 @@
|
||||
</template>
|
||||
<script>
|
||||
import TraceView from '@/TraceView.vue';
|
||||
import AccessibilityTraceView from '@/AccessibilityTraceView.vue';
|
||||
import WindowManagerTraceView from '@/WindowManagerTraceView.vue';
|
||||
import SurfaceFlingerTraceView from '@/SurfaceFlingerTraceView.vue';
|
||||
import TransactionsView from '@/TransactionsView.vue';
|
||||
@@ -143,15 +160,32 @@ export default {
|
||||
// Pass click event to parent, so that click event handler can be attached
|
||||
// to component.
|
||||
this.$emit('click', e);
|
||||
this.newEventOccurred(e.toString());
|
||||
},
|
||||
/** Filter data view files by current show settings */
|
||||
updateShowFileTypes() {
|
||||
this.store.showFileTypes = this.dataViewFiles
|
||||
.filter((file) => file.show)
|
||||
.map(file => file.type);
|
||||
},
|
||||
/** Expand or collapse data view */
|
||||
toggleView() {
|
||||
this.file.show = !this.file.show;
|
||||
this.updateShowFileTypes();
|
||||
},
|
||||
/** Check if data view file should be shown */
|
||||
isShowFileType(type) {
|
||||
return this.store.showFileTypes.find(fileType => fileType===type);
|
||||
},
|
||||
},
|
||||
props: ['store', 'file'],
|
||||
props: ['store', 'file', 'presentTags', 'presentErrors', 'dataViewFiles'],
|
||||
mixins: [FileType],
|
||||
components: {
|
||||
'traceview': TraceView,
|
||||
'transactionsview': TransactionsView,
|
||||
'logview': LogView,
|
||||
'flat-card': FlatCard,
|
||||
AccessibilityTraceView,
|
||||
WindowManagerTraceView,
|
||||
SurfaceFlingerTraceView,
|
||||
},
|
||||
@@ -163,4 +197,18 @@ export default {
|
||||
font-size: 4em;
|
||||
color: red;
|
||||
}
|
||||
|
||||
.toggle-view-button {
|
||||
background: none;
|
||||
color: inherit;
|
||||
border: none;
|
||||
font: inherit;
|
||||
cursor: pointer;
|
||||
padding-right: 10px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.md-title {
|
||||
display: inline-block;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -43,13 +43,28 @@
|
||||
{{c.long}}
|
||||
</md-tooltip>
|
||||
</div>
|
||||
<div class="flicker-tags" v-for="transition in transitions" :key="transition">
|
||||
<Arrow
|
||||
class="transition-arrow"
|
||||
:style="{color: transitionArrowColor(transition)}"
|
||||
/>
|
||||
<md-tooltip md-direction="right"> {{transitionTooltip(transition)}} </md-tooltip>
|
||||
</div>
|
||||
<div class="flicker-tags" v-for="error in errors" :key="error.message">
|
||||
<Arrow class="error-arrow"/>
|
||||
<md-tooltip md-direction="right"> {{errorTooltip(error.message)}} </md-tooltip>
|
||||
</div>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import Arrow from './components/TagDisplay/Arrow.vue';
|
||||
import {transitionMap} from './utils/consts.js';
|
||||
|
||||
export default {
|
||||
name: 'DefaultTreeElement',
|
||||
props: ['item', 'simplify-names'],
|
||||
props: ['item', 'simplify-names', 'errors', 'transitions'],
|
||||
methods: {
|
||||
chipClassForChip(c) {
|
||||
return [
|
||||
@@ -59,6 +74,21 @@ export default {
|
||||
(c.type?.toString() || c.class?.toString() || 'default'),
|
||||
];
|
||||
},
|
||||
transitionArrowColor(transition) {
|
||||
return transitionMap.get(transition).color;
|
||||
},
|
||||
transitionTooltip(transition) {
|
||||
return transitionMap.get(transition).desc;
|
||||
},
|
||||
errorTooltip(errorMessage) {
|
||||
if (errorMessage.length>100) {
|
||||
return `Error: ${errorMessage.substring(0,100)}...`;
|
||||
}
|
||||
return `Error: ${errorMessage}`;
|
||||
},
|
||||
},
|
||||
components: {
|
||||
Arrow,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -100,4 +130,12 @@ span {
|
||||
flex: 1 1 auto;
|
||||
width: 0;
|
||||
}
|
||||
|
||||
.flicker-tags {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.error-arrow {
|
||||
color: red;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -15,24 +15,31 @@
|
||||
<template>
|
||||
<md-card-content class="container">
|
||||
<div class="navigation">
|
||||
<md-button
|
||||
class="md-dense md-primary"
|
||||
@click.native="scrollToRow(lastOccuredVisibleIndex)"
|
||||
<md-content
|
||||
md-tag="md-toolbar"
|
||||
md-elevation="0"
|
||||
class="card-toolbar md-transparent md-dense"
|
||||
>
|
||||
Jump to latest entry
|
||||
</md-button>
|
||||
<md-button
|
||||
class="md-icon-button" :class="{'md-primary': pinnedToLatest}"
|
||||
@click.native="togglePin"
|
||||
>
|
||||
<md-icon>push_pin</md-icon>
|
||||
<md-tooltip md-direction="top" v-if="pinnedToLatest">
|
||||
Unpin to latest message
|
||||
</md-tooltip>
|
||||
<md-tooltip md-direction="top" v-else>
|
||||
Pin to latest message
|
||||
</md-tooltip>
|
||||
</md-button>
|
||||
<h2 class="md-title" style="flex: 1">Log View</h2>
|
||||
<md-button
|
||||
class="md-dense md-primary"
|
||||
@click.native="scrollToRow(lastOccuredVisibleIndex)"
|
||||
>
|
||||
Jump to latest entry
|
||||
</md-button>
|
||||
<md-button
|
||||
class="md-icon-button" :class="{'md-primary': pinnedToLatest}"
|
||||
@click.native="togglePin"
|
||||
>
|
||||
<md-icon>push_pin</md-icon>
|
||||
<md-tooltip md-direction="top" v-if="pinnedToLatest">
|
||||
Unpin to latest message
|
||||
</md-tooltip>
|
||||
<md-tooltip md-direction="top" v-else>
|
||||
Pin to latest message
|
||||
</md-tooltip>
|
||||
</md-button>
|
||||
</md-content>
|
||||
</div>
|
||||
|
||||
<div class="filters">
|
||||
|
||||
@@ -1,15 +1,11 @@
|
||||
<template>
|
||||
<vue-context ref="menu">
|
||||
<li>
|
||||
<a href="#" @click.prevent="$emit('collapseAllOtherNodes')">
|
||||
Collapse all other nodes
|
||||
</a>
|
||||
</li>
|
||||
|
||||
</vue-context>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {VueContext} from 'vue-context';
|
||||
import VueContext from 'vue-context';
|
||||
|
||||
export default {
|
||||
name: 'NodeContextMenu',
|
||||
|
||||
@@ -52,10 +52,26 @@
|
||||
>
|
||||
<div class="nav-content">
|
||||
<div class="">
|
||||
<searchbar
|
||||
class="search-bar"
|
||||
v-if="search"
|
||||
:searchTypes="searchTypes"
|
||||
:store="store"
|
||||
:presentTags="Object.freeze(presentTags)"
|
||||
:presentErrors="Object.freeze(presentErrors)"
|
||||
:timeline="mergedTimeline.timeline"
|
||||
/>
|
||||
<md-toolbar
|
||||
md-elevation="0"
|
||||
class="md-transparent">
|
||||
|
||||
<md-button
|
||||
@click="toggleSearch()"
|
||||
class="drop-search"
|
||||
>
|
||||
Toggle search bar
|
||||
</md-button>
|
||||
|
||||
<div class="toolbar" :class="{ expanded: expanded }">
|
||||
<div class="resize-bar" v-show="expanded">
|
||||
<div v-if="video" @mousedown="resizeBottomNav">
|
||||
@@ -81,8 +97,9 @@
|
||||
</div>
|
||||
|
||||
<md-field
|
||||
v-if="multipleTraces"
|
||||
ref="navigationTypeSelection"
|
||||
class="nagivation-style-selection-field"
|
||||
class="navigation-style-selection-field"
|
||||
>
|
||||
|
||||
<label>Navigation</label>
|
||||
@@ -91,7 +108,8 @@
|
||||
name="navigationStyle"
|
||||
md-dense
|
||||
>
|
||||
<md-icon-option :value="NAVIGATION_STYLE.GLOBAL"
|
||||
<md-icon-option
|
||||
:value="NAVIGATION_STYLE.GLOBAL"
|
||||
icon="public"
|
||||
desc="Consider all timelines for navigation"
|
||||
/>
|
||||
@@ -133,10 +151,20 @@
|
||||
v-show="minimized"
|
||||
v-if="hasTimeline"
|
||||
>
|
||||
<label>
|
||||
{{ seekTime }}
|
||||
</label>
|
||||
<input
|
||||
class="timestamp-search-input"
|
||||
v-model="searchInput"
|
||||
spellcheck="false"
|
||||
:placeholder="seekTime"
|
||||
@focus="updateInputMode(true)"
|
||||
@blur="updateInputMode(false)"
|
||||
@keyup.enter="updateSearchForTimestamp"
|
||||
/>
|
||||
<timeline
|
||||
:store="store"
|
||||
:flickerMode="flickerMode"
|
||||
:tags="Object.freeze(presentTags)"
|
||||
:errors="Object.freeze(presentErrors)"
|
||||
:timeline="Object.freeze(minimizedTimeline.timeline)"
|
||||
:selected-index="minimizedTimeline.selectedIndex"
|
||||
:scale="scale"
|
||||
@@ -168,11 +196,11 @@
|
||||
>
|
||||
<md-icon v-if="minimized">
|
||||
expand_less
|
||||
<md-tooltip md-direction="top">Expand timeline</md-tooltip>
|
||||
<md-tooltip md-direction="top" @click="buttonClicked(`Expand Timeline`)">Expand timeline</md-tooltip>
|
||||
</md-icon>
|
||||
<md-icon v-else>
|
||||
expand_more
|
||||
<md-tooltip md-direction="top">Collapse timeline</md-tooltip>
|
||||
<md-tooltip md-direction="top" @click="buttonClicked(`Collapse Timeline`)">Collapse timeline</md-tooltip>
|
||||
</md-icon>
|
||||
</md-button>
|
||||
</div>
|
||||
@@ -193,7 +221,17 @@
|
||||
:style="`padding-top: ${resizeOffset}px;`"
|
||||
>
|
||||
<div class="seek-time" v-if="seekTime">
|
||||
<b>Seek time</b>: {{ seekTime }}
|
||||
<b>Seek time: </b>
|
||||
<input
|
||||
class="timestamp-search-input"
|
||||
:class="{ expanded: expanded }"
|
||||
v-model="searchInput"
|
||||
spellcheck="false"
|
||||
:placeholder="seekTime"
|
||||
@focus="updateInputMode(true)"
|
||||
@blur="updateInputMode(false)"
|
||||
@keyup.enter="updateSearchForTimestamp"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<timelines
|
||||
@@ -260,16 +298,17 @@ import TimelineSelection from './TimelineSelection.vue';
|
||||
import DraggableDiv from './DraggableDiv.vue';
|
||||
import VideoView from './VideoView.vue';
|
||||
import MdIconOption from './components/IconSelection/IconSelectOption.vue';
|
||||
import Searchbar from './Searchbar.vue';
|
||||
import FileType from './mixins/FileType.js';
|
||||
import {NAVIGATION_STYLE} from './utils/consts';
|
||||
import {TRACE_ICONS} from '@/decode.js';
|
||||
|
||||
// eslint-disable-next-line camelcase
|
||||
import {nanos_to_string} from './transform.js';
|
||||
import {nanos_to_string, getClosestTimestamp} from './transform.js';
|
||||
|
||||
export default {
|
||||
name: 'overlay',
|
||||
props: ['store'],
|
||||
props: ['store', 'presentTags', 'presentErrors', 'searchTypes'],
|
||||
mixins: [FileType],
|
||||
data() {
|
||||
return {
|
||||
@@ -290,6 +329,9 @@ export default {
|
||||
crop: null,
|
||||
cropIntent: null,
|
||||
TRACE_ICONS,
|
||||
search: false,
|
||||
searchInput: "",
|
||||
isSeekTimeInputMode: false,
|
||||
};
|
||||
},
|
||||
created() {
|
||||
@@ -302,6 +344,7 @@ export default {
|
||||
},
|
||||
destroyed() {
|
||||
this.$store.commit('removeMergedTimeline', this.mergedTimeline);
|
||||
this.updateInputMode(false);
|
||||
},
|
||||
watch: {
|
||||
navigationStyle(style) {
|
||||
@@ -376,7 +419,8 @@ export default {
|
||||
default:
|
||||
const split = this.navigationStyle.split('-');
|
||||
if (split[0] !== NAVIGATION_STYLE.TARGETED) {
|
||||
throw new Error('Unexpected nagivation type');
|
||||
console.warn('Unexpected navigation type; fallback to global');
|
||||
return 'All timelines';
|
||||
}
|
||||
|
||||
const fileType = split[1];
|
||||
@@ -398,7 +442,8 @@ export default {
|
||||
default:
|
||||
const split = this.navigationStyle.split('-');
|
||||
if (split[0] !== NAVIGATION_STYLE.TARGETED) {
|
||||
throw new Error('Unexpected nagivation type');
|
||||
console.warn('Unexpected navigation type; fallback to global');
|
||||
return 'public';
|
||||
}
|
||||
|
||||
const fileType = split[1];
|
||||
@@ -412,7 +457,11 @@ export default {
|
||||
}
|
||||
|
||||
if (this.navigationStyle === NAVIGATION_STYLE.FOCUSED) {
|
||||
return this.focusedFile;
|
||||
//dumps do not have a timeline, so if scrolling over a dump, show merged timeline
|
||||
if (this.focusedFile.timeline) {
|
||||
return this.focusedFile;
|
||||
}
|
||||
return this.mergedTimeline;
|
||||
}
|
||||
|
||||
if (this.navigationStyle === NAVIGATION_STYLE.CUSTOM) {
|
||||
@@ -420,17 +469,27 @@ export default {
|
||||
return this.mergedTimeline;
|
||||
}
|
||||
|
||||
if (this.navigationStyle.split('-')[0] === NAVIGATION_STYLE.TARGETED) {
|
||||
if (
|
||||
this.navigationStyle.split('-').length >= 2
|
||||
&& this.navigationStyle.split('-')[0] === NAVIGATION_STYLE.TARGETED
|
||||
) {
|
||||
return this.$store.state
|
||||
.traces[this.navigationStyle.split('-')[1]];
|
||||
}
|
||||
|
||||
throw new Error('Unexpected Nagivation Style');
|
||||
console.warn('Unexpected navigation type; fallback to global');
|
||||
return this.mergedTimeline;
|
||||
},
|
||||
isCropped() {
|
||||
return this.crop != null &&
|
||||
(this.crop.left !== 0 || this.crop.right !== 1);
|
||||
},
|
||||
multipleTraces() {
|
||||
return this.timelineFiles.length > 1;
|
||||
},
|
||||
flickerMode() {
|
||||
return this.presentTags.length>0 || this.presentErrors.length>0;
|
||||
},
|
||||
},
|
||||
updated() {
|
||||
this.$nextTick(() => {
|
||||
@@ -442,6 +501,31 @@ export default {
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
toggleSearch() {
|
||||
this.search = !(this.search);
|
||||
this.buttonClicked("Toggle Search Bar");
|
||||
},
|
||||
/**
|
||||
* determines whether left/right arrow keys should move cursor in input field
|
||||
* and upon click of input field, fills with current timestamp
|
||||
*/
|
||||
updateInputMode(isInputMode) {
|
||||
this.isSeekTimeInputMode = isInputMode;
|
||||
this.store.isInputMode = isInputMode;
|
||||
if (!isInputMode) {
|
||||
this.searchInput = "";
|
||||
} else {
|
||||
this.searchInput = this.seekTime;
|
||||
}
|
||||
},
|
||||
/** Navigates to closest timestamp in timeline to search input*/
|
||||
updateSearchForTimestamp() {
|
||||
const closestTimestamp = getClosestTimestamp(this.searchInput, this.mergedTimeline.timeline);
|
||||
this.$store.dispatch("updateTimelineTime", closestTimestamp);
|
||||
this.updateInputMode(false);
|
||||
this.newEventOccurred("Searching for timestamp")
|
||||
},
|
||||
|
||||
emitBottomHeightUpdate() {
|
||||
if (this.$refs.bottomNav) {
|
||||
const newHeight = this.$refs.bottomNav.$el.clientHeight;
|
||||
@@ -461,9 +545,10 @@ export default {
|
||||
timelines.push(file.timeline);
|
||||
}
|
||||
|
||||
while (true) {
|
||||
var timelineToAdvance = 0;
|
||||
while (timelineToAdvance !== undefined) {
|
||||
timelineToAdvance = undefined;
|
||||
let minTime = Infinity;
|
||||
let timelineToAdvance;
|
||||
|
||||
for (let i = 0; i < timelines.length; i++) {
|
||||
const timeline = timelines[i];
|
||||
@@ -553,12 +638,15 @@ export default {
|
||||
},
|
||||
closeVideoOverlay() {
|
||||
this.showVideoOverlay = false;
|
||||
this.buttonClicked("Close Video Overlay")
|
||||
},
|
||||
openVideoOverlay() {
|
||||
this.showVideoOverlay = true;
|
||||
this.buttonClicked("Open Video Overlay")
|
||||
},
|
||||
toggleVideoOverlay() {
|
||||
this.showVideoOverlay = !this.showVideoOverlay;
|
||||
this.buttonClicked("Toggle Video Overlay")
|
||||
},
|
||||
videoLoaded() {
|
||||
this.$refs.videoOverlay.contentLoaded();
|
||||
@@ -589,7 +677,9 @@ export default {
|
||||
default:
|
||||
const split = this.navigationStyle.split('-');
|
||||
if (split[0] !== NAVIGATION_STYLE.TARGETED) {
|
||||
throw new Error('Unexpected nagivation type');
|
||||
console.warn('Unexpected navigation type; fallback to global');
|
||||
navigationStyleFilter = (f) => true;
|
||||
break;
|
||||
}
|
||||
|
||||
const fileType = split[1];
|
||||
@@ -631,6 +721,7 @@ export default {
|
||||
'videoview': VideoView,
|
||||
'draggable-div': DraggableDiv,
|
||||
'md-icon-option': MdIconOption,
|
||||
'searchbar': Searchbar,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -810,13 +901,14 @@ export default {
|
||||
color: rgba(0,0,0,0.54);
|
||||
font-size: 12px;
|
||||
font-family: inherit;
|
||||
cursor: text;
|
||||
}
|
||||
|
||||
.minimized-timeline-content .minimized-timeline {
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.nagivation-style-selection-field {
|
||||
.navigation-style-selection-field {
|
||||
width: 90px;
|
||||
margin-right: 10px;
|
||||
margin-bottom: 0;
|
||||
@@ -834,4 +926,29 @@ export default {
|
||||
margin-bottom: 15px;
|
||||
cursor: help;
|
||||
}
|
||||
|
||||
.timestamp-search-input {
|
||||
outline: none;
|
||||
border-width: 0 0 1px;
|
||||
border-color: gray;
|
||||
font-family: inherit;
|
||||
color: #448aff;
|
||||
font-size: 12px;
|
||||
padding: 0;
|
||||
letter-spacing: inherit;
|
||||
width: 125px;
|
||||
}
|
||||
|
||||
.timestamp-search-input:focus {
|
||||
border-color: #448aff;
|
||||
}
|
||||
|
||||
.timestamp-search-input.expanded {
|
||||
font-size: 14px;
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
.drop-search:hover {
|
||||
background-color: #9af39f;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
<script>
|
||||
|
||||
// eslint-disable-next-line camelcase
|
||||
import {multiply_rect} from './matrix_utils.js';
|
||||
import {multiplyRect} from './matrix_utils.js';
|
||||
|
||||
export default {
|
||||
name: 'rects',
|
||||
@@ -46,9 +46,9 @@ export default {
|
||||
return this.bounds;
|
||||
}
|
||||
const width = Math.max(
|
||||
...this.rects.map((r) => multiply_rect(r.transform, r).right));
|
||||
...this.rects.map((r) => multiplyRect(r.transform, r).right));
|
||||
const height = Math.max(
|
||||
...this.rects.map((r) => multiply_rect(r.transform, r).bottom));
|
||||
...this.rects.map((r) => multiplyRect(r.transform, r).bottom));
|
||||
return {width, height};
|
||||
},
|
||||
boundsStyle() {
|
||||
@@ -57,8 +57,9 @@ export default {
|
||||
},
|
||||
filteredRects() {
|
||||
return this.rects.filter((rect) => {
|
||||
const isVisible = rect.ref.visible ?? rect.ref.isVisible;
|
||||
console.warn(`Name: ${rect.ref.name} Kind: ${rect.ref.kind} isVisible=${isVisible}`);
|
||||
const isVisible = rect.ref.isVisible;
|
||||
console.warn(`Name: ${rect.ref.name}`, `Kind: ${rect.ref.kind}`,
|
||||
`isVisible=${isVisible}`);
|
||||
return isVisible;
|
||||
});
|
||||
},
|
||||
@@ -78,11 +79,23 @@ export default {
|
||||
const y = this.s(r.top);
|
||||
const w = this.s(r.right) - this.s(r.left);
|
||||
const h = this.s(r.bottom) - this.s(r.top);
|
||||
const t = r.transform;
|
||||
|
||||
let t;
|
||||
if (r.transform && r.transform.matrix) {
|
||||
t = r.transform.matrix;
|
||||
} else {
|
||||
t = r.transform;
|
||||
}
|
||||
|
||||
const tr = t ? `matrix(${t.dsdx}, ${t.dtdx}, ${t.dsdy}, ${t.dtdy}, ` +
|
||||
`${this.s(t.tx)}, ${this.s(t.ty)})` : '';
|
||||
return `top: ${y}px; left: ${x}px; height: ${h}px; width: ${w}px;` +
|
||||
`transform: ${tr}; transform-origin: 0 0;`;
|
||||
const rectStyle = `top: ${y}px; left: ` +
|
||||
`${x}px; height: ${h}px; width: ${w}px; ` +
|
||||
`transform: ${tr}; transform-origin: 0 0;`;
|
||||
if (r && r.ref) {
|
||||
console.log(`${r.ref.name} - ${rectStyle}`);
|
||||
}
|
||||
return rectStyle;
|
||||
},
|
||||
onClick(r) {
|
||||
this.$emit('rect-click', r.ref);
|
||||
|
||||
356
tools/winscope/src/Searchbar.vue
Normal file
356
tools/winscope/src/Searchbar.vue
Normal file
@@ -0,0 +1,356 @@
|
||||
<!-- Copyright (C) 2017 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.
|
||||
-->
|
||||
<template>
|
||||
<md-content class="searchbar">
|
||||
|
||||
<div class="tabs">
|
||||
<div class="search-timestamp" v-if="isTimestampSearch()">
|
||||
<md-field md-inline class="search-input">
|
||||
<label>Enter timestamp</label>
|
||||
<md-input
|
||||
v-model="searchInput"
|
||||
v-on:focus="updateInputMode(true)"
|
||||
v-on:blur="updateInputMode(false)"
|
||||
@keyup.enter.native="updateSearchForTimestamp"
|
||||
/>
|
||||
</md-field>
|
||||
<md-button
|
||||
class="md-dense md-primary search-timestamp-button"
|
||||
@click="updateSearchForTimestamp"
|
||||
>
|
||||
Go to timestamp
|
||||
</md-button>
|
||||
</div>
|
||||
|
||||
<div class="dropdown-content" v-if="isTransitionSearch()">
|
||||
<table>
|
||||
<tr class="header">
|
||||
<th style="width: 10%">Global Start</th>
|
||||
<th style="width: 10%">Global End</th>
|
||||
<th style="width: 80%">Transition</th>
|
||||
</tr>
|
||||
|
||||
<tr v-for="item in filteredTransitionsAndErrors" :key="item.id">
|
||||
<td
|
||||
v-if="isTransition(item)"
|
||||
class="inline-time"
|
||||
@click="
|
||||
setCurrentTimestamp(transitionStart(transitionTags(item.id)))
|
||||
"
|
||||
>
|
||||
<span>{{ transitionTags(item.id)[0].desc }}</span>
|
||||
</td>
|
||||
<td
|
||||
v-if="isTransition(item)"
|
||||
class="inline-time"
|
||||
@click="setCurrentTimestamp(transitionEnd(transitionTags(item.id)))"
|
||||
>
|
||||
<span>{{ transitionTags(item.id)[1].desc }}</span>
|
||||
</td>
|
||||
<td
|
||||
v-if="isTransition(item)"
|
||||
class="inline-transition"
|
||||
:style="{color: transitionTextColor(item.transition)}"
|
||||
@click="setCurrentTimestamp(transitionStart(transitionTags(item.id)))"
|
||||
>
|
||||
{{ transitionDesc(item.transition) }}
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<md-field md-inline class="search-input">
|
||||
<label>
|
||||
Filter by transition name. Click to navigate to closest
|
||||
timestamp in active timeline.
|
||||
</label>
|
||||
<md-input
|
||||
v-model="searchInput"
|
||||
v-on:focus="updateInputMode(true)"
|
||||
v-on:blur="updateInputMode(false)"
|
||||
/>
|
||||
</md-field>
|
||||
</div>
|
||||
|
||||
<div class="dropdown-content" v-if="isErrorSearch()">
|
||||
<table>
|
||||
<tr class="header">
|
||||
<th style="width: 10%">Timestamp</th>
|
||||
<th style="width: 90%">Error Message</th>
|
||||
</tr>
|
||||
|
||||
<tr v-for="item in filteredTransitionsAndErrors" :key="item.id">
|
||||
<td
|
||||
v-if="!isTransition(item)"
|
||||
class="inline-time"
|
||||
@click="setCurrentTimestamp(item.timestamp)"
|
||||
>
|
||||
{{ errorDesc(item.timestamp) }}
|
||||
</td>
|
||||
<td
|
||||
v-if="!isTransition(item)"
|
||||
class="inline-error"
|
||||
@click="setCurrentTimestamp(item.timestamp)"
|
||||
>
|
||||
{{item.message}}
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<md-field md-inline class="search-input">
|
||||
<label>
|
||||
Filter by error message. Click to navigate to closest
|
||||
timestamp in active timeline.
|
||||
</label>
|
||||
<md-input
|
||||
v-model="searchInput"
|
||||
v-on:focus="updateInputMode(true)"
|
||||
v-on:blur="updateInputMode(false)"
|
||||
/>
|
||||
</md-field>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tab-container" v-if="searchTypes.length > 0">
|
||||
Search mode:
|
||||
<md-button
|
||||
v-for="searchType in searchTypes"
|
||||
:key="searchType"
|
||||
@click="setSearchType(searchType)"
|
||||
:class="tabClass(searchType)"
|
||||
>
|
||||
{{ searchType }}
|
||||
</md-button>
|
||||
</div>
|
||||
</md-content>
|
||||
</template>
|
||||
<script>
|
||||
import { transitionMap, SEARCH_TYPE } from "./utils/consts";
|
||||
import { nanos_to_string, getClosestTimestamp } from "./transform";
|
||||
|
||||
export default {
|
||||
name: "searchbar",
|
||||
props: ["store", "presentTags", "timeline", "presentErrors", "searchTypes"],
|
||||
data() {
|
||||
return {
|
||||
searchType: SEARCH_TYPE.TIMESTAMP,
|
||||
searchInput: "",
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
/** Set search type depending on tab selected */
|
||||
setSearchType(searchType) {
|
||||
this.searchType = searchType;
|
||||
},
|
||||
/** Set tab class to determine color highlight for active tab */
|
||||
tabClass(searchType) {
|
||||
var isActive = (this.searchType === searchType) ? 'active' : 'inactive';
|
||||
return ['tab', isActive];
|
||||
},
|
||||
|
||||
/** Filter all the tags present in the trace by the searchbar input */
|
||||
filteredTags() {
|
||||
var tags = [];
|
||||
var filter = this.searchInput.toUpperCase();
|
||||
this.presentTags.forEach((tag) => {
|
||||
const tagTransition = tag.transition.toUpperCase();
|
||||
if (tagTransition.includes(filter)) tags.push(tag);
|
||||
});
|
||||
return tags;
|
||||
},
|
||||
/** Add filtered errors to filtered tags to integrate both into table*/
|
||||
filteredTagsAndErrors() {
|
||||
var tagsAndErrors = [...this.filteredTags()];
|
||||
var filter = this.searchInput.toUpperCase();
|
||||
this.presentErrors.forEach((error) => {
|
||||
const errorMessage = error.message.toUpperCase();
|
||||
if (errorMessage.includes(filter)) tagsAndErrors.push(error);
|
||||
});
|
||||
// sort into chronological order
|
||||
tagsAndErrors.sort((a, b) => (a.timestamp > b.timestamp ? 1 : -1));
|
||||
|
||||
return tagsAndErrors;
|
||||
},
|
||||
/** Each transition has two tags present
|
||||
* Isolate the tags for the desire transition
|
||||
* Add a desc to display the timestamps as strings
|
||||
*/
|
||||
transitionTags(id) {
|
||||
var tags = this.filteredTags().filter((tag) => tag.id === id);
|
||||
tags.forEach((tag) => {
|
||||
tag.desc = nanos_to_string(tag.timestamp);
|
||||
});
|
||||
return tags;
|
||||
},
|
||||
|
||||
/** Find the start as minimum timestamp in transition tags */
|
||||
transitionStart(tags) {
|
||||
var times = tags.map((tag) => tag.timestamp);
|
||||
return times[0];
|
||||
},
|
||||
/** Find the end as maximum timestamp in transition tags */
|
||||
transitionEnd(tags) {
|
||||
var times = tags.map((tag) => tag.timestamp);
|
||||
return times[times.length - 1];
|
||||
},
|
||||
/**
|
||||
* Upon selecting a start/end tag in the dropdown;
|
||||
* navigates to that timestamp in the timeline
|
||||
*/
|
||||
setCurrentTimestamp(timestamp) {
|
||||
this.$store.dispatch("updateTimelineTime", timestamp);
|
||||
},
|
||||
|
||||
/** Colour codes text of transition in dropdown */
|
||||
transitionTextColor(transition) {
|
||||
return transitionMap.get(transition).color;
|
||||
},
|
||||
/** Displays transition description rather than variable name */
|
||||
transitionDesc(transition) {
|
||||
return transitionMap.get(transition).desc;
|
||||
},
|
||||
/** Add a desc to display the error timestamps as strings */
|
||||
errorDesc(timestamp) {
|
||||
return nanos_to_string(timestamp);
|
||||
},
|
||||
|
||||
/** Navigates to closest timestamp in timeline to search input*/
|
||||
updateSearchForTimestamp() {
|
||||
const closestTimestamp = getClosestTimestamp(this.searchInput, this.timeline);
|
||||
this.setCurrentTimestamp(closestTimestamp);
|
||||
},
|
||||
|
||||
isTransitionSearch() {
|
||||
return this.searchType === SEARCH_TYPE.TRANSITIONS;
|
||||
},
|
||||
isErrorSearch() {
|
||||
return this.searchType === SEARCH_TYPE.ERRORS;
|
||||
},
|
||||
isTimestampSearch() {
|
||||
return this.searchType === SEARCH_TYPE.TIMESTAMP;
|
||||
},
|
||||
isTransition(item) {
|
||||
return item.stacktrace === undefined;
|
||||
},
|
||||
|
||||
/** determines whether left/right arrow keys should move cursor in input field */
|
||||
updateInputMode(isInputMode) {
|
||||
this.store.isInputMode = isInputMode;
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
filteredTransitionsAndErrors() {
|
||||
var ids = [];
|
||||
return this.filteredTagsAndErrors().filter((item) => {
|
||||
if (this.isTransition(item) && !ids.includes(item.id)) {
|
||||
item.transitionStart = true;
|
||||
ids.push(item.id);
|
||||
}
|
||||
return !this.isTransition(item) || this.isTransition(item) && item.transitionStart;
|
||||
});
|
||||
},
|
||||
},
|
||||
destroyed() {
|
||||
this.updateInputMode(false);
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style scoped>
|
||||
.searchbar {
|
||||
background-color: rgb(250, 243, 233) !important;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
width: 100%;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
bottom: 1px;
|
||||
}
|
||||
|
||||
.tabs {
|
||||
padding-top: 1rem;
|
||||
}
|
||||
|
||||
.tab-container {
|
||||
padding-left: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.tab.active {
|
||||
background-color: rgb(236, 222, 202);
|
||||
}
|
||||
|
||||
.tab.inactive {
|
||||
background-color: rgb(250, 243, 233);
|
||||
}
|
||||
|
||||
.search-timestamp {
|
||||
padding: 5px 20px 0px 20px;
|
||||
display: inline-flex;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.search-timestamp > .search-input {
|
||||
margin-top: -5px;
|
||||
max-width: 200px;
|
||||
}
|
||||
|
||||
.search-timestamp-button {
|
||||
left: 0;
|
||||
padding: 0 15px;
|
||||
}
|
||||
|
||||
.dropdown-content {
|
||||
padding: 5px 20px 0px 20px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.dropdown-content table {
|
||||
overflow-y: scroll;
|
||||
max-height: 150px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.dropdown-content table td {
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.dropdown-content table th {
|
||||
text-align: left;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.inline-time:hover {
|
||||
background: rgb(216, 250, 218);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.inline-transition {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.inline-transition:hover {
|
||||
background: rgb(216, 250, 218);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.inline-error {
|
||||
font-weight: bold;
|
||||
color: red;
|
||||
}
|
||||
|
||||
.inline-error:hover {
|
||||
background: rgb(216, 250, 218);
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
@@ -14,15 +14,21 @@
|
||||
-->
|
||||
|
||||
<template>
|
||||
<TraceView :store="store" :file="file" :summarizer="summarizer" />
|
||||
<TraceView
|
||||
:store="store"
|
||||
:file="file"
|
||||
:summarizer="summarizer"
|
||||
:presentTags="presentTags"
|
||||
:presentErrors="presentErrors"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import TraceView from '@/TraceView.vue';
|
||||
|
||||
export default {
|
||||
name: 'WindowManagerTraceView',
|
||||
props: ['store', 'file'],
|
||||
name: 'SurfaceFlingerTraceView',
|
||||
props: ['store', 'file', 'presentTags', 'presentErrors'],
|
||||
components: {
|
||||
TraceView,
|
||||
},
|
||||
@@ -30,23 +36,23 @@ export default {
|
||||
summarizer(layer) {
|
||||
const summary = [];
|
||||
|
||||
if (layer.invisibleDueTo) {
|
||||
summary.push({key: 'Invisible due to', value: layer.invisibleDueTo});
|
||||
if (layer?.visibilityReason) {
|
||||
summary.push({key: 'Invisible due to', value: layer.visibilityReason});
|
||||
}
|
||||
|
||||
if (layer.occludedBy?.length > 0) {
|
||||
summary.push({key: 'Occluded by', value: layer.occludedBy.join(', ')});
|
||||
if (layer?.occludedBy?.length > 0) {
|
||||
summary.push({key: 'Occluded by', value: layer.occludedBy.map(it => it.id).join(', ')});
|
||||
}
|
||||
|
||||
if (layer.partiallyOccludedBy?.length > 0) {
|
||||
if (layer?.partiallyOccludedBy?.length > 0) {
|
||||
summary.push({
|
||||
key: 'Partially occluded by',
|
||||
value: layer.partiallyOccludedBy.join(', '),
|
||||
value: layer.partiallyOccludedBy.map(it => it.id).join(', '),
|
||||
});
|
||||
}
|
||||
|
||||
if (layer.coveredBy?.length > 0) {
|
||||
summary.push({key: 'Covered by', value: layer.coveredBy.join(', ')});
|
||||
if (layer?.coveredBy?.length > 0) {
|
||||
summary.push({key: 'Covered by', value: layer.coveredBy.map(it => it.id).join(', ')});
|
||||
}
|
||||
|
||||
return summary;
|
||||
|
||||
@@ -13,41 +13,72 @@
|
||||
limitations under the License.
|
||||
-->
|
||||
<template>
|
||||
<svg
|
||||
width="100%"
|
||||
height="20"
|
||||
class="timeline-svg"
|
||||
:class="{disabled: disabled}"
|
||||
ref="timeline"
|
||||
>
|
||||
<rect
|
||||
:x="`${block.startPos}%`"
|
||||
y="0"
|
||||
:width="`${block.width}%`"
|
||||
:height="pointHeight"
|
||||
:rx="corner"
|
||||
v-for="(block, idx) in timelineBlocks"
|
||||
:key="idx"
|
||||
@click="onBlockClick"
|
||||
class="point"
|
||||
/>
|
||||
<rect
|
||||
:x="`${position(selected)}%`"
|
||||
y="0"
|
||||
:width="`${pointWidth}%`"
|
||||
:height="pointHeight"
|
||||
:rx="corner"
|
||||
class="point selected"
|
||||
/>
|
||||
</svg>
|
||||
<div class="timeline-container">
|
||||
<div class="tag-timeline" v-if="flickerMode" :style="maxOverlap">
|
||||
<transition-container
|
||||
class="container"
|
||||
v-for="transition in timelineTransitions"
|
||||
:key="transition.type"
|
||||
:startPos="transition.startPos"
|
||||
:startTime="transition.startTime"
|
||||
:endTime="transition.endTime"
|
||||
:width="transition.width"
|
||||
:color="transition.color"
|
||||
:overlap="transition.overlap"
|
||||
:tooltip="transition.tooltip"
|
||||
:store="store"
|
||||
/>
|
||||
</div>
|
||||
<svg
|
||||
width="100%"
|
||||
height="20"
|
||||
class="timeline-svg"
|
||||
:class="{disabled: disabled}"
|
||||
ref="timeline"
|
||||
>
|
||||
<rect
|
||||
:x="`${block.startPos}%`"
|
||||
y="0"
|
||||
:width="`${block.width}%`"
|
||||
:height="pointHeight"
|
||||
:rx="corner"
|
||||
v-for="(block, idx) in timelineBlocks"
|
||||
:key="idx"
|
||||
@click="onBlockClick"
|
||||
class="point"
|
||||
/>
|
||||
<rect
|
||||
:x="`${position(selected)}%`"
|
||||
y="0"
|
||||
:width="`${pointWidth}%`"
|
||||
:height="pointHeight"
|
||||
:rx="corner"
|
||||
class="point selected"
|
||||
/>
|
||||
<line
|
||||
v-for="error in errorPositions"
|
||||
:key="error.pos"
|
||||
:x1="`${error.pos}%`"
|
||||
:x2="`${error.pos}%`"
|
||||
y1="0"
|
||||
y2="18px"
|
||||
class="error"
|
||||
@click="onErrorClick(error.ts)"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import TimelineMixin from "./mixins/Timeline.js";
|
||||
import TransitionContainer from './components/TagDisplay/TransitionContainer.vue';
|
||||
|
||||
export default {
|
||||
name: "timeline",
|
||||
// TODO: Add indication of trim, at least for collasped timeline
|
||||
props: ["selectedIndex", "crop", "disabled"],
|
||||
components: {
|
||||
'transition-container': TransitionContainer,
|
||||
},
|
||||
props: ["selectedIndex", "crop", "disabled", "store"],
|
||||
data() {
|
||||
return {
|
||||
pointHeight: 15,
|
||||
@@ -55,7 +86,6 @@ export default {
|
||||
};
|
||||
},
|
||||
mixins: [TimelineMixin],
|
||||
methods: {},
|
||||
computed: {
|
||||
timestamps() {
|
||||
if (this.timeline.length == 1) {
|
||||
@@ -66,10 +96,35 @@ export default {
|
||||
selected() {
|
||||
return this.timeline[this.selectedIndex];
|
||||
},
|
||||
maxOverlap() {
|
||||
if (!this.timelineTransitions) {
|
||||
return {
|
||||
marginTop: '0px',
|
||||
}
|
||||
}
|
||||
var overlaps = [];
|
||||
for (const transition in this.timelineTransitions) {
|
||||
overlaps.push(this.timelineTransitions[transition].overlap);
|
||||
}
|
||||
return {
|
||||
marginTop: (Math.max(...overlaps)+1)*10 + 'px',
|
||||
}
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<style scoped>
|
||||
.timeline-container {
|
||||
width: 100%;
|
||||
}
|
||||
.container:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
.tag-timeline {
|
||||
width: 100%;
|
||||
position: relative;
|
||||
height: 10px;
|
||||
}
|
||||
.timeline-svg .point {
|
||||
cursor: pointer;
|
||||
}
|
||||
@@ -78,9 +133,14 @@ export default {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
.timeline-svg:not(.disabled) .point.selected {
|
||||
fill: rgb(240, 59, 59);
|
||||
fill: #b2f6faff;
|
||||
}
|
||||
.timeline-svg.disabled .point.selected {
|
||||
fill: rgba(240, 59, 59, 0.596);
|
||||
}
|
||||
</style>
|
||||
.error {
|
||||
stroke: rgb(255, 0, 0);
|
||||
stroke-width: 8px;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
@@ -32,7 +32,7 @@
|
||||
>
|
||||
<h2 class="md-title" style="flex: 1;">Hierarchy</h2>
|
||||
<md-checkbox
|
||||
v-model="showHierachyDiff"
|
||||
v-model="showHierarchyDiff"
|
||||
v-if="diffVisualizationAvailable"
|
||||
>
|
||||
Show Diff
|
||||
@@ -42,9 +42,14 @@
|
||||
</md-checkbox>
|
||||
<md-checkbox v-model="store.onlyVisible">Only visible</md-checkbox>
|
||||
<md-checkbox v-model="store.flattened">Flat</md-checkbox>
|
||||
<md-checkbox v-if="hasTagsOrErrors" v-model="store.flickerTraceView">Flicker</md-checkbox>
|
||||
<md-field md-inline class="filter">
|
||||
<label>Filter...</label>
|
||||
<md-input v-model="hierarchyPropertyFilterString"></md-input>
|
||||
<md-input
|
||||
v-model="hierarchyPropertyFilterString"
|
||||
v-on:focus="updateInputMode(true)"
|
||||
v-on:blur="updateInputMode(false)"
|
||||
/>
|
||||
</md-field>
|
||||
</md-content>
|
||||
<div class="tree-view-wrapper">
|
||||
@@ -55,6 +60,10 @@
|
||||
:selected="hierarchySelected"
|
||||
:filter="hierarchyFilter"
|
||||
:flattened="store.flattened"
|
||||
:onlyVisible="store.onlyVisible"
|
||||
:flickerTraceView="store.flickerTraceView"
|
||||
:presentTags="presentTags"
|
||||
:presentErrors="presentErrors"
|
||||
:items-clickable="true"
|
||||
:useGlobalCollapsedState="true"
|
||||
:simplify-names="store.simplifyNames"
|
||||
@@ -72,6 +81,19 @@
|
||||
class="card-toolbar md-transparent md-dense"
|
||||
>
|
||||
<h2 class="md-title" style="flex: 1">Properties</h2>
|
||||
<div>
|
||||
<md-checkbox
|
||||
v-model="displayDefaults"
|
||||
@change="checkboxChange"
|
||||
>
|
||||
Show Defaults
|
||||
</md-checkbox>
|
||||
<md-tooltip md-direction="bottom">
|
||||
If checked, shows the value of all properties.
|
||||
Otherwise, hides all properties whose value is
|
||||
the default for its data type.
|
||||
</md-tooltip>
|
||||
</div>
|
||||
<md-checkbox
|
||||
v-model="showPropertiesDiff"
|
||||
v-if="diffVisualizationAvailable"
|
||||
@@ -80,7 +102,11 @@
|
||||
</md-checkbox>
|
||||
<md-field md-inline class="filter">
|
||||
<label>Filter...</label>
|
||||
<md-input v-model="propertyFilterString"></md-input>
|
||||
<md-input
|
||||
v-model="propertyFilterString"
|
||||
v-on:focus="updateInputMode(true)"
|
||||
v-on:blur="updateInputMode(false)"
|
||||
/>
|
||||
</md-field>
|
||||
</md-content>
|
||||
<div class="properties-content">
|
||||
@@ -96,7 +122,6 @@
|
||||
:item="selectedTree"
|
||||
:filter="propertyFilter"
|
||||
:collapseChildren="true"
|
||||
:useGlobalCollapsedState="true"
|
||||
:elementView="PropertiesTreeElement"
|
||||
/>
|
||||
</div>
|
||||
@@ -104,7 +129,7 @@
|
||||
<i class="material-icons none-icon">
|
||||
filter_none
|
||||
</i>
|
||||
<span>No element selected in the hierachy.</span>
|
||||
<span>No element selected in the hierarchy.</span>
|
||||
</div>
|
||||
</div>
|
||||
</flat-card>
|
||||
@@ -121,8 +146,10 @@ import PropertiesTreeElement from './PropertiesTreeElement.vue';
|
||||
import {ObjectTransformer} from './transform.js';
|
||||
import {DiffGenerator, defaultModifiedCheck} from './utils/diff.js';
|
||||
import {TRACE_TYPES, DUMP_TYPES} from './decode.js';
|
||||
import {stableIdCompatibilityFixup} from './utils/utils.js';
|
||||
import {isPropertyMatch, stableIdCompatibilityFixup} from './utils/utils.js';
|
||||
import {CompatibleFeatures} from './utils/compatibility.js';
|
||||
import {getPropertiesForDisplay} from './flickerlib/mixin';
|
||||
import ObjectFormatter from './flickerlib/ObjectFormatter';
|
||||
|
||||
function formatProto(obj) {
|
||||
if (obj?.prettyPrint) {
|
||||
@@ -151,7 +178,7 @@ function findEntryInTree(tree, id) {
|
||||
|
||||
export default {
|
||||
name: 'traceview',
|
||||
props: ['store', 'file', 'summarizer'],
|
||||
props: ['store', 'file', 'summarizer', 'presentTags', 'presentErrors'],
|
||||
data() {
|
||||
return {
|
||||
propertyFilterString: '',
|
||||
@@ -164,12 +191,16 @@ export default {
|
||||
item: null,
|
||||
tree: null,
|
||||
highlight: null,
|
||||
showHierachyDiff: false,
|
||||
showHierarchyDiff: false,
|
||||
displayDefaults: false,
|
||||
showPropertiesDiff: false,
|
||||
PropertiesTreeElement,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
checkboxChange(checked) {
|
||||
this.itemSelected(this.item);
|
||||
},
|
||||
itemSelected(item) {
|
||||
this.hierarchySelected = item;
|
||||
this.selectedTree = this.getTransformedProperties(item);
|
||||
@@ -178,8 +209,9 @@ export default {
|
||||
this.$emit('focus');
|
||||
},
|
||||
getTransformedProperties(item) {
|
||||
ObjectFormatter.displayDefaults = this.displayDefaults;
|
||||
const transformer = new ObjectTransformer(
|
||||
item.obj,
|
||||
getPropertiesForDisplay(item),
|
||||
item.name,
|
||||
stableIdCompatibilityFixup(item),
|
||||
).setOptions({
|
||||
@@ -189,7 +221,7 @@ export default {
|
||||
|
||||
if (this.showPropertiesDiff && this.diffVisualizationAvailable) {
|
||||
const prevItem = this.getItemFromPrevTree(item);
|
||||
transformer.withDiff(prevItem?.obj);
|
||||
transformer.withDiff(getPropertiesForDisplay(prevItem));
|
||||
}
|
||||
|
||||
return transformer.transform();
|
||||
@@ -200,12 +232,14 @@ export default {
|
||||
}
|
||||
},
|
||||
generateTreeFromItem(item) {
|
||||
if (!this.showHierachyDiff || !this.diffVisualizationAvailable) {
|
||||
if (!this.showHierarchyDiff || !this.diffVisualizationAvailable) {
|
||||
return item;
|
||||
}
|
||||
|
||||
return new DiffGenerator(this.item)
|
||||
.compareWith(this.getDataWithOffset(-1))
|
||||
const thisItem = this.item;
|
||||
const prevItem = this.getDataWithOffset(-1);
|
||||
return new DiffGenerator(thisItem)
|
||||
.compareWith(prevItem)
|
||||
.withUniqueNodeId((node) => {
|
||||
return node.stableId;
|
||||
})
|
||||
@@ -216,7 +250,7 @@ export default {
|
||||
this.item = item;
|
||||
this.tree = this.generateTreeFromItem(item);
|
||||
|
||||
const rects = item.rects //.toArray()
|
||||
const rects = item.rects; // .toArray()
|
||||
this.rects = [...rects].reverse();
|
||||
this.bounds = item.bounds;
|
||||
|
||||
@@ -285,15 +319,38 @@ export default {
|
||||
|
||||
return prevEntry;
|
||||
},
|
||||
|
||||
/** Performs check for id match between entry and present tags/errors
|
||||
* must be carried out for every present tag/error
|
||||
*/
|
||||
matchItems(flickerItems, entryItem) {
|
||||
var match = false;
|
||||
flickerItems.forEach(flickerItem => {
|
||||
if (isPropertyMatch(flickerItem, entryItem)) match = true;
|
||||
});
|
||||
return match;
|
||||
},
|
||||
/** Returns check for id match between entry and present tags/errors */
|
||||
isEntryTagMatch(entryItem) {
|
||||
return this.matchItems(this.presentTags, entryItem) || this.matchItems(this.presentErrors, entryItem);
|
||||
},
|
||||
|
||||
/** determines whether left/right arrow keys should move cursor in input field */
|
||||
updateInputMode(isInputMode) {
|
||||
this.store.isInputMode = isInputMode;
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.setData(this.file.data[this.file.selectedIndex ?? 0]);
|
||||
},
|
||||
destroyed() {
|
||||
this.store.flickerTraceView = false;
|
||||
},
|
||||
watch: {
|
||||
selectedIndex() {
|
||||
this.setData(this.file.data[this.file.selectedIndex ?? 0]);
|
||||
},
|
||||
showHierachyDiff() {
|
||||
showHierarchyDiff() {
|
||||
this.tree = this.generateTreeFromItem(this.item);
|
||||
},
|
||||
showPropertiesDiff() {
|
||||
@@ -316,9 +373,12 @@ export default {
|
||||
hierarchyFilter() {
|
||||
const hierarchyPropertyFilter =
|
||||
getFilter(this.hierarchyPropertyFilterString);
|
||||
return this.store.onlyVisible ? (c) => {
|
||||
return c.visible && hierarchyPropertyFilter(c);
|
||||
var fil = this.store.onlyVisible ? (c) => {
|
||||
return c.isVisible && hierarchyPropertyFilter(c);
|
||||
} : hierarchyPropertyFilter;
|
||||
return this.store.flickerTraceView ? (c) => {
|
||||
return this.isEntryTagMatch(c);
|
||||
} : fil;
|
||||
},
|
||||
propertyFilter() {
|
||||
return getFilter(this.propertyFilterString);
|
||||
@@ -342,6 +402,9 @@ export default {
|
||||
|
||||
return summary;
|
||||
},
|
||||
hasTagsOrErrors() {
|
||||
return this.presentTags.length > 0 || this.presentErrors.length > 0;
|
||||
},
|
||||
},
|
||||
components: {
|
||||
'tree-view': TreeView,
|
||||
|
||||
@@ -33,14 +33,21 @@
|
||||
v-for="(surface, index) in sufacesAffectedBy(source)"
|
||||
v-bind:key="surface.id"
|
||||
>
|
||||
<!-- eslint-disable-next-line max-len -->
|
||||
<span v-if="surface.name" class="surface-name">{{ surface.name }}</span>
|
||||
<span class="surface-id">
|
||||
<!-- eslint-disable-next-line max-len -->
|
||||
<span v-if="surface.name">(</span>{{surface.id}}<span v-if="surface.name">)</span>
|
||||
<span
|
||||
v-if="simplifyNames && surface.shortName &&
|
||||
surface.shortName !== surface.name"
|
||||
>{{surface.shortName}}>
|
||||
</span>
|
||||
<span v-else>
|
||||
<!-- eslint-disable-next-line max-len -->
|
||||
<span v-if="surface.name" class="surface-name">{{ surface.name }}</span>
|
||||
<span class="surface-id">
|
||||
<!-- eslint-disable-next-line max-len -->
|
||||
<span v-if="surface.name">(</span>{{surface.id}}<span v-if="surface.name">)</span>
|
||||
</span>
|
||||
<!-- eslint-disable-next-line max-len -->
|
||||
<span v-if="index + 1 < sufacesAffectedBy(source).length">, </span>
|
||||
</span>
|
||||
<!-- eslint-disable-next-line max-len -->
|
||||
<span v-if="index + 1 < sufacesAffectedBy(source).length">, </span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="extra-info-column">
|
||||
@@ -59,6 +66,8 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { shortenName } from './flickerlib/mixin'
|
||||
|
||||
export default {
|
||||
name: 'transaction-entry',
|
||||
props: {
|
||||
@@ -83,6 +92,9 @@ export default {
|
||||
prettifyTransactionId: {
|
||||
type: Function,
|
||||
},
|
||||
simplifyNames: {
|
||||
type: Boolean,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
currentTimestamp() {
|
||||
@@ -135,8 +147,12 @@ export default {
|
||||
},
|
||||
sufacesAffectedBy(transaction) {
|
||||
if (transaction.type !== 'transaction') {
|
||||
// TODO (b/162402459): Shorten layer name
|
||||
return [{name: transaction.layerName, id: transaction.obj.id}];
|
||||
return [
|
||||
{
|
||||
name: transaction.layerName,
|
||||
shortName: shortenName(transaction.layerName),
|
||||
id: transaction.obj.id
|
||||
}];
|
||||
}
|
||||
|
||||
const surfaceIds = new Set();
|
||||
@@ -145,7 +161,12 @@ export default {
|
||||
const id = transaction.obj.id;
|
||||
if (!surfaceIds.has(id)) {
|
||||
surfaceIds.add(id);
|
||||
affectedSurfaces.push({name: transaction.layerName, id});
|
||||
affectedSurfaces.push(
|
||||
{
|
||||
name: transaction.layerName,
|
||||
shortName: shortenName(transaction.layerName),
|
||||
id
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,13 @@
|
||||
<md-card-content class="container">
|
||||
|
||||
<flat-card class="changes card">
|
||||
<md-content
|
||||
md-tag="md-toolbar"
|
||||
md-elevation="0"
|
||||
class="card-toolbar md-transparent md-dense"
|
||||
>
|
||||
<h2 class="md-title" style="flex: 1">Transactions</h2>
|
||||
</md-content>
|
||||
<div class="filters">
|
||||
<div class="input">
|
||||
<md-field>
|
||||
@@ -75,6 +82,11 @@
|
||||
<div class="md-helper-text">Press enter to add</div>
|
||||
</md-chips>
|
||||
</div>
|
||||
|
||||
<md-checkbox v-model="trace.simplifyNames">
|
||||
Simplify names
|
||||
</md-checkbox>
|
||||
|
||||
</div>
|
||||
|
||||
<virtual-list style="height: 600px; overflow-y: auto;"
|
||||
@@ -86,6 +98,7 @@
|
||||
selectedTransaction,
|
||||
transactionsTrace,
|
||||
prettifyTransactionId,
|
||||
simplifyNames: trace.simplifyNames,
|
||||
}"
|
||||
ref="loglist"
|
||||
/>
|
||||
@@ -289,7 +302,6 @@ export default {
|
||||
const perpareForTreeViewTransform = (change) => {
|
||||
this.removeNullFields(change);
|
||||
change[META_DATA_KEY] = {
|
||||
// TODO (b/162402459): Shorten layer name
|
||||
layerName: change.layerName,
|
||||
};
|
||||
// remove redundant properties
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
<button
|
||||
class="toggle-tree-btn"
|
||||
@click="toggleTree"
|
||||
v-if="!isLeaf"
|
||||
v-if="!isLeaf && !flattened"
|
||||
v-on:click.stop
|
||||
>
|
||||
<i aria-hidden="true" class="md-icon md-theme-default material-icons">
|
||||
@@ -50,7 +50,12 @@
|
||||
/>
|
||||
</div>
|
||||
<div v-else>
|
||||
<DefaultTreeElement :item="item" :simplify-names="simplifyNames"/>
|
||||
<DefaultTreeElement
|
||||
:item="item"
|
||||
:simplify-names="simplifyNames"
|
||||
:errors="errors"
|
||||
:transitions="transitions"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div v-show="isCollapsed">
|
||||
@@ -78,7 +83,7 @@
|
||||
v-on:collapseAllOtherNodes="collapseAllOtherNodes"
|
||||
/>
|
||||
|
||||
<div class="children" v-if="children" v-show="!isCollapsed">
|
||||
<div class="children" v-if="children" v-show="!isCollapsed" :style="childrenIndentation()">
|
||||
<tree-view
|
||||
v-for="(c,i) in children"
|
||||
:item="c"
|
||||
@@ -87,7 +92,11 @@
|
||||
:key="i"
|
||||
:filter="childFilter(c)"
|
||||
:flattened="flattened"
|
||||
:onlyVisible="onlyVisible"
|
||||
:simplify-names="simplifyNames"
|
||||
:flickerTraceView="flickerTraceView"
|
||||
:presentTags="currentTags"
|
||||
:presentErrors="currentErrors"
|
||||
:force-flattened="applyingFlattened"
|
||||
v-show="filterMatches(c)"
|
||||
:items-clickable="itemsClickable"
|
||||
@@ -100,7 +109,7 @@
|
||||
v-on:selected="immediateChildSelected = true"
|
||||
v-on:unselected="immediateChildSelected = false"
|
||||
:elementView="elementView"
|
||||
v-on:collapseSibbling="collapseSibbling"
|
||||
v-on:collapseSibling="collapseSibling"
|
||||
v-on:collapseAllOtherNodes="collapseAllOtherNodes"
|
||||
v-on:closeAllContextMenus="closeAllContextMenus"
|
||||
ref="children"
|
||||
@@ -112,8 +121,8 @@
|
||||
<script>
|
||||
import DefaultTreeElement from './DefaultTreeElement.vue';
|
||||
import NodeContextMenu from './NodeContextMenu.vue';
|
||||
|
||||
import {DiffType} from './utils/diff.js';
|
||||
import {isPropertyMatch} from './utils/utils.js';
|
||||
|
||||
/* in px, must be kept in sync with css, maybe find a better solution... */
|
||||
const levelOffset = 24;
|
||||
@@ -141,6 +150,10 @@ export default {
|
||||
'useGlobalCollapsedState',
|
||||
// Custom view to use to render the elements in the tree view
|
||||
'elementView',
|
||||
'onlyVisible',
|
||||
'flickerTraceView',
|
||||
'presentTags',
|
||||
'presentErrors',
|
||||
],
|
||||
data() {
|
||||
const isCollapsedByDefault = this.collapse ?? false;
|
||||
@@ -161,6 +174,10 @@ export default {
|
||||
[DiffType.MODIFIED]: '.',
|
||||
[DiffType.MOVED]: '.',
|
||||
},
|
||||
currentTags: [],
|
||||
currentErrors: [],
|
||||
transitions: [],
|
||||
errors: [],
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
@@ -177,6 +194,10 @@ export default {
|
||||
},
|
||||
currentTimestamp() {
|
||||
// Update anything that is required to change when time changes.
|
||||
this.currentTags = this.getCurrentItems(this.presentTags);
|
||||
this.currentErrors = this.getCurrentItems(this.presentErrors);
|
||||
this.transitions = this.getCurrentTransitions();
|
||||
this.errors = this.getCurrentErrorTags();
|
||||
this.updateCollapsedDiffClass();
|
||||
},
|
||||
isSelected(isSelected) {
|
||||
@@ -200,6 +221,9 @@ export default {
|
||||
},
|
||||
toggleTree() {
|
||||
this.setCollapseValue(!this.isCollapsed);
|
||||
if (!this.isCollapsed) {
|
||||
this.openedToSeeAttributeField(this.item.name)
|
||||
}
|
||||
},
|
||||
expandTree() {
|
||||
this.setCollapseValue(false);
|
||||
@@ -277,7 +301,7 @@ export default {
|
||||
}
|
||||
|
||||
if (!this.isLeaf && e.detail % 2 === 0) {
|
||||
// Double click collaspable node
|
||||
// Double click collapsable node
|
||||
this.toggleTree();
|
||||
} else {
|
||||
this.select();
|
||||
@@ -352,9 +376,9 @@ export default {
|
||||
},
|
||||
collapseAllOtherNodes() {
|
||||
this.$emit('collapseAllOtherNodes');
|
||||
this.$emit('collapseSibbling', this.item);
|
||||
this.$emit('collapseSibling', this.item);
|
||||
},
|
||||
collapseSibbling(item) {
|
||||
collapseSibling(item) {
|
||||
if (!this.$refs.children) {
|
||||
return;
|
||||
}
|
||||
@@ -408,6 +432,60 @@ export default {
|
||||
child.closeAllChildrenContextMenus();
|
||||
}
|
||||
},
|
||||
childrenIndentation() {
|
||||
if (this.flattened || this.forceFlattened) {
|
||||
return {
|
||||
marginLeft: '0px',
|
||||
paddingLeft: '0px',
|
||||
marginTop: '0px',
|
||||
}
|
||||
} else {
|
||||
//Aligns border with collapse arrows
|
||||
return {
|
||||
marginLeft: '12px',
|
||||
paddingLeft: '11px',
|
||||
borderLeft: '1px solid rgb(238, 238, 238)',
|
||||
marginTop: '0px',
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/** Performs check for id match between entry and present tags/errors
|
||||
* exits once match has been found
|
||||
*/
|
||||
matchItems(flickerItems) {
|
||||
var match = false;
|
||||
flickerItems.every(flickerItem => {
|
||||
if (isPropertyMatch(flickerItem, this.item)) {
|
||||
match = true;
|
||||
return false;
|
||||
}
|
||||
});
|
||||
return match;
|
||||
},
|
||||
/** Returns check for id match between entry and present tags/errors */
|
||||
isEntryTagMatch() {
|
||||
return this.matchItems(this.currentTags) || this.matchItems(this.currentErrors);
|
||||
},
|
||||
|
||||
getCurrentItems(items) {
|
||||
if (!items) return [];
|
||||
else return items.filter(item => item.timestamp===this.currentTimestamp);
|
||||
},
|
||||
getCurrentTransitions() {
|
||||
var transitions = [];
|
||||
var ids = [];
|
||||
this.currentTags.forEach(tag => {
|
||||
if (!ids.includes(tag.id) && isPropertyMatch(tag, this.item)) {
|
||||
transitions.push(tag.transition);
|
||||
ids.push(tag.id);
|
||||
}
|
||||
});
|
||||
return transitions;
|
||||
},
|
||||
getCurrentErrorTags() {
|
||||
return this.currentErrors.filter(error => isPropertyMatch(error, this.item));
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
hasDiff() {
|
||||
@@ -464,11 +542,20 @@ export default {
|
||||
return this.initialDepth || 0;
|
||||
},
|
||||
nodeOffsetStyle() {
|
||||
const offest = levelOffset * (this.depth + this.isLeaf) + 'px';
|
||||
const offset = levelOffset * (this.depth + this.isLeaf) + 'px';
|
||||
|
||||
var display = "";
|
||||
if (!this.item.timestamp
|
||||
&& this.flattened
|
||||
&& (this.onlyVisible && !this.item.isVisible ||
|
||||
this.flickerTraceView && !this.isEntryTagMatch())) {
|
||||
display = 'none';
|
||||
}
|
||||
|
||||
return {
|
||||
marginLeft: '-' + offest,
|
||||
paddingLeft: offest,
|
||||
marginLeft: '-' + offset,
|
||||
paddingLeft: offset,
|
||||
display: display,
|
||||
};
|
||||
},
|
||||
},
|
||||
@@ -567,14 +654,6 @@ export default {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.children {
|
||||
/* Aligns border with collapse arrows */
|
||||
margin-left: 12px;
|
||||
padding-left: 11px;
|
||||
border-left: 1px solid rgb(238, 238, 238);
|
||||
margin-top: 0px;
|
||||
}
|
||||
|
||||
.tree-view .node.child-selected + .children {
|
||||
border-left: 1px solid #b4b4b4;
|
||||
}
|
||||
|
||||
@@ -14,7 +14,13 @@
|
||||
-->
|
||||
|
||||
<template>
|
||||
<TraceView :store="store" :file="file" :summarizer="summarizer" />
|
||||
<TraceView
|
||||
:store="store"
|
||||
:file="file"
|
||||
:summarizer="summarizer"
|
||||
:presentTags="presentTags"
|
||||
:presentErrors="presentErrors"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@@ -22,7 +28,7 @@ import TraceView from "@/TraceView.vue"
|
||||
|
||||
export default {
|
||||
name: "WindowManagerTraceView",
|
||||
props: ["store", "file"],
|
||||
props: ["store", "file", "presentTags", "presentErrors"],
|
||||
components: {
|
||||
TraceView
|
||||
},
|
||||
@@ -30,8 +36,8 @@ export default {
|
||||
summarizer(item) {
|
||||
const summary = [];
|
||||
|
||||
if (item.obj.isIncompleteReason) {
|
||||
summary.push({key: 'Incomplete state reason', value: item.obj.isIncompleteReason});
|
||||
if (item.isIncompleteReason) {
|
||||
summary.push({key: 'Incomplete state reason', value: item.isIncompleteReason});
|
||||
}
|
||||
|
||||
return summary;
|
||||
|
||||
33
tools/winscope/src/components/TagDisplay/Arrow.vue
Normal file
33
tools/winscope/src/components/TagDisplay/Arrow.vue
Normal file
@@ -0,0 +1,33 @@
|
||||
<!-- 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.
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div class="arrow"/>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name: 'arrow',
|
||||
};
|
||||
</script>
|
||||
<style scoped>
|
||||
.arrow {
|
||||
display: inline-block;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-left: 6px solid transparent;
|
||||
border-right: 6px solid transparent;
|
||||
border-top: 10px solid;
|
||||
}
|
||||
</style>
|
||||
113
tools/winscope/src/components/TagDisplay/TransitionContainer.vue
Normal file
113
tools/winscope/src/components/TagDisplay/TransitionContainer.vue
Normal file
@@ -0,0 +1,113 @@
|
||||
<!-- 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.
|
||||
-->
|
||||
<template>
|
||||
<div class="transition-container" :style="transitionStyle" @click="handleTransitionClick()">
|
||||
<md-tooltip md-direction="left"> {{tooltip}} </md-tooltip>
|
||||
<arrow class="arrow-start" :style="transitionComponentColor"/>
|
||||
<div class="connector" :style="transitionComponentColor"/>
|
||||
<arrow class="arrow-end" :style="transitionComponentColor"/>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import Arrow from './Arrow.vue';
|
||||
import {LocalStore} from '../../localstore.js';
|
||||
|
||||
var transitionCount = false;
|
||||
|
||||
export default {
|
||||
name: 'transition-container',
|
||||
components: {
|
||||
'arrow': Arrow,
|
||||
},
|
||||
props: {
|
||||
'width': {
|
||||
type: Number,
|
||||
},
|
||||
'startPos': {
|
||||
type: Number,
|
||||
},
|
||||
'startTime': {
|
||||
type: Number,
|
||||
},
|
||||
'endTime': {
|
||||
type: Number,
|
||||
},
|
||||
'color': {
|
||||
type: String,
|
||||
},
|
||||
'overlap': {
|
||||
type: Number,
|
||||
},
|
||||
'tooltip': {
|
||||
type: String,
|
||||
},
|
||||
'store': {
|
||||
type: LocalStore,
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
handleTransitionClick() {
|
||||
if (transitionCount) {
|
||||
this.$store.dispatch('updateTimelineTime', this.startTime);
|
||||
transitionCount = false;
|
||||
} else {
|
||||
this.$store.dispatch('updateTimelineTime', this.endTime);
|
||||
transitionCount = true;
|
||||
}
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
transitionStyle() {
|
||||
return {
|
||||
width: this.width + '%',
|
||||
left: this.startPos + '%',
|
||||
bottom: this.overlap * 100 + '%',
|
||||
}
|
||||
},
|
||||
transitionComponentColor() {
|
||||
return {
|
||||
borderTopColor: this.color,
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style scoped>
|
||||
.transition-container {
|
||||
position: absolute;
|
||||
height: 15px;
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
.arrow-start {
|
||||
position: absolute;
|
||||
left: 0%;
|
||||
}
|
||||
|
||||
.arrow-end {
|
||||
position: absolute;
|
||||
right: 0%;
|
||||
}
|
||||
|
||||
.connector {
|
||||
position: absolute;
|
||||
display: inline-block;
|
||||
width: auto;
|
||||
height: 9px;
|
||||
left: 5px;
|
||||
right: 5px;
|
||||
border-top: 1px solid;
|
||||
}
|
||||
</style>
|
||||
36
tools/winscope/src/config/Configuration.json
Normal file
36
tools/winscope/src/config/Configuration.json
Normal file
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"invalidProperties": [
|
||||
"length",
|
||||
"prototype",
|
||||
"ref",
|
||||
"diff",
|
||||
"rects",
|
||||
"chips",
|
||||
"parent",
|
||||
"timestamp",
|
||||
"shortName",
|
||||
"kind",
|
||||
"resolvedChildren",
|
||||
"visibilityReason",
|
||||
"absoluteZ",
|
||||
"name",
|
||||
"children",
|
||||
"stableId"
|
||||
],
|
||||
"intDefColumn": {
|
||||
"WindowLayoutParams.type": "android.view.WindowManager.LayoutParams.WindowType",
|
||||
"WindowLayoutParams.flags": "android.view.WindowManager.LayoutParams.Flags",
|
||||
"WindowLayoutParams.privateFlags": "android.view.WindowManager.LayoutParams.PrivateFlags",
|
||||
"WindowLayoutParams.gravity": "android.view.Gravity.GravityFlags",
|
||||
"WindowLayoutParams.softInputMode": "android.view.WindowManager.LayoutParams.WindowType",
|
||||
"WindowLayoutParams.systemUiVisibilityFlags": "android.view.WindowManager.LayoutParams.SystemUiVisibilityFlags",
|
||||
"WindowLayoutParams.subtreeSystemUiVisibilityFlags": "android.view.WindowManager.LayoutParams.SystemUiVisibilityFlags",
|
||||
"WindowLayoutParams.behavior": "android.view.WindowInsetsController.Behavior",
|
||||
"WindowLayoutParams.fitInsetsSides": "android.view.WindowInsets.Side.InsetsSide",
|
||||
"Configuration.windowingMode": "android.app.WindowConfiguration.WindowingMode",
|
||||
"WindowConfiguration.windowingMode": "android.app.WindowConfiguration.WindowingMode",
|
||||
"Configuration.orientation": "android.content.pm.ActivityInfo.ScreenOrientation",
|
||||
"WindowConfiguration.orientation": "android.content.pm.ActivityInfo.ScreenOrientation",
|
||||
"WindowState.orientation": "android.content.pm.ActivityInfo.ScreenOrientation"
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,7 @@
|
||||
/* eslint-disable camelcase */
|
||||
/* eslint-disable max-len */
|
||||
|
||||
import jsonProtoDefsAccessibility from 'frameworks/base/core/proto/android/server/accessibilitytrace.proto';
|
||||
import jsonProtoDefsWm from 'frameworks/base/core/proto/android/server/windowmanagertrace.proto';
|
||||
import jsonProtoDefsProtoLog from 'frameworks/base/core/proto/android/internal/protolog.proto';
|
||||
import jsonProtoDefsSf from 'frameworks/native/services/surfaceflinger/layerproto/layerstrace.proto';
|
||||
@@ -25,17 +26,19 @@ import jsonProtoDefsWl from 'WaylandSafePath/waylandtrace.proto';
|
||||
import jsonProtoDefsSysUi from 'frameworks/base/packages/SystemUI/src/com/android/systemui/tracing/sysui_trace.proto';
|
||||
import jsonProtoDefsLauncher from 'packages/apps/Launcher3/protos/launcher_trace_file.proto';
|
||||
import jsonProtoDefsIme from 'frameworks/base/core/proto/android/view/inputmethod/inputmethodeditortrace.proto';
|
||||
import jsonProtoDefsTags from 'platform_testing/libraries/flicker/src/com/android/server/wm/proto/tags.proto';
|
||||
import jsonProtoDefsErrors from 'platform_testing/libraries/flicker/src/com/android/server/wm/proto/errors.proto';
|
||||
import protobuf from 'protobufjs';
|
||||
import {transformLayers, transformLayersTrace} from './transform_sf.js';
|
||||
import {transform_accessibility_trace} from './transform_accessibility.js';
|
||||
import {transform_transaction_trace} from './transform_transaction.js';
|
||||
import {transform_wl_outputstate, transform_wayland_trace} from './transform_wl.js';
|
||||
import {transformProtolog} from './transform_protolog.js';
|
||||
import {transform_sysui_trace} from './transform_sys_ui.js';
|
||||
import {transform_launcher_trace} from './transform_launcher.js';
|
||||
import {transform_ime_trace_clients, transform_ime_trace_service, transform_ime_trace_managerservice} from './transform_ime.js';
|
||||
import {fill_transform_data} from './matrix_utils.js';
|
||||
import {mp4Decoder} from './decodeVideo.js';
|
||||
|
||||
import AccessibilityTrace from '@/traces/Accessibility.ts';
|
||||
import SurfaceFlingerTrace from '@/traces/SurfaceFlinger.ts';
|
||||
import WindowManagerTrace from '@/traces/WindowManager.ts';
|
||||
import TransactionsTrace from '@/traces/Transactions.ts';
|
||||
@@ -52,6 +55,10 @@ import SurfaceFlingerDump from '@/dumps/SurfaceFlinger.ts';
|
||||
import WindowManagerDump from '@/dumps/WindowManager.ts';
|
||||
import WaylandDump from '@/dumps/Wayland.ts';
|
||||
|
||||
import TagTrace from '@/traces/TraceTag.ts';
|
||||
import ErrorTrace from '@/traces/TraceError.ts';
|
||||
|
||||
const AccessibilityTraceMessage = lookup_type(jsonProtoDefsAccessibility, 'com.android.server.accessibility.AccessibilityTraceFileProto');
|
||||
const WmTraceMessage = lookup_type(jsonProtoDefsWm, 'com.android.server.wm.WindowManagerTraceFileProto');
|
||||
const WmDumpMessage = lookup_type(jsonProtoDefsWm, 'com.android.server.wm.WindowManagerServiceDumpProto');
|
||||
const SfTraceMessage = lookup_type(jsonProtoDefsSf, 'android.surfaceflinger.LayersTraceFileProto');
|
||||
@@ -62,10 +69,13 @@ const WaylandDumpMessage = lookup_type(jsonProtoDefsWl, 'org.chromium.arc.waylan
|
||||
const ProtoLogMessage = lookup_type(jsonProtoDefsProtoLog, 'com.android.internal.protolog.ProtoLogFileProto');
|
||||
const SystemUiTraceMessage = lookup_type(jsonProtoDefsSysUi, 'com.android.systemui.tracing.SystemUiTraceFileProto');
|
||||
const LauncherTraceMessage = lookup_type(jsonProtoDefsLauncher, 'com.android.launcher3.tracing.LauncherTraceFileProto');
|
||||
const InputMethodClientsTraceMessage = lookup_type(jsonProtoDefsIme, "android.view.inputmethod.InputMethodClientsTraceFileProto");
|
||||
const InputMethodServiceTraceMessage = lookup_type(jsonProtoDefsIme, "android.view.inputmethod.InputMethodServiceTraceFileProto");
|
||||
const InputMethodManagerServiceTraceMessage = lookup_type(jsonProtoDefsIme, "android.view.inputmethod.InputMethodManagerServiceTraceFileProto");
|
||||
const InputMethodClientsTraceMessage = lookup_type(jsonProtoDefsIme, 'android.view.inputmethod.InputMethodClientsTraceFileProto');
|
||||
const InputMethodServiceTraceMessage = lookup_type(jsonProtoDefsIme, 'android.view.inputmethod.InputMethodServiceTraceFileProto');
|
||||
const InputMethodManagerServiceTraceMessage = lookup_type(jsonProtoDefsIme, 'android.view.inputmethod.InputMethodManagerServiceTraceFileProto');
|
||||
const TagTraceMessage = lookup_type(jsonProtoDefsTags, 'com.android.server.wm.flicker.FlickerTagTraceProto');
|
||||
const ErrorTraceMessage = lookup_type(jsonProtoDefsErrors, 'com.android.server.wm.flicker.FlickerErrorTraceProto');
|
||||
|
||||
const ACCESSIBILITY_MAGIC_NUMBER = [0x09, 0x41, 0x31, 0x31, 0x59, 0x54, 0x52, 0x41, 0x43]; // .A11YTRAC
|
||||
const LAYER_TRACE_MAGIC_NUMBER = [0x09, 0x4c, 0x59, 0x52, 0x54, 0x52, 0x41, 0x43, 0x45]; // .LYRTRACE
|
||||
const WINDOW_TRACE_MAGIC_NUMBER = [0x09, 0x57, 0x49, 0x4e, 0x54, 0x52, 0x41, 0x43, 0x45]; // .WINTRACE
|
||||
const MPEG4_MAGIC_NMBER = [0x00, 0x00, 0x00, 0x18, 0x66, 0x74, 0x79, 0x70, 0x6d, 0x70, 0x34, 0x32]; // ....ftypmp42
|
||||
@@ -73,11 +83,14 @@ const WAYLAND_TRACE_MAGIC_NUMBER = [0x09, 0x57, 0x59, 0x4c, 0x54, 0x52, 0x41, 0x
|
||||
const PROTO_LOG_MAGIC_NUMBER = [0x09, 0x50, 0x52, 0x4f, 0x54, 0x4f, 0x4c, 0x4f, 0x47]; // .PROTOLOG
|
||||
const SYSTEM_UI_MAGIC_NUMBER = [0x09, 0x53, 0x59, 0x53, 0x55, 0x49, 0x54, 0x52, 0x43]; // .SYSUITRC
|
||||
const LAUNCHER_MAGIC_NUMBER = [0x09, 0x4C, 0x4E, 0x43, 0x48, 0x52, 0x54, 0x52, 0x43]; // .LNCHRTRC
|
||||
const IMC_TRACE_MAGIC_NUMBER = [0x09, 0x49, 0x4d, 0x43, 0x54, 0x52, 0x41, 0x43, 0x45] //.IMCTRACE
|
||||
const IMS_TRACE_MAGIC_NUMBER = [0x09, 0x49, 0x4d, 0x53, 0x54, 0x52, 0x41, 0x43, 0x45] //.IMSTRACE
|
||||
const IMM_TRACE_MAGIC_NUMBER = [0x09, 0x49, 0x4d, 0x4d, 0x54, 0x52, 0x41, 0x43, 0x45] //.IMMTRACE
|
||||
const IMC_TRACE_MAGIC_NUMBER = [0x09, 0x49, 0x4d, 0x43, 0x54, 0x52, 0x41, 0x43, 0x45]; // .IMCTRACE
|
||||
const IMS_TRACE_MAGIC_NUMBER = [0x09, 0x49, 0x4d, 0x53, 0x54, 0x52, 0x41, 0x43, 0x45]; // .IMSTRACE
|
||||
const IMM_TRACE_MAGIC_NUMBER = [0x09, 0x49, 0x4d, 0x4d, 0x54, 0x52, 0x41, 0x43, 0x45]; // .IMMTRACE
|
||||
const TAG_TRACE_MAGIC_NUMBER = [0x09, 0x54, 0x41, 0x47, 0x54, 0x52, 0x41, 0x43, 0x45]; //.TAGTRACE
|
||||
const ERROR_TRACE_MAGIC_NUMBER = [0x09, 0x45, 0x52, 0x52, 0x54, 0x52, 0x41, 0x43, 0x45]; //.ERRORTRACE
|
||||
|
||||
const FILE_TYPES = Object.freeze({
|
||||
ACCESSIBILITY_TRACE: 'AccessibilityTrace',
|
||||
WINDOW_MANAGER_TRACE: 'WindowManagerTrace',
|
||||
SURFACE_FLINGER_TRACE: 'SurfaceFlingerTrace',
|
||||
WINDOW_MANAGER_DUMP: 'WindowManagerDump',
|
||||
@@ -92,6 +105,8 @@ const FILE_TYPES = Object.freeze({
|
||||
IME_TRACE_CLIENTS: 'ImeTraceClients',
|
||||
IME_TRACE_SERVICE: 'ImeTrace InputMethodService',
|
||||
IME_TRACE_MANAGERSERVICE: 'ImeTrace InputMethodManagerService',
|
||||
TAG_TRACE: 'TagTrace',
|
||||
ERROR_TRACE: 'ErrorTrace',
|
||||
});
|
||||
|
||||
const WINDOW_MANAGER_ICON = 'view_compact';
|
||||
@@ -103,8 +118,12 @@ const PROTO_LOG_ICON = 'notes';
|
||||
const SYSTEM_UI_ICON = 'filter_none';
|
||||
const LAUNCHER_ICON = 'filter_none';
|
||||
const IME_ICON = 'keyboard';
|
||||
const ACCESSIBILITY_ICON = 'filter_none';
|
||||
const TAG_ICON = 'details';
|
||||
const TRACE_ERROR_ICON = 'warning';
|
||||
|
||||
const FILE_ICONS = {
|
||||
[FILE_TYPES.ACCESSIBILITY_TRACE]: ACCESSIBILITY_ICON,
|
||||
[FILE_TYPES.WINDOW_MANAGER_TRACE]: WINDOW_MANAGER_ICON,
|
||||
[FILE_TYPES.SURFACE_FLINGER_TRACE]: SURFACE_FLINGER_ICON,
|
||||
[FILE_TYPES.WINDOW_MANAGER_DUMP]: WINDOW_MANAGER_ICON,
|
||||
@@ -119,17 +138,16 @@ const FILE_ICONS = {
|
||||
[FILE_TYPES.IME_TRACE_CLIENTS]: IME_ICON,
|
||||
[FILE_TYPES.IME_TRACE_SERVICE]: IME_ICON,
|
||||
[FILE_TYPES.IME_TRACE_MANAGERSERVICE]: IME_ICON,
|
||||
[FILE_TYPES.TAG_TRACE]: TAG_ICON,
|
||||
[FILE_TYPES.ERROR_TRACE]: TRACE_ERROR_ICON,
|
||||
};
|
||||
|
||||
function oneOf(dataType) {
|
||||
return {oneOf: true, type: dataType};
|
||||
}
|
||||
|
||||
function manyOf(dataType, fold = null) {
|
||||
return {manyOf: true, type: dataType, fold};
|
||||
}
|
||||
|
||||
const TRACE_TYPES = Object.freeze({
|
||||
ACCESSIBILITY: 'AccessibilityTrace',
|
||||
WINDOW_MANAGER: 'WindowManagerTrace',
|
||||
SURFACE_FLINGER: 'SurfaceFlingerTrace',
|
||||
SCREEN_RECORDING: 'ScreenRecording',
|
||||
@@ -141,9 +159,17 @@ const TRACE_TYPES = Object.freeze({
|
||||
IME_CLIENTS: 'ImeTrace Clients',
|
||||
IME_SERVICE: 'ImeTrace InputMethodService',
|
||||
IME_MANAGERSERVICE: 'ImeTrace InputMethodManagerService',
|
||||
TAG: 'TagTrace',
|
||||
ERROR: 'ErrorTrace',
|
||||
});
|
||||
|
||||
const TRACE_INFO = {
|
||||
[TRACE_TYPES.ACCESSIBILITY]: {
|
||||
name: 'Accessibility',
|
||||
icon: ACCESSIBILITY_ICON,
|
||||
files: [oneOf(FILE_TYPES.ACCESSIBILITY_TRACE)],
|
||||
constructor: AccessibilityTrace,
|
||||
},
|
||||
[TRACE_TYPES.WINDOW_MANAGER]: {
|
||||
name: 'WindowManager',
|
||||
icon: WINDOW_MANAGER_ICON,
|
||||
@@ -212,6 +238,18 @@ const TRACE_INFO = {
|
||||
files: [oneOf(FILE_TYPES.IME_TRACE_MANAGERSERVICE)],
|
||||
constructor: ImeTraceManagerService,
|
||||
},
|
||||
[TRACE_TYPES.TAG]: {
|
||||
name: 'Tag',
|
||||
icon: TAG_ICON,
|
||||
files: [oneOf(FILE_TYPES.TAG_TRACE)],
|
||||
constructor: TagTrace,
|
||||
},
|
||||
[TRACE_TYPES.ERROR]: {
|
||||
name: 'Error',
|
||||
icon: TRACE_ERROR_ICON,
|
||||
files: [oneOf(FILE_TYPES.ERROR_TRACE)],
|
||||
constructor: ErrorTrace,
|
||||
},
|
||||
};
|
||||
|
||||
const DUMP_TYPES = Object.freeze({
|
||||
@@ -253,6 +291,8 @@ export const TRACE_ICONS = {
|
||||
[TRACE_TYPES.IME_CLIENTS]: IME_ICON,
|
||||
[TRACE_TYPES.IME_SERVICE]: IME_ICON,
|
||||
[TRACE_TYPES.IME_MANAGERSERVICE]: IME_ICON,
|
||||
[TRACE_TYPES.TAG_TRACE]: TAG_ICON,
|
||||
[TRACE_TYPES.ERROR_TRACE]: TRACE_ERROR_ICON,
|
||||
|
||||
[DUMP_TYPES.WINDOW_MANAGER]: WINDOW_MANAGER_ICON,
|
||||
[DUMP_TYPES.SURFACE_FLINGER]: SURFACE_FLINGER_ICON,
|
||||
@@ -261,12 +301,22 @@ export const TRACE_ICONS = {
|
||||
|
||||
// TODO: Rename name to defaultName
|
||||
const FILE_DECODERS = {
|
||||
[FILE_TYPES.ACCESSIBILITY_TRACE]: {
|
||||
name: 'Accessibility trace',
|
||||
decoder: protoDecoder,
|
||||
decoderParams: {
|
||||
type: FILE_TYPES.ACCESSIBILITY_TRACE,
|
||||
objTypeProto: AccessibilityTraceMessage,
|
||||
transform: transform_accessibility_trace,
|
||||
timeline: true,
|
||||
},
|
||||
},
|
||||
[FILE_TYPES.WINDOW_MANAGER_TRACE]: {
|
||||
name: 'WindowManager trace',
|
||||
decoder: protoDecoder,
|
||||
decoderParams: {
|
||||
type: FILE_TYPES.WINDOW_MANAGER_TRACE,
|
||||
protoType: WmTraceMessage,
|
||||
objTypeProto: WmTraceMessage,
|
||||
transform: WindowManagerTrace.fromProto,
|
||||
timeline: true,
|
||||
},
|
||||
@@ -277,8 +327,8 @@ const FILE_DECODERS = {
|
||||
decoderParams: {
|
||||
type: FILE_TYPES.SURFACE_FLINGER_TRACE,
|
||||
mime: 'application/octet-stream',
|
||||
protoType: SfTraceMessage,
|
||||
transform: transformLayersTrace,
|
||||
objTypeProto: SfTraceMessage,
|
||||
transform: SurfaceFlingerTrace.fromProto,
|
||||
timeline: true,
|
||||
},
|
||||
},
|
||||
@@ -288,7 +338,7 @@ const FILE_DECODERS = {
|
||||
decoderParams: {
|
||||
type: FILE_TYPES.WAYLAND_TRACE,
|
||||
mime: 'application/octet-stream',
|
||||
protoType: WaylandTraceMessage,
|
||||
objTypeProto: WaylandTraceMessage,
|
||||
transform: transform_wayland_trace,
|
||||
timeline: true,
|
||||
},
|
||||
@@ -299,9 +349,9 @@ const FILE_DECODERS = {
|
||||
decoderParams: {
|
||||
type: FILE_TYPES.SURFACE_FLINGER_DUMP,
|
||||
mime: 'application/octet-stream',
|
||||
protoType: SfDumpMessage,
|
||||
transform: (decoded) => transformLayers(true /* includesCompositionState*/, decoded),
|
||||
timeline: false,
|
||||
objTypeProto: [SfDumpMessage, SfTraceMessage],
|
||||
transform: [SurfaceFlingerDump.fromProto, SurfaceFlingerTrace.fromProto],
|
||||
timeline: true,
|
||||
},
|
||||
},
|
||||
[FILE_TYPES.WINDOW_MANAGER_DUMP]: {
|
||||
@@ -310,9 +360,9 @@ const FILE_DECODERS = {
|
||||
decoderParams: {
|
||||
type: FILE_TYPES.WINDOW_MANAGER_DUMP,
|
||||
mime: 'application/octet-stream',
|
||||
protoType: WmDumpMessage,
|
||||
objTypeProto: WmDumpMessage,
|
||||
transform: WindowManagerDump.fromProto,
|
||||
timeline: false,
|
||||
timeline: true,
|
||||
},
|
||||
},
|
||||
[FILE_TYPES.WAYLAND_DUMP]: {
|
||||
@@ -321,9 +371,9 @@ const FILE_DECODERS = {
|
||||
decoderParams: {
|
||||
type: FILE_TYPES.WAYLAND_DUMP,
|
||||
mime: 'application/octet-stream',
|
||||
protoType: WaylandDumpMessage,
|
||||
objTypeProto: WaylandDumpMessage,
|
||||
transform: transform_wl_outputstate,
|
||||
timeline: false,
|
||||
timeline: true,
|
||||
},
|
||||
},
|
||||
[FILE_TYPES.SCREEN_RECORDING]: {
|
||||
@@ -341,7 +391,7 @@ const FILE_DECODERS = {
|
||||
decoderParams: {
|
||||
type: FILE_TYPES.TRANSACTIONS_TRACE,
|
||||
mime: 'application/octet-stream',
|
||||
protoType: SfTransactionTraceMessage,
|
||||
objTypeProto: SfTransactionTraceMessage,
|
||||
transform: transform_transaction_trace,
|
||||
timeline: true,
|
||||
},
|
||||
@@ -352,7 +402,7 @@ const FILE_DECODERS = {
|
||||
decoderParams: {
|
||||
type: FILE_TYPES.PROTO_LOG,
|
||||
mime: 'application/octet-stream',
|
||||
protoType: ProtoLogMessage,
|
||||
objTypeProto: ProtoLogMessage,
|
||||
transform: transformProtolog,
|
||||
timeline: true,
|
||||
},
|
||||
@@ -363,7 +413,7 @@ const FILE_DECODERS = {
|
||||
decoderParams: {
|
||||
type: FILE_TYPES.SYSTEM_UI,
|
||||
mime: 'application/octet-stream',
|
||||
protoType: SystemUiTraceMessage,
|
||||
objTypeProto: SystemUiTraceMessage,
|
||||
transform: transform_sysui_trace,
|
||||
timeline: true,
|
||||
},
|
||||
@@ -374,7 +424,7 @@ const FILE_DECODERS = {
|
||||
decoderParams: {
|
||||
type: FILE_TYPES.LAUNCHER,
|
||||
mime: 'application/octet-stream',
|
||||
protoType: LauncherTraceMessage,
|
||||
objTypeProto: LauncherTraceMessage,
|
||||
transform: transform_launcher_trace,
|
||||
timeline: true,
|
||||
},
|
||||
@@ -385,7 +435,7 @@ const FILE_DECODERS = {
|
||||
decoderParams: {
|
||||
type: FILE_TYPES.IME_TRACE_CLIENTS,
|
||||
mime: 'application/octet-stream',
|
||||
protoType: InputMethodClientsTraceMessage,
|
||||
objTypeProto: InputMethodClientsTraceMessage,
|
||||
transform: transform_ime_trace_clients,
|
||||
timeline: true,
|
||||
},
|
||||
@@ -396,7 +446,7 @@ const FILE_DECODERS = {
|
||||
decoderParams: {
|
||||
type: FILE_TYPES.IME_TRACE_SERVICE,
|
||||
mime: 'application/octet-stream',
|
||||
protoType: InputMethodServiceTraceMessage,
|
||||
objTypeProto: InputMethodServiceTraceMessage,
|
||||
transform: transform_ime_trace_service,
|
||||
timeline: true,
|
||||
},
|
||||
@@ -407,11 +457,31 @@ const FILE_DECODERS = {
|
||||
decoderParams: {
|
||||
type: FILE_TYPES.IME_TRACE_MANAGERSERVICE,
|
||||
mime: 'application/octet-stream',
|
||||
protoType: InputMethodManagerServiceTraceMessage,
|
||||
objTypeProto: InputMethodManagerServiceTraceMessage,
|
||||
transform: transform_ime_trace_managerservice,
|
||||
timeline: true,
|
||||
},
|
||||
},
|
||||
[FILE_TYPES.TAG_TRACE]: {
|
||||
name: 'Tag trace',
|
||||
decoder: protoDecoder,
|
||||
decoderParams: {
|
||||
type: FILE_TYPES.TAG_TRACE,
|
||||
objTypeProto: TagTraceMessage,
|
||||
transform: TagTrace.fromProto,
|
||||
timeline: true,
|
||||
},
|
||||
},
|
||||
[FILE_TYPES.ERROR_TRACE]: {
|
||||
name: 'Error trace',
|
||||
decoder: protoDecoder,
|
||||
decoderParams: {
|
||||
type: FILE_TYPES.ERROR_TRACE,
|
||||
objTypeProto: ErrorTraceMessage,
|
||||
transform: ErrorTrace.fromProto,
|
||||
timeline: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
function lookup_type(protoPath, type) {
|
||||
@@ -443,11 +513,6 @@ function modifyProtoFields(protoObj, displayDefaults) {
|
||||
protoObj[fieldName] = fieldProperties.defaultValue;
|
||||
}
|
||||
|
||||
if (fieldProperties.type === 'TransformProto') {
|
||||
fill_transform_data(protoObj[fieldName]);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (fieldProperties.resolvedType && fieldProperties.resolvedType.valuesById) {
|
||||
protoObj[fieldName] = fieldProperties.resolvedType.valuesById[protoObj[fieldProperties.name]];
|
||||
continue;
|
||||
@@ -458,15 +523,45 @@ function modifyProtoFields(protoObj, displayDefaults) {
|
||||
}
|
||||
|
||||
function decodeAndTransformProto(buffer, params, displayDefaults) {
|
||||
const decoded = params.protoType.decode(buffer);
|
||||
modifyProtoFields(decoded, displayDefaults);
|
||||
const transformed = params.transform(decoded);
|
||||
|
||||
return transformed;
|
||||
var objTypesProto = [];
|
||||
var transforms = [];
|
||||
if (!Array.isArray(params.objTypeProto)) {
|
||||
objTypesProto = [params.objTypeProto];
|
||||
transforms = [params.transform];
|
||||
} else {
|
||||
objTypesProto = params.objTypeProto;
|
||||
transforms = params.transform;
|
||||
}
|
||||
// each trace or dump may have different processors, for example, until S, SF dumps
|
||||
// returne a list of layers and winscope built a [LayerTraceEntry] from them.
|
||||
// From S onwards, returns a LayerTrace object, iterating over multiple items allows
|
||||
// winscope to handle both the new and legacy formats
|
||||
// TODO Refactor the decode.js code into a set of decoders to clean up the code
|
||||
for (var x = 0; x < objTypesProto.length; x++) {
|
||||
const objType = objTypesProto[x];
|
||||
const transform = transforms[x];
|
||||
try {
|
||||
const decoded = objType.decode(buffer);
|
||||
modifyProtoFields(decoded, displayDefaults);
|
||||
const transformed = transform(decoded);
|
||||
return transformed;
|
||||
} catch (e) {
|
||||
// check next parser
|
||||
}
|
||||
}
|
||||
throw new UndetectableFileType('Unable to parse file');
|
||||
}
|
||||
|
||||
function protoDecoder(buffer, params, fileName, store) {
|
||||
const transformed = decodeAndTransformProto(buffer, params, store.displayDefaults);
|
||||
|
||||
// add tagGenerationTrace to dataFile for WM/SF traces so tags can be generated
|
||||
var tagGenerationTrace = null;
|
||||
if (params.type === FILE_TYPES.WINDOW_MANAGER_TRACE ||
|
||||
params.type === FILE_TYPES.SURFACE_FLINGER_TRACE) {
|
||||
tagGenerationTrace = transformed;
|
||||
}
|
||||
|
||||
let data;
|
||||
if (params.timeline) {
|
||||
data = transformed.entries ?? transformed.children;
|
||||
@@ -474,7 +569,15 @@ function protoDecoder(buffer, params, fileName, store) {
|
||||
data = [transformed];
|
||||
}
|
||||
const blobUrl = URL.createObjectURL(new Blob([buffer], {type: params.mime}));
|
||||
return dataFile(fileName, data.map((x) => x.timestamp), data, blobUrl, params.type);
|
||||
|
||||
return dataFile(
|
||||
fileName,
|
||||
data.map((x) => x.timestamp),
|
||||
data,
|
||||
blobUrl,
|
||||
params.type,
|
||||
tagGenerationTrace
|
||||
);
|
||||
}
|
||||
|
||||
function videoDecoder(buffer, params, fileName, store) {
|
||||
@@ -483,7 +586,7 @@ function videoDecoder(buffer, params, fileName, store) {
|
||||
return dataFile(fileName, timeline, blobUrl, blobUrl, params.type);
|
||||
}
|
||||
|
||||
function dataFile(filename, timeline, data, blobUrl, type) {
|
||||
function dataFile(filename, timeline, data, blobUrl, type, tagGenerationTrace = null) {
|
||||
return {
|
||||
filename: filename,
|
||||
// Object is frozen for performance reasons
|
||||
@@ -491,6 +594,7 @@ function dataFile(filename, timeline, data, blobUrl, type) {
|
||||
timeline: Object.freeze(timeline),
|
||||
data: data,
|
||||
blobUrl: blobUrl,
|
||||
tagGenerationTrace: tagGenerationTrace,
|
||||
type: type,
|
||||
selectedIndex: 0,
|
||||
destroy() {
|
||||
@@ -524,6 +628,9 @@ function detectAndDecode(buffer, fileName, store) {
|
||||
if (arrayStartsWith(buffer, LAYER_TRACE_MAGIC_NUMBER)) {
|
||||
return decodedFile(FILE_TYPES.SURFACE_FLINGER_TRACE, buffer, fileName, store);
|
||||
}
|
||||
if (arrayStartsWith(buffer, ACCESSIBILITY_MAGIC_NUMBER)) {
|
||||
return decodedFile(FILE_TYPES.ACCESSIBILITY_TRACE, buffer, fileName, store);
|
||||
}
|
||||
if (arrayStartsWith(buffer, WINDOW_TRACE_MAGIC_NUMBER)) {
|
||||
return decodedFile(FILE_TYPES.WINDOW_MANAGER_TRACE, buffer, fileName, store);
|
||||
}
|
||||
@@ -551,6 +658,12 @@ function detectAndDecode(buffer, fileName, store) {
|
||||
if (arrayStartsWith(buffer, IMM_TRACE_MAGIC_NUMBER)) {
|
||||
return decodedFile(FILE_TYPES.IME_TRACE_MANAGERSERVICE, buffer, fileName, store);
|
||||
}
|
||||
if (arrayStartsWith(buffer, TAG_TRACE_MAGIC_NUMBER)) {
|
||||
return decodedFile(FILE_TYPES.TAG_TRACE, buffer, fileName, store);
|
||||
}
|
||||
if (arrayStartsWith(buffer, ERROR_TRACE_MAGIC_NUMBER)) {
|
||||
return decodedFile(FILE_TYPES.ERROR_TRACE, buffer, fileName, store);
|
||||
}
|
||||
|
||||
// TODO(b/169305853): Add magic number at beginning of file for better auto detection
|
||||
for (const [filetype, condition] of [
|
||||
@@ -582,4 +695,17 @@ function detectAndDecode(buffer, fileName, store) {
|
||||
*/
|
||||
class UndetectableFileType extends Error { }
|
||||
|
||||
export {detectAndDecode, decodeAndTransformProto, FILE_TYPES, TRACE_INFO, TRACE_TYPES, DUMP_TYPES, DUMP_INFO, FILE_DECODERS, FILE_ICONS, UndetectableFileType};
|
||||
export {
|
||||
dataFile,
|
||||
detectAndDecode,
|
||||
decodeAndTransformProto,
|
||||
TagTraceMessage,
|
||||
FILE_TYPES,
|
||||
TRACE_INFO,
|
||||
TRACE_TYPES,
|
||||
DUMP_TYPES,
|
||||
DUMP_INFO,
|
||||
FILE_DECODERS,
|
||||
FILE_ICONS,
|
||||
UndetectableFileType
|
||||
};
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
import { FILE_TYPES, DUMP_TYPES } from "@/decode.js";
|
||||
import DumpBase from "./DumpBase";
|
||||
import LayersTraceEntry from '../flickerlib/layers/LayerTraceEntry';
|
||||
import LayersTrace from '../flickerlib/LayersTrace';
|
||||
|
||||
export default class SurfaceFlinger extends DumpBase {
|
||||
sfDumpFile: any;
|
||||
@@ -30,4 +32,15 @@ export default class SurfaceFlinger extends DumpBase {
|
||||
get type() {
|
||||
return DUMP_TYPES.SURFACE_FLINGER;
|
||||
}
|
||||
}
|
||||
|
||||
static fromProto(proto: any): LayersTrace {
|
||||
const source = null;
|
||||
const entry = LayersTraceEntry.fromProto(
|
||||
/* protos */ proto.layers,
|
||||
/* displays */ proto.displays,
|
||||
/* timestamp */ 0,
|
||||
/* hwcBlob */ ""
|
||||
);
|
||||
return new LayersTrace([entry], source);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,7 +32,9 @@ export default class WindowManager extends DumpBase {
|
||||
return DUMP_TYPES.WINDOW_MANAGER;
|
||||
}
|
||||
|
||||
static fromProto(proto): WindowManagerTrace {
|
||||
return WindowManagerTrace.fromDump(proto);
|
||||
static fromProto(proto: any): WindowManagerTrace {
|
||||
const source = null;
|
||||
const state = WindowManagerTrace.fromDump(proto);
|
||||
return new WindowManagerTrace([state], source);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
33
tools/winscope/src/flickerlib/ErrorTrace.ts
Normal file
33
tools/winscope/src/flickerlib/ErrorTrace.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright 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.
|
||||
*/
|
||||
|
||||
import { ErrorTrace } from "./common"
|
||||
import ErrorState from "./errors/ErrorState"
|
||||
|
||||
ErrorTrace.fromProto = function (proto: any) {
|
||||
const states = [];
|
||||
for (const stateProto of proto.states) {
|
||||
const transformedState = ErrorState.fromProto(
|
||||
stateProto.errors,
|
||||
stateProto.timestamp);
|
||||
|
||||
states.push(transformedState);
|
||||
}
|
||||
const source = null;
|
||||
return new ErrorTrace(states, source);
|
||||
}
|
||||
|
||||
export default ErrorTrace;
|
||||
36
tools/winscope/src/flickerlib/LayersTrace.ts
Normal file
36
tools/winscope/src/flickerlib/LayersTrace.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright 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.
|
||||
*/
|
||||
|
||||
import { LayersTrace } from "./common"
|
||||
import LayerTraceEntry from './layers/LayerTraceEntry'
|
||||
|
||||
LayersTrace.fromProto = function (proto: any): LayersTrace {
|
||||
const entries = []
|
||||
for (const entryProto of proto.entry) {
|
||||
const transformedEntry = LayerTraceEntry.fromProto(
|
||||
/* protos */ entryProto.layers.layers,
|
||||
/* displays */ entryProto.displays,
|
||||
/* timestamp */ entryProto.elapsedRealtimeNanos,
|
||||
/* hwcBlob */ entryProto.hwcBlob);
|
||||
|
||||
entries.push(transformedEntry);
|
||||
}
|
||||
const source = null;
|
||||
const trace = new LayersTrace(entries, source);
|
||||
return trace;
|
||||
}
|
||||
|
||||
export default LayersTrace;
|
||||
@@ -14,48 +14,103 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {toBounds, toBuffer, toColor, toPoint, toRect,
|
||||
import {toSize, toBuffer, toColor, toPoint, toRect,
|
||||
toRectF, toRegion, toTransform} from './common';
|
||||
import intDefMapping from
|
||||
'../../../../../prebuilts/misc/common/winscope/intDefMapping.json';
|
||||
import config from '../config/Configuration.json'
|
||||
|
||||
function readIntdefMap(): Map<string, string> {
|
||||
const map = new Map<string, string>();
|
||||
const keys = Object.keys(config.intDefColumn);
|
||||
|
||||
keys.forEach(key => {
|
||||
const value = config.intDefColumn[key];
|
||||
map.set(key, value);
|
||||
});
|
||||
|
||||
return map;
|
||||
}
|
||||
export default class ObjectFormatter {
|
||||
private static INVALID_ELEMENT_PROPERTIES = ['length', 'name', 'prototype', 'children',
|
||||
'childrenWindows', 'ref', 'root', 'layers', 'resolvedChildren']
|
||||
static displayDefaults: boolean = false
|
||||
private static INVALID_ELEMENT_PROPERTIES = config.invalidProperties;
|
||||
|
||||
private static FLICKER_INTDEF_MAP = new Map([
|
||||
[`WindowLayoutParams.type`, `android.view.WindowManager.LayoutParams.WindowType`],
|
||||
[`WindowLayoutParams.flags`, `android.view.WindowManager.LayoutParams.Flags`],
|
||||
[`WindowLayoutParams.privateFlags`, `android.view.WindowManager.LayoutParams.PrivateFlags`],
|
||||
[`WindowLayoutParams.gravity`, `android.view.Gravity.GravityFlags`],
|
||||
[`WindowLayoutParams.softInputMode`, `android.view.WindowManager.LayoutParams.WindowType`],
|
||||
[`WindowLayoutParams.systemUiVisibilityFlags`, `android.view.WindowManager.LayoutParams.SystemUiVisibilityFlags`],
|
||||
[`WindowLayoutParams.subtreeSystemUiVisibilityFlags`, `android.view.WindowManager.LayoutParams.SystemUiVisibilityFlags`],
|
||||
[`WindowLayoutParams.behavior`, `android.view.WindowInsetsController.Behavior`],
|
||||
[`WindowLayoutParams.fitInsetsSides`, `android.view.WindowInsets.Side.InsetsSide`],
|
||||
private static FLICKER_INTDEF_MAP = readIntdefMap();
|
||||
|
||||
[`Configuration.windowingMode`, `android.app.WindowConfiguration.WindowingMode`],
|
||||
[`WindowConfiguration.windowingMode`, `android.app.WindowConfiguration.WindowingMode`],
|
||||
[`Configuration.orientation`, `android.content.pm.ActivityInfo.ScreenOrientation`],
|
||||
[`WindowConfiguration.orientation`, `android.content.pm.ActivityInfo.ScreenOrientation`],
|
||||
[`WindowState.orientation`, `android.content.pm.ActivityInfo.ScreenOrientation`],
|
||||
])
|
||||
static cloneObject(entry: any): any {
|
||||
let obj: any = {}
|
||||
const properties = ObjectFormatter.getProperties(entry);
|
||||
properties.forEach(prop => obj[prop] = entry[prop]);
|
||||
return obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the true properties of an entry excluding functions, kotlin gernerated
|
||||
* variables, explicitly excluded properties, and flicker objects already in
|
||||
* the hierarchy that shouldn't be traversed when formatting the entry
|
||||
* @param entry The entry for which we want to get the properties for
|
||||
* @return The "true" properties of the entry as described above
|
||||
*/
|
||||
static getProperties(entry: any): string[] {
|
||||
var props = [];
|
||||
let obj = entry;
|
||||
|
||||
do {
|
||||
const properties = Object.getOwnPropertyNames(obj).filter(it => {
|
||||
// filter out functions
|
||||
if (typeof(entry[it]) === 'function') return false;
|
||||
// internal propertires from kotlinJs
|
||||
if (it.includes(`$`)) return false;
|
||||
// private kotlin variables from kotlin
|
||||
if (it.startsWith(`_`)) return false;
|
||||
// some predefined properties used only internally (e.g., children, ref, diff)
|
||||
if (this.INVALID_ELEMENT_PROPERTIES.includes(it)) return false;
|
||||
|
||||
const value = entry[it];
|
||||
// only non-empty arrays of non-flicker objects (otherwise they are in hierarchy)
|
||||
if (Array.isArray(value) && value.length > 0) return !value[0].stableId;
|
||||
// non-flicker object
|
||||
return !(value?.stableId);
|
||||
});
|
||||
properties.forEach(function (prop) {
|
||||
if (typeof(entry[prop]) !== 'function' && props.indexOf(prop) === -1) {
|
||||
props.push(prop);
|
||||
}
|
||||
});
|
||||
} while (obj = Object.getPrototypeOf(obj));
|
||||
|
||||
return props;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a Winscope entry to be displayed in the UI
|
||||
* Accounts for different user display settings (e.g. hiding empty/default values)
|
||||
* @param obj The raw object to format
|
||||
* @return The formatted object
|
||||
*/
|
||||
static format(obj: any): {} {
|
||||
const entries = Object.entries(obj)
|
||||
.filter(it => !it[0].includes(`$`))
|
||||
.filter(it => !this.INVALID_ELEMENT_PROPERTIES.includes(it[0]))
|
||||
const sortedEntries = entries.sort()
|
||||
const properties = this.getProperties(obj);
|
||||
const sortedProperties = properties.sort()
|
||||
|
||||
const result: any = {}
|
||||
sortedEntries.forEach(entry => {
|
||||
const key = entry[0]
|
||||
const value: any = entry[1]
|
||||
sortedProperties.forEach(entry => {
|
||||
const key = entry;
|
||||
const value: any = obj[key];
|
||||
|
||||
if (value) {
|
||||
if (value === null || value === undefined) {
|
||||
if (this.displayDefaults) {
|
||||
result[key] = value
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if (value || this.displayDefaults) {
|
||||
// flicker obj
|
||||
if (value.prettyPrint) {
|
||||
result[key] = value.prettyPrint()
|
||||
const isEmpty = value.isEmpty === true;
|
||||
if (!isEmpty || this.displayDefaults) {
|
||||
result[key] = value.prettyPrint()
|
||||
}
|
||||
} else {
|
||||
// converted proto to flicker
|
||||
const translatedObject = this.translateObject(value)
|
||||
@@ -63,7 +118,11 @@ export default class ObjectFormatter {
|
||||
result[key] = translatedObject.prettyPrint()
|
||||
// objects - recursive call
|
||||
} else if (value && typeof(value) == `object`) {
|
||||
result[key] = this.format(value)
|
||||
const childObj = this.format(value) as any
|
||||
const isEmpty = Object.entries(childObj).length == 0 || childObj.isEmpty
|
||||
if (!isEmpty || this.displayDefaults) {
|
||||
result[key] = childObj
|
||||
}
|
||||
} else {
|
||||
// values
|
||||
result[key] = this.translateIntDef(obj, key, value)
|
||||
@@ -73,7 +132,8 @@ export default class ObjectFormatter {
|
||||
}
|
||||
})
|
||||
|
||||
return Object.freeze(result)
|
||||
// return Object.freeze(result)
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -86,7 +146,7 @@ export default class ObjectFormatter {
|
||||
private static translateObject(obj) {
|
||||
const type = obj?.$type?.name
|
||||
switch(type) {
|
||||
case `SizeProto`: return toBounds(obj)
|
||||
case `SizeProto`: return toSize(obj)
|
||||
case `ActiveBufferProto`: return toBuffer(obj)
|
||||
case `ColorProto`: return toColor(obj)
|
||||
case `PointProto`: return toPoint(obj)
|
||||
@@ -206,4 +266,4 @@ export default class ObjectFormatter {
|
||||
|
||||
return flags.join(' | ');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
33
tools/winscope/src/flickerlib/TagTrace.ts
Normal file
33
tools/winscope/src/flickerlib/TagTrace.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright 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.
|
||||
*/
|
||||
|
||||
import { TagTrace } from "./common"
|
||||
import TagState from "./tags/TagState"
|
||||
|
||||
TagTrace.fromProto = function (proto: any): TagTrace {
|
||||
const states = [];
|
||||
for (const stateProto of proto.states) {
|
||||
const transformedState = TagState.fromProto(
|
||||
stateProto.timestamp,
|
||||
stateProto.tags);
|
||||
|
||||
states.push(transformedState);
|
||||
}
|
||||
const source = null;
|
||||
return new TagTrace(states, source);
|
||||
}
|
||||
|
||||
export default TagTrace;
|
||||
@@ -14,9 +14,6 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { asRawTreeViewObject } from '../utils/diff.js'
|
||||
import { getPropertiesForDisplay } from './mixin'
|
||||
|
||||
import {
|
||||
KeyguardControllerState,
|
||||
RootWindowContainer,
|
||||
@@ -26,17 +23,20 @@ import {
|
||||
|
||||
import WindowContainer from "./windows/WindowContainer"
|
||||
|
||||
WindowManagerState.fromProto = function ({proto, timestamp = 0, where = ""}): WindowManagerState {
|
||||
var inputMethodWIndowAppToken = ""
|
||||
WindowManagerState.fromProto = function (proto: any, timestamp: number = 0, where: string = ""): WindowManagerState {
|
||||
var inputMethodWIndowAppToken = "";
|
||||
if (proto.inputMethodWindow != null) {
|
||||
proto.inputMethodWindow.hashCode.toString(16)
|
||||
}
|
||||
const rootWindowContainer = newRootWindowContainer(proto.rootWindowContainer)
|
||||
const keyguardControllerState = newKeyguardControllerState(
|
||||
proto.rootWindowContainer.keyguardController)
|
||||
};
|
||||
|
||||
const rootWindowContainer = createRootWindowContainer(proto.rootWindowContainer);
|
||||
const keyguardControllerState = createKeyguardControllerState(
|
||||
proto.rootWindowContainer.keyguardController);
|
||||
const policy = createWindowManagerPolicy(proto.policy);
|
||||
|
||||
const entry = new WindowManagerState(
|
||||
where,
|
||||
newWindowManagerPolicy(proto.policy),
|
||||
policy,
|
||||
proto.focusedApp,
|
||||
proto.focusedDisplayId,
|
||||
proto.focusedWindow?.title ?? "",
|
||||
@@ -46,27 +46,31 @@ WindowManagerState.fromProto = function ({proto, timestamp = 0, where = ""}): Wi
|
||||
proto.rootWindowContainer.pendingActivities.map(it => it.title),
|
||||
rootWindowContainer,
|
||||
keyguardControllerState,
|
||||
timestamp = timestamp
|
||||
)
|
||||
|
||||
entry.kind = entry.constructor.name
|
||||
entry.rects = entry.windowStates.reverse().map(it => it.rect)
|
||||
if (!entry.isComplete()) {
|
||||
entry.isIncompleteReason = entry.getIsIncompleteReason()
|
||||
}
|
||||
entry.obj = getPropertiesForDisplay(proto, entry)
|
||||
entry.shortName = entry.name
|
||||
entry.chips = []
|
||||
entry.visible = true
|
||||
entry.rawTreeViewObject = asRawTreeViewObject(entry)
|
||||
/*timestamp */ `${timestamp}`
|
||||
);
|
||||
|
||||
addAttributes(entry, proto);
|
||||
console.warn("Created ", entry.kind, " stableId=", entry.stableId)
|
||||
return entry
|
||||
}
|
||||
|
||||
function newWindowManagerPolicy(proto): WindowManagerPolicy {
|
||||
function addAttributes(entry: WindowManagerState, proto: any) {
|
||||
entry.kind = entry.constructor.name;
|
||||
// There no JVM/JS translation for Longs yet
|
||||
entry.timestampMs = entry.timestamp.toString();
|
||||
entry.rects = entry.windowStates.reverse().map(it => it.rect);
|
||||
if (!entry.isComplete()) {
|
||||
entry.isIncompleteReason = entry.getIsIncompleteReason();
|
||||
}
|
||||
entry.proto = proto;
|
||||
entry.shortName = entry.name;
|
||||
entry.chips = [];
|
||||
entry.isVisible = true;
|
||||
}
|
||||
|
||||
function createWindowManagerPolicy(proto: any): WindowManagerPolicy {
|
||||
return new WindowManagerPolicy(
|
||||
proto.focusedAppToken || "",
|
||||
proto.focusedAppToken ?? "",
|
||||
proto.forceStatusBar,
|
||||
proto.forceStatusBarFromKeyguard,
|
||||
proto.keyguardDrawComplete,
|
||||
@@ -79,36 +83,35 @@ function newWindowManagerPolicy(proto): WindowManagerPolicy {
|
||||
proto.rotationMode,
|
||||
proto.screenOnFully,
|
||||
proto.windowManagerDrawComplete
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function newRootWindowContainer(proto): RootWindowContainer {
|
||||
const children = proto.windowContainer.children.reverse()
|
||||
.filter(it => it != null)
|
||||
.map(it => WindowContainer.childrenFromProto(it, /* isActivityInTree */ false))
|
||||
function createRootWindowContainer(proto: any): RootWindowContainer {
|
||||
const windowContainer = WindowContainer.fromProto(
|
||||
{proto: proto.windowContainer, children: children})
|
||||
if (windowContainer == null) {
|
||||
throw "Window container should not be null: " + JSON.stringify(proto)
|
||||
}
|
||||
const entry = new RootWindowContainer(windowContainer)
|
||||
/* proto */ proto.windowContainer,
|
||||
/* childrenProto */ proto.windowContainer.children.reverse()
|
||||
);
|
||||
|
||||
return entry
|
||||
if (windowContainer == null) {
|
||||
throw new Error(`Window container should not be null.\n${JSON.stringify(proto)}`);
|
||||
}
|
||||
const entry = new RootWindowContainer(windowContainer);
|
||||
return entry;
|
||||
}
|
||||
|
||||
function newKeyguardControllerState(proto): KeyguardControllerState {
|
||||
const keyguardOccludedStates = {}
|
||||
function createKeyguardControllerState(proto: any): KeyguardControllerState {
|
||||
const keyguardOccludedStates = {};
|
||||
|
||||
if (proto) {
|
||||
proto.keyguardOccludedStates.forEach(it =>
|
||||
keyguardOccludedStates[it.displayId] = it.keyguardOccluded)
|
||||
keyguardOccludedStates[it.displayId] = it.keyguardOccluded);
|
||||
}
|
||||
|
||||
return new KeyguardControllerState(
|
||||
proto?.isAodShowing ?? false,
|
||||
proto?.isKeyguardShowing ?? false,
|
||||
keyguardOccludedStates
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export default WindowManagerState;
|
||||
@@ -17,23 +17,22 @@
|
||||
import { WindowManagerTrace } from "./common"
|
||||
import WindowManagerState from "./WindowManagerState"
|
||||
|
||||
WindowManagerTrace.fromProto = function (proto) {
|
||||
const entries = []
|
||||
WindowManagerTrace.fromProto = function (proto: any) {
|
||||
const entries = [];
|
||||
for (const entryProto of proto.entry) {
|
||||
const transformedEntry = WindowManagerState.fromProto({
|
||||
proto: entryProto.windowManagerService,
|
||||
timestamp: entryProto.elapsedRealtimeNanos,
|
||||
where: entryProto.where})
|
||||
const transformedEntry = WindowManagerState.fromProto(
|
||||
entryProto.windowManagerService,
|
||||
entryProto.elapsedRealtimeNanos,
|
||||
entryProto.where);
|
||||
|
||||
entries.push(transformedEntry)
|
||||
entries.push(transformedEntry);
|
||||
}
|
||||
const source = null
|
||||
const sourceChecksum = null
|
||||
return new WindowManagerTrace(entries, source, sourceChecksum)
|
||||
const source = null;
|
||||
return new WindowManagerTrace(entries, source);
|
||||
}
|
||||
|
||||
WindowManagerTrace.fromDump = function(proto): WindowManagerTrace {
|
||||
return WindowManagerState.fromProto({proto: proto})
|
||||
WindowManagerTrace.fromDump = function(proto: any): WindowManagerTrace {
|
||||
return WindowManagerState.fromProto(proto);
|
||||
}
|
||||
|
||||
export default WindowManagerTrace;
|
||||
|
||||
@@ -17,44 +17,60 @@
|
||||
// Imports all the compiled common Flicker library classes and exports them
|
||||
// as clean es6 modules rather than having them be commonjs modules
|
||||
|
||||
const WindowManagerTrace = require('flicker').com.android.server.wm.traces.common.
|
||||
windowmanager.WindowManagerTrace;
|
||||
const WindowManagerState = require('flicker').com.android.server.wm.traces.common.
|
||||
windowmanager.WindowManagerState;
|
||||
|
||||
// WM
|
||||
const WindowManagerTrace = require('flicker').com.android.server.wm.traces.
|
||||
common.windowmanager.WindowManagerTrace;
|
||||
const WindowManagerState = require('flicker').com.android.server.wm.traces.
|
||||
common.windowmanager.WindowManagerState;
|
||||
const Activity = require('flicker').com.android.server.wm.traces.common.
|
||||
windowmanager.windows.Activity;
|
||||
const ActivityTask = require('flicker').com.android.server.wm.traces.common.
|
||||
windowmanager.windows.ActivityTask;
|
||||
const Configuration = require('flicker').com.android.server.wm.traces.common.
|
||||
windowmanager.windows.Configuration;
|
||||
const ConfigurationContainer = require('flicker').com.android.server.wm.traces.common.
|
||||
windowmanager.windows.ConfigurationContainer;
|
||||
const ConfigurationContainer = require('flicker').com.android.server.wm.traces.
|
||||
common.windowmanager.windows.ConfigurationContainer;
|
||||
const DisplayArea = require('flicker').com.android.server.wm.traces.common.
|
||||
windowmanager.windows.DisplayArea;
|
||||
const DisplayContent = require('flicker').com.android.server.wm.traces.common.
|
||||
windowmanager.windows.DisplayContent;
|
||||
const KeyguardControllerState = require('flicker').com.android.server.wm.traces.common.
|
||||
windowmanager.windows.KeyguardControllerState;
|
||||
const RootWindowContainer = require('flicker').com.android.server.wm.traces.common.
|
||||
windowmanager.windows.RootWindowContainer;
|
||||
const WindowConfiguration = require('flicker').com.android.server.wm.traces.common.
|
||||
windowmanager.windows.WindowConfiguration;
|
||||
const KeyguardControllerState = require('flicker').com.android.server.wm.
|
||||
traces.common.windowmanager.windows.KeyguardControllerState;
|
||||
const RootWindowContainer = require('flicker').com.android.server.wm.traces.
|
||||
common.windowmanager.windows.RootWindowContainer;
|
||||
const Task = require('flicker').com.android.server.wm.traces.common.
|
||||
windowmanager.windows.Task;
|
||||
const TaskFragment = require('flicker').com.android.server.wm.traces.common.
|
||||
windowmanager.windows.TaskFragment;
|
||||
const WindowConfiguration = require('flicker').com.android.server.wm.traces.
|
||||
common.windowmanager.windows.WindowConfiguration;
|
||||
const WindowContainer = require('flicker').com.android.server.wm.traces.common.
|
||||
windowmanager.windows.WindowContainer;
|
||||
const WindowLayoutParams= require('flicker').com.android.server.wm.traces.common.
|
||||
windowmanager.windows.WindowLayoutParams;
|
||||
const WindowManagerPolicy = require('flicker').com.android.server.wm.traces.common.
|
||||
windowmanager.windows.WindowManagerPolicy;
|
||||
const WindowLayoutParams= require('flicker').com.android.server.wm.traces.
|
||||
common.windowmanager.windows.WindowLayoutParams;
|
||||
const WindowManagerPolicy = require('flicker').com.android.server.wm.traces.
|
||||
common.windowmanager.windows.WindowManagerPolicy;
|
||||
const WindowState = require('flicker').com.android.server.wm.traces.common.
|
||||
windowmanager.windows.WindowState;
|
||||
const WindowToken = require('flicker').com.android.server.wm.traces.common.
|
||||
windowmanager.windows.WindowToken;
|
||||
|
||||
const Matrix = require('flicker').com.android.server.wm.traces.common.layers.Transform.Matrix;
|
||||
const Transform = require('flicker').com.android.server.wm.traces.common.layers.Transform;
|
||||
// SF
|
||||
const Layer = require('flicker').com.android.server.wm.traces.common.
|
||||
layers.Layer;
|
||||
const LayerTraceEntry = require('flicker').com.android.server.wm.traces.common.
|
||||
layers.LayerTraceEntry;
|
||||
const LayerTraceEntryBuilder = require('flicker').com.android.server.wm.traces.
|
||||
common.layers.LayerTraceEntryBuilder;
|
||||
const LayersTrace = require('flicker').com.android.server.wm.traces.common.
|
||||
layers.LayersTrace;
|
||||
const Matrix = require('flicker').com.android.server.wm.traces.common.layers.
|
||||
Transform.Matrix;
|
||||
const Transform = require('flicker').com.android.server.wm.traces.common.
|
||||
layers.Transform;
|
||||
const Display = require('flicker').com.android.server.wm.traces.common.
|
||||
layers.Display;
|
||||
|
||||
const Bounds = require('flicker').com.android.server.wm.traces.common.Bounds;
|
||||
// Common
|
||||
const Size = require('flicker').com.android.server.wm.traces.common.Size;
|
||||
const Buffer = require('flicker').com.android.server.wm.traces.common.Buffer;
|
||||
const Color = require('flicker').com.android.server.wm.traces.common.Color;
|
||||
const Point = require('flicker').com.android.server.wm.traces.common.Point;
|
||||
@@ -62,102 +78,194 @@ const Rect = require('flicker').com.android.server.wm.traces.common.Rect;
|
||||
const RectF = require('flicker').com.android.server.wm.traces.common.RectF;
|
||||
const Region = require('flicker').com.android.server.wm.traces.common.Region;
|
||||
|
||||
function toBounds(proto) {
|
||||
if (proto == null) {
|
||||
return null
|
||||
}
|
||||
return new Bounds(proto.width ?? proto.w ?? 0, proto.height ?? proto.h ?? 0)
|
||||
//Tags
|
||||
const Tag = require('flicker').com.android.server.wm.traces.common.tags.Tag;
|
||||
const TagState = require('flicker').com.android.server.wm.traces.common.tags.TagState;
|
||||
const TagTrace = require('flicker').com.android.server.wm.traces.common.tags.TagTrace;
|
||||
|
||||
//Errors
|
||||
const Error = require('flicker').com.android.server.wm.traces.common.errors.Error;
|
||||
const ErrorState = require('flicker').com.android.server.wm.traces.common.errors.ErrorState;
|
||||
const ErrorTrace = require('flicker').com.android.server.wm.traces.common.errors.ErrorTrace;
|
||||
|
||||
// Service
|
||||
const TaggingEngine = require('flicker').com.android.server.wm.traces.common.service.TaggingEngine;
|
||||
|
||||
const EMPTY_BUFFER = new Buffer(0, 0, 0, 0);
|
||||
const EMPTY_COLOR = new Color(-1, -1, -1, 0);
|
||||
const EMPTY_RECT = new Rect(0, 0, 0, 0);
|
||||
const EMPTY_RECTF = new RectF(0, 0, 0, 0);
|
||||
const EMPTY_POINT = new Point(0, 0);
|
||||
const EMPTY_MATRIX = new Matrix(0, 0, 0, 0, 0, 0);
|
||||
const EMPTY_TRANSFORM = new Transform(0, EMPTY_MATRIX);
|
||||
|
||||
function toSize(proto) {
|
||||
if (proto == null) {
|
||||
return EMPTY_BOUNDS;
|
||||
}
|
||||
const width = proto.width ?? proto.w ?? 0;
|
||||
const height = proto.height ?? proto.h ?? 0;
|
||||
if (width || height) {
|
||||
return new Size(width, height);
|
||||
}
|
||||
return EMPTY_BOUNDS;
|
||||
}
|
||||
|
||||
function toBuffer(proto) {
|
||||
if (proto == null) {
|
||||
return null
|
||||
}
|
||||
return new Buffer(proto.width ?? 0, proto.height ?? 0, proto.stride ?? 0, proto.format ?? 0)
|
||||
const width = proto?.width ?? 0;
|
||||
const height = proto?.height ?? 0;
|
||||
const stride = proto?.stride ?? 0;
|
||||
const format = proto?.format ?? 0;
|
||||
|
||||
if (width || height || stride || format) {
|
||||
return new Buffer(width, height, stride, format);
|
||||
}
|
||||
return EMPTY_BUFFER;
|
||||
}
|
||||
|
||||
function toColor(proto) {
|
||||
if (proto == null) {
|
||||
return null
|
||||
}
|
||||
return new Color(proto.r ?? 0, proto.g ?? 0, proto.b ?? 0, proto.a ?? 0)
|
||||
if (proto == null) {
|
||||
return EMPTY_COLOR;
|
||||
}
|
||||
const r = proto.r ?? 0;
|
||||
const g = proto.g ?? 0;
|
||||
const b = proto.b ?? 0;
|
||||
const a = proto.a ?? 0;
|
||||
if (r || g || b || a) {
|
||||
return new Color(r, g, b, a);
|
||||
}
|
||||
return EMPTY_COLOR;
|
||||
}
|
||||
|
||||
function toPoint(proto) {
|
||||
if (proto == null) {
|
||||
return null
|
||||
}
|
||||
return new Point(proto.x ?? 0, proto.y ?? 0)
|
||||
if (proto == null) {
|
||||
return null;
|
||||
}
|
||||
const x = proto.x ?? 0;
|
||||
const y = proto.y ?? 0;
|
||||
if (x || y) {
|
||||
return new Point(x, y);
|
||||
}
|
||||
return EMPTY_POINT;
|
||||
}
|
||||
|
||||
function toRect(proto) {
|
||||
if (proto == null) {
|
||||
return null
|
||||
}
|
||||
return new Rect(proto.left ?? 0, proto.top ?? 0, proto.right ?? 0, proto.bottom ?? 0)
|
||||
if (proto == null) {
|
||||
return EMPTY_RECT;
|
||||
}
|
||||
|
||||
const left = proto?.left ?? 0;
|
||||
const top = proto?.top ?? 0;
|
||||
const right = proto?.right ?? 0;
|
||||
const bottom = proto?.bottom ?? 0;
|
||||
if (left || top || right || bottom) {
|
||||
return new Rect(left, top, right, bottom);
|
||||
}
|
||||
return EMPTY_RECT;
|
||||
}
|
||||
|
||||
function toRectF(proto) {
|
||||
if (proto == null) {
|
||||
return null
|
||||
}
|
||||
return new RectF(proto.left ?? 0, proto.top ?? 0, proto.right ?? 0, proto.bottom ?? 0)
|
||||
if (proto == null) {
|
||||
return EMPTY_RECTF;
|
||||
}
|
||||
|
||||
const left = proto?.left ?? 0;
|
||||
const top = proto?.top ?? 0;
|
||||
const right = proto?.right ?? 0;
|
||||
const bottom = proto?.bottom ?? 0;
|
||||
if (left || top || right || bottom) {
|
||||
return new RectF(left, top, right, bottom);
|
||||
}
|
||||
return EMPTY_RECTF;
|
||||
}
|
||||
|
||||
function toRegion(proto) {
|
||||
if (proto == null) {
|
||||
return null
|
||||
}
|
||||
if (proto == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let rects = []
|
||||
for (let rectNr in proto.rect) {
|
||||
const rect = proto.rect[rectNr]
|
||||
const parsedRect = toRect(rect)
|
||||
rects.push(parsedRect)
|
||||
}
|
||||
const rects = [];
|
||||
for (let x = 0; x < proto.rect.length; x++) {
|
||||
const rect = proto.rect[x];
|
||||
const parsedRect = toRect(rect);
|
||||
rects.push(parsedRect);
|
||||
}
|
||||
|
||||
return new Region(rects)
|
||||
return new Region(rects);
|
||||
}
|
||||
|
||||
function toTransform(proto) {
|
||||
if (proto == null) {
|
||||
return null
|
||||
}
|
||||
const matrix = new Matrix(proto.dsdx ?? 0, proto.dtdx ?? 0,
|
||||
proto.tx ?? 0, proto.dsdy ?? 0, proto.dtdy ?? 0, proto.ty ?? 0)
|
||||
return new Transform(proto.type ?? 0, matrix)
|
||||
if (proto == null) {
|
||||
return EMPTY_TRANSFORM;
|
||||
}
|
||||
const dsdx = proto.dsdx ?? 0;
|
||||
const dtdx = proto.dtdx ?? 0;
|
||||
const tx = proto.tx ?? 0;
|
||||
const dsdy = proto.dsdy ?? 0;
|
||||
const dtdy = proto.dtdy ?? 0;
|
||||
const ty = proto.ty ?? 0;
|
||||
|
||||
if (dsdx || dtdx || tx || dsdy || dtdy || ty) {
|
||||
const matrix = new Matrix(dsdx, dtdx, tx, dsdy, dtdy, ty);
|
||||
return new Transform(proto.type ?? 0, matrix);
|
||||
}
|
||||
|
||||
if (proto.type) {
|
||||
return new Transform(proto.type ?? 0, EMPTY_MATRIX);
|
||||
}
|
||||
return EMPTY_TRANSFORM;
|
||||
}
|
||||
|
||||
export {
|
||||
Activity,
|
||||
ActivityTask,
|
||||
Configuration,
|
||||
ConfigurationContainer,
|
||||
DisplayArea,
|
||||
DisplayContent,
|
||||
KeyguardControllerState,
|
||||
RootWindowContainer,
|
||||
WindowConfiguration,
|
||||
WindowContainer,
|
||||
WindowState,
|
||||
WindowToken,
|
||||
WindowLayoutParams,
|
||||
WindowManagerPolicy,
|
||||
WindowManagerTrace,
|
||||
WindowManagerState,
|
||||
Bounds,
|
||||
Buffer,
|
||||
Color,
|
||||
Point,
|
||||
Rect,
|
||||
RectF,
|
||||
Region,
|
||||
toBounds,
|
||||
toBuffer,
|
||||
toColor,
|
||||
toPoint,
|
||||
toRect,
|
||||
toRectF,
|
||||
toRegion,
|
||||
toTransform
|
||||
Activity,
|
||||
Configuration,
|
||||
ConfigurationContainer,
|
||||
DisplayArea,
|
||||
DisplayContent,
|
||||
KeyguardControllerState,
|
||||
RootWindowContainer,
|
||||
Task,
|
||||
TaskFragment,
|
||||
WindowConfiguration,
|
||||
WindowContainer,
|
||||
WindowState,
|
||||
WindowToken,
|
||||
WindowLayoutParams,
|
||||
WindowManagerPolicy,
|
||||
WindowManagerTrace,
|
||||
WindowManagerState,
|
||||
// SF
|
||||
Layer,
|
||||
LayerTraceEntry,
|
||||
LayerTraceEntryBuilder,
|
||||
LayersTrace,
|
||||
Transform,
|
||||
Matrix,
|
||||
Display,
|
||||
// Tags
|
||||
Tag,
|
||||
TagState,
|
||||
TagTrace,
|
||||
// Errors
|
||||
Error,
|
||||
ErrorState,
|
||||
ErrorTrace,
|
||||
// Common
|
||||
Size,
|
||||
Buffer,
|
||||
Color,
|
||||
Point,
|
||||
Rect,
|
||||
RectF,
|
||||
Region,
|
||||
// Service
|
||||
TaggingEngine,
|
||||
toSize,
|
||||
toBuffer,
|
||||
toColor,
|
||||
toPoint,
|
||||
toRect,
|
||||
toRectF,
|
||||
toRegion,
|
||||
toTransform,
|
||||
};
|
||||
|
||||
31
tools/winscope/src/flickerlib/errors/Error.ts
Normal file
31
tools/winscope/src/flickerlib/errors/Error.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright 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.
|
||||
*/
|
||||
|
||||
|
||||
import { Error } from "../common"
|
||||
|
||||
Error.fromProto = function (proto: any): Error {
|
||||
const error = new Error(
|
||||
proto.stacktrace,
|
||||
proto.message,
|
||||
proto.layerId,
|
||||
proto.windowToken,
|
||||
proto.taskId
|
||||
);
|
||||
return error;
|
||||
}
|
||||
|
||||
export default Error;
|
||||
26
tools/winscope/src/flickerlib/errors/ErrorState.ts
Normal file
26
tools/winscope/src/flickerlib/errors/ErrorState.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright 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.
|
||||
*/
|
||||
|
||||
import { ErrorState } from "../common";
|
||||
import Error from './Error';
|
||||
|
||||
ErrorState.fromProto = function (protos: any[], timestamp: number): ErrorState {
|
||||
const errors = protos.map(it => Error.fromProto(it));
|
||||
const state = new ErrorState(errors, `${timestamp}`);
|
||||
return state;
|
||||
}
|
||||
|
||||
export default ErrorState;
|
||||
@@ -14,12 +14,15 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import LayersTrace from './LayersTrace';
|
||||
import WindowManagerState from './WindowManagerState';
|
||||
import WindowManagerTrace from './WindowManagerTrace';
|
||||
|
||||
import ObjectFormatter from './ObjectFormatter';
|
||||
import TagTrace from './TagTrace';
|
||||
import ErrorTrace from './ErrorTrace';
|
||||
/**
|
||||
* Entry point into the flickerlib for Winscope.
|
||||
* Expose everything we want Winscope to have access to here.
|
||||
*/
|
||||
export {WindowManagerState, WindowManagerTrace};
|
||||
export {ObjectFormatter, LayersTrace, WindowManagerState, WindowManagerTrace, TagTrace, ErrorTrace};
|
||||
|
||||
|
||||
95
tools/winscope/src/flickerlib/layers/Layer.ts
Normal file
95
tools/winscope/src/flickerlib/layers/Layer.ts
Normal file
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* Copyright 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.
|
||||
*/
|
||||
|
||||
|
||||
import { Layer, Rect, toBuffer, toColor, toRect, toRectF, toRegion } from "../common"
|
||||
import { shortenName } from '../mixin'
|
||||
import { RELATIVE_Z_CHIP, GPU_CHIP, HWC_CHIP } from '../treeview/Chips'
|
||||
import Transform from './Transform'
|
||||
|
||||
Layer.fromProto = function (proto: any): Layer {
|
||||
const visibleRegion = toRegion(proto.visibleRegion)
|
||||
const activeBuffer = toBuffer(proto.activeBuffer)
|
||||
const bounds = toRectF(proto.bounds)
|
||||
const color = toColor(proto.color)
|
||||
const screenBounds = toRectF(proto.screenBounds)
|
||||
const sourceBounds = toRectF(proto.sourceBounds)
|
||||
const transform = Transform.fromProto(proto.transform, proto.position)
|
||||
const bufferTransform = Transform.fromProto(proto.bufferTransform, /* position */ null)
|
||||
const hwcCrop = toRectF(proto.hwcCrop)
|
||||
const hwcFrame = toRect(proto.hwcFrame)
|
||||
let crop: Rect
|
||||
if (proto.crop) {
|
||||
crop = toRect(proto.crop)
|
||||
};
|
||||
|
||||
const entry = new Layer(
|
||||
proto.name ?? ``,
|
||||
proto.id,
|
||||
proto.parent,
|
||||
proto.z,
|
||||
visibleRegion,
|
||||
activeBuffer,
|
||||
proto.flags,
|
||||
bounds,
|
||||
color,
|
||||
proto.isOpaque,
|
||||
proto.shadowRadius,
|
||||
proto.cornerRadius,
|
||||
proto.type ?? ``,
|
||||
screenBounds,
|
||||
transform,
|
||||
sourceBounds,
|
||||
proto.currFrame,
|
||||
proto.effectiveScalingMode,
|
||||
bufferTransform,
|
||||
proto.hwcCompositionType,
|
||||
hwcCrop,
|
||||
hwcFrame,
|
||||
proto.backgroundBlurRadius,
|
||||
crop,
|
||||
proto.isRelativeOf,
|
||||
proto.zOrderRelativeOf
|
||||
);
|
||||
|
||||
addAttributes(entry, proto);
|
||||
return entry
|
||||
}
|
||||
|
||||
function addAttributes(entry: Layer, proto: any) {
|
||||
entry.kind = `${entry.id}`;
|
||||
entry.shortName = shortenName(entry.name);
|
||||
entry.proto = proto;
|
||||
entry.rect = entry.bounds;
|
||||
entry.rect.transform = entry.transform;
|
||||
entry.rect.ref = entry;
|
||||
entry.rect.label = entry.name;
|
||||
entry.chips = [];
|
||||
updateChips(entry);
|
||||
}
|
||||
|
||||
function updateChips(entry) {
|
||||
if ((entry.zOrderRelativeOf || -1) !== -1) {
|
||||
entry.chips.push(RELATIVE_Z_CHIP);
|
||||
}
|
||||
if (entry.hwcCompositionType === 'CLIENT') {
|
||||
entry.chips.push(GPU_CHIP);
|
||||
} else if (entry.hwcCompositionType === 'DEVICE' || entry.hwcCompositionType === 'SOLID_COLOR') {
|
||||
entry.chips.push(HWC_CHIP);
|
||||
}
|
||||
}
|
||||
|
||||
export default Layer;
|
||||
79
tools/winscope/src/flickerlib/layers/LayerTraceEntry.ts
Normal file
79
tools/winscope/src/flickerlib/layers/LayerTraceEntry.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright 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.
|
||||
*/
|
||||
|
||||
import { Display, LayerTraceEntry, LayerTraceEntryBuilder, toRect, toSize, toTransform } from "../common"
|
||||
import Layer from './Layer'
|
||||
import { VISIBLE_CHIP, RELATIVE_Z_PARENT_CHIP, MISSING_LAYER } from '../treeview/Chips'
|
||||
|
||||
LayerTraceEntry.fromProto = function (protos: any[], displayProtos: any[],
|
||||
timestamp: number, hwcBlob: string, where: string = ''): LayerTraceEntry {
|
||||
const layers = protos.map(it => Layer.fromProto(it));
|
||||
const displays = (displayProtos || []).map(it => newDisplay(it));
|
||||
const builder = new LayerTraceEntryBuilder(timestamp, layers, displays, hwcBlob, where);
|
||||
const entry: LayerTraceEntry = builder.build();
|
||||
|
||||
updateChildren(entry);
|
||||
addAttributes(entry, protos);
|
||||
return entry;
|
||||
}
|
||||
|
||||
function addAttributes(entry: LayerTraceEntry, protos: any) {
|
||||
entry.kind = "entry"
|
||||
// There no JVM/JS translation for Longs yet
|
||||
entry.timestampMs = entry.timestamp.toString()
|
||||
entry.rects = entry.visibleLayers
|
||||
.sort((a, b) => (b.absoluteZ > a.absoluteZ) ? 1 : (a.absoluteZ == b.absoluteZ) ? 0 : -1)
|
||||
.map(it => it.rect);
|
||||
|
||||
// Avoid parsing the entry root because it is an array of layers
|
||||
// containing all trace information, this slows down the property tree.
|
||||
// Instead parse only key properties for debugging
|
||||
const entryIds = {}
|
||||
protos.forEach(it =>
|
||||
entryIds[it.id] = `\nparent=${it.parent}\ntype=${it.type}\nname=${it.name}`
|
||||
);
|
||||
entry.proto = entryIds;
|
||||
entry.shortName = entry.name;
|
||||
entry.chips = [];
|
||||
entry.isVisible = true;
|
||||
}
|
||||
|
||||
function updateChildren(entry: LayerTraceEntry) {
|
||||
entry.flattenedLayers.forEach(it => {
|
||||
if (it.isVisible) {
|
||||
it.chips.push(VISIBLE_CHIP);
|
||||
}
|
||||
if (it.zOrderRelativeOf) {
|
||||
it.chips.push(RELATIVE_Z_PARENT_CHIP);
|
||||
}
|
||||
if (it.isMissing) {
|
||||
it.chips.push(MISSING_LAYER);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function newDisplay(proto: any): Display {
|
||||
return new Display(
|
||||
proto.id,
|
||||
proto.name,
|
||||
proto.layerStack,
|
||||
toSize(proto.size),
|
||||
toRect(proto.layerStackSpaceRect),
|
||||
toTransform(proto.transform)
|
||||
)
|
||||
}
|
||||
|
||||
export default LayerTraceEntry;
|
||||
90
tools/winscope/src/flickerlib/layers/Transform.ts
Normal file
90
tools/winscope/src/flickerlib/layers/Transform.ts
Normal file
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* Copyright 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.
|
||||
*/
|
||||
|
||||
import { Transform, Matrix } from "../common"
|
||||
|
||||
Transform.fromProto = function (transformProto, positionProto): Transform {
|
||||
const entry = new Transform(
|
||||
transformProto?.type ?? 0,
|
||||
getMatrix(transformProto, positionProto))
|
||||
|
||||
return entry
|
||||
}
|
||||
|
||||
function getMatrix(transform, position): Matrix {
|
||||
const x = position?.x ?? 0
|
||||
const y = position?.y ?? 0
|
||||
|
||||
if (transform == null || isSimpleTransform(transform.type)) {
|
||||
return getDefaultTransform(transform?.type, x, y)
|
||||
}
|
||||
|
||||
return new Matrix(transform.dsdx, transform.dtdx, x, transform.dsdy, transform.dtdy, y)
|
||||
}
|
||||
|
||||
function getDefaultTransform(type, x, y): Matrix {
|
||||
// IDENTITY
|
||||
if (!type) {
|
||||
return new Matrix(1, 0, x, 0, 1, y)
|
||||
}
|
||||
|
||||
// ROT_270 = ROT_90|FLIP_H|FLIP_V
|
||||
if (isFlagSet(type, ROT_90_VAL | FLIP_V_VAL | FLIP_H_VAL)) {
|
||||
return new Matrix(0, -1, x, 1, 0, y)
|
||||
}
|
||||
|
||||
// ROT_180 = FLIP_H|FLIP_V
|
||||
if (isFlagSet(type, FLIP_V_VAL | FLIP_H_VAL)) {
|
||||
return new Matrix(-1, 0, x, 0, -1, y)
|
||||
}
|
||||
|
||||
// ROT_90
|
||||
if (isFlagSet(type, ROT_90_VAL)) {
|
||||
return new Matrix(0, 1, x, -1, 0, y)
|
||||
}
|
||||
|
||||
// IDENTITY
|
||||
if (isFlagClear(type, SCALE_VAL | ROTATE_VAL)) {
|
||||
return new Matrix(1, 0, x, 0, 1, y)
|
||||
}
|
||||
|
||||
throw new Error(`Unknown transform type ${type}`)
|
||||
}
|
||||
|
||||
export function isFlagSet(type, bits): Boolean {
|
||||
var type = type || 0;
|
||||
return (type & bits) === bits;
|
||||
}
|
||||
|
||||
export function isFlagClear(type, bits): Boolean {
|
||||
return (type & bits) === 0;
|
||||
}
|
||||
|
||||
export function isSimpleTransform(type): Boolean {
|
||||
return isFlagClear(type, ROT_INVALID_VAL | SCALE_VAL)
|
||||
}
|
||||
|
||||
/* transform type flags */
|
||||
const ROTATE_VAL = 0x0002
|
||||
const SCALE_VAL = 0x0004
|
||||
|
||||
/* orientation flags */
|
||||
const FLIP_H_VAL = 0x0100 // (1 << 0 << 8)
|
||||
const FLIP_V_VAL = 0x0200 // (1 << 1 << 8)
|
||||
const ROT_90_VAL = 0x0400 // (1 << 2 << 8)
|
||||
const ROT_INVALID_VAL = 0x8000 // (0x80 << 8)
|
||||
|
||||
export default Transform
|
||||
@@ -22,12 +22,22 @@ import ObjectFormatter from "./ObjectFormatter"
|
||||
* @param entry WM hierarchy element
|
||||
* @param proto Associated proto object
|
||||
*/
|
||||
export function getPropertiesForDisplay(proto: any, entry: any): any {
|
||||
let obj = Object.assign({}, entry)
|
||||
if (obj.children) delete obj.children
|
||||
// obj = ObjectFormatter.format(obj)
|
||||
export function getPropertiesForDisplay(entry: any): any {
|
||||
if (!entry) {
|
||||
return
|
||||
}
|
||||
|
||||
obj.proto = Object.assign({}, proto)
|
||||
let obj: any = {}
|
||||
const properties = ObjectFormatter.getProperties(entry)
|
||||
properties.forEach(prop => obj[prop] = entry[prop]);
|
||||
|
||||
// we remove the children property from the object to avoid it showing the
|
||||
// the properties view of the element as we can always see those elements'
|
||||
// properties by changing the target element in the hierarchy tree view.
|
||||
if (obj.children) delete obj.children
|
||||
if (obj.proto) delete obj.proto
|
||||
|
||||
obj.proto = Object.assign({}, entry.proto)
|
||||
if (obj.proto.children) delete obj.proto.children
|
||||
if (obj.proto.childWindows) delete obj.proto.childWindows
|
||||
if (obj.proto.childrenWindows) delete obj.proto.childrenWindows
|
||||
|
||||
47
tools/winscope/src/flickerlib/tags/Tag.ts
Normal file
47
tools/winscope/src/flickerlib/tags/Tag.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright 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.
|
||||
*/
|
||||
|
||||
|
||||
import { Tag } from "../common";
|
||||
import TransitionType from "./TransitionType";
|
||||
|
||||
const transitionTypeMap = new Map([
|
||||
['ROTATION', TransitionType.ROTATION],
|
||||
['PIP_ENTER', TransitionType.PIP_ENTER],
|
||||
['PIP_RESIZE', TransitionType.PIP_RESIZE],
|
||||
['PIP_CLOSE', TransitionType.PIP_CLOSE],
|
||||
['PIP_EXIT', TransitionType.PIP_EXIT],
|
||||
['APP_LAUNCH', TransitionType.APP_LAUNCH],
|
||||
['APP_CLOSE', TransitionType.APP_CLOSE],
|
||||
['IME_APPEAR', TransitionType.IME_APPEAR],
|
||||
['IME_DISAPPEAR', TransitionType.IME_DISAPPEAR],
|
||||
['APP_PAIRS_ENTER', TransitionType.APP_PAIRS_ENTER],
|
||||
['APP_PAIRS_EXIT', TransitionType.APP_PAIRS_EXIT],
|
||||
]);
|
||||
|
||||
Tag.fromProto = function (proto: any): Tag {
|
||||
const tag = new Tag(
|
||||
proto.id,
|
||||
transitionTypeMap.get(proto.transition),
|
||||
proto.isStartTag,
|
||||
proto.layerId,
|
||||
proto.windowToken,
|
||||
proto.taskId
|
||||
);
|
||||
return tag;
|
||||
};
|
||||
|
||||
export default Tag;
|
||||
@@ -14,8 +14,13 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import ITreeViewElement from "./ITreeViewElement"
|
||||
import { TagState } from "../common";
|
||||
import Tag from './Tag';
|
||||
|
||||
export default interface IClickableTreeViewElement extends ITreeViewElement {
|
||||
obj: any
|
||||
}
|
||||
TagState.fromProto = function (timestamp: number, protos: any[]): TagState {
|
||||
const tags = protos.map(it => Tag.fromProto(it));
|
||||
const state = new TagState(`${timestamp}`, tags);
|
||||
return state;
|
||||
}
|
||||
|
||||
export default TagState;
|
||||
31
tools/winscope/src/flickerlib/tags/TransitionType.ts
Normal file
31
tools/winscope/src/flickerlib/tags/TransitionType.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright 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.
|
||||
*/
|
||||
|
||||
enum TransitionType {
|
||||
ROTATION = 'ROTATION',
|
||||
PIP_ENTER = 'PIP_ENTER',
|
||||
PIP_RESIZE ='PIP_RESIZE',
|
||||
PIP_CLOSE = 'PIP_CLOSE',
|
||||
PIP_EXIT = 'PIP_EXIT',
|
||||
APP_LAUNCH = 'APP_LAUNCH',
|
||||
APP_CLOSE = 'APP_CLOSE',
|
||||
IME_APPEAR = 'IME_APPEAR',
|
||||
IME_DISAPPEAR = 'IME_DISAPPEAR',
|
||||
APP_PAIRS_ENTER = 'APP_PAIRS_ENTER',
|
||||
APP_PAIRS_EXIT = 'APP_PAIRS_EXIT',
|
||||
};
|
||||
|
||||
export default TransitionType;
|
||||
@@ -17,4 +17,34 @@
|
||||
import Chip from "./Chip"
|
||||
import ChipType from "./ChipType"
|
||||
|
||||
export const VISIBLE_CHIP = new Chip("V", "visible", ChipType.DEFAULT)
|
||||
export const VISIBLE_CHIP = new Chip("V", "visible", ChipType.DEFAULT)
|
||||
|
||||
export const RELATIVE_Z_CHIP = {
|
||||
short: 'RelZ',
|
||||
long: 'Is relative Z-ordered to another surface',
|
||||
class: 'warn',
|
||||
};
|
||||
|
||||
export const RELATIVE_Z_PARENT_CHIP = {
|
||||
short: 'RelZParent',
|
||||
long: 'Something is relative Z-ordered to this surface',
|
||||
class: 'warn',
|
||||
};
|
||||
|
||||
export const MISSING_LAYER = {
|
||||
short: 'MissingLayer',
|
||||
long: 'This layer was referenced from the parent, but not present in the trace',
|
||||
class: 'error',
|
||||
};
|
||||
|
||||
export const GPU_CHIP = {
|
||||
short: 'GPU',
|
||||
long: 'This layer was composed on the GPU',
|
||||
class: 'gpu',
|
||||
};
|
||||
|
||||
export const HWC_CHIP = {
|
||||
short: 'HWC',
|
||||
long: 'This layer was composed by Hardware Composer',
|
||||
class: 'hwc',
|
||||
};
|
||||
@@ -1,34 +0,0 @@
|
||||
/*
|
||||
* Copyright 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.
|
||||
*/
|
||||
|
||||
import Chip from "./Chip"
|
||||
import { TreeViewObject } from "./types"
|
||||
|
||||
export default interface ITreeViewElement {
|
||||
kind: String
|
||||
name: String
|
||||
shortName: String
|
||||
stableId: Number | String
|
||||
chips: Chip[]
|
||||
children: ITreeViewElement[]
|
||||
|
||||
// This is used for compatibility with the "legacy" Winscope infrastructure
|
||||
// where a class object would cause things to not work properly so this should
|
||||
// return a raw javascript object with the relevant information.
|
||||
// IMPORTANT: The implementation of this function should always return the
|
||||
// same object every time it is called and not generate a new object.
|
||||
asRawTreeViewObject(): TreeViewObject
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
import Chip from './Chip'
|
||||
|
||||
export type TreeViewObject = {
|
||||
kind: String
|
||||
name: String
|
||||
shortName: String
|
||||
stableId: String | Number
|
||||
chips: Chip[]
|
||||
obj: any
|
||||
children: TreeViewObject[]
|
||||
ref: any
|
||||
}
|
||||
@@ -14,23 +14,23 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { getPropertiesForDisplay, shortenName } from '../mixin'
|
||||
import { asRawTreeViewObject } from '../../utils/diff.js'
|
||||
import { shortenName } from '../mixin'
|
||||
import { Activity } from "../common"
|
||||
import { VISIBLE_CHIP } from '../treeview/Chips'
|
||||
import WindowContainer from "./WindowContainer"
|
||||
|
||||
Activity.fromProto = function (proto): Activity {
|
||||
Activity.fromProto = function (proto: any): Activity {
|
||||
if (proto == null) {
|
||||
return null
|
||||
return null;
|
||||
} else {
|
||||
const children = proto.windowToken.windowContainer.children.reverse()
|
||||
.filter(it => it != null)
|
||||
.map(it => WindowContainer.childrenFromProto(it, /* isActivityInTree */ true))
|
||||
const windowContainer = WindowContainer.fromProto({proto: proto.windowToken.windowContainer,
|
||||
children: children, identifierOverride: proto.identifier})
|
||||
if (windowContainer == null) {
|
||||
throw "Window container should not be null: " + JSON.stringify(proto)
|
||||
}
|
||||
const windowContainer = WindowContainer.fromProto(
|
||||
/* proto */ proto.windowToken.windowContainer,
|
||||
/* protoChildren */ proto.windowToken.windowContainer.children.reverse(),
|
||||
/* isActivityInTree */ true,
|
||||
/* nameOverride */ null,
|
||||
/* identifierOverride */ proto.identifier
|
||||
);
|
||||
|
||||
const entry = new Activity(
|
||||
proto.name,
|
||||
proto.state,
|
||||
@@ -39,16 +39,19 @@ Activity.fromProto = function (proto): Activity {
|
||||
proto.procId,
|
||||
proto.translucent,
|
||||
windowContainer
|
||||
)
|
||||
);
|
||||
|
||||
entry.obj = getPropertiesForDisplay(proto, entry)
|
||||
entry.kind = entry.constructor.name
|
||||
entry.shortName = shortenName(entry.name)
|
||||
entry.rawTreeViewObject = asRawTreeViewObject(entry)
|
||||
|
||||
console.warn("Created ", entry.kind, " stableId=", entry.stableId)
|
||||
return entry
|
||||
addAttributes(entry, proto);
|
||||
console.warn("Created ", entry.kind, " stableId=", entry.stableId);
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
|
||||
export default Activity
|
||||
function addAttributes(entry: Activity, proto: any) {
|
||||
entry.proto = proto;
|
||||
entry.kind = entry.constructor.name;
|
||||
entry.shortName = shortenName(entry.name);
|
||||
entry.chips = entry.isVisible ? [VISIBLE_CHIP] : [];
|
||||
}
|
||||
|
||||
export default Activity;
|
||||
|
||||
@@ -14,33 +14,33 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { getPropertiesForDisplay, shortenName } from '../mixin'
|
||||
import { asRawTreeViewObject } from '../../utils/diff.js'
|
||||
import { shortenName } from '../mixin'
|
||||
import { DisplayArea } from "../common"
|
||||
import WindowContainer from "./WindowContainer"
|
||||
|
||||
DisplayArea.fromProto = function (proto, isActivityInTree: Boolean): DisplayArea {
|
||||
DisplayArea.fromProto = function (proto: any, isActivityInTree: Boolean): DisplayArea {
|
||||
if (proto == null) {
|
||||
return null
|
||||
return null;
|
||||
} else {
|
||||
const children = proto.windowContainer.children.reverse()
|
||||
.filter(it => it != null)
|
||||
.map(it => WindowContainer.childrenFromProto(it, isActivityInTree))
|
||||
const windowContainer = WindowContainer.fromProto({proto: proto.windowContainer,
|
||||
children: children, nameOverride: proto.name})
|
||||
if (windowContainer == null) {
|
||||
throw "Window container should not be null: " + JSON.stringify(proto)
|
||||
}
|
||||
const entry = new DisplayArea(proto.isTaskDisplayArea, windowContainer)
|
||||
const windowContainer = WindowContainer.fromProto(
|
||||
/* proto */ proto.windowContainer,
|
||||
/* protoChildren */ proto.windowContainer.children.reverse(),
|
||||
/* isActivityInTree */ isActivityInTree,
|
||||
/* nameOverride */ proto.name
|
||||
);
|
||||
|
||||
entry.obj = getPropertiesForDisplay(proto, entry)
|
||||
entry.kind = entry.constructor.name
|
||||
entry.shortName = shortenName(entry.name)
|
||||
entry.rawTreeViewObject = asRawTreeViewObject(entry)
|
||||
const entry = new DisplayArea(proto.isTaskDisplayArea, windowContainer);
|
||||
|
||||
console.warn("Created ", entry.kind, " stableId=", entry.stableId)
|
||||
return entry
|
||||
addAttributes(entry, proto);
|
||||
console.warn("Created ", entry.kind, " stableId=", entry.stableId);
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
|
||||
export default DisplayArea
|
||||
function addAttributes(entry: DisplayArea, proto: any) {
|
||||
entry.proto = proto;
|
||||
entry.kind = entry.constructor.name;
|
||||
entry.shortName = shortenName(entry.name);
|
||||
}
|
||||
|
||||
export default DisplayArea;
|
||||
|
||||
@@ -14,31 +14,26 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { getPropertiesForDisplay, shortenName } from '../mixin'
|
||||
import { asRawTreeViewObject } from '../../utils/diff.js'
|
||||
import { shortenName } from '../mixin'
|
||||
import { toRect, DisplayContent, Rect } from "../common"
|
||||
import WindowContainer from "./WindowContainer"
|
||||
|
||||
DisplayContent.fromProto = function (proto, isActivityInTree: Boolean): DisplayContent {
|
||||
DisplayContent.fromProto = function (proto: any, isActivityInTree: Boolean): DisplayContent {
|
||||
if (proto == null) {
|
||||
return null
|
||||
return null;
|
||||
} else {
|
||||
const children = proto.rootDisplayArea.windowContainer.children.reverse()
|
||||
.filter(it => it != null)
|
||||
.map(it => WindowContainer.childrenFromProto(it, isActivityInTree))
|
||||
const windowContainer = WindowContainer.fromProto({proto: proto.rootDisplayArea.windowContainer,
|
||||
children: children, nameOverride: proto.displayInfo?.name ?? null})
|
||||
if (windowContainer == null) {
|
||||
throw "Window container should not be null: " + JSON.stringify(proto)
|
||||
}
|
||||
|
||||
const displayRectWidth = proto.displayInfo?.logicalWidth ?? 0
|
||||
const displayRectHeight = proto.displayInfo?.logicalHeight ?? 0
|
||||
const appRectWidth = proto.displayInfo?.appWidth ?? 0
|
||||
const appRectHeight = proto.displayInfo?.appHeight ?? 0
|
||||
|
||||
const defaultBounds = proto.pinnedStackController?.defaultBounds ?? null
|
||||
const movementBounds = proto.pinnedStackController?.movementBounds ?? null
|
||||
const windowContainer = WindowContainer.fromProto(
|
||||
/* proto */ proto.rootDisplayArea.windowContainer,
|
||||
/* protoChildren */ proto.rootDisplayArea.windowContainer.children.reverse(),
|
||||
/* isActivityInTree */ isActivityInTree,
|
||||
/* nameOverride */ proto.displayInfo?.name ?? null
|
||||
);
|
||||
const displayRectWidth = proto.displayInfo?.logicalWidth ?? 0;
|
||||
const displayRectHeight = proto.displayInfo?.logicalHeight ?? 0;
|
||||
const appRectWidth = proto.displayInfo?.appWidth ?? 0;
|
||||
const appRectHeight = proto.displayInfo?.appHeight ?? 0;
|
||||
const defaultBounds = proto.pinnedStackController?.defaultBounds ?? null;
|
||||
const movementBounds = proto.pinnedStackController?.movementBounds ?? null;
|
||||
|
||||
const entry = new DisplayContent(
|
||||
proto.id,
|
||||
@@ -59,16 +54,18 @@ DisplayContent.fromProto = function (proto, isActivityInTree: Boolean): DisplayC
|
||||
proto.displayRotation?.rotation ?? 0,
|
||||
proto.displayRotation?.lastOrientation ?? 0,
|
||||
windowContainer
|
||||
)
|
||||
);
|
||||
|
||||
entry.obj = getPropertiesForDisplay(proto, entry)
|
||||
entry.kind = entry.constructor.name
|
||||
entry.shortName = shortenName(entry.name)
|
||||
entry.rawTreeViewObject = asRawTreeViewObject(entry)
|
||||
|
||||
console.warn("Created ", entry.kind, " stableId=", entry.stableId)
|
||||
return entry
|
||||
addAttributes(entry, proto);
|
||||
console.warn("Created ", entry.kind, " stableId=", entry.stableId);
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
|
||||
export default DisplayContent
|
||||
function addAttributes(entry: DisplayContent, proto: any) {
|
||||
entry.proto = proto;
|
||||
entry.kind = entry.constructor.name;
|
||||
entry.shortName = shortenName(entry.name);
|
||||
}
|
||||
|
||||
export default DisplayContent;
|
||||
|
||||
@@ -14,30 +14,28 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { getPropertiesForDisplay, shortenName } from '../mixin'
|
||||
import { asRawTreeViewObject } from '../../utils/diff.js'
|
||||
import { ActivityTask, toRect } from "../common"
|
||||
import { shortenName } from '../mixin'
|
||||
import { Task, toRect } from "../common"
|
||||
import WindowContainer from "./WindowContainer"
|
||||
|
||||
ActivityTask.fromProto = function (proto, isActivityInTree: Boolean): ActivityTask {
|
||||
Task.fromProto = function (proto: any, isActivityInTree: Boolean): Task {
|
||||
if (proto == null) {
|
||||
return null
|
||||
return null;
|
||||
} else {
|
||||
const children = proto.windowContainer.children.reverse()
|
||||
.filter(it => it != null)
|
||||
.map(it => WindowContainer.childrenFromProto(it, isActivityInTree))
|
||||
const windowContainer = WindowContainer.fromProto({proto: proto.windowContainer,
|
||||
children: children})
|
||||
if (windowContainer == null) {
|
||||
throw "Window container should not be null: " + JSON.stringify(proto)
|
||||
}
|
||||
const entry = new ActivityTask(
|
||||
proto.activityType,
|
||||
const windowContainerProto = proto.taskFragment?.windowContainer ?? proto.windowContainer;
|
||||
const windowContainer = WindowContainer.fromProto(
|
||||
/* proto */ windowContainerProto,
|
||||
/* protoChildren */ windowContainerProto.children.reverse(),
|
||||
/* isActivityInTree */ isActivityInTree
|
||||
);
|
||||
|
||||
const entry = new Task(
|
||||
proto.taskFragment?.activityType ?? proto.activityType,
|
||||
proto.fillsParent,
|
||||
toRect(proto.bounds),
|
||||
proto.id,
|
||||
proto.rootTaskId,
|
||||
proto.displayId,
|
||||
proto.taskFragment?.displayId,
|
||||
toRect(proto.lastNonFullscreenBounds),
|
||||
proto.realActivity,
|
||||
proto.origActivity,
|
||||
@@ -47,19 +45,21 @@ ActivityTask.fromProto = function (proto, isActivityInTree: Boolean): ActivityTa
|
||||
proto.surfaceWidth,
|
||||
proto.surfaceHeight,
|
||||
proto.createdByOrganizer,
|
||||
proto.minWidth,
|
||||
proto.minHeight,
|
||||
proto.taskFragment?.minWidth ?? proto.minWidth,
|
||||
proto.taskFragment?.minHeight ?? proto.minHeight,
|
||||
windowContainer
|
||||
)
|
||||
);
|
||||
|
||||
entry.obj = getPropertiesForDisplay(proto, entry)
|
||||
entry.kind = entry.constructor.name
|
||||
entry.shortName = shortenName(entry.name)
|
||||
entry.rawTreeViewObject = asRawTreeViewObject(entry)
|
||||
|
||||
console.warn("Created ", entry.kind, " stableId=", entry.stableId)
|
||||
return entry
|
||||
addAttributes(entry, proto);
|
||||
console.warn("Created ", entry.kind, " stableId=", entry.stableId);
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
|
||||
export default ActivityTask
|
||||
function addAttributes(entry: Task, proto: any) {
|
||||
entry.proto = proto;
|
||||
entry.kind = entry.constructor.name;
|
||||
entry.shortName = shortenName(entry.name);
|
||||
}
|
||||
|
||||
export default Task;
|
||||
49
tools/winscope/src/flickerlib/windows/TaskFragment.ts
Normal file
49
tools/winscope/src/flickerlib/windows/TaskFragment.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright 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.
|
||||
*/
|
||||
|
||||
import { shortenName } from '../mixin'
|
||||
import { TaskFragment } from "../common"
|
||||
import WindowContainer from "./WindowContainer"
|
||||
|
||||
TaskFragment.fromProto = function (proto: any, isActivityInTree: Boolean): TaskFragment {
|
||||
if (proto == null) {
|
||||
return null;
|
||||
} else {
|
||||
const windowContainer = WindowContainer.fromProto(
|
||||
/* proto */ proto.windowContainer,
|
||||
/* protoChildren */ proto.windowContainer.children.reverse(),
|
||||
/* isActivityInTree */ isActivityInTree);
|
||||
const entry = new TaskFragment(
|
||||
proto.activityType,
|
||||
proto.displayId,
|
||||
proto.minWidth,
|
||||
proto.minHeight,
|
||||
windowContainer
|
||||
);
|
||||
|
||||
addAttributes(entry, proto);
|
||||
console.warn("Created ", entry.kind, " stableId=", entry.stableId);
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
|
||||
function addAttributes(entry: TaskFragment, proto: any) {
|
||||
entry.proto = proto;
|
||||
entry.kind = entry.constructor.name;
|
||||
entry.shortName = shortenName(entry.name);
|
||||
}
|
||||
|
||||
export default TaskFragment;
|
||||
@@ -14,8 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { getPropertiesForDisplay, shortenName } from '../mixin'
|
||||
import { asRawTreeViewObject } from '../../utils/diff.js'
|
||||
import { shortenName } from '../mixin'
|
||||
|
||||
import {
|
||||
Configuration,
|
||||
@@ -29,73 +28,82 @@ import {
|
||||
import Activity from "./Activity"
|
||||
import DisplayArea from "./DisplayArea"
|
||||
import DisplayContent from "./DisplayContent"
|
||||
import ActivityTask from "./ActivityTask"
|
||||
import Task from "./Task"
|
||||
import TaskFragment from "./TaskFragment"
|
||||
import WindowState from "./WindowState"
|
||||
import WindowToken from "./WindowToken"
|
||||
|
||||
WindowContainer.fromProto = function ({
|
||||
proto,
|
||||
children,
|
||||
nameOverride = null,
|
||||
identifierOverride = null,
|
||||
tokenOverride = null
|
||||
}): WindowContainer {
|
||||
WindowContainer.fromProto = function (
|
||||
proto: any,
|
||||
protoChildren: any[],
|
||||
isActivityInTree: boolean,
|
||||
nameOverride: string = null,
|
||||
identifierOverride: string = null,
|
||||
tokenOverride = null,
|
||||
): WindowContainer {
|
||||
if (proto == null) {
|
||||
return null
|
||||
return null;
|
||||
}
|
||||
const identifier = identifierOverride ?? proto.identifier
|
||||
var name = nameOverride ?? identifier?.title ?? ""
|
||||
var token = tokenOverride?.toString(16) ?? identifier?.hashCode?.toString(16) ?? ""
|
||||
|
||||
const config = newConfigurationContainer(proto.configurationContainer)
|
||||
const children = protoChildren
|
||||
.filter(it => it != null)
|
||||
.map(it => WindowContainer.childrenFromProto(it, isActivityInTree));
|
||||
|
||||
const identifier = identifierOverride ?? proto.identifier;
|
||||
var name = nameOverride ?? identifier?.title ?? "";
|
||||
var token = tokenOverride?.toString(16) ?? identifier?.hashCode?.toString(16) ?? "";
|
||||
|
||||
const config = createConfigurationContainer(proto.configurationContainer);
|
||||
const entry = new WindowContainer(
|
||||
name,
|
||||
token,
|
||||
proto.orientation,
|
||||
proto.surfaceControl?.layerId ?? 0,
|
||||
proto.visible,
|
||||
config,
|
||||
children
|
||||
)
|
||||
);
|
||||
|
||||
// we remove the children property from the object to avoid it showing the
|
||||
// the properties view of the element as we can always see those elements'
|
||||
// properties by changing the target element in the hierarchy tree view.
|
||||
entry.obj = getPropertiesForDisplay(proto, entry)
|
||||
entry.kind = entry.constructor.name
|
||||
entry.shortName = shortenName(entry.name)
|
||||
entry.rawTreeViewObject = asRawTreeViewObject(entry)
|
||||
return entry
|
||||
addAttributes(entry, proto);
|
||||
return entry;
|
||||
}
|
||||
|
||||
WindowContainer.childrenFromProto = function(proto, isActivityInTree: Boolean): WindowContainerChild {
|
||||
function addAttributes(entry: WindowContainer, proto: any) {
|
||||
entry.proto = proto;
|
||||
entry.kind = entry.constructor.name;
|
||||
entry.shortName = shortenName(entry.name);
|
||||
}
|
||||
|
||||
WindowContainer.childrenFromProto = function(proto: any, isActivityInTree: Boolean): WindowContainerChild {
|
||||
return DisplayContent.fromProto(proto.displayContent, isActivityInTree) ??
|
||||
DisplayArea.fromProto(proto.displayArea, isActivityInTree) ??
|
||||
ActivityTask.fromProto(proto.task, isActivityInTree) ??
|
||||
Task.fromProto(proto.task, isActivityInTree) ??
|
||||
TaskFragment.fromProto(proto.taskFragment, isActivityInTree) ??
|
||||
Activity.fromProto(proto.activity) ??
|
||||
WindowToken.fromProto(proto.windowToken, isActivityInTree) ??
|
||||
WindowState.fromProto(proto.window, isActivityInTree) ??
|
||||
WindowContainer.fromProto({proto: proto.windowContainer})
|
||||
WindowContainer.fromProto(proto.windowContainer);
|
||||
}
|
||||
|
||||
function newConfigurationContainer(proto): ConfigurationContainer {
|
||||
function createConfigurationContainer(proto: any): ConfigurationContainer {
|
||||
const entry = new ConfigurationContainer(
|
||||
newConfiguration(proto?.overrideConfiguration ?? null),
|
||||
newConfiguration(proto?.fullConfiguration ?? null),
|
||||
newConfiguration(proto?.mergedOverrideConfiguration ?? null)
|
||||
)
|
||||
createConfiguration(proto?.overrideConfiguration ?? null),
|
||||
createConfiguration(proto?.fullConfiguration ?? null),
|
||||
createConfiguration(proto?.mergedOverrideConfiguration ?? null)
|
||||
);
|
||||
|
||||
entry.obj = entry
|
||||
return entry
|
||||
entry.obj = entry;
|
||||
return entry;
|
||||
}
|
||||
|
||||
function newConfiguration(proto): Configuration {
|
||||
function createConfiguration(proto: any): Configuration {
|
||||
if (proto == null) {
|
||||
return null
|
||||
return null;
|
||||
}
|
||||
var windowConfiguration = null
|
||||
var windowConfiguration = null;
|
||||
|
||||
if (proto != null && proto.windowConfiguration != null) {
|
||||
windowConfiguration = newWindowConfiguration(proto.windowConfiguration)
|
||||
windowConfiguration = createWindowConfiguration(proto.windowConfiguration);
|
||||
}
|
||||
|
||||
return new Configuration(
|
||||
@@ -107,17 +115,17 @@ function newConfiguration(proto): Configuration {
|
||||
proto?.smallestScreenWidthDp ?? 0,
|
||||
proto?.screenLayout ?? 0,
|
||||
proto?.uiMode ?? 0
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function newWindowConfiguration(proto): WindowConfiguration {
|
||||
function createWindowConfiguration(proto: any): WindowConfiguration {
|
||||
return new WindowConfiguration(
|
||||
toRect(proto.appBounds),
|
||||
toRect(proto.bounds),
|
||||
toRect(proto.maxBounds),
|
||||
proto.windowingMode,
|
||||
proto.activityType
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export default WindowContainer
|
||||
export default WindowContainer;
|
||||
|
||||
@@ -14,55 +14,35 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { getPropertiesForDisplay, shortenName } from '../mixin'
|
||||
import { asRawTreeViewObject } from '../../utils/diff.js'
|
||||
import { toRect, Bounds, WindowState, WindowLayoutParams } from "../common"
|
||||
import { shortenName } from '../mixin'
|
||||
import { toRect, Size, WindowState, WindowLayoutParams } from "../common"
|
||||
import { VISIBLE_CHIP } from '../treeview/Chips'
|
||||
import WindowContainer from "./WindowContainer"
|
||||
|
||||
WindowState.fromProto = function (proto, isActivityInTree: Boolean): WindowState {
|
||||
WindowState.fromProto = function (proto: any, isActivityInTree: Boolean): WindowState {
|
||||
if (proto == null) {
|
||||
return null
|
||||
return null;
|
||||
} else {
|
||||
const identifierName = proto.windowContainer.identifier?.title ?? proto.identifier?.title ?? ""
|
||||
var windowType = 0
|
||||
if (identifierName.startsWith(WindowState.STARTING_WINDOW_PREFIX)) {
|
||||
windowType = WindowState.WINDOW_TYPE_STARTING
|
||||
} else if (proto.animatingExit) {
|
||||
windowType = WindowState.WINDOW_TYPE_EXITING
|
||||
} else if (identifierName.startsWith(WindowState.DEBUGGER_WINDOW_PREFIX)) {
|
||||
windowType = WindowState.WINDOW_TYPE_STARTING
|
||||
}
|
||||
|
||||
var nameOverride = identifierName
|
||||
|
||||
if (identifierName.startsWith(WindowState.STARTING_WINDOW_PREFIX)) {
|
||||
nameOverride = identifierName.substring(WindowState.STARTING_WINDOW_PREFIX.length)
|
||||
} else if (identifierName.startsWith(WindowState.DEBUGGER_WINDOW_PREFIX)) {
|
||||
nameOverride = identifierName.substring(WindowState.DEBUGGER_WINDOW_PREFIX.length)
|
||||
}
|
||||
|
||||
const children = proto.windowContainer.children.reverse()
|
||||
.filter(it => it != null)
|
||||
.map(it => WindowContainer.childrenFromProto(it, isActivityInTree))
|
||||
|
||||
const windowContainer = WindowContainer.fromProto({
|
||||
proto: proto.windowContainer,
|
||||
children: children,
|
||||
nameOverride: nameOverride,
|
||||
identifierOverride: proto.identifier})
|
||||
if (windowContainer == null) {
|
||||
throw "Window container should not be null: " + JSON.stringify(proto)
|
||||
}
|
||||
const windowParams = createWindowLayoutParams(proto.attributes);
|
||||
const identifierName = getIdentifier(proto);
|
||||
const windowType = getWindowType(proto, identifierName);
|
||||
const name = getName(identifierName);
|
||||
const windowContainer = WindowContainer.fromProto(
|
||||
/* proto */ proto.windowContainer,
|
||||
/* protoChildren */ proto.windowContainer.children.reverse(),
|
||||
/* isActivityInTree */ isActivityInTree,
|
||||
/* nameOverride */ name,
|
||||
/* identifierOverride */ proto.identifier
|
||||
);
|
||||
|
||||
const entry = new WindowState(
|
||||
newWindowLayoutParams(proto.attributes),
|
||||
windowParams,
|
||||
proto.displayId,
|
||||
proto.stackId,
|
||||
proto.animator?.surface?.layer ?? 0,
|
||||
proto.animator?.surface?.shown ?? false,
|
||||
windowType,
|
||||
new Bounds(proto.requestedWidth, proto.requestedHeight),
|
||||
new Size(proto.requestedWidth, proto.requestedHeight),
|
||||
toRect(proto.surfacePosition),
|
||||
toRect(proto.windowFrames?.frame ?? null),
|
||||
toRect(proto.windowFrames?.containingFrame ?? null),
|
||||
@@ -74,22 +54,14 @@ import WindowContainer from "./WindowContainer"
|
||||
toRect(proto.animator?.lastClipRect ?? null),
|
||||
windowContainer,
|
||||
/* isAppWindow */ isActivityInTree
|
||||
)
|
||||
);
|
||||
|
||||
entry.kind = entry.constructor.name
|
||||
entry.rect = entry.frame
|
||||
entry.rect.ref = entry
|
||||
entry.rect.label = entry.name
|
||||
entry.obj = getPropertiesForDisplay(proto, entry)
|
||||
entry.shortName = shortenName(entry.name)
|
||||
entry.visible = entry.isVisible ?? false
|
||||
entry.chips = entry.isVisible ? [VISIBLE_CHIP] : []
|
||||
entry.rawTreeViewObject = asRawTreeViewObject(entry)
|
||||
return entry
|
||||
addAttributes(entry, proto);
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
|
||||
function newWindowLayoutParams(proto): WindowLayoutParams {
|
||||
function createWindowLayoutParams(proto: any): WindowLayoutParams {
|
||||
return new WindowLayoutParams(
|
||||
/* type */ proto?.type ?? 0,
|
||||
/* x */ proto?.x ?? 0,
|
||||
@@ -124,4 +96,42 @@ function newWindowLayoutParams(proto): WindowLayoutParams {
|
||||
)
|
||||
}
|
||||
|
||||
function getWindowType(proto: any, identifierName: string): number {
|
||||
if (identifierName.startsWith(WindowState.STARTING_WINDOW_PREFIX)) {
|
||||
return WindowState.WINDOW_TYPE_STARTING;
|
||||
} else if (proto.animatingExit) {
|
||||
return WindowState.WINDOW_TYPE_EXITING;
|
||||
} else if (identifierName.startsWith(WindowState.DEBUGGER_WINDOW_PREFIX)) {
|
||||
return WindowState.WINDOW_TYPE_STARTING;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
function getName(identifierName: string): string {
|
||||
var name = identifierName;
|
||||
|
||||
if (identifierName.startsWith(WindowState.STARTING_WINDOW_PREFIX)) {
|
||||
name = identifierName.substring(WindowState.STARTING_WINDOW_PREFIX.length);
|
||||
} else if (identifierName.startsWith(WindowState.DEBUGGER_WINDOW_PREFIX)) {
|
||||
name = identifierName.substring(WindowState.DEBUGGER_WINDOW_PREFIX.length);
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
function getIdentifier(proto: any): string {
|
||||
return proto.windowContainer.identifier?.title ?? proto.identifier?.title ?? "";
|
||||
}
|
||||
|
||||
function addAttributes(entry: WindowState, proto: any) {
|
||||
entry.kind = entry.constructor.name;
|
||||
entry.rect = entry.frame;
|
||||
entry.rect.ref = entry;
|
||||
entry.rect.label = entry.name;
|
||||
entry.proto = proto;
|
||||
entry.shortName = shortenName(entry.name);
|
||||
entry.chips = entry.isVisible ? [VISIBLE_CHIP] : [];
|
||||
}
|
||||
|
||||
export default WindowState
|
||||
|
||||
@@ -14,32 +14,29 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { getPropertiesForDisplay, shortenName } from '../mixin'
|
||||
import { asRawTreeViewObject } from '../../utils/diff.js'
|
||||
import { shortenName } from '../mixin'
|
||||
import { WindowToken } from "../common"
|
||||
import WindowContainer from "./WindowContainer"
|
||||
|
||||
WindowToken.fromProto = function (proto, isActivityInTree: Boolean): WindowToken {
|
||||
WindowToken.fromProto = function (proto: any, isActivityInTree: Boolean): WindowToken {
|
||||
if (proto == null) {
|
||||
return null
|
||||
return null;
|
||||
}
|
||||
|
||||
const children = proto.windowContainer.children.reverse()
|
||||
.filter(it => it != null)
|
||||
.map(it => WindowContainer.childrenFromProto(it, isActivityInTree))
|
||||
const windowContainer = WindowContainer.fromProto({proto: proto.windowContainer,
|
||||
children: children, tokenOverride: proto.hashCode})
|
||||
if (windowContainer == null) {
|
||||
throw "Window container should not be null: " + JSON.stringify(proto)
|
||||
}
|
||||
const entry = new WindowToken(windowContainer)
|
||||
entry.kind = entry.constructor.name
|
||||
entry.obj = getPropertiesForDisplay(proto, entry)
|
||||
entry.shortName = shortenName(entry.name)
|
||||
entry.rawTreeViewObject = asRawTreeViewObject(entry)
|
||||
|
||||
console.warn("Created ", entry.kind, " stableId=", entry.stableId)
|
||||
return entry
|
||||
const windowContainer = WindowContainer.fromProto(
|
||||
/* proto */ proto.windowContainer,
|
||||
/* protoChildren */ proto.windowContainer.children.reverse(),
|
||||
/* isActivityInTree */ isActivityInTree,
|
||||
/* nameOverride */ null,
|
||||
/* identifierOverride */ null,
|
||||
/* tokenOverride */ proto.hashCode
|
||||
);
|
||||
const entry = new WindowToken(windowContainer);
|
||||
entry.kind = entry.constructor.name;
|
||||
entry.proto = proto;
|
||||
entry.shortName = shortenName(entry.name);
|
||||
console.warn("Created ", entry.kind, " stableId=", entry.stableId);
|
||||
return entry;
|
||||
}
|
||||
|
||||
export default WindowToken
|
||||
export default WindowToken;
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
import Vue from 'vue'
|
||||
import Vuex from 'vuex'
|
||||
import VueMaterial from 'vue-material'
|
||||
import VueGtag from "vue-gtag";
|
||||
|
||||
import App from './App.vue'
|
||||
import { TRACE_TYPES, DUMP_TYPES, TRACE_INFO, DUMP_INFO } from './decode.js'
|
||||
@@ -66,6 +67,8 @@ const store = new Vuex.Store({
|
||||
dumps: {},
|
||||
excludeFromTimeline: [
|
||||
TRACE_TYPES.PROTO_LOG,
|
||||
TRACE_TYPES.TAG,
|
||||
TRACE_TYPES.ERROR
|
||||
],
|
||||
activeFile: null,
|
||||
focusedFile: null,
|
||||
@@ -93,12 +96,26 @@ const store = new Vuex.Store({
|
||||
return Object.values(state.traces)
|
||||
.filter(file => !state.excludeFromTimeline.includes(file.type));
|
||||
},
|
||||
tagFiles(state, getters) {
|
||||
return Object.values(state.traces)
|
||||
.filter(file => file.type === TRACE_TYPES.TAG);
|
||||
},
|
||||
errorFiles(state, getters) {
|
||||
return Object.values(state.traces)
|
||||
.filter(file => file.type === TRACE_TYPES.ERROR);
|
||||
},
|
||||
sortedTimelineFiles(state, getters) {
|
||||
return sortFiles(getters.timelineFiles);
|
||||
},
|
||||
video(state) {
|
||||
return state.traces[TRACE_TYPES.SCREEN_RECORDING];
|
||||
},
|
||||
tagGenerationWmTrace(state, getters) {
|
||||
return state.traces[TRACE_TYPES.WINDOW_MANAGER].tagGenerationTrace;
|
||||
},
|
||||
tagGenerationSfTrace(state, getters) {
|
||||
return state.traces[TRACE_TYPES.SURFACE_FLINGER].tagGenerationTrace;
|
||||
}
|
||||
},
|
||||
mutations: {
|
||||
setCurrentTimestamp(state, timestamp) {
|
||||
@@ -252,6 +269,9 @@ const store = new Vuex.Store({
|
||||
},
|
||||
updateTimelineTime(context, timestamp) {
|
||||
for (const file of context.getters.files) {
|
||||
//dumps do not have a timeline, so only look at files with timelines to update the timestamp
|
||||
if (!file.timeline) continue;
|
||||
|
||||
const type = file.type;
|
||||
const entryIndex = findLastMatchingSorted(
|
||||
file.timeline,
|
||||
@@ -339,6 +359,61 @@ const store = new Vuex.Store({
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* Make Google analytics functionalities available for recording events.
|
||||
*/
|
||||
Vue.use(VueGtag, {
|
||||
config: { id: 'G-RRV0M08Y76'}
|
||||
})
|
||||
|
||||
Vue.mixin({
|
||||
methods: {
|
||||
buttonClicked(button) {
|
||||
const string = "Clicked " + button + " Button";
|
||||
this.$gtag.event(string, {
|
||||
'event_category': 'Button Clicked',
|
||||
'event_label': "Winscope Interactions",
|
||||
'value': button,
|
||||
});
|
||||
},
|
||||
draggedAndDropped(val) {
|
||||
this.$gtag.event("Dragged And DroppedFile", {
|
||||
'event_category': 'Uploaded file',
|
||||
'event_label': "Winscope Interactions",
|
||||
'value': val,
|
||||
});
|
||||
},
|
||||
uploadedFileThroughFilesystem(val) {
|
||||
this.$gtag.event("Uploaded File From Filesystem", {
|
||||
'event_category': 'Uploaded file',
|
||||
'event_label': "Winscope Interactions",
|
||||
'value': val,
|
||||
});
|
||||
},
|
||||
newEventOccurred(event) {
|
||||
this.$gtag.event(event, {
|
||||
'event_category': event,
|
||||
'event_label': "Winscope Interactions",
|
||||
'value': 1,
|
||||
});
|
||||
},
|
||||
seeingNewScreen(screenname) {
|
||||
this.$gtag.screenview({
|
||||
app_name: "Winscope",
|
||||
screen_name: screenname,
|
||||
})
|
||||
},
|
||||
openedToSeeAttributeField(field) {
|
||||
const string = "Opened field " + field;
|
||||
this.$gtag.event(string, {
|
||||
'event_category': "Opened attribute field",
|
||||
'event_label': "Winscope Interactions",
|
||||
'value': field,
|
||||
});
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
new Vue({
|
||||
el: '#app',
|
||||
store, // inject the Vuex store into all components
|
||||
|
||||
@@ -14,198 +14,38 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/* transform type flags */
|
||||
const TRANSLATE_VAL = 0x0001;
|
||||
const ROTATE_VAL = 0x0002;
|
||||
const SCALE_VAL = 0x0004;
|
||||
|
||||
/* orientation flags */
|
||||
const FLIP_H_VAL = 0x0100; // (1 << 0 << 8)
|
||||
const FLIP_V_VAL = 0x0200; // (1 << 1 << 8)
|
||||
const ROT_90_VAL = 0x0400; // (1 << 2 << 8)
|
||||
const ROT_INVALID_VAL = 0x8000; // (0x80 << 8)
|
||||
|
||||
function is_proto_2(transform) {
|
||||
/*
|
||||
* Checks if the loaded file was a stored with ProtoBuf2 or Protobuf3
|
||||
*
|
||||
* Proto2 files don't have a Type for the transform object but all other
|
||||
* fields of the transform are set.
|
||||
*
|
||||
* Proto3 has a type field for the transform but doesn't store default
|
||||
* values (0 for transform type), also, the framework/native implementation
|
||||
* doesn't write a transform in case it is an identity matrix.
|
||||
*/
|
||||
var propertyNames = Object.getOwnPropertyNames(transform);
|
||||
return (!propertyNames.includes("type") && propertyNames.includes("dsdx"));
|
||||
}
|
||||
|
||||
function is_simple_transform(transform) {
|
||||
transform = transform || {};
|
||||
if (is_proto_2(transform)) {
|
||||
return false;
|
||||
}
|
||||
return is_type_flag_clear(transform, ROT_INVALID_VAL|SCALE_VAL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a transform type into readable format.
|
||||
* Adapted from the dump function from framework/native
|
||||
*
|
||||
* @param {*} transform Transform object ot be converter
|
||||
*/
|
||||
function format_transform_type(transform) {
|
||||
if (is_proto_2(transform)) {
|
||||
return "";
|
||||
}
|
||||
|
||||
if (is_type_flag_clear(transform, SCALE_VAL | ROTATE_VAL | TRANSLATE_VAL)) {
|
||||
return "IDENTITY";
|
||||
}
|
||||
|
||||
var type_flags = [];
|
||||
if (is_type_flag_set(transform, SCALE_VAL)) {
|
||||
type_flags.push("SCALE");
|
||||
}
|
||||
if (is_type_flag_set(transform, TRANSLATE_VAL)) {
|
||||
type_flags.push("TRANSLATE");
|
||||
}
|
||||
|
||||
if (is_type_flag_set(transform, ROT_INVALID_VAL)) {
|
||||
type_flags.push("ROT_INVALID");
|
||||
} else if (is_type_flag_set(transform, ROT_90_VAL|FLIP_V_VAL|FLIP_H_VAL)) {
|
||||
type_flags.push("ROT_270");
|
||||
} else if (is_type_flag_set(transform, FLIP_V_VAL|FLIP_H_VAL)) {
|
||||
type_flags.push("ROT_180");
|
||||
} else {
|
||||
if (is_type_flag_set(transform, ROT_90_VAL)) {
|
||||
type_flags.push("ROT_90");
|
||||
}
|
||||
if (is_type_flag_set(transform, FLIP_V_VAL)) {
|
||||
type_flags.push("FLIP_V");
|
||||
}
|
||||
if (is_type_flag_set(transform, FLIP_H_VAL)) {
|
||||
type_flags.push("FLIP_H");
|
||||
}
|
||||
}
|
||||
|
||||
if (type_flags.length == 0) {
|
||||
throw "Unknown transform type " + transform ;
|
||||
}
|
||||
|
||||
return type_flags.join(', ');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Ensures all values of the transform object are set.
|
||||
*/
|
||||
function fill_transform_data(transform) {
|
||||
function fill_simple_transform(transform) {
|
||||
// ROT_270 = ROT_90|FLIP_H|FLIP_V;
|
||||
if (is_type_flag_set(transform, ROT_90_VAL|FLIP_V_VAL|FLIP_H_VAL)) {
|
||||
transform.dsdx = 0.0;
|
||||
transform.dtdx = -1.0;
|
||||
transform.dsdy = 1.0;
|
||||
transform.dtdy = 0.0;
|
||||
return;
|
||||
}
|
||||
|
||||
// ROT_180 = FLIP_H|FLIP_V;
|
||||
if (is_type_flag_set(transform, FLIP_V_VAL|FLIP_H_VAL)) {
|
||||
transform.dsdx = -1.0;
|
||||
transform.dtdx = 0.0;
|
||||
transform.dsdy = 0.0;
|
||||
transform.dtdy = -1.0;
|
||||
return;
|
||||
}
|
||||
|
||||
// ROT_90
|
||||
if (is_type_flag_set(transform, ROT_90_VAL)) {
|
||||
transform.dsdx = 0.0;
|
||||
transform.dtdx = 1.0;
|
||||
transform.dsdy = -1.0;
|
||||
transform.dtdy = 0.0;
|
||||
return;
|
||||
}
|
||||
|
||||
// IDENTITY
|
||||
if (is_type_flag_clear(transform, SCALE_VAL | ROTATE_VAL)) {
|
||||
transform.dsdx = 1.0;
|
||||
transform.dtdx = 0.0;
|
||||
transform.dsdy = 0.0;
|
||||
transform.dtdy = 1.0;
|
||||
transform.type = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
throw "Unknown transform type " + transform;
|
||||
}
|
||||
|
||||
if (!transform) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_proto_2(transform)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_simple_transform(transform)){
|
||||
fill_simple_transform(transform);
|
||||
}
|
||||
|
||||
transform.dsdx = transform.dsdx || 0.0;
|
||||
transform.dtdx = transform.dtdx || 0.0;
|
||||
transform.dsdy = transform.dsdy || 0.0;
|
||||
transform.dtdy = transform.dtdy || 0.0;
|
||||
}
|
||||
|
||||
function is_type_flag_set(transform, bits) {
|
||||
transform = transform || {};
|
||||
var type = transform.type || 0;
|
||||
return (type & bits) === bits;
|
||||
}
|
||||
|
||||
function is_type_flag_clear(transform, bits) {
|
||||
transform = transform || {};
|
||||
var type = transform.type || 0;
|
||||
return (type & bits) === 0;
|
||||
}
|
||||
|
||||
function multiply_vec2(matrix, x, y) {
|
||||
function multiplyVec2(matrix, x, y) {
|
||||
if (!matrix) return {x, y};
|
||||
// |dsdx dsdy tx| | x |
|
||||
// |dtdx dtdy ty| x | y |
|
||||
// |0 0 1 | | 1 |
|
||||
return {
|
||||
x: matrix.dsdx * x + matrix.dsdy * y + matrix.tx,
|
||||
y: matrix.dtdx * x + matrix.dtdy * y + matrix.ty
|
||||
return {
|
||||
x: matrix.dsdx * x + matrix.dsdy * y + matrix.tx,
|
||||
y: matrix.dtdx * x + matrix.dtdy * y + matrix.ty,
|
||||
};
|
||||
}
|
||||
|
||||
function multiply_rect(matrix, rect) {
|
||||
function multiplyRect(transform, rect) {
|
||||
let matrix = transform;
|
||||
if (transform && transform.matrix) {
|
||||
matrix = transform.matrix;
|
||||
}
|
||||
// |dsdx dsdy tx| | left, top |
|
||||
// matrix = |dtdx dtdy ty| rect = | |
|
||||
// |0 0 1 | | right, bottom |
|
||||
|
||||
var left_top = multiply_vec2(matrix, rect.left, rect.top);
|
||||
var right_top = multiply_vec2(matrix, rect.right, rect.top);
|
||||
var left_bottom = multiply_vec2(matrix, rect.left, rect.bottom);
|
||||
var right_bottom = multiply_vec2(matrix, rect.right, rect.bottom);
|
||||
const leftTop = multiplyVec2(matrix, rect.left, rect.top);
|
||||
const rightTop = multiplyVec2(matrix, rect.right, rect.top);
|
||||
const leftBottom = multiplyVec2(matrix, rect.left, rect.bottom);
|
||||
const rightBottom = multiplyVec2(matrix, rect.right, rect.bottom);
|
||||
|
||||
var outrect = {};
|
||||
outrect.left = Math.min(left_top.x, right_top.x, left_bottom.x, right_bottom.x);
|
||||
outrect.top = Math.min(left_top.y, right_top.y, left_bottom.y, right_bottom.y);
|
||||
outrect.right = Math.max(left_top.x, right_top.x, left_bottom.x, right_bottom.x);
|
||||
outrect.bottom = Math.max(left_top.y, right_top.y, left_bottom.y, right_bottom.y);
|
||||
const outrect = {};
|
||||
outrect.left = Math.min(leftTop.x, rightTop.x, leftBottom.x, rightBottom.x);
|
||||
outrect.top = Math.min(leftTop.y, rightTop.y, leftBottom.y, rightBottom.y);
|
||||
outrect.right = Math.max(leftTop.x, rightTop.x, leftBottom.x, rightBottom.x);
|
||||
outrect.bottom = Math.max(leftTop.y, rightTop.y, leftBottom.y,
|
||||
rightBottom.y);
|
||||
return outrect;
|
||||
}
|
||||
|
||||
// Returns true if the applying the transform on an an axis aligned rectangle
|
||||
// results in another axis aligned rectangle.
|
||||
function is_simple_rotation(transform) {
|
||||
return !is_type_flag_set(transform, ROT_INVALID_VAL);
|
||||
}
|
||||
|
||||
export {format_transform_type, fill_transform_data, is_simple_transform,
|
||||
multiply_rect, is_simple_rotation};
|
||||
export {multiplyRect};
|
||||
|
||||
@@ -19,6 +19,7 @@ import {TRACE_TYPES, DUMP_TYPES} from '@/decode.js';
|
||||
const mixin = {
|
||||
showInTraceView(file) {
|
||||
return file.type == TRACE_TYPES.WINDOW_MANAGER ||
|
||||
file.type == TRACE_TYPES.ACCESSIBILITY ||
|
||||
file.type == TRACE_TYPES.SURFACE_FLINGER ||
|
||||
file.type == TRACE_TYPES.WAYLAND ||
|
||||
file.type == TRACE_TYPES.SYSTEM_UI ||
|
||||
@@ -30,6 +31,9 @@ const mixin = {
|
||||
file.type == DUMP_TYPES.SURFACE_FLINGER ||
|
||||
file.type == DUMP_TYPES.WAYLAND;
|
||||
},
|
||||
showInAccessibilityTraceView(file) {
|
||||
return file.type == TRACE_TYPES.ACCESSIBILITY;
|
||||
},
|
||||
showInWindowManagerTraceView(file) {
|
||||
return file.type == TRACE_TYPES.WINDOW_MANAGER ||
|
||||
file.type == DUMP_TYPES.WINDOW_MANAGER;
|
||||
|
||||
@@ -33,14 +33,38 @@ export default {
|
||||
|
||||
document.body.removeChild(a);
|
||||
},
|
||||
/**
|
||||
* Returns the file name, if the file has an extension use its default,
|
||||
* otherwise use ".mp4" for screen recording (name from proxy script) and
|
||||
* ".winscope" for traces
|
||||
* @param {*} fileName
|
||||
*/
|
||||
getFileName(fileName) {
|
||||
var re = /(?:\.([^.]+))?$/;
|
||||
var extension = re.exec(fileName)[1];
|
||||
if (!extension) {
|
||||
extension = "";
|
||||
}
|
||||
switch (extension) {
|
||||
case "": {
|
||||
if (fileName == "Screen recording") {
|
||||
return fileName + ".mp4"
|
||||
}
|
||||
return fileName + ".winscope"
|
||||
}
|
||||
default: return fileName
|
||||
}
|
||||
},
|
||||
async downloadAsZip(traces) {
|
||||
const zip = new JSZip();
|
||||
this.buttonClicked("Download All")
|
||||
|
||||
for (const trace of traces) {
|
||||
const traceFolder = zip.folder(trace.type);
|
||||
for (const file of trace.files) {
|
||||
var fileName = this.getFileName(file.filename);
|
||||
const blob = await fetch(file.blobUrl).then((r) => r.blob());
|
||||
traceFolder.file(file.filename, blob);
|
||||
traceFolder.file(fileName, blob);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,9 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import _ from "lodash";
|
||||
import { nanos_to_string } from "../transform";
|
||||
import { transitionMap } from "../utils/consts";
|
||||
|
||||
/**
|
||||
* Represents a continuous section of the timeline that is rendered into the
|
||||
@@ -33,10 +36,36 @@ class Block {
|
||||
}
|
||||
}
|
||||
|
||||
//Represents a continuous section of the tag display that relates to a specific transition
|
||||
class Transition {
|
||||
/**
|
||||
* Create a transition.
|
||||
* @param {number} startPos - The position of the start tag as a percentage
|
||||
* of the timeline width.
|
||||
* @param {number} startTime - The start timestamp in ms of the transition.
|
||||
* @param {number} endTime - The end timestamp in ms of the transition.
|
||||
* @param {number} width - The width of the transition as a percentage of the
|
||||
* timeline width.
|
||||
* @param {string} color - the color of transition depending on type.
|
||||
* @param {number} overlap - number of transitions with which this transition overlaps.
|
||||
* @param {string} tooltip - The tooltip of the transition, minus the type of transition.
|
||||
*/
|
||||
constructor(startPos, startTime, endTime, width, color, overlap, tooltip) {
|
||||
this.startPos = startPos;
|
||||
this.startTime = startTime;
|
||||
this.endTime = endTime;
|
||||
this.width = width;
|
||||
this.color = color;
|
||||
this.overlap = overlap;
|
||||
this.tooltip = tooltip;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This Mixin should only be injected into components which have the following:
|
||||
* - An element in the template referenced as 'timeline' (this.$refs.timeline).
|
||||
*/
|
||||
|
||||
export default {
|
||||
name: 'timeline',
|
||||
props: {
|
||||
@@ -53,6 +82,15 @@ export default {
|
||||
'scale': {
|
||||
type: Array,
|
||||
},
|
||||
'tags': {
|
||||
type: Array,
|
||||
},
|
||||
'errors': {
|
||||
type: Array,
|
||||
},
|
||||
'flickerMode': {
|
||||
type: Boolean,
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@@ -99,6 +137,73 @@ export default {
|
||||
|
||||
return Object.freeze(blocks);
|
||||
},
|
||||
|
||||
//Generates list of transitions to be displayed in flicker mode
|
||||
timelineTransitions() {
|
||||
const transitions = [];
|
||||
|
||||
//group tags by transition and 'id' property
|
||||
const groupedTags = _.groupBy(this.tags, tag => `"${tag.transition} ${tag.id}"`);
|
||||
|
||||
for (const transitionId in groupedTags) {
|
||||
const id = groupedTags[transitionId];
|
||||
//there are at least two tags per id, maybe more if multiple traces
|
||||
// determine which tag is the start (min of start times), which is end (max of end times)
|
||||
const startTimes = id.filter(tag => tag.isStartTag).map(tag => tag.timestamp);
|
||||
const endTimes = id.filter(tag => !tag.isStartTag).map(tag => tag.timestamp);
|
||||
|
||||
const transitionStartTime = Math.min(...startTimes);
|
||||
const transitionEndTime = Math.max(...endTimes);
|
||||
|
||||
//do not freeze new transition, as overlap still to be handled (defaulted to 0)
|
||||
const transition = this.generateTransition(
|
||||
transitionStartTime,
|
||||
transitionEndTime,
|
||||
id[0].transition,
|
||||
0,
|
||||
id[0].layerId,
|
||||
id[0].taskId,
|
||||
id[0].windowToken
|
||||
);
|
||||
transitions.push(transition);
|
||||
}
|
||||
|
||||
//sort transitions in ascending start position in order to handle overlap
|
||||
transitions.sort((a, b) => (a.startPos > b.startPos) ? 1 : -1);
|
||||
|
||||
//compare each transition to the ones that came before
|
||||
for (let curr=0; curr<transitions.length; curr++) {
|
||||
let processedTransitions = [];
|
||||
|
||||
for (let prev=0; prev<curr; prev++) {
|
||||
processedTransitions.push(transitions[prev]);
|
||||
|
||||
if (this.isSimultaneousTransition(transitions[curr], transitions[prev])) {
|
||||
transitions[curr].overlap++;
|
||||
}
|
||||
}
|
||||
|
||||
let overlapStore = processedTransitions.map(transition => transition.overlap);
|
||||
|
||||
if (transitions[curr].overlap === Math.max(...overlapStore)) {
|
||||
let previousTransition = processedTransitions.find(transition => {
|
||||
return transition.overlap===transitions[curr].overlap;
|
||||
});
|
||||
if (this.isSimultaneousTransition(transitions[curr], previousTransition)) {
|
||||
transitions[curr].overlap++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Object.freeze(transitions);
|
||||
},
|
||||
errorPositions() {
|
||||
if (!this.flickerMode) return [];
|
||||
const errorPositions = this.errors.map(
|
||||
error => ({ pos: this.position(error.timestamp), ts: error.timestamp })
|
||||
);
|
||||
return Object.freeze(errorPositions);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
position(item) {
|
||||
@@ -143,6 +248,16 @@ export default {
|
||||
return pos * (this.crop.right - this.crop.left) + this.crop.left;
|
||||
},
|
||||
|
||||
objectWidth(startTs, endTs) {
|
||||
return this.position(endTs) - this.position(startTs) + this.pointWidth;
|
||||
},
|
||||
|
||||
isSimultaneousTransition(currTransition, prevTransition) {
|
||||
return prevTransition.startPos <= currTransition.startPos
|
||||
&& currTransition.startPos <= prevTransition.startPos+prevTransition.width
|
||||
&& currTransition.overlap === prevTransition.overlap;
|
||||
},
|
||||
|
||||
/**
|
||||
* Converts a position as a percentage of the timeline width to a timestamp.
|
||||
* @param {number} position - target position as a percentage of the
|
||||
@@ -229,10 +344,27 @@ export default {
|
||||
if (this.disabled) {
|
||||
return;
|
||||
}
|
||||
const timestamp = parseInt(this.timeline[clickedOnTsIndex]);
|
||||
|
||||
var timestamp = parseInt(this.timeline[clickedOnTsIndex]);
|
||||
|
||||
//pointWidth is always 1
|
||||
//if offset percentage < 1, clickedOnTsIndex becomes negative, leading to a negative index
|
||||
if (clickedOnTsIndex < 0) {
|
||||
timestamp = parseInt(this.timeline[0])
|
||||
}
|
||||
this.$store.dispatch('updateTimelineTime', timestamp);
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles the error click event.
|
||||
* When an error in the timeline is clicked this function will update the timeline
|
||||
* to match the error timestamp.
|
||||
* @param {number} errorTimestamp
|
||||
*/
|
||||
onErrorClick(errorTimestamp) {
|
||||
this.$store.dispatch('updateTimelineTime', errorTimestamp);
|
||||
},
|
||||
|
||||
/**
|
||||
* Generate a block object that can be used by the timeline SVG to render
|
||||
* a transformed block that starts at `startTs` and ends at `endTs`.
|
||||
@@ -242,9 +374,35 @@ export default {
|
||||
* scale parameter.
|
||||
*/
|
||||
generateTimelineBlock(startTs, endTs) {
|
||||
const blockWidth = this.position(endTs) - this.position(startTs) +
|
||||
this.pointWidth;
|
||||
const blockWidth = this.objectWidth(startTs, endTs);
|
||||
return Object.freeze(new Block(this.position(startTs), blockWidth));
|
||||
},
|
||||
/**
|
||||
* Generate a transition object that can be used by the tag-timeline to render
|
||||
* a transformed transition that starts at `startTs` and ends at `endTs`.
|
||||
* @param {number} startTs - The timestamp at which the transition starts.
|
||||
* @param {number} endTs - The timestamp at which the transition ends.
|
||||
* @param {string} transitionType - The type of transition.
|
||||
* @param {number} overlap - The degree to which the transition overlaps with others.
|
||||
* @param {number} layerId - Helps determine if transition is associated with SF trace.
|
||||
* @param {number} taskId - Helps determine if transition is associated with WM trace.
|
||||
* @param {number} windowToken - Helps determine if transition is associated with WM trace.
|
||||
* @return {Transition} A transition object transformed to the timeline's crop and
|
||||
* scale parameter.
|
||||
*/
|
||||
generateTransition(startTs, endTs, transitionType, overlap, layerId, taskId, windowToken) {
|
||||
const transitionWidth = this.objectWidth(startTs, endTs);
|
||||
const transitionDesc = transitionMap.get(transitionType).desc;
|
||||
const transitionColor = transitionMap.get(transitionType).color;
|
||||
var tooltip = `${transitionDesc}. Start: ${nanos_to_string(startTs)}. End: ${nanos_to_string(endTs)}.`;
|
||||
|
||||
if (layerId !== 0 && taskId === 0 && windowToken === "") {
|
||||
tooltip += " SF only.";
|
||||
} else if ((taskId !== 0 || windowToken !== "") && layerId === 0) {
|
||||
tooltip += " WM only.";
|
||||
}
|
||||
|
||||
return new Transition(this.position(startTs), startTs, endTs, transitionWidth, transitionColor, overlap, tooltip);
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
@@ -1,350 +0,0 @@
|
||||
/*
|
||||
* Copyright 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Utility class for deriving state and visibility from the hierarchy. This
|
||||
* duplicates some of the logic in surface flinger. If the trace contains
|
||||
* composition state (visibleRegion), it will be used otherwise it will be
|
||||
* derived.
|
||||
*/
|
||||
import {multiply_rect, is_simple_rotation} from './matrix_utils.js';
|
||||
|
||||
// Layer flags
|
||||
const FLAG_HIDDEN = 0x01;
|
||||
const FLAG_OPAQUE = 0x02;
|
||||
const FLAG_SECURE = 0x80;
|
||||
|
||||
function flags_to_string(flags) {
|
||||
if (!flags) return '';
|
||||
const verboseFlags = [];
|
||||
if (flags & FLAG_HIDDEN) verboseFlags.push('HIDDEN');
|
||||
if (flags & FLAG_OPAQUE) verboseFlags.push('OPAQUE');
|
||||
if (flags & FLAG_SECURE) verboseFlags.push('SECURE');
|
||||
return verboseFlags.join('|') + ' (' + flags + ')';
|
||||
}
|
||||
|
||||
function is_empty(region) {
|
||||
return region == undefined ||
|
||||
region.rect == undefined ||
|
||||
region.rect.length == 0 ||
|
||||
region.rect.every(function(r) {
|
||||
return is_empty_rect(r);
|
||||
} );
|
||||
}
|
||||
|
||||
function is_empty_rect(rect) {
|
||||
const right = rect.right || 0;
|
||||
const left = rect.left || 0;
|
||||
const top = rect.top || 0;
|
||||
const bottom = rect.bottom || 0;
|
||||
|
||||
return (right - left) <= 0 || (bottom - top) <= 0;
|
||||
}
|
||||
|
||||
function is_rect_empty_and_valid(rect) {
|
||||
return rect &&
|
||||
(rect.left - rect.right === 0 || rect.top - rect.bottom === 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* The transformation matrix is defined as the product of:
|
||||
* | cos(a) -sin(a) | \/ | X 0 |
|
||||
* | sin(a) cos(a) | /\ | 0 Y |
|
||||
*
|
||||
* where a is a rotation angle, and X and Y are scaling factors.
|
||||
* A transformation matrix is invalid when either X or Y is zero,
|
||||
* as a rotation matrix is valid for any angle. When either X or Y
|
||||
* is 0, then the scaling matrix is not invertible, which makes the
|
||||
* transformation matrix not invertible as well. A 2D matrix with
|
||||
* components | A B | is not invertible if and only if AD - BC = 0.
|
||||
* | C D |
|
||||
* This check is included above.
|
||||
*/
|
||||
function is_transform_invalid(transform) {
|
||||
return !transform || (transform.dsdx * transform.dtdy ===
|
||||
transform.dtdx * transform.dsdy); // determinant of transform
|
||||
}
|
||||
|
||||
function is_opaque(layer) {
|
||||
if (layer.color == undefined || layer.color.a == undefined || layer.color.a != 1) return false;
|
||||
return layer.isOpaque;
|
||||
}
|
||||
|
||||
function fills_color(layer) {
|
||||
return layer.color && layer.color.a > 0 &&
|
||||
layer.color.r >= 0 && layer.color.g >= 0 &&
|
||||
layer.color.b >= 0;
|
||||
}
|
||||
|
||||
function draws_shadows(layer) {
|
||||
return layer.shadowRadius && layer.shadowRadius > 0;
|
||||
}
|
||||
|
||||
function has_blur(layer) {
|
||||
return layer.backgroundBlurRadius && layer.backgroundBlurRadius > 0;
|
||||
}
|
||||
|
||||
function has_effects(layer) {
|
||||
// Support previous color layer
|
||||
if (layer.type === 'ColorLayer') return true;
|
||||
|
||||
// Support newer effect layer
|
||||
return layer.type === 'EffectLayer' &&
|
||||
(fills_color(layer) || draws_shadows(layer) || has_blur(layer));
|
||||
}
|
||||
|
||||
function is_hidden_by_policy(layer) {
|
||||
return layer.flags & FLAG_HIDDEN == FLAG_HIDDEN ||
|
||||
// offscreen layer root has a unique layer id
|
||||
layer.id == 0x7FFFFFFD;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the layer is visible based on its visibleRegion if available
|
||||
* or its type, active buffer content, alpha and properties.
|
||||
*/
|
||||
function is_visible(layer, hiddenByPolicy, includesCompositionState) {
|
||||
if (includesCompositionState) {
|
||||
return !is_empty(layer.visibleRegion);
|
||||
}
|
||||
|
||||
if (hiddenByPolicy) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!layer.activeBuffer && !has_effects(layer)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!layer.color || !layer.color.a || layer.color.a == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (layer.occludedBy && layer.occludedBy.length > 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!layer.bounds || is_empty_rect(layer.bounds)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function get_visibility_reason(layer, includesCompositionState) {
|
||||
if (layer.type === 'ContainerLayer') {
|
||||
return 'ContainerLayer';
|
||||
}
|
||||
|
||||
if (is_hidden_by_policy(layer)) {
|
||||
return 'Flag is hidden';
|
||||
}
|
||||
|
||||
if (layer.hidden) {
|
||||
return 'Hidden by parent';
|
||||
}
|
||||
|
||||
const isBufferLayer = (layer.type === 'BufferStateLayer' ||
|
||||
layer.type === 'BufferQueueLayer');
|
||||
if (isBufferLayer && (!layer.activeBuffer ||
|
||||
layer.activeBuffer.height === 0 || layer.activeBuffer.width === 0)) {
|
||||
return 'Buffer is empty';
|
||||
}
|
||||
|
||||
if (!layer.color || !layer.color.a || layer.color.a == 0) {
|
||||
return 'Alpha is 0';
|
||||
}
|
||||
|
||||
if (is_rect_empty_and_valid(layer.crop)) {
|
||||
return 'Crop is 0x0';
|
||||
}
|
||||
|
||||
if (!layer.bounds || is_empty_rect(layer.bounds)) {
|
||||
return 'Bounds is 0x0';
|
||||
}
|
||||
|
||||
if (is_transform_invalid(layer.transform)) {
|
||||
return 'Transform is invalid';
|
||||
}
|
||||
if (layer.isRelativeOf && layer.zOrderRelativeOf == -1) {
|
||||
return 'RelativeOf layer has been removed';
|
||||
}
|
||||
|
||||
const isEffectLayer = (layer.type === 'EffectLayer');
|
||||
if (isEffectLayer && !fills_color(layer) &&
|
||||
!draws_shadows(layer) && !has_blur(layer)) {
|
||||
return 'Effect layer does not have color fill, shadow or blur';
|
||||
}
|
||||
|
||||
if (layer.occludedBy && layer.occludedBy.length > 0) {
|
||||
return 'Layer is occluded by:' + layer.occludedBy.join();
|
||||
}
|
||||
|
||||
if (includesCompositionState && is_empty(layer.visibleRegion)) {
|
||||
return 'Visible region calculated by Composition Engine is empty';
|
||||
}
|
||||
|
||||
if (layer.visible) {
|
||||
return 'Unknown';
|
||||
};
|
||||
}
|
||||
|
||||
// Returns true if rectA overlaps rectB
|
||||
function overlaps(rectA, rectB) {
|
||||
return rectA.left < rectB.right && rectA.right > rectB.left &&
|
||||
rectA.top < rectB.bottom && rectA.bottom > rectA.top;
|
||||
}
|
||||
|
||||
// Returns true if outer rect contains inner rect
|
||||
function contains(outerLayer, innerLayer) {
|
||||
if (!is_simple_rotation(outerLayer.transform) ||
|
||||
!is_simple_rotation(innerLayer.transform)) {
|
||||
return false;
|
||||
}
|
||||
const outer = screen_bounds(outerLayer);
|
||||
const inner = screen_bounds(innerLayer);
|
||||
return inner.left >= outer.left && inner.top >= outer.top &&
|
||||
inner.right <= outer.right && inner.bottom <= outer.bottom;
|
||||
}
|
||||
|
||||
function screen_bounds(layer) {
|
||||
if (layer.screenBounds) return layer.screenBounds;
|
||||
const transformMatrix = layer.transform;
|
||||
const tx = layer.position ? layer.position.x || 0 : 0;
|
||||
const ty = layer.position ? layer.position.y || 0 : 0;
|
||||
|
||||
transformMatrix.tx = tx;
|
||||
transformMatrix.ty = ty;
|
||||
return multiply_rect(transformMatrix, layer.bounds);
|
||||
}
|
||||
|
||||
// Traverse in z-order from top to bottom and fill in occlusion data
|
||||
function fill_occlusion_state(layerMap, rootLayers, includesCompositionState) {
|
||||
const layers = rootLayers.filter((layer) => !layer.isRelativeOf);
|
||||
traverse_top_to_bottom(layerMap, layers, {opaqueRects: [], transparentRects: [], screenBounds: null}, (layer, globalState) => {
|
||||
if (layer.name.startsWith('Root#0') && layer.sourceBounds) {
|
||||
globalState.screenBounds = {left: 0, top: 0, bottom: layer.sourceBounds.bottom, right: layer.sourceBounds.right};
|
||||
}
|
||||
|
||||
const visible = is_visible(layer, layer.hidden, includesCompositionState);
|
||||
if (visible) {
|
||||
const fullyOccludes = (testLayer) => contains(testLayer, layer) && !layer.cornerRadius;
|
||||
const partiallyOccludes = (testLayer) => overlaps(screen_bounds(testLayer), screen_bounds(layer));
|
||||
const covers = (testLayer) => overlaps(screen_bounds(testLayer), screen_bounds(layer));
|
||||
|
||||
layer.occludedBy = globalState.opaqueRects.filter(fullyOccludes).map((layer) => layer.id);
|
||||
layer.partiallyOccludedBy = globalState.opaqueRects.filter(partiallyOccludes)
|
||||
.filter((p) => layer.occludedBy.indexOf(p.id) == -1)
|
||||
.map((layer) => layer.id);
|
||||
layer.coveredBy = globalState.transparentRects.filter(covers).map((layer) => layer.id);
|
||||
|
||||
if (is_opaque(layer)) {
|
||||
globalState.opaqueRects.push(layer);
|
||||
} else {
|
||||
globalState.transparentRects.push(layer);
|
||||
}
|
||||
}
|
||||
|
||||
layer.visible = is_visible(layer, layer.hidden, includesCompositionState);
|
||||
if (!layer.visible) {
|
||||
layer.invisibleDueTo = get_visibility_reason(layer, includesCompositionState);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function traverse_top_to_bottom(layerMap, rootLayers, globalState, fn) {
|
||||
for (let i = rootLayers.length-1; i >=0; i--) {
|
||||
const relatives = [];
|
||||
for (const id of rootLayers[i].relatives) {
|
||||
if (!layerMap.hasOwnProperty(id)) {
|
||||
// TODO (b/162500053): so that this doesn't need to be checked here
|
||||
console.warn(
|
||||
`Relative layer with id ${id} not found in dumped layers... ` +
|
||||
`Skipping layer in traversal...`);
|
||||
} else {
|
||||
relatives.push(layerMap[id]);
|
||||
}
|
||||
}
|
||||
|
||||
const children = [];
|
||||
for (const id of rootLayers[i].children) {
|
||||
if (!layerMap.hasOwnProperty(id)) {
|
||||
// TODO (b/162500053): so that this doesn't need to be checked here
|
||||
console.warn(
|
||||
`Child layer with id ${id} not found in dumped layers... ` +
|
||||
`Skipping layer in traversal...`);
|
||||
} else {
|
||||
children.push(layerMap[id]);
|
||||
}
|
||||
}
|
||||
|
||||
// traverse through relatives and children that are not relatives
|
||||
const traverseList = relatives
|
||||
.concat(children.filter((layer) => !layer.isRelativeOf));
|
||||
|
||||
traverseList.sort((lhs, rhs) => rhs.z - lhs.z);
|
||||
|
||||
traverseList.filter((layer) => layer.z >=0).forEach((layer) => {
|
||||
traverse_top_to_bottom(layerMap, [layer], globalState, fn);
|
||||
});
|
||||
|
||||
fn(rootLayers[i], globalState);
|
||||
|
||||
traverseList.filter((layer) => layer.z < 0).forEach((layer) => {
|
||||
traverse_top_to_bottom(layerMap, [layer], globalState, fn);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Traverse all children and fill in any inherited states.
|
||||
function fill_inherited_state(layerMap, rootLayers) {
|
||||
traverse(layerMap, rootLayers, (layer, parent) => {
|
||||
const parentHidden = parent && parent.hidden;
|
||||
layer.hidden = is_hidden_by_policy(layer) || parentHidden;
|
||||
layer.verboseFlags = flags_to_string(layer.flags);
|
||||
|
||||
if (!layer.bounds) {
|
||||
if (!layer.sourceBounds) {
|
||||
layer.bounds = layer.sourceBounds;
|
||||
} else if (parent) {
|
||||
layer.bounds = parent.bounds;
|
||||
} else {
|
||||
layer.bounds = {left: 0, top: 0, right: 0, bottom: 0};
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function traverse(layerMap, rootLayers, fn) {
|
||||
for (let i = rootLayers.length-1; i >=0; i--) {
|
||||
const parentId = rootLayers[i].parent;
|
||||
const parent = parentId == -1 ? null : layerMap[parentId];
|
||||
fn(rootLayers[i], parent);
|
||||
const children = rootLayers[i].children.map(
|
||||
(id) => {
|
||||
const child = layerMap[id];
|
||||
if (child == null) {
|
||||
console.warn(
|
||||
`Child layer with id ${id} in parent layer id ${rootLayers[i].id} not found... ` +
|
||||
`Skipping layer in traversal...`);
|
||||
}
|
||||
return child;
|
||||
}).filter(item => item !== undefined);
|
||||
traverse(layerMap, children, fn);
|
||||
}
|
||||
}
|
||||
|
||||
export {fill_occlusion_state, fill_inherited_state};
|
||||
33
tools/winscope/src/traces/Accessibility.ts
Normal file
33
tools/winscope/src/traces/Accessibility.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright 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.
|
||||
*/
|
||||
|
||||
import { FILE_TYPES, TRACE_TYPES } from '@/decode.js';
|
||||
import TraceBase from './TraceBase';
|
||||
|
||||
export default class Accessibility extends TraceBase {
|
||||
accessibilityTraceFile: Object;
|
||||
|
||||
constructor(files) {
|
||||
const accessibilityTraceFile = files[FILE_TYPES.ACCESSIBILITY_TRACE];
|
||||
super(accessibilityTraceFile.data, accessibilityTraceFile.timeline, files);
|
||||
|
||||
this.accessibilityTraceFile = accessibilityTraceFile;
|
||||
}
|
||||
|
||||
get type() {
|
||||
return TRACE_TYPES.ACCESSIBILITY;
|
||||
}
|
||||
}
|
||||
@@ -16,18 +16,26 @@
|
||||
|
||||
import { FILE_TYPES, TRACE_TYPES } from '@/decode.js';
|
||||
import TraceBase from './TraceBase';
|
||||
import { LayersTrace } from '@/flickerlib';
|
||||
|
||||
export default class SurfaceFlinger extends TraceBase {
|
||||
sfTraceFile: any;
|
||||
sfTraceFile: Object;
|
||||
tagGenerationTrace: Object;
|
||||
|
||||
constructor(files) {
|
||||
const sfTraceFile = files[FILE_TYPES.SURFACE_FLINGER_TRACE];
|
||||
const tagGenerationTrace = files[FILE_TYPES.SURFACE_FLINGER_TRACE].tagGenerationTrace;
|
||||
super(sfTraceFile.data, sfTraceFile.timeline, files);
|
||||
|
||||
this.tagGenerationTrace = tagGenerationTrace;
|
||||
this.sfTraceFile = sfTraceFile;
|
||||
}
|
||||
|
||||
get type() {
|
||||
return TRACE_TYPES.SURFACE_FLINGER;
|
||||
}
|
||||
|
||||
static fromProto(proto: any): LayersTrace {
|
||||
return LayersTrace.fromProto(proto);
|
||||
}
|
||||
}
|
||||
|
||||
37
tools/winscope/src/traces/TraceError.ts
Normal file
37
tools/winscope/src/traces/TraceError.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright 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.
|
||||
*/
|
||||
|
||||
import { FILE_TYPES, TRACE_TYPES } from '@/decode.js';
|
||||
import TraceBase from './TraceBase';
|
||||
import { ErrorTrace } from '@/flickerlib';
|
||||
|
||||
export default class TraceError extends TraceBase {
|
||||
errorTraceFile: Object;
|
||||
|
||||
constructor(files) {
|
||||
const errorTraceFile = files[FILE_TYPES.ERROR_TRACE];
|
||||
super(errorTraceFile.data, errorTraceFile.timeline, files);
|
||||
this.errorTraceFile = errorTraceFile;
|
||||
}
|
||||
|
||||
get type() {
|
||||
return TRACE_TYPES.ERROR;
|
||||
}
|
||||
|
||||
static fromProto(proto: any): ErrorTrace {
|
||||
return ErrorTrace.fromProto(proto);
|
||||
}
|
||||
}
|
||||
37
tools/winscope/src/traces/TraceTag.ts
Normal file
37
tools/winscope/src/traces/TraceTag.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright 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.
|
||||
*/
|
||||
|
||||
import { FILE_TYPES, TRACE_TYPES } from '@/decode.js';
|
||||
import TraceBase from './TraceBase';
|
||||
import { TagTrace } from '@/flickerlib';
|
||||
|
||||
export default class TraceTag extends TraceBase {
|
||||
tagTraceFile: Object;
|
||||
|
||||
constructor(files) {
|
||||
const tagTraceFile = files[FILE_TYPES.TAG_TRACE];
|
||||
super(tagTraceFile.data, tagTraceFile.timeline, files);
|
||||
this.tagTraceFile = tagTraceFile;
|
||||
}
|
||||
|
||||
get type() {
|
||||
return TRACE_TYPES.TAG;
|
||||
}
|
||||
|
||||
static fromProto(proto: any): TagTrace {
|
||||
return TagTrace.fromProto(proto);
|
||||
}
|
||||
}
|
||||
@@ -21,11 +21,14 @@ import { WindowManagerTrace } from '@/flickerlib';
|
||||
|
||||
export default class WindowManager extends TraceBase {
|
||||
wmTraceFile: Object;
|
||||
tagGenerationTrace: Object;
|
||||
|
||||
constructor(files) {
|
||||
const wmTraceFile = files[FILE_TYPES.WINDOW_MANAGER_TRACE];
|
||||
const tagGenerationTrace = files[FILE_TYPES.WINDOW_MANAGER_TRACE].tagGenerationTrace;
|
||||
super(wmTraceFile.data, wmTraceFile.timeline, files);
|
||||
|
||||
this.tagGenerationTrace = tagGenerationTrace;
|
||||
this.wmTraceFile = wmTraceFile;
|
||||
}
|
||||
|
||||
@@ -33,7 +36,7 @@ export default class WindowManager extends TraceBase {
|
||||
return TRACE_TYPES.WINDOW_MANAGER;
|
||||
}
|
||||
|
||||
static fromProto(proto): WindowManagerTrace {
|
||||
static fromProto(proto: any): WindowManagerTrace {
|
||||
return WindowManagerTrace.fromProto(proto);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
|
||||
import {DiffType} from './utils/diff.js';
|
||||
import {regExpTimestampSearch} from './utils/consts';
|
||||
|
||||
// kind - a type used for categorization of different levels
|
||||
// name - name of the node
|
||||
@@ -103,7 +104,7 @@ function transform({
|
||||
stableId: stableIdResolved,
|
||||
visible: call(visible, obj),
|
||||
childrenVisible: transformedChildren.some((c) => {
|
||||
return c.childrenVisible || c.visible;
|
||||
return c.childrenVisible || c.isVisible;
|
||||
}),
|
||||
flattened: call(flattened, obj),
|
||||
};
|
||||
@@ -309,7 +310,7 @@ class ObjectTransformer {
|
||||
|
||||
transformedObj = {
|
||||
kind: '',
|
||||
name: name + ': ' + child.name,
|
||||
name: (isTerminal(name) ? compareWithName : name) + ': ' + child.name,
|
||||
stableId,
|
||||
children: child.children,
|
||||
combined: true,
|
||||
@@ -376,11 +377,42 @@ function nanos_to_string(elapsedRealtimeNanos) {
|
||||
return parts.reverse().join('');
|
||||
}
|
||||
|
||||
function string_to_nanos(stringTime) {
|
||||
//isolate the times for each unit in an array
|
||||
var times = stringTime.split(/\D+/).filter(unit => unit.length > 0);
|
||||
|
||||
//add zeroes to start of array if only partial timestamp is input
|
||||
while (times.length<5) {
|
||||
times.unshift("0");
|
||||
}
|
||||
|
||||
var units = [24*60*60, 60*60, 60, 1, 0.001];
|
||||
var nanos = 0;
|
||||
//multiply the times by the relevant unit and sum
|
||||
for (var x=0; x<5; x++) {
|
||||
nanos += units[x]*parseInt(times[x]);
|
||||
}
|
||||
return nanos*(10**9);
|
||||
}
|
||||
|
||||
// Returns a UI element used highlight a visible entry.
|
||||
// eslint-disable-next-line camelcase
|
||||
function get_visible_chip() {
|
||||
return {short: 'V', long: 'visible', class: 'default'};
|
||||
}
|
||||
|
||||
// Returns closest timestamp in timeline based on search input*/
|
||||
function getClosestTimestamp(searchInput, timeline) {
|
||||
if (regExpTimestampSearch.test(searchInput)) {
|
||||
var roundedTimestamp = parseInt(searchInput);
|
||||
} else {
|
||||
var roundedTimestamp = string_to_nanos(searchInput);
|
||||
}
|
||||
const closestTimestamp = timeline.reduce((prev, curr) => {
|
||||
return Math.abs(curr-roundedTimestamp) < Math.abs(prev-roundedTimestamp) ? curr : prev;
|
||||
});
|
||||
return closestTimestamp;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line camelcase
|
||||
export {transform, ObjectTransformer, nanos_to_string, get_visible_chip};
|
||||
export {transform, ObjectTransformer, nanos_to_string, string_to_nanos, get_visible_chip, getClosestTimestamp};
|
||||
|
||||
52
tools/winscope/src/transform_accessibility.js
Normal file
52
tools/winscope/src/transform_accessibility.js
Normal file
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright 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.
|
||||
*/
|
||||
|
||||
import { transform, nanos_to_string, get_visible_chip } from './transform.js'
|
||||
|
||||
function transform_accessibility(accessibility) {
|
||||
return transform({
|
||||
obj: accessibility,
|
||||
kind: 'accessibility',
|
||||
name: 'accessibility',
|
||||
children: []
|
||||
});
|
||||
}
|
||||
|
||||
function transform_entry(entry) {
|
||||
return transform({
|
||||
obj: entry,
|
||||
kind: 'entry',
|
||||
name: nanos_to_string(entry.elapsedRealtimeNanos),
|
||||
children: [
|
||||
[entry.accessibilityService, transform_accessibility],
|
||||
],
|
||||
timestamp: entry.elapsedRealtimeNanos,
|
||||
stableId: 'entry'
|
||||
});
|
||||
}
|
||||
|
||||
function transform_accessibility_trace(entries) {
|
||||
return transform({
|
||||
obj: entries,
|
||||
kind: 'entries',
|
||||
name: 'entries',
|
||||
children: [
|
||||
[entries.entry, transform_entry],
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
export { transform_accessibility_trace };
|
||||
@@ -1,240 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017, 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.
|
||||
*/
|
||||
|
||||
// eslint-disable-next-line camelcase
|
||||
import {transform, nanos_to_string, get_visible_chip} from './transform.js';
|
||||
// eslint-disable-next-line camelcase
|
||||
import {fill_occlusion_state, fill_inherited_state} from './sf_visibility.js';
|
||||
import {getSimplifiedLayerName} from './utils/names';
|
||||
import ObjectFormatter from './flickerlib/ObjectFormatter'
|
||||
|
||||
const RELATIVE_Z_CHIP = {
|
||||
short: 'RelZ',
|
||||
long: 'Is relative Z-ordered to another surface',
|
||||
class: 'warn',
|
||||
};
|
||||
const RELATIVE_Z_PARENT_CHIP = {
|
||||
short: 'RelZParent',
|
||||
long: 'Something is relative Z-ordered to this surface',
|
||||
class: 'warn',
|
||||
};
|
||||
const MISSING_LAYER = {
|
||||
short: 'MissingLayer',
|
||||
long:
|
||||
'This layer was referenced from the parent, but not present in the trace',
|
||||
class: 'error',
|
||||
};
|
||||
const GPU_CHIP = {
|
||||
short: 'GPU',
|
||||
long: 'This layer was composed on the GPU',
|
||||
class: 'gpu',
|
||||
};
|
||||
const HWC_CHIP = {
|
||||
short: 'HWC',
|
||||
long: 'This layer was composed by Hardware Composer',
|
||||
class: 'hwc',
|
||||
};
|
||||
|
||||
function transformLayer(layer) {
|
||||
function offsetTo(bounds, x, y) {
|
||||
return {
|
||||
right: bounds.right - (bounds.left - x),
|
||||
bottom: bounds.bottom - (bounds.top - y),
|
||||
left: x,
|
||||
top: y,
|
||||
};
|
||||
}
|
||||
|
||||
function getRect(layer) {
|
||||
let result = layer.bounds;
|
||||
const tx = layer.position ? layer.position.x || 0 : 0;
|
||||
const ty = layer.position ? layer.position.y || 0 : 0;
|
||||
result.label = layer.name;
|
||||
result.transform = layer.transform;
|
||||
result.transform.tx = tx;
|
||||
result.transform.ty = ty;
|
||||
return result;
|
||||
}
|
||||
|
||||
function addHwcCompositionTypeChip(layer) {
|
||||
if (layer.hwcCompositionType === 'CLIENT') {
|
||||
chips.push(GPU_CHIP);
|
||||
} else if (layer.hwcCompositionType === 'DEVICE' ||
|
||||
layer.hwcCompositionType === 'SOLID_COLOR') {
|
||||
chips.push(HWC_CHIP);
|
||||
}
|
||||
}
|
||||
|
||||
const chips = [];
|
||||
if (layer.visible) {
|
||||
chips.push(get_visible_chip());
|
||||
}
|
||||
if ((layer.zOrderRelativeOf || -1) !== -1) {
|
||||
chips.push(RELATIVE_Z_CHIP);
|
||||
}
|
||||
if (layer.zOrderRelativeParentOf !== undefined) {
|
||||
chips.push(RELATIVE_Z_PARENT_CHIP);
|
||||
}
|
||||
if (layer.missing) {
|
||||
chips.push(MISSING_LAYER);
|
||||
}
|
||||
addHwcCompositionTypeChip(layer);
|
||||
|
||||
const rect = layer.visible && layer.bounds !== null ?
|
||||
getRect(layer) : undefined;
|
||||
|
||||
const simplifiedLayerName = getSimplifiedLayerName(layer.name);
|
||||
const shortName = simplifiedLayerName ?
|
||||
layer.id + ': ' + simplifiedLayerName : undefined;
|
||||
|
||||
const transformedLayer = transform({
|
||||
obj: ObjectFormatter.format(layer),
|
||||
kind: '',
|
||||
name: layer.id + ': ' + layer.name,
|
||||
shortName,
|
||||
children: [[layer.resolvedChildren, transformLayer]],
|
||||
rect,
|
||||
undefined /* bounds */,
|
||||
highlight: rect,
|
||||
chips,
|
||||
visible: layer.visible,
|
||||
freeze: false,
|
||||
});
|
||||
|
||||
// NOTE: Temporary until refactored to use flickerlib
|
||||
transformedLayer.invisibleDueTo = layer.invisibleDueTo;
|
||||
transformedLayer.occludedBy = layer.occludedBy;
|
||||
transformedLayer.partiallyOccludedBy = layer.partiallyOccludedBy;
|
||||
transformedLayer.coveredBy = layer.coveredBy;
|
||||
|
||||
return Object.freeze(transformedLayer);
|
||||
}
|
||||
|
||||
function missingLayer(childId) {
|
||||
return {
|
||||
name: 'layer #' + childId,
|
||||
missing: true,
|
||||
zOrderRelativeOf: -1,
|
||||
transform: {dsdx: 1, dtdx: 0, dsdy: 0, dtdy: 1},
|
||||
};
|
||||
}
|
||||
|
||||
function transformLayers(includesCompositionState, layers) {
|
||||
const idToItem = {};
|
||||
const isChild = {};
|
||||
|
||||
const layersList = layers.layers || [];
|
||||
|
||||
layersList.forEach((e) => {
|
||||
idToItem[e.id] = e;
|
||||
});
|
||||
layersList.forEach((e) => {
|
||||
e.resolvedChildren = [];
|
||||
if (Array.isArray(e.children)) {
|
||||
e.resolvedChildren = e.children.map(
|
||||
(childId) => idToItem[childId] || missingLayer(childId));
|
||||
e.children.forEach((childId) => {
|
||||
isChild[childId] = true;
|
||||
});
|
||||
}
|
||||
// We don't clean up relatives when the relative parent is removed, so it
|
||||
// may be inconsistent
|
||||
if ((e.zOrderRelativeOf || -1) !== -1 && (idToItem[e.zOrderRelativeOf])) {
|
||||
idToItem[e.zOrderRelativeOf].zOrderRelativeParentOf = e.id;
|
||||
}
|
||||
});
|
||||
|
||||
const roots = layersList.filter((e) => !isChild[e.id]);
|
||||
fill_inherited_state(idToItem, roots);
|
||||
|
||||
// Backwards compatibility check
|
||||
const occlusionDetectionCompatible = roots[0].bounds !== null;
|
||||
if (occlusionDetectionCompatible) {
|
||||
fill_occlusion_state(idToItem, roots, includesCompositionState);
|
||||
}
|
||||
function foreachTree(nodes, fun) {
|
||||
nodes.forEach((n) => {
|
||||
fun(n);
|
||||
foreachTree(n.children, fun);
|
||||
});
|
||||
}
|
||||
|
||||
const idToTransformed = {};
|
||||
const transformedRoots = roots.map((r) =>
|
||||
transformLayer(r, {
|
||||
parentBounds: {left: 0, right: 0, top: 0, bottom: 0},
|
||||
parentHidden: null,
|
||||
}));
|
||||
|
||||
foreachTree(transformedRoots, (n) => {
|
||||
idToTransformed[n.obj.id] = n;
|
||||
});
|
||||
const flattened = [];
|
||||
layersList.forEach((e) => {
|
||||
flattened.push(idToTransformed[e.id]);
|
||||
});
|
||||
|
||||
return transform({
|
||||
obj: {},
|
||||
kind: 'layers',
|
||||
name: 'layers',
|
||||
children: [
|
||||
[transformedRoots, (c) => c],
|
||||
],
|
||||
rectsTransform(r) {
|
||||
const res = [];
|
||||
flattened.forEach((l) => {
|
||||
if (l.rect) {
|
||||
res.push(l.rect);
|
||||
}
|
||||
});
|
||||
return res.reverse();
|
||||
},
|
||||
flattened,
|
||||
});
|
||||
}
|
||||
|
||||
function transformLayersEntry(entry) {
|
||||
const includesCompositionState = !entry.excludesCompositionState;
|
||||
return transform({
|
||||
obj: entry,
|
||||
kind: 'entry',
|
||||
name: nanos_to_string(entry.elapsedRealtimeNanos) + ' - ' + entry.where,
|
||||
children: [
|
||||
[
|
||||
[entry.layers],
|
||||
(layer) => transformLayers(includesCompositionState, layer),
|
||||
],
|
||||
],
|
||||
timestamp: entry.elapsedRealtimeNanos,
|
||||
stableId: 'entry',
|
||||
});
|
||||
}
|
||||
|
||||
function transformLayersTrace(entries) {
|
||||
const r = transform({
|
||||
obj: entries,
|
||||
kind: 'layerstrace',
|
||||
name: 'layerstrace',
|
||||
children: [
|
||||
[entries.entry, transformLayersEntry],
|
||||
],
|
||||
});
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
export {transformLayers, transformLayersTrace};
|
||||
@@ -14,6 +14,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import TransitionType from "../flickerlib/tags/TransitionType";
|
||||
|
||||
/**
|
||||
* Should be kept in sync with ENUM is in Google3 under:
|
||||
* google3/wireless/android/tools/android_bug_tool/extension/common/actions
|
||||
@@ -31,6 +33,12 @@ const NAVIGATION_STYLE = {
|
||||
TARGETED: 'Targeted',
|
||||
};
|
||||
|
||||
const SEARCH_TYPE = {
|
||||
TRANSITIONS: 'Transitions',
|
||||
ERRORS: 'Errors',
|
||||
TIMESTAMP: 'Timestamp',
|
||||
};
|
||||
|
||||
const logLevel = {
|
||||
INFO: 'info',
|
||||
DEBUG: 'debug',
|
||||
@@ -38,6 +46,23 @@ const logLevel = {
|
||||
WARN: 'warn',
|
||||
ERROR: 'error',
|
||||
WTF: 'wtf',
|
||||
}
|
||||
};
|
||||
|
||||
export { WebContentScriptMessageType, NAVIGATION_STYLE, logLevel };
|
||||
const transitionMap = new Map([
|
||||
[TransitionType.ROTATION, {desc: 'Rotation', color: '#9900ffff'}],
|
||||
[TransitionType.PIP_ENTER, {desc: 'Entering PIP mode', color: '#4a86e8ff'}],
|
||||
[TransitionType.PIP_RESIZE, {desc: 'Resizing PIP mode', color: '#2b9e94ff'}],
|
||||
[TransitionType.PIP_CLOSE, {desc: 'Closing PIP mode', color: 'rgb(57, 57, 182)'}],
|
||||
[TransitionType.PIP_EXIT, {desc: 'Exiting PIP mode', color: 'darkblue'}],
|
||||
[TransitionType.APP_LAUNCH, {desc: 'Launching app', color: '#ef6befff'}],
|
||||
[TransitionType.APP_CLOSE, {desc: 'Closing app', color: '#d10ddfff'}],
|
||||
[TransitionType.IME_APPEAR, {desc: 'IME appearing', color: '#ff9900ff'}],
|
||||
[TransitionType.IME_DISAPPEAR, {desc: 'IME disappearing', color: '#ad6800ff'}],
|
||||
[TransitionType.APP_PAIRS_ENTER, {desc: 'Entering app pairs mode', color: 'rgb(58, 151, 39)'}],
|
||||
[TransitionType.APP_PAIRS_EXIT, {desc: 'Exiting app pairs mode', color: 'rgb(45, 110, 32)'}],
|
||||
])
|
||||
|
||||
//used to split timestamp search input by unit, to convert to nanoseconds
|
||||
const regExpTimestampSearch = new RegExp(/^\d+$/);
|
||||
|
||||
export { WebContentScriptMessageType, NAVIGATION_STYLE, SEARCH_TYPE, logLevel, transitionMap, regExpTimestampSearch };
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
|
||||
// TODO (b/162300507): Get rid of cloning
|
||||
import cloneDeep from 'lodash.clonedeep';
|
||||
import ObjectFormatter from '../flickerlib/ObjectFormatter';
|
||||
|
||||
export const DiffType = Object.freeze({
|
||||
NONE: 'none',
|
||||
@@ -26,21 +26,6 @@ export const DiffType = Object.freeze({
|
||||
MODIFIED: 'modified',
|
||||
});
|
||||
|
||||
export function asRawTreeViewObject(obj) {
|
||||
const children = obj.children?.map(child => child.rawTreeViewObject) ?? []
|
||||
|
||||
return {
|
||||
kind: obj.kind,
|
||||
name: obj.name,
|
||||
shortName: obj.shortName,
|
||||
stableId: obj.stableId,
|
||||
chips: obj.chips,
|
||||
obj: obj.obj,
|
||||
children,
|
||||
ref: obj,
|
||||
};
|
||||
}
|
||||
|
||||
export function defaultModifiedCheck(newNode, oldNode) {
|
||||
if (!newNode && !oldNode) {
|
||||
return false;
|
||||
@@ -50,29 +35,16 @@ export function defaultModifiedCheck(newNode, oldNode) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return JSON.stringify(newNode.obj) !== JSON.stringify(oldNode.obj);
|
||||
}
|
||||
|
||||
function isPrimitive(test) {
|
||||
return test !== Object(test);
|
||||
return !newNode.equals(oldNode);
|
||||
}
|
||||
|
||||
export class DiffGenerator {
|
||||
constructor(tree) {
|
||||
if (tree.rawTreeViewObject) {
|
||||
this.tree = tree.rawTreeViewObject;
|
||||
} else {
|
||||
this.tree = tree;
|
||||
}
|
||||
this.tree = tree;
|
||||
}
|
||||
|
||||
compareWith(tree) {
|
||||
if (tree?.rawTreeViewObject) {
|
||||
this.diffWithTree = tree.rawTreeViewObject;
|
||||
} else {
|
||||
this.diffWithTree = tree;
|
||||
}
|
||||
|
||||
this.diffWithTree = tree;
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -140,16 +112,13 @@ export class DiffGenerator {
|
||||
return Object.keys(obj).length === 0 && obj.constructor === Object;
|
||||
}
|
||||
|
||||
_cloneNodeWithoutChildren(node) {
|
||||
const clone = {};
|
||||
|
||||
for (const key in node) {
|
||||
if (key !== 'children') {
|
||||
const val = node[key];
|
||||
clone[key] = isPrimitive(val) ? val : cloneDeep(val);
|
||||
}
|
||||
}
|
||||
|
||||
_cloneNode(node) {
|
||||
const clone = ObjectFormatter.cloneObject(node);
|
||||
clone.children = node.children;
|
||||
clone.name = node.name;
|
||||
clone.kind = node.kind;
|
||||
clone.stableId = node.stableId;
|
||||
clone.shortName = node.shortName;
|
||||
return clone;
|
||||
}
|
||||
|
||||
@@ -167,7 +136,7 @@ export class DiffGenerator {
|
||||
// Deep clone newTree omitting children field
|
||||
// Clone is required because trees are frozen objects — we can't
|
||||
// modify the original tree object. Also means there is no side effect.
|
||||
const diffTree = this._cloneNodeWithoutChildren(newTree);
|
||||
const diffTree = this._cloneNode(newTree);
|
||||
|
||||
// Default to no changes
|
||||
diffTree.diff = {type: DiffType.NONE};
|
||||
@@ -200,7 +169,7 @@ export class DiffGenerator {
|
||||
|
||||
// Check if oldTree has been deleted of moved
|
||||
if (oldTree && !newTreeSiblingIds.includes(oldId)) {
|
||||
const deletedTreeDiff = this._cloneNodeWithoutChildren(oldTree);
|
||||
const deletedTreeDiff = this._cloneNode(oldTree);
|
||||
|
||||
if (this.newMapping[oldId]) {
|
||||
deletedTreeDiff.diff = {type: DiffType.DELETED_MOVE};
|
||||
@@ -233,7 +202,7 @@ export class DiffGenerator {
|
||||
} else if (oldTree) {
|
||||
if (!newTreeSiblingIds.includes(oldId)) {
|
||||
// Deep clone oldTree omitting children field
|
||||
const diffTree = this._cloneNodeWithoutChildren(oldTree);
|
||||
const diffTree = this._cloneNode(oldTree);
|
||||
|
||||
// newTree doesn't exists, oldTree has either been moved or deleted.
|
||||
if (this.newMapping[oldId]) {
|
||||
@@ -258,12 +227,12 @@ export class DiffGenerator {
|
||||
|
||||
// TODO: Try replacing this with some sort of zipWith.
|
||||
const numOfChildren = Math.max(
|
||||
newTree?.children.length ?? 0, oldTree?.children.length ?? 0);
|
||||
newTree?.children?.length ?? 0, oldTree?.children?.length ?? 0);
|
||||
for (let i = 0; i < numOfChildren; i++) {
|
||||
const newChild = i < newTree?.children.length ?
|
||||
const newChild = i < newTree?.children?.length ?
|
||||
newTree.children[i] : null;
|
||||
|
||||
const oldChild = i < oldTree?.children.length ?
|
||||
const oldChild = i < oldTree?.children?.length ?
|
||||
oldTree.children[i] : null;
|
||||
|
||||
const childDiffTrees = this._generateDiffTree(
|
||||
|
||||
@@ -84,4 +84,21 @@ function nanosToString(elapsedRealtimeNanos, precision) {
|
||||
return parts.reverse().join('');
|
||||
}
|
||||
|
||||
export { DIRECTION, findLastMatchingSorted, stableIdCompatibilityFixup, nanosToString, TimeUnits }
|
||||
/** Checks for match in window manager properties taskId, layerId, or windowToken,
|
||||
* or surface flinger property id
|
||||
*/
|
||||
function isPropertyMatch(flickerItem, entryItem) {
|
||||
return flickerItem.taskId === entryItem.taskId ||
|
||||
(flickerItem.windowToken === entryItem.windowToken) ||
|
||||
((flickerItem.layerId === entryItem.layerId) && flickerItem.layerId !== 0) ||
|
||||
flickerItem.layerId === entryItem.id;
|
||||
}
|
||||
|
||||
export {
|
||||
DIRECTION,
|
||||
findLastMatchingSorted,
|
||||
isPropertyMatch,
|
||||
stableIdCompatibilityFixup,
|
||||
nanosToString,
|
||||
TimeUnits
|
||||
}
|
||||
@@ -30,8 +30,9 @@ for arg in "$@"; do
|
||||
esac
|
||||
done
|
||||
|
||||
outfileTrans=${outfile}_transactiontrace.pb
|
||||
outfileSurf=${outfile}_layerstrace.pb
|
||||
WINSCOPE_EXT=.winscope
|
||||
outfileTrans=${outfile}_transactiontrace$WINSCOPE_EXT
|
||||
outfileSurf=${outfile}_layerstrace$WINSCOPE_EXT
|
||||
|
||||
outfileTrans_abs="$(cd "$(dirname "$outfileTrans")"; pwd)/$(basename "$outfileTrans")"
|
||||
outfileSurf_abs="$(cd "$(dirname "$outfileSurf")"; pwd)/$(basename "$outfileSurf")"
|
||||
@@ -39,8 +40,8 @@ outfileSurf_abs="$(cd "$(dirname "$outfileSurf")"; pwd)/$(basename "$outfileSurf
|
||||
if [ "$help" != "" ]; then
|
||||
echo "usage: $0 [-h | --help] [OUTFILE]"
|
||||
echo
|
||||
echo "Records Transaction traces (default transactiontrace.pb)."
|
||||
echo "Records Surface traces (default layerstrace.pb)."
|
||||
echo "Records Transaction traces (default transactiontrace$WINSCOPE_EXT)."
|
||||
echo "Records Surface traces (default layerstrace$WINSCOPE_EXT)."
|
||||
echo "To view the traces, use $WINSCOPE_URL."
|
||||
exit 1
|
||||
fi
|
||||
@@ -75,8 +76,8 @@ start_tracing
|
||||
read -p "Press ENTER to stop recording" -s x
|
||||
echo
|
||||
stop_tracing
|
||||
adb exec-out su root cat /data/misc/wmtrace/transaction_trace.pb >"$outfileTrans"
|
||||
adb exec-out su root cat /data/misc/wmtrace/layers_trace.pb >"$outfileSurf"
|
||||
adb exec-out su root cat /data/misc/wmtrace/transaction_trace$WINSCOPE_EXT >"$outfileTrans"
|
||||
adb exec-out su root cat /data/misc/wmtrace/layers_trace$WINSCOPE_EXT >"$outfileSurf"
|
||||
|
||||
echo
|
||||
echo "To view the trace, go to $WINSCOPE_URL, and open"
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
|
||||
const VueLoaderPlugin = require('vue-loader/lib/plugin');
|
||||
const { VueLoaderPlugin } = require("vue-loader")
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||
const KotlinWebpackPlugin = require('@jetbrains/kotlin-webpack-plugin');
|
||||
const HtmlWebpackInlineSourcePlugin =
|
||||
@@ -129,7 +129,7 @@ const webpackConfig = {
|
||||
inlineSource: isDev ? false : '.(js|css)',
|
||||
template: 'src/index_template.html',
|
||||
}),
|
||||
new HtmlWebpackInlineSourcePlugin(),
|
||||
new HtmlWebpackInlineSourcePlugin(HtmlWebpackPlugin),
|
||||
new KotlinWebpackPlugin({
|
||||
src: [
|
||||
path.join(__dirname, '../../../platform_testing/libraries/flicker/' +
|
||||
|
||||
@@ -64,7 +64,7 @@ module.exports = {
|
||||
},
|
||||
},
|
||||
{
|
||||
test: /\.pb/,
|
||||
test: /\.(pb|winscope)/,
|
||||
loader: 'file-loader',
|
||||
options: {
|
||||
paths: [
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,46 @@
|
||||
FWK-ONLY-RS: libft2.so
|
||||
FWK-ONLY-RS: libmediandk.so
|
||||
|
||||
LLNDK: libclang_rt.asan-aarch64-android.so
|
||||
LLNDK: libclang_rt.asan-arm-android.so
|
||||
LLNDK: libclang_rt.asan-i686-android.so
|
||||
LLNDK: libclang_rt.asan-mips-android.so
|
||||
LLNDK: libclang_rt.asan-mips64-android.so
|
||||
LLNDK: libclang_rt.asan-x86_64-android.so
|
||||
|
||||
VNDK-core: libclang_rt.scudo-aarch64-android.so
|
||||
VNDK-core: libclang_rt.scudo-arm-android.so
|
||||
VNDK-core: libclang_rt.scudo-i686-android.so
|
||||
VNDK-core: libclang_rt.scudo-x86_64-android.so
|
||||
|
||||
VNDK-core: libclang_rt.scudo_minimal-aarch64-android.so
|
||||
VNDK-core: libclang_rt.scudo_minimal-arm-android.so
|
||||
VNDK-core: libclang_rt.scudo_minimal-i686-android.so
|
||||
VNDK-core: libclang_rt.scudo_minimal-x86_64-android.so
|
||||
|
||||
VNDK-core: libclang_rt.ubsan_standalone-aarch64-android.so
|
||||
VNDK-core: libclang_rt.ubsan_standalone-arm-android.so
|
||||
VNDK-core: libclang_rt.ubsan_standalone-i686-android.so
|
||||
VNDK-core: libclang_rt.ubsan_standalone-mips-android.so
|
||||
VNDK-core: libclang_rt.ubsan_standalone-mips64-android.so
|
||||
VNDK-core: libclang_rt.ubsan_standalone-x86_64-android.so
|
||||
|
||||
LLNDK-private: ld-android.so
|
||||
LLNDK-private: libandroid_runtime_lazy.so
|
||||
LLNDK-private: libc_malloc_debug.so
|
||||
LLNDK-private: libdl_android.so
|
||||
LLNDK-private: libnetd_client.so
|
||||
LLNDK-private: libtextclassifier_hash.so
|
||||
|
||||
# Same-Process HAL implementations
|
||||
SP-HAL: [regex]^.*/android\.hardware\.graphics\.mapper@\d+\.\d+-impl\.so$
|
||||
SP-HAL: [regex]^.*/android\.hardware\.renderscript@1\.0-impl\.so$
|
||||
SP-HAL: [regex]^.*/gralloc\..*\.so$
|
||||
SP-HAL: [regex]^/vendor/.*/libEGL_.*\.so$
|
||||
SP-HAL: [regex]^/vendor/.*/libGLES_.*\.so$
|
||||
SP-HAL: [regex]^/vendor/.*/libGLESv1_CM_.*\.so$
|
||||
SP-HAL: [regex]^/vendor/.*/libGLESv2_.*\.so$
|
||||
SP-HAL: [regex]^/vendor/.*/libGLESv3_.*\.so$
|
||||
SP-HAL: [regex]^/vendor/.*/libPVRRS\.so$
|
||||
SP-HAL: [regex]^/vendor/.*/libRSDriver.*\.so$
|
||||
SP-HAL: [regex]^/vendor/.*/vulkan.*\.so$
|
||||
Reference in New Issue
Block a user