Merge "Merge Android 12L"

This commit is contained in:
Xin Li
2022-03-08 06:53:24 +00:00
committed by Gerrit Code Review
90 changed files with 5821 additions and 3169 deletions

2
.gitignore vendored
View File

@@ -5,4 +5,6 @@
Thumbs.db
*.iml
.idea/
*.yarn/
.yarnrc
gen/

View File

@@ -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");
}
}

View File

@@ -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

View File

@@ -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

View File

@@ -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:

View File

@@ -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": {

View File

@@ -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);
});
});
});

View File

@@ -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);
});
});
});

View File

@@ -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]);
}
}
});
});
});

View 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)]);
})
});

View 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 };

Binary file not shown.

Binary file not shown.

View File

@@ -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 };

View 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>

View File

@@ -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>

View File

@@ -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;
}

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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">

View File

@@ -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',

View File

@@ -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>

View File

@@ -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);

View 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>

View File

@@ -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;

View File

@@ -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>

View File

@@ -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,

View File

@@ -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">,&nbsp;</span>
</span>
<!-- eslint-disable-next-line max-len -->
<span v-if="index + 1 < sufacesAffectedBy(source).length">,&nbsp;</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
});
}
}

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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;

View 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>

View 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>

View 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"
}
}

View File

@@ -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
};

View File

@@ -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);
}
}

View File

@@ -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);
}
}
}

View 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;

View 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;

View File

@@ -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(' | ');
}
}
}

View 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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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,
};

View 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;

View 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;

View File

@@ -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};

View 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;

View 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;

View 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

View File

@@ -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

View 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;

View File

@@ -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;

View 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;

View File

@@ -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',
};

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View 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;

View File

@@ -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;

View File

@@ -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

View File

@@ -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;

View File

@@ -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

View File

@@ -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};

View File

@@ -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;

View File

@@ -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);
}
}

View File

@@ -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);
},
},
};
};

View File

@@ -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};

View 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;
}
}

View File

@@ -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);
}
}

View 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);
}
}

View 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);
}
}

View File

@@ -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);
}
}

View File

@@ -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};

View 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 };

View File

@@ -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};

View File

@@ -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 };

View File

@@ -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(

View File

@@ -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
}

View File

@@ -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"

View File

@@ -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/' +

View File

@@ -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

View File

@@ -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$