| // Copyright (C) 2026 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 m from 'mithril'; |
| import type {TrackEventDetailsPanel} from '../../public/details_panel'; |
| import type {TrackEventSelection} from '../../public/selection'; |
| import type {Trace} from '../../public/trace'; |
| import { |
| asSliceSqlId, |
| type SliceSqlId, |
| } from '../../components/sql_utils/core_types'; |
| import {getSlice, type SliceDetails} from '../../components/sql_utils/slice'; |
| import {renderDetails} from '../../components/details/slice_details'; |
| import type {Engine} from '../../trace_processor/engine'; |
| import {STR_NULL} from '../../trace_processor/query_result'; |
| import {Tree, TreeNode} from '../../widgets/tree'; |
| import {DetailsShell} from '../../widgets/details_shell'; |
| import {Section} from '../../widgets/section'; |
| import {TrackEventRef} from '../../components/widgets/track_event_ref'; |
| |
| interface BinderTxnDetails { |
| // Whether this is the client or server side of the binder transaction. |
| txnRole: string; |
| interfaceName?: string; |
| methodName?: string; |
| } |
| |
| async function getBinderTxnDetails( |
| engine: Engine, |
| id: SliceSqlId, |
| ): Promise<BinderTxnDetails | undefined> { |
| const queryResult = await engine.query(` |
| SELECT |
| IF(binder_txn_id = ${id}, 'Client', IF(binder_reply_id = ${id}, 'Server', '')) as txnRole, |
| interface as interfaceName, |
| method_name as methodName |
| FROM android_binder_txns |
| WHERE binder_txn_id = ${id} OR binder_reply_id = ${id} -- slice id is either client or server slice |
| `); |
| |
| const it = queryResult.iter({ |
| txnRole: STR_NULL, |
| interfaceName: STR_NULL, |
| methodName: STR_NULL, |
| }); |
| |
| if (it.valid()) { |
| return { |
| txnRole: it.txnRole || 'Unknown', |
| interfaceName: it.interfaceName ?? undefined, |
| methodName: it.methodName ?? undefined, |
| }; |
| } |
| return undefined; |
| } |
| |
| export class BinderSliceDetailsPanel implements TrackEventDetailsPanel { |
| private sliceDetails: SliceDetails | undefined; |
| private binderTxnDetails: BinderTxnDetails | undefined; |
| private isLoading = true; |
| |
| constructor(private readonly trace: Trace) {} |
| |
| async load(selection: TrackEventSelection): Promise<void> { |
| const sliceId = asSliceSqlId(selection.eventId); |
| |
| this.isLoading = true; |
| this.sliceDetails = await getSlice(this.trace.engine, sliceId); |
| this.binderTxnDetails = await getBinderTxnDetails( |
| this.trace.engine, |
| sliceId, |
| ); |
| this.isLoading = false; |
| } |
| |
| render() { |
| if (this.isLoading) { |
| return m(DetailsShell, { |
| title: 'Binder Transaction', |
| description: 'Loading...', |
| }); |
| } |
| |
| if (!this.sliceDetails) { |
| return m(DetailsShell, { |
| title: 'Binder Transaction', |
| description: 'Slice not found', |
| }); |
| } |
| |
| const txnRole = this.binderTxnDetails?.txnRole; |
| const sliceId = this.sliceDetails?.id; |
| const name = this.sliceDetails?.name; |
| |
| return m( |
| DetailsShell, |
| {title: `${txnRole} ${name}`}, |
| this.binderTxnDetails && |
| m( |
| Section, |
| {title: 'Binder'}, |
| m( |
| Tree, |
| m(TreeNode, { |
| left: 'Interface', |
| right: this.binderTxnDetails.interfaceName, |
| }), |
| m(TreeNode, { |
| left: 'Method', |
| right: this.binderTxnDetails.methodName, |
| }), |
| m(TreeNode, { |
| left: txnRole + ' slice', |
| right: m(TrackEventRef, { |
| trace: this.trace, |
| table: 'slice', |
| id: sliceId, |
| name: `slice[${sliceId}]`, |
| }), |
| }), |
| ), |
| ), |
| renderDetails(this.trace, this.sliceDetails), |
| ); |
| } |
| } |