Skip to main content
Glama
AwaitIdle.ts7.83 kB
import { AdbUtils } from "../../utils/android-cmdline-tools/adb"; import { logger } from "../../utils/logger"; import { Idle } from "./Idle"; import { BootedDevice } from "../../models"; export class AwaitIdle { private adb: AdbUtils; private idle: Idle; private stabilityThresholdMs = 60; private pollIntervalMs = 17; /** * Create a AwaitIdle instance * @param device - Optional device * @param adb - Optional AdbUtils instance for testing */ constructor(device: BootedDevice, adb: AdbUtils | null = null) { this.adb = adb || new AdbUtils(device); this.idle = new Idle(device, this.adb); } /** * Wait for the device rotation to complete * @param targetRotation - The expected rotation value (0 for portrait, 1 for landscape) * @param timeoutMs - Maximum time to wait in milliseconds * @returns Promise that resolves when rotation completes or rejects on timeout */ async waitForRotation(targetRotation: number, timeoutMs: number = 500): Promise<void> { const startTime = Date.now(); while (true) { const rotationResult = await this.idle.getRotationStatus(targetRotation, startTime, timeoutMs); if (rotationResult.rotationComplete) { return; // Rotation complete } if (!rotationResult.shouldContinue) { // If we get here, we timed out waiting for rotation throw new Error(`Timeout waiting for rotation to ${targetRotation} after ${timeoutMs}ms`); } // Wait a short interval before checking again await new Promise(resolve => setTimeout(resolve, this.pollIntervalMs)); } } /** * Initialize UI stability tracking state * @param packageName - Package name to monitor * @param timeoutMs - Maximum time to wait for stability * @returns Promise with initialized state */ public async initializeUiStabilityTracking(packageName: string, timeoutMs: number): Promise<{ startTime: number; lastNonIdleTime: number; prevMissedVsync: number | null; prevSlowUiThread: number | null; prevFrameDeadlineMissed: number | null; firstGfxInfoLog: boolean; }> { const startTime = Date.now(); const lastNonIdleTime = startTime; // Reset the gfxinfo stats for the package logger.info(`[AwaitIdle] Starting UI stability wait for ${packageName} (timeout: ${timeoutMs}ms, threshold: ${this.stabilityThresholdMs}ms)`); try { await this.adb.executeCommand(`shell dumpsys gfxinfo ${packageName} reset`); } catch (error) { logger.info(`[AwaitIdle] Failed to reset gfxinfo for ${packageName}: ${error}`); // Continue anyway - some packages might not support gfxinfo } // Give a moment for frame data to accumulate await new Promise(resolve => setTimeout(resolve, 50)); return { startTime, lastNonIdleTime, prevMissedVsync: null, prevSlowUiThread: null, prevFrameDeadlineMissed: null, firstGfxInfoLog: true }; } /** * Check if UI stability requirements are met * @param lastNonIdleTime - Time when UI was last detected as non-idle * @param startTime - When stability checking started * @param timeoutMs - Maximum time to wait * @returns Object indicating if stable and if should continue */ public checkUiStabilityTimeout( lastNonIdleTime: number, startTime: number, timeoutMs: number ): { isStable: boolean; shouldContinue: boolean; stableTime: number; elapsedTime: number } { const elapsedTime = Date.now() - startTime; const stableTime = Date.now() - lastNonIdleTime; const isStable = stableTime >= this.stabilityThresholdMs; const shouldContinue = elapsedTime < timeoutMs; return { isStable, shouldContinue, stableTime, elapsedTime }; } /** * Process single UI stability check iteration * @param packageName - Package name to monitor * @param state - Current tracking state * @returns Promise with updated state and stability result */ public async processSingleUiStabilityCheck( packageName: string, state: { prevMissedVsync: number | null; prevSlowUiThread: number | null; prevFrameDeadlineMissed: number | null; firstGfxInfoLog: boolean; lastNonIdleTime: number; } ): Promise<{ updatedState: typeof state; shouldUpdateLastNonIdleTime: boolean; }> { const stabilityResult = await this.idle.getUiStability( packageName, state.prevMissedVsync, state.prevSlowUiThread, state.prevFrameDeadlineMissed, state.firstGfxInfoLog ); const updatedState = { prevMissedVsync: stabilityResult.updatedPrevMissedVsync, prevSlowUiThread: stabilityResult.updatedPrevSlowUiThread, prevFrameDeadlineMissed: stabilityResult.updatedPrevFrameDeadlineMissed, firstGfxInfoLog: stabilityResult.updatedFirstGfxInfoLog, lastNonIdleTime: stabilityResult.shouldUpdateLastNonIdleTime ? Date.now() : state.lastNonIdleTime }; return { updatedState, shouldUpdateLastNonIdleTime: stabilityResult.shouldUpdateLastNonIdleTime }; } /** * Wait for UI to become stable by monitoring frame rendering with existing initialization state * @param packageName - Package name of the app to monitor * @param timeoutMs - Maximum time to wait for stability * @param initState - Pre-initialized state from initializeUiStabilityTracking * @returns Promise that resolves when UI is stable */ async waitForUiStabilityWithState( packageName: string, timeoutMs: number, initState: { startTime: number; lastNonIdleTime: number; prevMissedVsync: number | null; prevSlowUiThread: number | null; prevFrameDeadlineMissed: number | null; firstGfxInfoLog: boolean; } ): Promise<void> { logger.info(`[AwaitIdle] Continuing UI stability wait with existing state for package: ${packageName}`); // Use the provided state instead of initializing let state = initState; try { while (true) { const timeoutCheck = this.checkUiStabilityTimeout( state.lastNonIdleTime, state.startTime, timeoutMs, ); logger.info(`[AwaitIdle] Checking stability: ${timeoutCheck.elapsedTime}ms elapsed of ${timeoutMs}ms timeout`); if (timeoutCheck.isStable) { logger.info(`[AwaitIdle] UI stable after ${timeoutCheck.elapsedTime}ms (stable for ${timeoutCheck.stableTime}ms)`); return; } if (!timeoutCheck.shouldContinue) { logger.info(`[AwaitIdle] Timeout waiting for UI stability after ${timeoutMs}ms`); return; } // Process single stability check const checkResult = await this.processSingleUiStabilityCheck(packageName, state); state = { ...state, ...checkResult.updatedState }; logger.info(`[AwaitIdle] Waiting for stability: ${timeoutCheck.stableTime}ms/${this.stabilityThresholdMs}ms`); // Wait before checking again await new Promise(resolve => setTimeout(resolve, this.pollIntervalMs)); } } catch { logger.error("[AwaitIdle] Encountered an error while waiting for UI stability"); } } /** * Wait for UI to become stable by monitoring frame rendering * @param packageName - Package name of the app to monitor * @param timeoutMs - Maximum time to wait for stability * @returns Promise that resolves when UI is stable */ async waitForUiStability( packageName: string, timeoutMs: number, ): Promise<void> { logger.info(`[AwaitIdle] Waiting for UI stability for package: ${packageName} with timeoutMs: ${timeoutMs}`); const state = await this.initializeUiStabilityTracking(packageName, timeoutMs); await this.waitForUiStabilityWithState(packageName, timeoutMs, state); } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/zillow/auto-mobile'

If you have feedback or need assistance with the MCP directory API, please join our Discord server