blob: 58717ccc78afe99b2262f31b0f44d2ee4da156f4 [file]
// 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;
}
}