| // 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 PixelInputLifecycleExtension implements InputLifecycleExtension { | |
| readonly id = 'com.google.PixelInputLifecycle'; | |
| readonly name = 'Google Pixel Touch'; | |
| readonly requiredModules = ['pixel.touch']; | |
| async isEligible(trace: Trace): Promise<boolean> { | |
| const result = await trace.engine.query( | |
| `SELECT 1 FROM slice WHERE name GLOB 'algo->processFrame:*' LIMIT 1`, | |
| ); | |
| return result.numRows() > 0; | |
| } | |
| getSqlJoinSpec(): SqlJoinSpec { | |
| return { | |
| tableName: 'pixel_touch_events', | |
| tableAlias: 'pixel', | |
| joinOn: | |
| '((cast_int!(pixel.in_ts + pixel.resample_latency_offset) / 1000) * 1000) = ((cast_int!(a_evt.event_time) / 1000) * 1000)', | |
| }; | |
| } | |
| getStages(): StageDefinition[] { | |
| return [ | |
| { | |
| key: 'pixel_touch_th', | |
| headerName: 'Pixel Touch Top Half', | |
| sequenceNumber: 100, | |
| idField: 'id_pixel_touch_th', | |
| trackField: 'track_pixel_touch_th', | |
| tsField: 'ts_pixel_touch_th', | |
| durField: 'dur_pixel_touch_th', | |
| }, | |
| { | |
| key: 'pixel_touch_bh', | |
| headerName: 'Pixel Touch Bottom Half', | |
| sequenceNumber: 300, | |
| idField: 'id_pixel_touch_bh', | |
| trackField: 'track_pixel_touch_bh', | |
| tsField: 'ts_pixel_touch_bh', | |
| durField: 'dur_pixel_touch_bh', | |
| }, | |
| { | |
| key: 'pixel_touch', | |
| headerName: 'Pixel Touch Twoshay', | |
| sequenceNumber: 500, | |
| idField: 'id_pixel_touch', | |
| trackField: 'track_pixel_touch', | |
| tsField: 'ts_pixel_touch', | |
| durField: 'dur_pixel_touch', | |
| }, | |
| ]; | |
| } | |
| async resolveInputId( | |
| trace: Trace, | |
| sliceId: number, | |
| ): Promise<string | undefined> { | |
| const stages = this.getStages(); | |
| const whereClauses = stages | |
| .map((s) => `p.${s.idField} = ${sliceId}`) | |
| .join(' OR '); | |
| const query = ` | |
| SELECT e.input_event_id AS input_id | |
| FROM android_input_events e | |
| JOIN pixel_touch_events p ON | |
| ((cast_int!(p.in_ts + p.resample_latency_offset) / 1000) * 1000) = ((cast_int!(e.event_time) / 1000) * 1000) | |
| WHERE ${whereClauses} | |
| 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; | |
| } | |
| } |