Skip to main content
Glama
RecentApps.ts6.66 kB
import { AdbUtils } from "../../utils/android-cmdline-tools/adb"; import { BaseVisualChange, ProgressCallback } from "./BaseVisualChange"; import { BootedDevice, RecentAppsResult, ViewHierarchyResult } from "../../models"; import { SwipeOnScreen } from "./SwipeOnScreen"; import { PressButton } from "./PressButton"; import { ElementUtils } from "../utility/ElementUtils"; import { ObserveResult } from "../../models"; import { Axe } from "../../utils/ios-cmdline-tools/axe"; /** * Opens the recent apps screen using intelligent navigation detection */ export class RecentApps extends BaseVisualChange { private swipeOnScreen: SwipeOnScreen; private pressButton: PressButton; private elementUtils: ElementUtils; constructor(device: BootedDevice, adb: AdbUtils | null = null, axe: Axe | null = null) { super(device, adb, axe); this.swipeOnScreen = new SwipeOnScreen(device, adb); this.pressButton = new PressButton(device, adb); this.elementUtils = new ElementUtils(); } /** * Execute recent apps navigation with intelligent detection * @param progress - Optional progress callback * @returns Result of the recent apps operation */ async execute(progress?: ProgressCallback): Promise<RecentAppsResult> { return this.observedInteraction( async (observeResult: ObserveResult) => { const viewHierarchy = observeResult.viewHierarchy; if (!viewHierarchy) { return { success: false, method: "unknown" }; } const navigationMethod = this.detectNavigationStyle(viewHierarchy); switch (navigationMethod) { case "gesture": await this.executeGestureNavigation(observeResult); return { success: true, method: "gesture" }; case "legacy": await this.executeLegacyNavigation(observeResult); return { success: true, method: "legacy" }; case "hardware": default: await this.executeHardwareNavigation(); return { success: true, method: "hardware" }; } }, { changeExpected: true, timeoutMs: 3000, progress } ); } /** * Detect navigation style from view hierarchy * @param viewHierarchy - Latest ViewHierarchyResult * @returns Navigation style type */ private detectNavigationStyle(viewHierarchy: ViewHierarchyResult): "gesture" | "legacy" | "hardware" { // Look for common navigation bar elements const navigationBarIds = [ "navigationBarBackground", "navigation_bar_frame", "navbar", "nav_bar" ]; const recentAppsButtonIds = [ "recent_apps", "recent", "overview", "recents_button", "overview_button" ]; // Check for legacy navigation bar with recent apps button for (const buttonId of recentAppsButtonIds) { const element = this.elementUtils.findElementByResourceId(viewHierarchy, buttonId, "@android:id/content", true); if (element) { return "legacy"; } } // Check for navigation bar presence (indicates gesture navigation if no recent button found) for (const navId of navigationBarIds) { const element = this.elementUtils.findElementByResourceId(viewHierarchy, navId, "@android:id/content", true); if (element) { return "gesture"; } } // Check for common gesture navigation indicators const gestureIndicators = [ "home_handle", "navigation_handle", "gesture_hint", "pill" ]; for (const indicator of gestureIndicators) { const element = this.elementUtils.findElementByResourceId(viewHierarchy, indicator, "@android:id/content", true); if (element) { return "gesture"; } } // Default to hardware button if no navigation elements detected return "hardware"; } /** * Execute gesture-based navigation (swipe up from bottom) * @param observeResult - Current observation result * @returns Recent apps result */ private async executeGestureNavigation(observeResult: any): Promise<RecentAppsResult> { if (!observeResult.screenSize || !observeResult.systemInsets) { throw new Error("Screen size or system insets not available for gesture navigation"); } // Calculate gesture coordinates for recent apps (swipe up and hold from bottom center) const screenWidth = observeResult.screenSize.width; const screenHeight = observeResult.screenSize.height; const insets = observeResult.systemInsets; const startX = Math.floor(screenWidth / 2); const startY = screenHeight - (insets.bottom > 0 ? Math.floor(insets.bottom / 2) : 20); const endX = startX; const endY = Math.floor(screenHeight * 0.5); // Swipe up to middle of screen // Execute swipe gesture with longer duration for recent apps await this.adb.executeCommand( `shell input swipe ${startX} ${startY} ${endX} ${endY} 500` ); return { success: true, method: "gesture" }; } /** * Execute legacy navigation (tap recent apps button) * @param observeResult - Current observation result * @returns Recent apps result */ private async executeLegacyNavigation(observeResult: ObserveResult): Promise<RecentAppsResult> { const recentAppsButtonIds = [ "recent_apps", "recent", "overview", "recents_button", "overview_button" ]; const viewHierarchy = observeResult.viewHierarchy; if (!viewHierarchy) { return { success: false, method: "legacy", error: "View hierarchy not available" }; } // Find the recent apps button let recentButton = null; for (const buttonId of recentAppsButtonIds) { const element = this.elementUtils.findElementByResourceId(viewHierarchy, buttonId, "@android:id/content", true); if (element) { recentButton = element; break; } } if (!recentButton) { throw new Error("Recent apps button not found in navigation bar"); } // Tap on the recent apps button const center = this.elementUtils.getElementCenter(recentButton); await this.adb.executeCommand(`shell input tap ${center.x} ${center.y}`); return { success: true, method: "legacy" }; } /** * Execute hardware button navigation * @returns Recent apps result */ private async executeHardwareNavigation(): Promise<RecentAppsResult> { // Use hardware recent apps key (keycode 187) await this.adb.executeCommand("shell input keyevent 187"); return { success: true, method: "hardware" }; } }

Implementation Reference

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