IME refactor - parsing side. - Moved 3 parsers and associated tests in parsers/input_method - HierarchyTreeBuilderInputMethod used in all three ime parsers - Added option in PropertyTreeBuilderFromProto/PropertyTreeNodeFactory to not visit object prototype, as ime protos have defaults in prototype - Made 'children' property optional in HierarchyTreeBuilder, so we don't have to create a children property for every WM node in ParserWindowManagerUtils Bug: b/311643292 Test: npm run test:unit:ci Change-Id: Ie50eb45b3760854d59b9f5c6a22c925284c8984e
diff --git a/tools/winscope/src/parsers/hierarchy_tree_builder.ts b/tools/winscope/src/parsers/hierarchy_tree_builder.ts index d0fc093..ac24320 100644 --- a/tools/winscope/src/parsers/hierarchy_tree_builder.ts +++ b/tools/winscope/src/parsers/hierarchy_tree_builder.ts
@@ -71,16 +71,16 @@ identifierToChild: Map<string | number, PropertiesProvider[]> ): HierarchyTreeNode { const childProperties = child.getEagerProperties(); - const subChildren: HierarchyTreeNode[] = assertDefined( - childProperties.getChildByName('children') - ) - .getAllChildren() - .flatMap((identifier: PropertyTreeNode) => { - const key = this.getIdentifierValue(identifier); - return assertDefined(identifierToChild.get(key)).map((child) => { - return this.buildSubtree(child, identifierToChild); - }); - }); + const subChildren: HierarchyTreeNode[] = + childProperties + .getChildByName('children') + ?.getAllChildren() + .flatMap((identifier: PropertyTreeNode) => { + const key = this.getIdentifierValue(identifier); + return assertDefined(identifierToChild.get(key)).map((child) => { + return this.buildSubtree(child, identifierToChild); + }); + }) ?? []; return this.buildHierarchyTreeNode( childProperties.id,
diff --git a/tools/winscope/src/parsers/input_method/hierarchy_tree_builder_input_method.ts b/tools/winscope/src/parsers/input_method/hierarchy_tree_builder_input_method.ts new file mode 100644 index 0000000..a4a0c03 --- /dev/null +++ b/tools/winscope/src/parsers/input_method/hierarchy_tree_builder_input_method.ts
@@ -0,0 +1,45 @@ +/* + * Copyright (C) 2024 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 {HierarchyTreeBuilder} from 'parsers/hierarchy_tree_builder'; +import {HierarchyTreeNode} from 'trace/tree_node/hierarchy_tree_node'; +import {PropertiesProvider} from 'trace/tree_node/properties_provider'; +import {PropertyTreeNode} from 'trace/tree_node/property_tree_node'; + +export class HierarchyTreeBuilderInputMethod extends HierarchyTreeBuilder { + protected override buildIdentifierToChildMap( + children: PropertiesProvider[] + ): Map<string | number, PropertiesProvider[]> { + // only ever one child (client, service, manager service) so map unnecessary + return new Map<string | number, PropertiesProvider[]>(); + } + + protected override makeRootChildren( + children: PropertiesProvider[], + identifierToChild: Map<string | number, PropertiesProvider[]> + ): ReadonlyArray<HierarchyTreeNode> { + if (children.length === 0) return []; + return [this.buildSubtree(children[0], identifierToChild)]; + } + + protected override getIdentifierValue(identifier: PropertyTreeNode): number { + return identifier.getValue(); + } + + protected override getSubtreeName(propertyTreeName: string): string { + return propertyTreeName; + } +}
diff --git a/tools/winscope/src/parsers/input_method/hierarchy_tree_builder_input_method_test.ts b/tools/winscope/src/parsers/input_method/hierarchy_tree_builder_input_method_test.ts new file mode 100644 index 0000000..d7c0228 --- /dev/null +++ b/tools/winscope/src/parsers/input_method/hierarchy_tree_builder_input_method_test.ts
@@ -0,0 +1,107 @@ +/* + * Copyright (C) 2024 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 {PropertyTreeBuilder} from 'test/unit/property_tree_builder'; +import {TreeNodeUtils} from 'test/unit/tree_node_utils'; +import {HierarchyTreeNode} from 'trace/tree_node/hierarchy_tree_node'; +import {OperationChain} from 'trace/tree_node/operations/operation_chain'; +import {PropertiesProvider} from 'trace/tree_node/properties_provider'; +import {PropertyTreeNode} from 'trace/tree_node/property_tree_node'; +import {HierarchyTreeBuilderInputMethod} from './hierarchy_tree_builder_input_method'; + +describe('HierarchyTreeBuilderInputMethod', () => { + let builder: HierarchyTreeBuilderInputMethod; + let entry: PropertiesProvider; + let entryPropertiesTree: PropertyTreeNode; + + beforeEach(() => { + jasmine.addCustomEqualityTester(TreeNodeUtils.treeNodeEqualityTester); + builder = new HierarchyTreeBuilderInputMethod(); + entryPropertiesTree = new PropertyTreeBuilder() + .setIsRoot(true) + .setRootId('InputMethod') + .setName('entry') + .build(); + entry = new PropertiesProvider( + entryPropertiesTree, + async () => entryPropertiesTree, + OperationChain.emptyChain<PropertyTreeNode>(), + OperationChain.emptyChain<PropertyTreeNode>(), + OperationChain.emptyChain<PropertyTreeNode>() + ); + }); + + it('throws error if entry not set', () => { + const noEntryError = new Error('root not set'); + expect(() => builder.setChildren([]).build()).toThrow(noEntryError); + }); + + it('throws error if children not set', () => { + const noChildrenError = new Error('children not set'); + expect(() => builder.setRoot(entry).build()).toThrow(noChildrenError); + }); + + it('builds root with no children correctly', () => { + const root = builder.setRoot(entry).setChildren([]).build(); + + const expectedRoot = new HierarchyTreeNode( + 'InputMethod entry', + 'entry', + new PropertiesProvider( + entryPropertiesTree, + async () => entryPropertiesTree, + OperationChain.emptyChain<PropertyTreeNode>(), + OperationChain.emptyChain<PropertyTreeNode>(), + OperationChain.emptyChain<PropertyTreeNode>() + ) + ); + + expect(root).toEqual(expectedRoot); + }); + + it('builds root with children correctly', () => { + const childProps = new PropertyTreeBuilder() + .setRootId('Service') + .setName('child') + .setIsRoot(true) + .build(); + + const childProvider = new PropertiesProvider( + childProps, + async () => childProps, + OperationChain.emptyChain<PropertyTreeNode>(), + OperationChain.emptyChain<PropertyTreeNode>(), + OperationChain.emptyChain<PropertyTreeNode>() + ); + + const root = builder.setRoot(entry).setChildren([childProvider]).build(); + + const expectedRoot = new HierarchyTreeNode( + 'InputMethod entry', + 'entry', + new PropertiesProvider( + entryPropertiesTree, + async () => entryPropertiesTree, + OperationChain.emptyChain<PropertyTreeNode>(), + OperationChain.emptyChain<PropertyTreeNode>(), + OperationChain.emptyChain<PropertyTreeNode>() + ) + ); + expectedRoot.addOrReplaceChild(new HierarchyTreeNode('Service child', 'child', childProvider)); + + expect(root).toEqual(expectedRoot); + }); +});
diff --git a/tools/winscope/src/parsers/input_method/parser_input_method_clients.ts b/tools/winscope/src/parsers/input_method/parser_input_method_clients.ts new file mode 100644 index 0000000..46697c9 --- /dev/null +++ b/tools/winscope/src/parsers/input_method/parser_input_method_clients.ts
@@ -0,0 +1,271 @@ +/* + * Copyright (C) 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. + */ + +import {assertDefined} from 'common/assert_utils'; +import {Timestamp, TimestampType} from 'common/time'; +import {TimeUtils} from 'common/time_utils'; +import {AbstractParser} from 'parsers/abstract_parser'; +import {AddDefaults} from 'parsers/operations/add_defaults'; +import {SetFormatters} from 'parsers/operations/set_formatters'; +import {TranslateIntDef} from 'parsers/operations/translate_intdef'; +import {TamperedMessageType} from 'parsers/tampered_message_type'; +import root from 'protos/ime/latest/json'; +import {android} from 'protos/ime/latest/static'; +import {TraceFile} from 'trace/trace_file'; +import {TraceTreeNode} from 'trace/trace_tree_node'; +import {TraceType} from 'trace/trace_type'; +import {HierarchyTreeNode} from 'trace/tree_node/hierarchy_tree_node'; +import {LazyPropertiesStrategyType} from 'trace/tree_node/properties_provider'; +import {PropertiesProviderBuilder} from 'trace/tree_node/properties_provider_builder'; +import {PropertyTreeBuilderFromProto} from 'trace/tree_node/property_tree_builder_from_proto'; +import {PropertyTreeNode} from 'trace/tree_node/property_tree_node'; +import {ImeUtils} from 'viewers/common/ime_utils'; +import {HierarchyTreeBuilderInputMethod} from './hierarchy_tree_builder_input_method'; + +class ParserInputMethodClients extends AbstractParser { + private static readonly MAGIC_NUMBER = [0x09, 0x49, 0x4d, 0x43, 0x54, 0x52, 0x41, 0x43, 0x45]; // .IMCTRACE + + private static readonly ENTRY_DENYLIST_PROPERTIES = ['client']; + private static readonly ENTRY_EAGER_PROPERTIES = ['where']; + private static readonly CLIENT_EAGER_PROPERTIES = [ + 'viewRootImpl', + 'inputMethodManager', + 'editorInfo', + ]; + + private static readonly InputMethodClientsTraceFileProto = TamperedMessageType.tamper( + root.lookupType('android.view.inputmethod.InputMethodClientsTraceFileProto') + ); + private static readonly entryField = + ParserInputMethodClients.InputMethodClientsTraceFileProto.fields['entry']; + private static readonly clientField = assertDefined( + ParserInputMethodClients.entryField.tamperedMessageType + ).fields['client']; + + private static readonly Operations = { + SetFormattersClient: new SetFormatters(ParserInputMethodClients.clientField), + TranslateIntDefClient: new TranslateIntDef(ParserInputMethodClients.clientField), + AddDefaultsClientEager: new AddDefaults( + ParserInputMethodClients.clientField, + ParserInputMethodClients.CLIENT_EAGER_PROPERTIES + ), + AddDefaultsClientLazy: new AddDefaults( + ParserInputMethodClients.clientField, + undefined, + ParserInputMethodClients.CLIENT_EAGER_PROPERTIES + ), + SetFormattersEntry: new SetFormatters(ParserInputMethodClients.entryField), + AddDefaultsEntryEager: new AddDefaults( + ParserInputMethodClients.entryField, + ParserInputMethodClients.ENTRY_EAGER_PROPERTIES + ), + AddDefaultsEntryLazy: new AddDefaults( + ParserInputMethodClients.entryField, + undefined, + ParserInputMethodClients.ENTRY_EAGER_PROPERTIES.concat( + ParserInputMethodClients.ENTRY_DENYLIST_PROPERTIES + ) + ), + }; + + private realToElapsedTimeOffsetNs: undefined | bigint; + + constructor(trace: TraceFile) { + super(trace); + this.realToElapsedTimeOffsetNs = undefined; + } + + getTraceType(): TraceType { + return TraceType.INPUT_METHOD_CLIENTS; + } + + override getMagicNumber(): number[] { + return ParserInputMethodClients.MAGIC_NUMBER; + } + + override decodeTrace( + buffer: Uint8Array + ): android.view.inputmethod.IInputMethodClientsTraceProto[] { + const decoded = ParserInputMethodClients.InputMethodClientsTraceFileProto.decode( + buffer + ) as android.view.inputmethod.IInputMethodClientsTraceFileProto; + const timeOffset = BigInt(decoded.realToElapsedTimeOffsetNanos?.toString() ?? '0'); + this.realToElapsedTimeOffsetNs = timeOffset !== 0n ? timeOffset : undefined; + return decoded.entry ?? []; + } + + override getTimestamp( + type: TimestampType, + entry: android.view.inputmethod.IInputMethodClientsTraceProto + ): undefined | Timestamp { + const elapsedRealtimeNanos = BigInt(assertDefined(entry.elapsedRealtimeNanos).toString()); + if (type === TimestampType.ELAPSED) { + return new Timestamp(type, elapsedRealtimeNanos); + } else if (type === TimestampType.REAL && this.realToElapsedTimeOffsetNs !== undefined) { + return new Timestamp(type, elapsedRealtimeNanos + this.realToElapsedTimeOffsetNs); + } + return undefined; + } + + override processDecodedEntry( + index: number, + timestampType: TimestampType, + entry: android.view.inputmethod.IInputMethodClientsTraceProto + ): TraceTreeNode { + if (entry.elapsedRealtimeNanos === undefined || entry.elapsedRealtimeNanos === null) { + throw Error('Missing elapsedRealtimeNanos on entry'); + } + + const elapsedRealtimeNanos = BigInt(entry.elapsedRealtimeNanos.toString()); + + let clockTimeNanos: bigint | undefined = undefined; + if (this.realToElapsedTimeOffsetNs !== undefined) { + clockTimeNanos = elapsedRealtimeNanos + this.realToElapsedTimeOffsetNs; + } + + const timestamp = Timestamp.from( + timestampType, + elapsedRealtimeNanos, + this.realToElapsedTimeOffsetNs + ); + + return { + name: TimeUtils.format(timestamp) + ' - ' + entry.where, + kind: 'InputMethodClient entry', + children: [ + { + obj: ImeUtils.transformInputConnectionCall(entry.client), + kind: 'Client', + name: entry.client?.viewRootImpl?.view ?? '', + children: [], + stableId: 'client', + id: 'client', + }, + ], + obj: entry, + stableId: 'entry', + id: 'entry', + elapsedRealtimeNanos, + clockTimeNanos, + }; + } + + private makeHierarchyTree( + entryProto: android.view.inputmethod.IInputMethodClientsTraceProto + ): HierarchyTreeNode { + const entry = new PropertiesProviderBuilder() + .setEagerProperties(this.makeEntryEagerPropertiesTree(entryProto)) + .setLazyPropertiesStrategy(this.makeEntryLazyPropertiesStrategy(entryProto)) + .setEagerOperations([ParserInputMethodClients.Operations.AddDefaultsEntryEager]) + .setCommonOperations([ParserInputMethodClients.Operations.SetFormattersEntry]) + .setLazyOperations([ParserInputMethodClients.Operations.AddDefaultsEntryLazy]) + .build(); + + const client = new PropertiesProviderBuilder() + .setEagerProperties(this.makeClientEagerPropertiesTree(entryProto.client)) + .setLazyPropertiesStrategy(this.makeClientLazyPropertiesStrategy(entryProto.client)) + .setEagerOperations( + entryProto.client ? [ParserInputMethodClients.Operations.AddDefaultsClientEager] : [] + ) + .setCommonOperations([ + ParserInputMethodClients.Operations.SetFormattersClient, + ParserInputMethodClients.Operations.TranslateIntDefClient, + ]) + .setLazyOperations( + entryProto.client ? [ParserInputMethodClients.Operations.AddDefaultsClientLazy] : [] + ) + .build(); + + return new HierarchyTreeBuilderInputMethod().setRoot(entry).setChildren([client]).build(); + } + + private makeEntryEagerPropertiesTree( + entryProto: android.view.inputmethod.IInputMethodClientsTraceProto + ): PropertyTreeNode { + const denyList: string[] = []; + Object.getOwnPropertyNames(entryProto).forEach((it) => { + if (!ParserInputMethodClients.ENTRY_EAGER_PROPERTIES.includes(it)) denyList.push(it); + }); + + return new PropertyTreeBuilderFromProto() + .setData(entryProto) + .setRootId('InputMethodClients') + .setRootName('entry') + .setDenyList(denyList) + .build(); + } + + private makeEntryLazyPropertiesStrategy( + entryProto: android.view.inputmethod.IInputMethodClientsTraceProto + ): LazyPropertiesStrategyType { + return async () => { + return new PropertyTreeBuilderFromProto() + .setData(entryProto) + .setRootId('InputMethodClients') + .setRootName('entry') + .setDenyList( + ParserInputMethodClients.ENTRY_EAGER_PROPERTIES.concat( + ParserInputMethodClients.ENTRY_DENYLIST_PROPERTIES + ) + ) + .build(); + }; + } + + private makeClientEagerPropertiesTree( + clientProto: + | android.view.inputmethod.InputMethodClientsTraceProto.IClientSideProto + | null + | undefined + ): PropertyTreeNode { + const denyList: string[] = []; + let data: any = clientProto; + if (clientProto) { + Object.getOwnPropertyNames(clientProto).forEach((it) => { + if (!ParserInputMethodClients.CLIENT_EAGER_PROPERTIES.includes(it)) denyList.push(it); + }); + } else { + data = {client: null}; + } + + return new PropertyTreeBuilderFromProto() + .setData(data) + .setRootId('InputMethodClients') + .setRootName('client') + .setDenyList(denyList) + .setVisitPrototype(false) + .build(); + } + + private makeClientLazyPropertiesStrategy( + clientProto: + | android.view.inputmethod.InputMethodClientsTraceProto.IClientSideProto + | null + | undefined + ): LazyPropertiesStrategyType { + return async () => { + return new PropertyTreeBuilderFromProto() + .setData(clientProto ?? {client: null}) + .setRootId('InputMethodClients') + .setRootName('client') + .setDenyList(ParserInputMethodClients.CLIENT_EAGER_PROPERTIES) + .setVisitPrototype(false) + .build(); + }; + } +} + +export {ParserInputMethodClients};
diff --git a/tools/winscope/src/parsers/parser_input_method_clients_test.ts b/tools/winscope/src/parsers/input_method/parser_input_method_clients_test.ts similarity index 100% rename from tools/winscope/src/parsers/parser_input_method_clients_test.ts rename to tools/winscope/src/parsers/input_method/parser_input_method_clients_test.ts
diff --git a/tools/winscope/src/parsers/input_method/parser_input_method_manager_service.ts b/tools/winscope/src/parsers/input_method/parser_input_method_manager_service.ts new file mode 100644 index 0000000..d4a1129 --- /dev/null +++ b/tools/winscope/src/parsers/input_method/parser_input_method_manager_service.ts
@@ -0,0 +1,272 @@ +/* + * Copyright (C) 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. + */ + +import {assertDefined} from 'common/assert_utils'; +import {Timestamp, TimestampType} from 'common/time'; +import {TimeUtils} from 'common/time_utils'; +import {AbstractParser} from 'parsers/abstract_parser'; +import {AddDefaults} from 'parsers/operations/add_defaults'; +import {SetFormatters} from 'parsers/operations/set_formatters'; +import {TranslateIntDef} from 'parsers/operations/translate_intdef'; +import {TamperedMessageType} from 'parsers/tampered_message_type'; +import root from 'protos/ime/latest/json'; +import {android} from 'protos/ime/latest/static'; +import {TraceFile} from 'trace/trace_file'; +import {TraceTreeNode} from 'trace/trace_tree_node'; +import {TraceType} from 'trace/trace_type'; +import {HierarchyTreeNode} from 'trace/tree_node/hierarchy_tree_node'; +import {LazyPropertiesStrategyType} from 'trace/tree_node/properties_provider'; +import {PropertiesProviderBuilder} from 'trace/tree_node/properties_provider_builder'; +import {PropertyTreeBuilderFromProto} from 'trace/tree_node/property_tree_builder_from_proto'; +import {PropertyTreeNode} from 'trace/tree_node/property_tree_node'; +import {HierarchyTreeBuilderInputMethod} from './hierarchy_tree_builder_input_method'; + +class ParserInputMethodManagerService extends AbstractParser { + private static readonly MAGIC_NUMBER = [0x09, 0x49, 0x4d, 0x4d, 0x54, 0x52, 0x41, 0x43, 0x45]; // .IMMTRACE + + private static readonly ENTRY_DENYLIST_PROPERTIES = ['inputMethodManagerService']; + private static readonly ENTRY_EAGER_PROPERTIES = ['where']; + private static readonly SERVICE_EAGER_PROPERTIES = [ + 'curMethodId', + 'curFocusedWindowName', + 'lastImeTargetWindowName', + 'inputShown', + ]; + + private static readonly InputMethodManagerServiceTraceFileProto = TamperedMessageType.tamper( + root.lookupType('android.view.inputmethod.InputMethodManagerServiceTraceFileProto') + ); + private static readonly entryField = + ParserInputMethodManagerService.InputMethodManagerServiceTraceFileProto.fields['entry']; + private static readonly serviceField = assertDefined( + ParserInputMethodManagerService.entryField.tamperedMessageType + ).fields['inputMethodManagerService']; + + private static readonly Operations = { + SetFormattersService: new SetFormatters(ParserInputMethodManagerService.serviceField), + TranslateIntDefService: new TranslateIntDef(ParserInputMethodManagerService.serviceField), + AddDefaultsServiceEager: new AddDefaults( + ParserInputMethodManagerService.serviceField, + ParserInputMethodManagerService.SERVICE_EAGER_PROPERTIES + ), + AddDefaultsServiceLazy: new AddDefaults( + ParserInputMethodManagerService.serviceField, + undefined, + ParserInputMethodManagerService.SERVICE_EAGER_PROPERTIES + ), + SetFormattersEntry: new SetFormatters(ParserInputMethodManagerService.entryField), + AddDefaultsEntryEager: new AddDefaults( + ParserInputMethodManagerService.entryField, + ParserInputMethodManagerService.ENTRY_EAGER_PROPERTIES + ), + AddDefaultsEntryLazy: new AddDefaults( + ParserInputMethodManagerService.entryField, + undefined, + ParserInputMethodManagerService.ENTRY_EAGER_PROPERTIES.concat( + ParserInputMethodManagerService.ENTRY_DENYLIST_PROPERTIES + ) + ), + }; + + private realToElapsedTimeOffsetNs: undefined | bigint; + + constructor(trace: TraceFile) { + super(trace); + this.realToElapsedTimeOffsetNs = undefined; + } + + getTraceType(): TraceType { + return TraceType.INPUT_METHOD_MANAGER_SERVICE; + } + + override getMagicNumber(): number[] { + return ParserInputMethodManagerService.MAGIC_NUMBER; + } + + override decodeTrace( + buffer: Uint8Array + ): android.view.inputmethod.IInputMethodManagerServiceTraceProto[] { + const decoded = ParserInputMethodManagerService.InputMethodManagerServiceTraceFileProto.decode( + buffer + ) as android.view.inputmethod.IInputMethodManagerServiceTraceFileProto; + const timeOffset = BigInt(decoded.realToElapsedTimeOffsetNanos?.toString() ?? '0'); + this.realToElapsedTimeOffsetNs = timeOffset !== 0n ? timeOffset : undefined; + return decoded.entry ?? []; + } + + protected override getTimestamp( + type: TimestampType, + entry: android.view.inputmethod.IInputMethodManagerServiceTraceProto + ): undefined | Timestamp { + const elapsedRealtimeNanos = BigInt(assertDefined(entry.elapsedRealtimeNanos).toString()); + if (type === TimestampType.ELAPSED) { + return new Timestamp(TimestampType.ELAPSED, elapsedRealtimeNanos); + } else if (type === TimestampType.REAL && this.realToElapsedTimeOffsetNs !== undefined) { + return new Timestamp(type, this.realToElapsedTimeOffsetNs + elapsedRealtimeNanos); + } + return undefined; + } + + protected override processDecodedEntry( + index: number, + timestampType: TimestampType, + entry: android.view.inputmethod.IInputMethodManagerServiceTraceProto + ): TraceTreeNode { + if (entry.elapsedRealtimeNanos === undefined || entry.elapsedRealtimeNanos === null) { + throw Error('Missing elapsedRealtimeNanos on entry'); + } + + const elapsedRealtimeNanos = BigInt(entry.elapsedRealtimeNanos.toString()); + + let clockTimeNanos: bigint | undefined = undefined; + if (this.realToElapsedTimeOffsetNs !== undefined && entry.elapsedRealtimeNanos !== undefined) { + clockTimeNanos = elapsedRealtimeNanos + this.realToElapsedTimeOffsetNs; + } + + const timestamp = Timestamp.from( + timestampType, + elapsedRealtimeNanos, + this.realToElapsedTimeOffsetNs + ); + + return { + name: TimeUtils.format(timestamp) + ' - ' + entry.where, + kind: 'InputMethodManagerService entry', + children: [ + { + obj: entry.inputMethodManagerService, + kind: 'InputMethodManagerService', + name: '', + children: [], + stableId: 'managerservice', + id: 'managerservice', + }, + ], + obj: entry, + stableId: 'entry', + id: 'entry', + elapsedRealtimeNanos, + clockTimeNanos, + }; + } + + private makeHierarchyTree( + entryProto: android.view.inputmethod.IInputMethodManagerServiceTraceProto + ): HierarchyTreeNode { + const entry = new PropertiesProviderBuilder() + .setEagerProperties(this.makeEntryEagerPropertiesTree(entryProto)) + .setLazyPropertiesStrategy(this.makeEntryLazyPropertiesStrategy(entryProto)) + .setEagerOperations([ParserInputMethodManagerService.Operations.AddDefaultsEntryEager]) + .setCommonOperations([ParserInputMethodManagerService.Operations.SetFormattersEntry]) + .setLazyOperations([ParserInputMethodManagerService.Operations.AddDefaultsEntryLazy]) + .build(); + + const inputMethodManagerService = entryProto.inputMethodManagerService + ? new PropertiesProviderBuilder() + .setEagerProperties( + this.makeServiceEagerPropertiesTree(entryProto.inputMethodManagerService) + ) + .setLazyPropertiesStrategy( + this.makeServiceLazyPropertiesStrategy(entryProto.inputMethodManagerService) + ) + .setEagerOperations([ParserInputMethodManagerService.Operations.AddDefaultsServiceEager]) + .setCommonOperations([ + ParserInputMethodManagerService.Operations.SetFormattersService, + ParserInputMethodManagerService.Operations.TranslateIntDefService, + ]) + .setLazyOperations([ParserInputMethodManagerService.Operations.AddDefaultsServiceLazy]) + .build() + : undefined; + + return new HierarchyTreeBuilderInputMethod() + .setRoot(entry) + .setChildren(inputMethodManagerService ? [inputMethodManagerService] : []) + .build(); + } + + private makeEntryEagerPropertiesTree( + entryProto: android.view.inputmethod.IInputMethodManagerServiceTraceProto + ): PropertyTreeNode { + const denyList: string[] = []; + Object.getOwnPropertyNames(entryProto).forEach((it) => { + if (!ParserInputMethodManagerService.ENTRY_EAGER_PROPERTIES.includes(it)) denyList.push(it); + }); + + return new PropertyTreeBuilderFromProto() + .setData(entryProto) + .setRootId('InputMethodManagerService') + .setRootName('entry') + .setDenyList(denyList) + .build(); + } + + private makeEntryLazyPropertiesStrategy( + entryProto: android.view.inputmethod.IInputMethodManagerServiceTraceProto + ): LazyPropertiesStrategyType { + return async () => { + return new PropertyTreeBuilderFromProto() + .setData(entryProto) + .setRootId('InputMethodManagerService') + .setRootName('entry') + .setDenyList( + ParserInputMethodManagerService.ENTRY_EAGER_PROPERTIES.concat( + ParserInputMethodManagerService.ENTRY_DENYLIST_PROPERTIES + ) + ) + .build(); + }; + } + + private makeServiceEagerPropertiesTree( + serviceProto: android.server.inputmethod.IInputMethodManagerServiceProto + ): PropertyTreeNode { + const denyList: string[] = []; + let data: any = serviceProto; + if (serviceProto) { + Object.getOwnPropertyNames(serviceProto).forEach((it) => { + if (!ParserInputMethodManagerService.SERVICE_EAGER_PROPERTIES.includes(it)) { + denyList.push(it); + } + }); + } else { + data = {inputMethodManagerService: null}; + } + + return new PropertyTreeBuilderFromProto() + .setData(data) + .setRootId('InputMethodManagerService') + .setRootName('inputMethodManagerService') + .setDenyList(denyList) + .setVisitPrototype(false) + .build(); + } + + private makeServiceLazyPropertiesStrategy( + serviceProto: android.server.inputmethod.IInputMethodManagerServiceProto + ): LazyPropertiesStrategyType { + return async () => { + return new PropertyTreeBuilderFromProto() + .setData(serviceProto ?? {inputMethodManagerService: null}) + .setRootId('InputMethodManagerService') + .setRootName('inputMethodManagerService') + .setDenyList(ParserInputMethodManagerService.SERVICE_EAGER_PROPERTIES) + .setVisitPrototype(false) + .build(); + }; + } +} + +export {ParserInputMethodManagerService};
diff --git a/tools/winscope/src/parsers/parser_input_method_manager_service_test.ts b/tools/winscope/src/parsers/input_method/parser_input_method_manager_service_test.ts similarity index 100% rename from tools/winscope/src/parsers/parser_input_method_manager_service_test.ts rename to tools/winscope/src/parsers/input_method/parser_input_method_manager_service_test.ts
diff --git a/tools/winscope/src/parsers/input_method/parser_input_method_service.ts b/tools/winscope/src/parsers/input_method/parser_input_method_service.ts new file mode 100644 index 0000000..a1e0519 --- /dev/null +++ b/tools/winscope/src/parsers/input_method/parser_input_method_service.ts
@@ -0,0 +1,267 @@ +/* + * Copyright (C) 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. + */ + +import {assertDefined} from 'common/assert_utils'; +import {Timestamp, TimestampType} from 'common/time'; +import {TimeUtils} from 'common/time_utils'; +import {AbstractParser} from 'parsers/abstract_parser'; +import {AddDefaults} from 'parsers/operations/add_defaults'; +import {SetFormatters} from 'parsers/operations/set_formatters'; +import {TranslateIntDef} from 'parsers/operations/translate_intdef'; +import {TamperedMessageType} from 'parsers/tampered_message_type'; +import root from 'protos/ime/latest/json'; +import {android} from 'protos/ime/latest/static'; +import {TraceFile} from 'trace/trace_file'; +import {TraceTreeNode} from 'trace/trace_tree_node'; +import {TraceType} from 'trace/trace_type'; +import {HierarchyTreeNode} from 'trace/tree_node/hierarchy_tree_node'; +import {LazyPropertiesStrategyType} from 'trace/tree_node/properties_provider'; +import {PropertiesProviderBuilder} from 'trace/tree_node/properties_provider_builder'; +import {PropertyTreeBuilderFromProto} from 'trace/tree_node/property_tree_builder_from_proto'; +import {PropertyTreeNode} from 'trace/tree_node/property_tree_node'; +import {ImeUtils} from 'viewers/common/ime_utils'; +import {HierarchyTreeBuilderInputMethod} from './hierarchy_tree_builder_input_method'; + +class ParserInputMethodService extends AbstractParser { + private static readonly MAGIC_NUMBER = [0x09, 0x49, 0x4d, 0x53, 0x54, 0x52, 0x41, 0x43, 0x45]; // .IMSTRACE + + private static readonly ENTRY_DENYLIST_PROPERTIES = ['inputMethodService']; + private static readonly ENTRY_EAGER_PROPERTIES = ['where']; + private static readonly SERVICE_EAGER_PROPERTIES = [ + 'windowVisible', + 'decorViewVisible', + 'inputEditorInfo', + ]; + + private static readonly InputMethodServiceTraceFileProto = TamperedMessageType.tamper( + root.lookupType('android.view.inputmethod.InputMethodServiceTraceFileProto') + ); + private static readonly entryField = + ParserInputMethodService.InputMethodServiceTraceFileProto.fields['entry']; + private static readonly serviceField = assertDefined( + ParserInputMethodService.entryField.tamperedMessageType + ).fields['inputMethodService']; + + private static readonly Operations = { + SetFormattersService: new SetFormatters(ParserInputMethodService.serviceField), + TranslateIntDefService: new TranslateIntDef(ParserInputMethodService.serviceField), + AddDefaultsServiceEager: new AddDefaults( + ParserInputMethodService.serviceField, + ParserInputMethodService.SERVICE_EAGER_PROPERTIES + ), + AddDefaultsServiceLazy: new AddDefaults( + ParserInputMethodService.serviceField, + undefined, + ParserInputMethodService.SERVICE_EAGER_PROPERTIES + ), + SetFormattersEntry: new SetFormatters(ParserInputMethodService.entryField), + AddDefaultsEntryEager: new AddDefaults( + ParserInputMethodService.entryField, + ParserInputMethodService.ENTRY_EAGER_PROPERTIES + ), + AddDefaultsEntryLazy: new AddDefaults( + ParserInputMethodService.entryField, + undefined, + ParserInputMethodService.ENTRY_EAGER_PROPERTIES.concat( + ParserInputMethodService.ENTRY_DENYLIST_PROPERTIES + ) + ), + }; + + private realToElapsedTimeOffsetNs: undefined | bigint; + + constructor(trace: TraceFile) { + super(trace); + this.realToElapsedTimeOffsetNs = undefined; + } + + getTraceType(): TraceType { + return TraceType.INPUT_METHOD_SERVICE; + } + + override getMagicNumber(): number[] { + return ParserInputMethodService.MAGIC_NUMBER; + } + + override decodeTrace( + buffer: Uint8Array + ): android.view.inputmethod.IInputMethodServiceTraceProto[] { + const decoded = ParserInputMethodService.InputMethodServiceTraceFileProto.decode( + buffer + ) as android.view.inputmethod.IInputMethodServiceTraceFileProto; + const timeOffset = BigInt(decoded.realToElapsedTimeOffsetNanos?.toString() ?? '0'); + this.realToElapsedTimeOffsetNs = timeOffset !== 0n ? timeOffset : undefined; + return decoded.entry ?? []; + } + + override getTimestamp( + type: TimestampType, + entry: android.view.inputmethod.IInputMethodServiceTraceProto + ): undefined | Timestamp { + const elapsedRealtimeNanos = BigInt(assertDefined(entry.elapsedRealtimeNanos).toString()); + if (type === TimestampType.ELAPSED) { + return new Timestamp(type, elapsedRealtimeNanos); + } else if (type === TimestampType.REAL && this.realToElapsedTimeOffsetNs !== undefined) { + return new Timestamp(type, this.realToElapsedTimeOffsetNs + elapsedRealtimeNanos); + } + return undefined; + } + + override processDecodedEntry( + index: number, + timestampType: TimestampType, + entry: android.view.inputmethod.IInputMethodServiceTraceProto + ): TraceTreeNode { + if (entry.elapsedRealtimeNanos === undefined || entry.elapsedRealtimeNanos === null) { + throw Error('Missing elapsedRealtimeNanos on entry'); + } + + const elapsedRealtimeNanos = BigInt(entry.elapsedRealtimeNanos.toString()); + + let clockTimeNanos: bigint | undefined = undefined; + if (this.realToElapsedTimeOffsetNs !== undefined && entry.elapsedRealtimeNanos !== undefined) { + clockTimeNanos = elapsedRealtimeNanos + this.realToElapsedTimeOffsetNs; + } + + const timestamp = Timestamp.from( + timestampType, + elapsedRealtimeNanos, + this.realToElapsedTimeOffsetNs + ); + + return { + name: TimeUtils.format(timestamp) + ' - ' + entry.where, + kind: 'InputMethodService entry', + children: [ + { + obj: ImeUtils.transformInputConnectionCall(entry.inputMethodService), + kind: 'InputMethodService', + name: '', + children: [], + stableId: 'service', + id: 'service', + }, + ], + obj: entry, + stableId: 'entry', + id: 'entry', + elapsedRealtimeNanos, + clockTimeNanos, + }; + } + + private makeHierarchyTree( + entryProto: android.view.inputmethod.IInputMethodServiceTraceProto + ): HierarchyTreeNode { + const entry = new PropertiesProviderBuilder() + .setEagerProperties(this.makeEntryEagerPropertiesTree(entryProto)) + .setLazyPropertiesStrategy(this.makeEntryLazyPropertiesStrategy(entryProto)) + .setEagerOperations([ParserInputMethodService.Operations.AddDefaultsEntryEager]) + .setCommonOperations([ParserInputMethodService.Operations.SetFormattersEntry]) + .setLazyOperations([ParserInputMethodService.Operations.AddDefaultsEntryLazy]) + .build(); + + const inputMethodService = entryProto.inputMethodService + ? new PropertiesProviderBuilder() + .setEagerProperties(this.makeServiceEagerPropertiesTree(entryProto.inputMethodService)) + .setLazyPropertiesStrategy( + this.makeServiceLazyPropertiesStrategy(entryProto.inputMethodService) + ) + .setEagerOperations([ParserInputMethodService.Operations.AddDefaultsServiceEager]) + .setCommonOperations([ + ParserInputMethodService.Operations.SetFormattersService, + ParserInputMethodService.Operations.TranslateIntDefService, + ]) + .setLazyOperations([ParserInputMethodService.Operations.AddDefaultsServiceLazy]) + .build() + : undefined; + + return new HierarchyTreeBuilderInputMethod() + .setRoot(entry) + .setChildren(inputMethodService ? [inputMethodService] : []) + .build(); + } + + private makeEntryEagerPropertiesTree( + entryProto: android.view.inputmethod.IInputMethodServiceTraceProto + ): PropertyTreeNode { + const denyList: string[] = []; + Object.getOwnPropertyNames(entryProto).forEach((it) => { + if (!ParserInputMethodService.ENTRY_EAGER_PROPERTIES.includes(it)) denyList.push(it); + }); + + return new PropertyTreeBuilderFromProto() + .setData(entryProto) + .setRootId('InputMethodService') + .setRootName('entry') + .setDenyList(denyList) + .build(); + } + + private makeEntryLazyPropertiesStrategy( + entryProto: android.view.inputmethod.IInputMethodServiceTraceProto + ): LazyPropertiesStrategyType { + return async () => { + return new PropertyTreeBuilderFromProto() + .setData(entryProto) + .setRootId('InputMethodService') + .setRootName('entry') + .setDenyList( + ParserInputMethodService.ENTRY_EAGER_PROPERTIES.concat( + ParserInputMethodService.ENTRY_DENYLIST_PROPERTIES + ) + ) + .build(); + }; + } + + private makeServiceEagerPropertiesTree( + serviceProto: android.inputmethodservice.IInputMethodServiceProto + ): PropertyTreeNode { + const denyList: string[] = []; + let data: any = serviceProto; + if (serviceProto) { + Object.getOwnPropertyNames(serviceProto).forEach((it) => { + if (!ParserInputMethodService.SERVICE_EAGER_PROPERTIES.includes(it)) denyList.push(it); + }); + } else { + data = {inputMethodService: null}; + } + return new PropertyTreeBuilderFromProto() + .setData(serviceProto) + .setRootId('InputMethodService') + .setRootName('inputMethodService') + .setDenyList(denyList) + .setVisitPrototype(false) + .build(); + } + + private makeServiceLazyPropertiesStrategy( + serviceProto: android.inputmethodservice.IInputMethodServiceProto + ): LazyPropertiesStrategyType { + return async () => { + return new PropertyTreeBuilderFromProto() + .setData(serviceProto ?? {inputMethodService: null}) + .setRootId('InputMethodService') + .setRootName('inputMethodService') + .setDenyList(ParserInputMethodService.SERVICE_EAGER_PROPERTIES) + .setVisitPrototype(false) + .build(); + }; + } +} + +export {ParserInputMethodService};
diff --git a/tools/winscope/src/parsers/parser_input_method_service_test.ts b/tools/winscope/src/parsers/input_method/parser_input_method_service_test.ts similarity index 100% rename from tools/winscope/src/parsers/parser_input_method_service_test.ts rename to tools/winscope/src/parsers/input_method/parser_input_method_service_test.ts
diff --git a/tools/winscope/src/parsers/parser_factory.ts b/tools/winscope/src/parsers/parser_factory.ts index cf31124..721d8a1 100644 --- a/tools/winscope/src/parsers/parser_factory.ts +++ b/tools/winscope/src/parsers/parser_factory.ts
@@ -22,10 +22,10 @@ import {Parser} from 'trace/parser'; import {TraceFile} from 'trace/trace_file'; import {FileAndParser} from './file_and_parser'; +import {ParserInputMethodClients} from './input_method/parser_input_method_clients'; +import {ParserInputMethodManagerService} from './input_method/parser_input_method_manager_service'; +import {ParserInputMethodService} from './input_method/parser_input_method_service'; import {ParserEventLog} from './parser_eventlog'; -import {ParserInputMethodClients} from './parser_input_method_clients'; -import {ParserInputMethodManagerService} from './parser_input_method_manager_service'; -import {ParserInputMethodService} from './parser_input_method_service'; import {ParserProtoLog} from './parser_protolog'; import {ParserScreenRecording} from './parser_screen_recording'; import {ParserScreenRecordingLegacy} from './parser_screen_recording_legacy';
diff --git a/tools/winscope/src/parsers/parser_input_method_clients.ts b/tools/winscope/src/parsers/parser_input_method_clients.ts deleted file mode 100644 index 42d1f51..0000000 --- a/tools/winscope/src/parsers/parser_input_method_clients.ts +++ /dev/null
@@ -1,117 +0,0 @@ -/* - * Copyright (C) 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. - */ - -import {assertDefined} from 'common/assert_utils'; -import {Timestamp, TimestampType} from 'common/time'; -import {TimeUtils} from 'common/time_utils'; -import root from 'protos/ime/latest/json'; -import {android} from 'protos/ime/latest/static'; -import {TraceFile} from 'trace/trace_file'; -import {TraceTreeNode} from 'trace/trace_tree_node'; -import {TraceType} from 'trace/trace_type'; -import {ImeUtils} from 'viewers/common/ime_utils'; -import {AbstractParser} from './abstract_parser'; - -class ParserInputMethodClients extends AbstractParser { - private static readonly InputMethodClientsTraceFileProto = root.lookupType( - 'android.view.inputmethod.InputMethodClientsTraceFileProto' - ); - - constructor(trace: TraceFile) { - super(trace); - this.realToElapsedTimeOffsetNs = undefined; - } - - getTraceType(): TraceType { - return TraceType.INPUT_METHOD_CLIENTS; - } - - override getMagicNumber(): number[] { - return ParserInputMethodClients.MAGIC_NUMBER; - } - - override decodeTrace( - buffer: Uint8Array - ): android.view.inputmethod.IInputMethodClientsTraceProto[] { - const decoded = ParserInputMethodClients.InputMethodClientsTraceFileProto.decode( - buffer - ) as android.view.inputmethod.IInputMethodClientsTraceFileProto; - const timeOffset = BigInt(decoded.realToElapsedTimeOffsetNanos?.toString() ?? '0'); - this.realToElapsedTimeOffsetNs = timeOffset !== 0n ? timeOffset : undefined; - return decoded.entry ?? []; - } - - override getTimestamp( - type: TimestampType, - entry: android.view.inputmethod.IInputMethodClientsTraceProto - ): undefined | Timestamp { - const elapsedRealtimeNanos = BigInt(assertDefined(entry.elapsedRealtimeNanos).toString()); - if (type === TimestampType.ELAPSED) { - return new Timestamp(type, elapsedRealtimeNanos); - } else if (type === TimestampType.REAL && this.realToElapsedTimeOffsetNs !== undefined) { - return new Timestamp(type, elapsedRealtimeNanos + this.realToElapsedTimeOffsetNs); - } - return undefined; - } - - override processDecodedEntry( - index: number, - timestampType: TimestampType, - entry: android.view.inputmethod.IInputMethodClientsTraceProto - ): TraceTreeNode { - if (entry.elapsedRealtimeNanos === undefined || entry.elapsedRealtimeNanos === null) { - throw Error('Missing elapsedRealtimeNanos on entry'); - } - - const elapsedRealtimeNanos = BigInt(entry.elapsedRealtimeNanos.toString()); - - let clockTimeNanos: bigint | undefined = undefined; - if (this.realToElapsedTimeOffsetNs !== undefined) { - clockTimeNanos = elapsedRealtimeNanos + this.realToElapsedTimeOffsetNs; - } - - const timestamp = Timestamp.from( - timestampType, - elapsedRealtimeNanos, - this.realToElapsedTimeOffsetNs - ); - - return { - name: TimeUtils.format(timestamp) + ' - ' + entry.where, - kind: 'InputMethodClient entry', - children: [ - { - obj: ImeUtils.transformInputConnectionCall(entry.client), - kind: 'Client', - name: entry.client?.viewRootImpl?.view ?? '', - children: [], - stableId: 'client', - id: 'client', - }, - ], - obj: entry, - stableId: 'entry', - id: 'entry', - elapsedRealtimeNanos, - clockTimeNanos, - }; - } - - private realToElapsedTimeOffsetNs: undefined | bigint; - private static readonly MAGIC_NUMBER = [0x09, 0x49, 0x4d, 0x43, 0x54, 0x52, 0x41, 0x43, 0x45]; // .IMCTRACE -} - -export {ParserInputMethodClients};
diff --git a/tools/winscope/src/parsers/parser_input_method_manager_service.ts b/tools/winscope/src/parsers/parser_input_method_manager_service.ts deleted file mode 100644 index 93847ac..0000000 --- a/tools/winscope/src/parsers/parser_input_method_manager_service.ts +++ /dev/null
@@ -1,116 +0,0 @@ -/* - * Copyright (C) 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. - */ - -import {Timestamp, TimestampType} from 'common/time'; -import {TimeUtils} from 'common/time_utils'; -import root from 'protos/ime/latest/json'; -import {android} from 'protos/ime/latest/static'; -import {TraceFile} from 'trace/trace_file'; -import {TraceTreeNode} from 'trace/trace_tree_node'; -import {TraceType} from 'trace/trace_type'; -import {assertDefined} from '../common/assert_utils'; -import {AbstractParser} from './abstract_parser'; - -class ParserInputMethodManagerService extends AbstractParser { - private static readonly InputMethodManagerServiceTraceFileProto = root.lookupType( - 'android.view.inputmethod.InputMethodManagerServiceTraceFileProto' - ); - - constructor(trace: TraceFile) { - super(trace); - this.realToElapsedTimeOffsetNs = undefined; - } - - getTraceType(): TraceType { - return TraceType.INPUT_METHOD_MANAGER_SERVICE; - } - - override getMagicNumber(): number[] { - return ParserInputMethodManagerService.MAGIC_NUMBER; - } - - override decodeTrace( - buffer: Uint8Array - ): android.view.inputmethod.IInputMethodManagerServiceTraceProto[] { - const decoded = ParserInputMethodManagerService.InputMethodManagerServiceTraceFileProto.decode( - buffer - ) as android.view.inputmethod.IInputMethodManagerServiceTraceFileProto; - const timeOffset = BigInt(decoded.realToElapsedTimeOffsetNanos?.toString() ?? '0'); - this.realToElapsedTimeOffsetNs = timeOffset !== 0n ? timeOffset : undefined; - return decoded.entry ?? []; - } - - protected override getTimestamp( - type: TimestampType, - entry: android.view.inputmethod.IInputMethodManagerServiceTraceProto - ): undefined | Timestamp { - const elapsedRealtimeNanos = BigInt(assertDefined(entry.elapsedRealtimeNanos).toString()); - if (type === TimestampType.ELAPSED) { - return new Timestamp(TimestampType.ELAPSED, elapsedRealtimeNanos); - } else if (type === TimestampType.REAL && this.realToElapsedTimeOffsetNs !== undefined) { - return new Timestamp(type, this.realToElapsedTimeOffsetNs + elapsedRealtimeNanos); - } - return undefined; - } - - protected override processDecodedEntry( - index: number, - timestampType: TimestampType, - entry: android.view.inputmethod.IInputMethodManagerServiceTraceProto - ): TraceTreeNode { - if (entry.elapsedRealtimeNanos === undefined || entry.elapsedRealtimeNanos === null) { - throw Error('Missing elapsedRealtimeNanos on entry'); - } - - const elapsedRealtimeNanos = BigInt(entry.elapsedRealtimeNanos.toString()); - - let clockTimeNanos: bigint | undefined = undefined; - if (this.realToElapsedTimeOffsetNs !== undefined && entry.elapsedRealtimeNanos !== undefined) { - clockTimeNanos = elapsedRealtimeNanos + this.realToElapsedTimeOffsetNs; - } - - const timestamp = Timestamp.from( - timestampType, - elapsedRealtimeNanos, - this.realToElapsedTimeOffsetNs - ); - - return { - name: TimeUtils.format(timestamp) + ' - ' + entry.where, - kind: 'InputMethodManagerService entry', - children: [ - { - obj: entry.inputMethodManagerService, - kind: 'InputMethodManagerService', - name: '', - children: [], - stableId: 'managerservice', - id: 'managerservice', - }, - ], - obj: entry, - stableId: 'entry', - id: 'entry', - elapsedRealtimeNanos, - clockTimeNanos, - }; - } - - private realToElapsedTimeOffsetNs: undefined | bigint; - private static readonly MAGIC_NUMBER = [0x09, 0x49, 0x4d, 0x4d, 0x54, 0x52, 0x41, 0x43, 0x45]; // .IMMTRACE -} - -export {ParserInputMethodManagerService};
diff --git a/tools/winscope/src/parsers/parser_input_method_service.ts b/tools/winscope/src/parsers/parser_input_method_service.ts deleted file mode 100644 index 3399916..0000000 --- a/tools/winscope/src/parsers/parser_input_method_service.ts +++ /dev/null
@@ -1,117 +0,0 @@ -/* - * Copyright (C) 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. - */ - -import {Timestamp, TimestampType} from 'common/time'; -import {TimeUtils} from 'common/time_utils'; -import root from 'protos/ime/latest/json'; -import {android} from 'protos/ime/latest/static'; -import {TraceFile} from 'trace/trace_file'; -import {TraceTreeNode} from 'trace/trace_tree_node'; -import {TraceType} from 'trace/trace_type'; -import {ImeUtils} from 'viewers/common/ime_utils'; -import {assertDefined} from '../common/assert_utils'; -import {AbstractParser} from './abstract_parser'; - -class ParserInputMethodService extends AbstractParser { - private static readonly InputMethodServiceTraceFileProto = root.lookupType( - 'android.view.inputmethod.InputMethodServiceTraceFileProto' - ); - - constructor(trace: TraceFile) { - super(trace); - this.realToElapsedTimeOffsetNs = undefined; - } - - getTraceType(): TraceType { - return TraceType.INPUT_METHOD_SERVICE; - } - - override getMagicNumber(): number[] { - return ParserInputMethodService.MAGIC_NUMBER; - } - - override decodeTrace( - buffer: Uint8Array - ): android.view.inputmethod.IInputMethodServiceTraceProto[] { - const decoded = ParserInputMethodService.InputMethodServiceTraceFileProto.decode( - buffer - ) as android.view.inputmethod.IInputMethodServiceTraceFileProto; - const timeOffset = BigInt(decoded.realToElapsedTimeOffsetNanos?.toString() ?? '0'); - this.realToElapsedTimeOffsetNs = timeOffset !== 0n ? timeOffset : undefined; - return decoded.entry ?? []; - } - - override getTimestamp( - type: TimestampType, - entry: android.view.inputmethod.IInputMethodServiceTraceProto - ): undefined | Timestamp { - const elapsedRealtimeNanos = BigInt(assertDefined(entry.elapsedRealtimeNanos).toString()); - if (type === TimestampType.ELAPSED) { - return new Timestamp(type, elapsedRealtimeNanos); - } else if (type === TimestampType.REAL && this.realToElapsedTimeOffsetNs !== undefined) { - return new Timestamp(type, this.realToElapsedTimeOffsetNs + elapsedRealtimeNanos); - } - return undefined; - } - - override processDecodedEntry( - index: number, - timestampType: TimestampType, - entry: android.view.inputmethod.IInputMethodServiceTraceProto - ): TraceTreeNode { - if (entry.elapsedRealtimeNanos === undefined || entry.elapsedRealtimeNanos === null) { - throw Error('Missing elapsedRealtimeNanos on entry'); - } - - const elapsedRealtimeNanos = BigInt(entry.elapsedRealtimeNanos.toString()); - - let clockTimeNanos: bigint | undefined = undefined; - if (this.realToElapsedTimeOffsetNs !== undefined && entry.elapsedRealtimeNanos !== undefined) { - clockTimeNanos = elapsedRealtimeNanos + this.realToElapsedTimeOffsetNs; - } - - const timestamp = Timestamp.from( - timestampType, - elapsedRealtimeNanos, - this.realToElapsedTimeOffsetNs - ); - - return { - name: TimeUtils.format(timestamp) + ' - ' + entry.where, - kind: 'InputMethodService entry', - children: [ - { - obj: ImeUtils.transformInputConnectionCall(entry.inputMethodService), - kind: 'InputMethodService', - name: '', - children: [], - stableId: 'service', - id: 'service', - }, - ], - obj: entry, - stableId: 'entry', - id: 'entry', - elapsedRealtimeNanos, - clockTimeNanos, - }; - } - - private realToElapsedTimeOffsetNs: undefined | bigint; - private static readonly MAGIC_NUMBER = [0x09, 0x49, 0x4d, 0x53, 0x54, 0x52, 0x41, 0x43, 0x45]; // .IMSTRACE -} - -export {ParserInputMethodService};
diff --git a/tools/winscope/src/parsers/surface_flinger/parser_surface_flinger_utils.ts b/tools/winscope/src/parsers/surface_flinger/parser_surface_flinger_utils.ts index dd905ca..7f8c8e7 100644 --- a/tools/winscope/src/parsers/surface_flinger/parser_surface_flinger_utils.ts +++ b/tools/winscope/src/parsers/surface_flinger/parser_surface_flinger_utils.ts
@@ -17,6 +17,7 @@ import {assertDefined} from 'common/assert_utils'; import {perfetto} from 'protos/surfaceflinger/latest/static'; import {android} from 'protos/surfaceflinger/udc/static'; +import {LazyPropertiesStrategyType} from 'trace/tree_node/properties_provider'; import {PropertyTreeBuilderFromProto} from 'trace/tree_node/property_tree_builder_from_proto'; import {PropertyTreeNode} from 'trace/tree_node/property_tree_node'; import {AddCompositionType} from './operations/add_composition_type'; @@ -120,7 +121,7 @@ static makeLayerLazyPropertiesStrategy( layer: android.surfaceflinger.ILayerProto | perfetto.protos.ILayerProto, duplicateCount: number - ) { + ): LazyPropertiesStrategyType { return async () => { return new PropertyTreeBuilderFromProto() .setData(layer) @@ -134,7 +135,7 @@ static makeEntryLazyPropertiesStrategy( entry: android.surfaceflinger.ILayersTraceProto | perfetto.protos.ILayersSnapshotProto - ) { + ): LazyPropertiesStrategyType { return async () => { return new PropertyTreeBuilderFromProto() .setData(entry)
diff --git a/tools/winscope/src/parsers/view_capture/parser_view_capture_window.ts b/tools/winscope/src/parsers/view_capture/parser_view_capture_window.ts index 91fd25f..a114bd7 100644 --- a/tools/winscope/src/parsers/view_capture/parser_view_capture_window.ts +++ b/tools/winscope/src/parsers/view_capture/parser_view_capture_window.ts
@@ -30,7 +30,7 @@ import {TraceType} from 'trace/trace_type'; import {HierarchyTreeNode} from 'trace/tree_node/hierarchy_tree_node'; import {Operation} from 'trace/tree_node/operations/operation'; -import {PropertiesProvider} from 'trace/tree_node/properties_provider'; +import {LazyPropertiesStrategyType, PropertiesProvider} from 'trace/tree_node/properties_provider'; import {PropertiesProviderBuilder} from 'trace/tree_node/properties_provider_builder'; import {PropertyTreeBuilderFromProto} from 'trace/tree_node/property_tree_builder_from_proto'; import {PropertyTreeNode} from 'trace/tree_node/property_tree_node'; @@ -235,7 +235,9 @@ return children.map((child) => assertDefined(child.hashcode)); } - private makeLazyPropertiesStrategy(node: com.android.app.viewcapture.data.IViewNode) { + private makeLazyPropertiesStrategy( + node: com.android.app.viewcapture.data.IViewNode + ): LazyPropertiesStrategyType { return async () => { const id = `${this.classNames[assertDefined(node.classnameIndex)]}@${node.hashcode}`; return new PropertyTreeBuilderFromProto()
diff --git a/tools/winscope/src/parsers/window_manager/parser_window_manager_utils.ts b/tools/winscope/src/parsers/window_manager/parser_window_manager_utils.ts index 333a5eb..98098d3 100644 --- a/tools/winscope/src/parsers/window_manager/parser_window_manager_utils.ts +++ b/tools/winscope/src/parsers/window_manager/parser_window_manager_utils.ts
@@ -16,7 +16,7 @@ import {assertDefined} from 'common/assert_utils'; import {com} from 'protos/windowmanager/latest/static'; -import {PropertiesProvider} from 'trace/tree_node/properties_provider'; +import {LazyPropertiesStrategyType, PropertiesProvider} from 'trace/tree_node/properties_provider'; import {PropertiesProviderBuilder} from 'trace/tree_node/properties_provider_builder'; import {PropertyTreeBuilderFromProto} from 'trace/tree_node/property_tree_builder_from_proto'; import {PropertyTreeNode} from 'trace/tree_node/property_tree_node'; @@ -110,7 +110,7 @@ private makeEntryLazyPropertiesStrategy( entry: com.android.server.wm.IWindowManagerServiceDumpProto - ) { + ): LazyPropertiesStrategyType { return async () => { return new PropertyTreeBuilderFromProto() .setData(entry) @@ -227,13 +227,15 @@ .setDenyList(denyList) .build(); - containerProperties.addOrReplaceChild( - DEFAULT_PROPERTY_TREE_NODE_FACTORY.makeCalculatedProperty( - containerProperties.id, - 'children', - this.mapChildrenToTokens(children) - ) - ); + if (children.length > 0) { + containerProperties.addOrReplaceChild( + DEFAULT_PROPERTY_TREE_NODE_FACTORY.makeCalculatedProperty( + containerProperties.id, + 'children', + this.mapChildrenToTokens(children) + ) + ); + } containerProperties.addOrReplaceChild( DEFAULT_PROPERTY_TREE_NODE_FACTORY.makeCalculatedProperty( @@ -249,7 +251,7 @@ private makeContainerChildLazyPropertiesStrategy( containerChild: com.android.server.wm.IWindowContainerChildProto, containerChildType: WmProtoType - ) { + ): LazyPropertiesStrategyType { return async () => { const identifier = this.getIdentifier(containerChild); const name = this.getName(containerChild, identifier);
diff --git a/tools/winscope/src/trace/tree_node/properties_provider.ts b/tools/winscope/src/trace/tree_node/properties_provider.ts index 61792d0..329c3a0 100644 --- a/tools/winscope/src/trace/tree_node/properties_provider.ts +++ b/tools/winscope/src/trace/tree_node/properties_provider.ts
@@ -19,6 +19,8 @@ import {PropertySource, PropertyTreeNode} from 'trace/tree_node/property_tree_node'; import {DEFAULT_PROPERTY_TREE_NODE_FACTORY} from './property_tree_node_factory'; +export type LazyPropertiesStrategyType = () => Promise<PropertyTreeNode>; + export class PropertiesProvider { private eagerPropertiesRoot: PropertyTreeNode; private lazyPropertiesRoot: PropertyTreeNode | undefined; @@ -26,7 +28,7 @@ constructor( eagerPropertiesRoot: PropertyTreeNode, - private readonly lazyPropertiesStrategy: () => Promise<PropertyTreeNode>, + private readonly lazyPropertiesStrategy: LazyPropertiesStrategyType, private readonly commonOperations: OperationChain<PropertyTreeNode>, private readonly eagerOperations: OperationChain<PropertyTreeNode>, private readonly lazyOperations: OperationChain<PropertyTreeNode>
diff --git a/tools/winscope/src/trace/tree_node/properties_provider_builder.ts b/tools/winscope/src/trace/tree_node/properties_provider_builder.ts index ce96a7a..b1fecf4 100644 --- a/tools/winscope/src/trace/tree_node/properties_provider_builder.ts +++ b/tools/winscope/src/trace/tree_node/properties_provider_builder.ts
@@ -18,11 +18,11 @@ import {Operation} from 'trace/tree_node/operations/operation'; import {OperationChain} from 'trace/tree_node/operations/operation_chain'; import {PropertyTreeNode} from 'trace/tree_node/property_tree_node'; -import {PropertiesProvider} from './properties_provider'; +import {LazyPropertiesStrategyType, PropertiesProvider} from './properties_provider'; export class PropertiesProviderBuilder { private eagerProperties: PropertyTreeNode | undefined; - private lazyPropertiesStrategy: (() => Promise<PropertyTreeNode>) | undefined; + private lazyPropertiesStrategy: LazyPropertiesStrategyType | undefined; private commonOperations = OperationChain.emptyChain<PropertyTreeNode>(); private eagerOperations = OperationChain.emptyChain<PropertyTreeNode>(); private lazyOperations = OperationChain.emptyChain<PropertyTreeNode>(); @@ -32,7 +32,7 @@ return this; } - setLazyPropertiesStrategy(value: () => Promise<PropertyTreeNode>): this { + setLazyPropertiesStrategy(value: LazyPropertiesStrategyType): this { this.lazyPropertiesStrategy = value; return this; }
diff --git a/tools/winscope/src/trace/tree_node/property_tree_builder_from_proto.ts b/tools/winscope/src/trace/tree_node/property_tree_builder_from_proto.ts index 3e6adb4..eafd031 100644 --- a/tools/winscope/src/trace/tree_node/property_tree_builder_from_proto.ts +++ b/tools/winscope/src/trace/tree_node/property_tree_builder_from_proto.ts
@@ -23,6 +23,7 @@ private proto: any | undefined; private rootId: string | number = 'UnknownRootId'; private rootName: string | undefined = 'UnknownRootName'; + private visitProtoType = true; setData(value: any): this { this.proto = value; @@ -49,6 +50,11 @@ return this; } + setVisitPrototype(value: boolean): this { + this.visitProtoType = value; + return this; + } + build(): PropertyTreeNode { if (this.proto === undefined) { throw Error('proto not set'); @@ -59,7 +65,7 @@ if (this.rootName === undefined) { throw Error('rootName not set'); } - const factory = new PropertyTreeNodeFactory(this.denylistProperties); + const factory = new PropertyTreeNodeFactory(this.denylistProperties, this.visitProtoType); return factory.makeProtoProperty(this.makeNodeId(), '', this.proto); }
diff --git a/tools/winscope/src/trace/tree_node/property_tree_node_factory.ts b/tools/winscope/src/trace/tree_node/property_tree_node_factory.ts index 9ec4635..d74aed8 100644 --- a/tools/winscope/src/trace/tree_node/property_tree_node_factory.ts +++ b/tools/winscope/src/trace/tree_node/property_tree_node_factory.ts
@@ -17,7 +17,7 @@ import {PropertySource, PropertyTreeNode} from 'trace/tree_node/property_tree_node'; export class PropertyTreeNodeFactory { - constructor(private denylistProperties: string[] = []) {} + constructor(private denylistProperties: string[] = [], private visitPrototype = true) {} makePropertyRoot( rootId: string, @@ -141,7 +141,7 @@ props.push(prop); } }); - obj = Object.getPrototypeOf(obj); + obj = this.visitPrototype ? Object.getPrototypeOf(obj) : undefined; } while (obj); return props.sort((a, b) => (a < b ? -1 : 1)); }