blob: 1fe913a783fd21882877f7ee7c149e3f52260c82 [file] [log] [blame]
/*
* Copyright 2022, 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.
*/
/**
* @fileoverview This file contains functions used after the decoding of proto
* trace files and before the display of trace entries in DataView panels
* to combine WM & SF trace properties into IME trace entries.
*/
import {TRACE_TYPES} from '@/decode';
import {WINDOW_MANAGER_KIND} from '@/flickerlib/common';
import {getFilter} from '@/utils/utils';
function combineWmSfWithImeDataIfExisting(dataFiles) {
// TODO(b/237744706): Add tests for this function
console.log('before combining', dataFiles);
let filesAsDict;
if (Array.isArray(dataFiles)) {
// transform dataFiles to a dictionary / object with filetype as the key
// this happens for the adb direct capture case
filesAsDict = {};
for (const dataFile of dataFiles) {
const dataType = dataFile.type;
filesAsDict[dataType] = dataFile;
}
} else {
filesAsDict = dataFiles;
}
const imeTraceFiles = Object.entries(filesAsDict)
.filter(
([filetype]) => (
// mapping it to an array of files; removes
// the key which is filetype
filetype.includes('ImeTrace')))
.map(([k, v]) => v);
for (const imeTraceFile of imeTraceFiles) {
if (filesAsDict[TRACE_TYPES.WINDOW_MANAGER]) {
console.log('combining WM file to', imeTraceFile.type, 'file');
combineWmSfPropertiesIntoImeData(
imeTraceFile, filesAsDict[TRACE_TYPES.WINDOW_MANAGER]);
}
if (filesAsDict[TRACE_TYPES.SURFACE_FLINGER] &&
imeTraceFile.type !== TRACE_TYPES.IME_MANAGERSERVICE) {
console.log('combining SF file to', imeTraceFile.type, 'file');
// don't need SF properties for ime manager service
combineWmSfPropertiesIntoImeData(
imeTraceFile, filesAsDict[TRACE_TYPES.SURFACE_FLINGER]);
}
processImeAfterCombiningWmAndSfProperties(imeTraceFile);
}
console.log('after combining', dataFiles);
}
function combineWmSfPropertiesIntoImeData(imeTraceFile, wmOrSfTraceFile) {
const imeTimestamps = imeTraceFile.timeline;
const wmOrSfTimestamps = wmOrSfTraceFile.timeline;
const intersectWmOrSfIndices =
matchCorrespondingTimestamps(imeTimestamps, wmOrSfTimestamps);
const wmOrSfData = wmOrSfTraceFile.data;
console.log('number of entries:', imeTimestamps.length);
for (let i = 0; i < imeTimestamps.length; i++) {
const wmOrSfIntersectIndex = intersectWmOrSfIndices[i];
const wmStateOrSfLayer = wmOrSfData[wmOrSfIntersectIndex];
// filter wmStateOrSfLayer to only relevant nodes & fields
if (wmStateOrSfLayer && wmStateOrSfLayer.kind === WINDOW_MANAGER_KIND) {
const wmProperties = filterWmStateForIme(wmStateOrSfLayer);
imeTraceFile.data[i].wmProperties = wmProperties;
imeTraceFile.data[0].hasWmSfProperties = true;
// hasWmSfProperties is added into data because the
// imeTraceFile object itself is inextensible if it's from file input
} else if (wmStateOrSfLayer) {
const sfProperties = extractSfProperties(wmStateOrSfLayer);
imeTraceFile.data[i].sfProperties = sfProperties;
const sfSubtrees = filterSfLayerForIme(
wmStateOrSfLayer); // for display in Hierarchy sub-panel
imeTraceFile.data[i].children.push(...sfSubtrees);
if (sfProperties || sfSubtrees.length > 0) {
imeTraceFile.data[0].hasWmSfProperties = true;
}
}
}
}
function matchCorrespondingTimestamps(imeTimestamps, wmOrSfTimestamps) {
// we want to take the earliest sf / wm entry that is within
// +-[FAULT_TOLERANCE] ns of the ime entry as the corresponding sf / wm entry
const FAULT_TOLERANCE = 200000000; // 200ms in ns
let wmOrSfIndex = 0;
const intersectWmOrSfIndices = [];
for (let imeIndex = 0; imeIndex < imeTimestamps.length; imeIndex++) {
const currImeTimestamp = imeTimestamps[imeIndex];
let wmOrSfTimestamp = wmOrSfTimestamps[wmOrSfIndex];
while (wmOrSfTimestamp < currImeTimestamp) {
wmOrSfIndex++;
wmOrSfTimestamp = wmOrSfTimestamps[wmOrSfIndex];
}
// wmOrSfIndex should now be at the first entry that is past
// [currImeTimestamp] ns. We want the most recent entry that comes
// before [currImeTimestamp] ns if it's within
// [currImeTimestamp - FAULT_TOLERANCE] ns. Otherwise, we want the most
// recent entry that comes after [currImeTimestamp] ns if it's within
// [currImeTimestamp + FAULT_TOLERANCE] ns.
const previousWmOrSfTimestamp = wmOrSfTimestamps[wmOrSfIndex - 1];
if (previousWmOrSfTimestamp >= currImeTimestamp - FAULT_TOLERANCE) {
intersectWmOrSfIndices.push(wmOrSfIndex - 1);
} else if (wmOrSfTimestamp <= currImeTimestamp + FAULT_TOLERANCE) {
intersectWmOrSfIndices.push(wmOrSfIndex);
} else {
intersectWmOrSfIndices.push(null);
}
}
console.log('done matching corresponding timestamps', intersectWmOrSfIndices);
return intersectWmOrSfIndices;
}
function filterWmStateForIme(wmState) {
// create and return a custom entry that just contains relevant properties
const displayContent = wmState.root.children[0];
const controlTargetActualName = getActualFieldNameFromPossibilities(
'controlTarget', displayContent.proto);
const inputTargetActualName =
getActualFieldNameFromPossibilities('inputTarget', displayContent.proto);
const layeringTargetActualName = getActualFieldNameFromPossibilities(
'layeringTarget', displayContent.proto);
const isInputMethodWindowVisible = findInputMethodVisibility(displayContent);
return {
'kind': 'WM State Properties',
'name': wmState.name, // 'name' is a timestamp of ..d..h..m..s..ms format
'stableId': wmState.stableId,
'focusedApp': wmState.focusedApp,
'focusedWindow': wmState.focusedWindow,
'focusedActivity': wmState.focusedActivity,
'isInputMethodWindowVisible': isInputMethodWindowVisible,
'imeControlTarget': displayContent.proto[controlTargetActualName],
'imeInputTarget': displayContent.proto[inputTargetActualName],
'imeLayeringTarget': displayContent.proto[layeringTargetActualName],
'imeInsetsSourceProvider': displayContent.proto.imeInsetsSourceProvider,
'proto': wmState,
};
}
function getActualFieldNameFromPossibilities(wantedKey, protoObject) {
// for backwards compatibility purposes: find the actual name in the
// protoObject out of a list of possible names, as field names may change
const possibleNamesMap = {
// inputMethod...Target is legacy name, ime...Target is new name
'controlTarget': ['inputMethodControlTarget', 'imeControlTarget'],
'inputTarget': ['inputMethodInputTarget', 'imeInputTarget'],
'layeringTarget': ['inputMethodTarget', 'imeLayeringTarget'],
};
const possibleNames = possibleNamesMap[wantedKey];
const actualName =
Object.keys(protoObject).find((el) => possibleNames.includes(el));
return actualName;
}
function filterSfLayerForIme(sfLayer) {
const parentTaskOfImeContainer =
findParentTaskOfNode(sfLayer, 'ImeContainer');
const parentTaskOfImeSnapshot = findParentTaskOfNode(sfLayer, 'IME-snapshot');
// we want to see both ImeContainer and IME-snapshot if there are
// cases where both exist
const resultSubtree = [parentTaskOfImeContainer, parentTaskOfImeSnapshot]
.filter((node) => node) // filter away null values
.map((node) => {
node.kind = 'SF subtree - ' + node.id;
return node;
});
return resultSubtree;
}
function findParentTaskOfNode(curr, nodeName) {
const isImeContainer = getFilter(nodeName);
if (isImeContainer(curr)) {
console.log('found ' + nodeName + '; searching for parent');
let parent = curr.parent;
const isTask = getFilter('Task, ImePlaceholder');
while (parent.parent && !isTask(parent)) {
// if parent.parent is null, 'parent' is already the root node -- use it
if (parent.parent != null) {
parent = parent.parent;
}
}
return parent;
}
// search for ImeContainer in children
for (const child of curr.children) {
const result = findParentTaskOfNode(child, nodeName);
if (result != null) {
return result;
}
}
return null;
}
function extractSfProperties(curr) {
const imeFields = extractImeFields(curr);
if (imeFields == null) {
return null;
}
return Object.assign({'name': curr.name, 'proto': curr}, imeFields);
// 'name' is a timestamp of ..d..h..m..s..ms format
}
function extractImeFields(curr) {
const isImeContainer = getFilter('ImeContainer');
const imeContainer = findWindowOrLayerMatch(isImeContainer, curr);
if (imeContainer != null) {
const isInputMethodSurface = getFilter('InputMethod');
const inputMethodSurface =
findWindowOrLayerMatch(isInputMethodSurface, imeContainer);
return {
'imeContainer': imeContainer,
'inputMethodSurface': inputMethodSurface,
'screenBounds': inputMethodSurface.screenBounds,
'rect': inputMethodSurface.rect,
'isInputMethodSurfaceVisible': inputMethodSurface.isVisible,
'zOrderRelativeOfId': imeContainer.zOrderRelativeOfId,
'z': imeContainer.z,
};
}
return null;
}
function findWindowOrLayerMatch(filter, curr) {
if (filter(curr)) {
return curr;
}
for (const child of curr.children) {
const result = findWindowOrLayerMatch(filter, child);
if (result) {
return result;
}
}
return null;
}
function findInputMethodVisibility(windowOrLayer) {
const isInputMethod = getFilter('InputMethod');
const inputMethodWindowOrLayer =
findWindowOrLayerMatch(isInputMethod, windowOrLayer);
return inputMethodWindowOrLayer && inputMethodWindowOrLayer.isVisible;
}
function processImeAfterCombiningWmAndSfProperties(imeTraceFile) {
for (const imeEntry of imeTraceFile.data) {
if (imeEntry.wmProperties?.focusedWindow && imeEntry.sfProperties?.proto) {
const focusedWindowRgba = findFocusedWindowRgba(
imeEntry.wmProperties.focusedWindow, imeEntry.sfProperties.proto);
imeEntry.sfProperties.focusedWindowRgba = focusedWindowRgba;
}
}
}
function findFocusedWindowRgba(focusedWindow, sfLayer) {
const focusedWindowToken = focusedWindow.token;
console.log(focusedWindowToken);
const isFocusedWindow = getFilter(focusedWindowToken);
const focusedWindowLayer = findWindowOrLayerMatch(isFocusedWindow, sfLayer);
return focusedWindowLayer.color;
}
export {combineWmSfWithImeDataIfExisting};