| // 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 type {Trace} from '../../../public/trace'; |
| import type { |
| InputLifecycleExtension, |
| StageDefinition, |
| SqlJoinSpec, |
| } from './interface'; |
| import {STR_NULL} from '../../../trace_processor/query_result'; |
| |
| export class AndroidFramesInputLifecycleExtension |
| implements InputLifecycleExtension |
| { |
| readonly id = 'android.InputLifecycleAndroidFrames'; |
| readonly name = 'Android Frame Pipeline'; |
| |
| async isEligible(trace: Trace): Promise<boolean> { |
| const result = await trace.engine.query( |
| `SELECT 1 FROM slice WHERE name GLOB 'Choreographer#doFrame *' LIMIT 1`, |
| ); |
| return result.numRows() > 0; |
| } |
| |
| getSqlJoinSpec(): SqlJoinSpec { |
| return { |
| tableName: '_android_input_frames', |
| tableAlias: 'frames', |
| joinOn: 'frames.frame_id = a_evt.frame_id AND frames.upid = a_evt.upid', |
| }; |
| } |
| |
| getStages(): StageDefinition[] { |
| return [ |
| { |
| key: 'choreographer_do_frame', |
| headerName: 'Choreographer#doFrame', |
| sequenceNumber: 5000, |
| idField: 'id_do_frame', |
| trackField: 'track_do_frame', |
| tsField: 'ts_do_frame', |
| durField: 'dur_do_frame', |
| }, |
| { |
| key: 'draw_frames', |
| headerName: 'DrawFrames', |
| sequenceNumber: 6000, |
| idField: 'id_draw_frames', |
| trackField: 'track_draw_frames', |
| tsField: 'ts_draw_frames', |
| durField: 'dur_draw_frames', |
| }, |
| { |
| key: 'sf', |
| headerName: 'SurfaceFlinger', |
| sequenceNumber: 7000, |
| idField: 'id_sf', |
| trackField: 'track_sf', |
| tsField: 'ts_sf', |
| durField: 'dur_sf', |
| }, |
| ]; |
| } |
| |
| async resolveInputId( |
| trace: Trace, |
| sliceId: number, |
| ): Promise<string | undefined> { |
| const query = ` |
| WITH selected_vsync AS ( |
| -- Case 1: Selected Choreographer slice |
| SELECT chor.frame_id AS vsync_id, chor.upid |
| FROM android_frames_choreographer_do_frame chor |
| WHERE chor.id = ${sliceId} |
| UNION ALL |
| -- Case 2: Selected DrawFrames slice |
| SELECT d.frame_id AS vsync_id, d.upid |
| FROM android_frames_draw_frame d |
| WHERE d.id = ${sliceId} |
| UNION ALL |
| -- Case 3: Selected SurfaceFlinger composite slice |
| SELECT sf.app_vsync AS vsync_id, sf.app_upid AS upid |
| FROM _input_sf_resolved sf |
| WHERE sf.id = ${sliceId} |
| ) |
| SELECT e.input_event_id AS input_id |
| FROM android_input_events e |
| JOIN selected_vsync cv ON e.frame_id = cv.vsync_id AND e.upid = cv.upid |
| LIMIT 1 |
| `; |
| const result = await trace.engine.query(query); |
| const it = result.iter({input_id: STR_NULL}); |
| return it.valid() ? it.input_id ?? undefined : undefined; |
| } |
| } |