Skip to content

Commit 7a8e048

Browse files
quanruclaude
andauthored
feat(android): add alwaysFetchScreenInfo option to control screen info caching (#1315)
* feat(android): add alwaysFetchScreenInfo option to control screen info caching Add a new optional parameter `alwaysFetchScreenInfo` to `AndroidDeviceOpt` that allows users to control whether screen size and orientation information should be fetched on every call or cached after the first fetch. - Add `alwaysFetchScreenInfo` parameter to `AndroidDeviceOpt` (defaults to false) - Add cache fields `cachedScreenSize` and `cachedOrientation` to `AndroidDevice` class - Update `getScreenSize()` to check and use cache when `alwaysFetchScreenInfo` is false - Update `getDisplayOrientation()` to use cache when `alwaysFetchScreenInfo` is false - Update documentation (both English and Chinese) to describe the new parameter By default, screen info is cached to reduce ADB calls and improve performance. Users can set `alwaysFetchScreenInfo: true` if they need real-time screen information (e.g., device rotation scenarios). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * fix(android): pass alwaysFetchScreenInfo option to AndroidDevice The alwaysFetchScreenInfo parameter was not being passed from agentFromAdbDevice to AndroidDevice constructor, causing the option to be ignored when creating agents. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent e97360a commit 7a8e048

5 files changed

Lines changed: 60 additions & 9 deletions

File tree

‎apps/site/docs/en/mcp-android.mdx‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ Midscene MCP provides the following Android device automation tools:
7676
Parameters:
7777
- deviceId: (Optional) Device ID to connect to. If not provided, uses the first available device.
7878
- displayId: (Optional) Display ID for multi-display Android devices (e.g., 0, 1, 2). When specified, all ADB input operations will target this specific display.
79+
- alwaysFetchScreenInfo: (Optional) Whether to always fetch screen size and orientation from the device on each call. Defaults to false (uses cache for better performance). Set to true if the device may rotate or you need real-time screen information.
7980
```
8081

8182
### App control

‎apps/site/docs/zh/mcp-android.mdx‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,8 @@ Midscene MCP 提供以下 Android 设备自动化工具:
7575
```
7676
参数:
7777
- deviceId:(可选)要连接的设备 ID。如果未提供,使用第一个可用设备
78-
- displayId:(可选)多屏 Android 设备的显示屏 ID(如 0、1、2),当指定时,所有 ADB 输入操作将针对此特定显示屏。
78+
- displayId:(可选)多屏 Android 设备的显示屏 ID(如 0、1、2),当指定时,所有 ADB 输入操作将针对此特定显示屏
79+
- alwaysFetchScreenInfo:(可选)是否每次都重新获取屏幕尺寸和方向信息。默认为 false(使用缓存以提高性能)。如果设备可能会旋转或需要实时屏幕信息,设置为 true
7980
```
8081

8182
### 应用控制

‎packages/android/src/agent.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ export async function agentFromAdbDevice(
4545
usePhysicalDisplayIdForDisplayLookup:
4646
opts?.usePhysicalDisplayIdForDisplayLookup,
4747
screenshotResizeScale: opts?.screenshotResizeScale,
48+
alwaysFetchScreenInfo: opts?.alwaysFetchScreenInfo,
4849
});
4950

5051
await device.connect();

‎packages/android/src/device.ts‎

Lines changed: 48 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ export type AndroidDeviceOpt = {
6969
usePhysicalDisplayIdForDisplayLookup?: boolean;
7070
customActions?: DeviceAction<any>[];
7171
screenshotResizeScale?: number;
72+
alwaysFetchScreenInfo?: boolean; // If true, always fetch screen size and orientation from device on each call; if false (default), cache the first result
7273
} & AndroidDeviceInputOpt;
7374

7475
export class AndroidDevice implements AbstractInterface {
@@ -82,6 +83,12 @@ export class AndroidDevice implements AbstractInterface {
8283
private destroyed = false;
8384
private description: string | undefined;
8485
private customActions?: DeviceAction<any>[];
86+
private cachedScreenSize: {
87+
override: string;
88+
physical: string;
89+
orientation: number;
90+
} | null = null;
91+
private cachedOrientation: number | null = null;
8592
interfaceType: InterfaceType = 'android';
8693
uri: string | undefined;
8794
options?: AndroidDeviceOpt;
@@ -516,6 +523,12 @@ ${Object.keys(size)
516523
physical: string;
517524
orientation: number; // 0=portrait, 1=landscape, 2=reverse portrait, 3=reverse landscape
518525
}> {
526+
// Return cached value if not always fetching and cache exists
527+
const shouldCache = !(this.options?.alwaysFetchScreenInfo ?? false);
528+
if (shouldCache && this.cachedScreenSize) {
529+
return this.cachedScreenSize;
530+
}
531+
519532
const adb = await this.getAdb();
520533

521534
// If we have an displayId, try to get size from display info
@@ -550,11 +563,18 @@ ${Object.keys(size)
550563
`Using display info for long ID ${physicalDisplayId}: ${sizeStr}, rotation: ${rotation}`,
551564
);
552565

553-
return {
566+
const result = {
554567
override: sizeStr,
555568
physical: sizeStr,
556569
orientation: rotation,
557570
};
571+
572+
// Cache the result if caching is enabled
573+
if (shouldCache) {
574+
this.cachedScreenSize = result;
575+
}
576+
577+
return result;
558578
}
559579
}
560580
}
@@ -581,11 +601,18 @@ ${Object.keys(size)
581601
`Using display info for display ID ${this.options.displayId}: ${sizeStr}, rotation: ${rotation}`,
582602
);
583603

584-
return {
604+
const result = {
585605
override: sizeStr,
586606
physical: sizeStr,
587607
orientation: rotation,
588608
};
609+
610+
// Cache the result if caching is enabled
611+
if (shouldCache) {
612+
this.cachedScreenSize = result;
613+
}
614+
615+
return result;
589616
}
590617
}
591618
}
@@ -628,7 +655,14 @@ ${Object.keys(size)
628655
const orientation = await this.getDisplayOrientation();
629656

630657
if (size.override || size.physical) {
631-
return { ...size, orientation };
658+
const result = { ...size, orientation };
659+
660+
// Cache the result if caching is enabled
661+
if (shouldCache) {
662+
this.cachedScreenSize = result;
663+
}
664+
665+
return result;
632666
}
633667

634668
throw new Error(`Failed to get screen size, output: ${stdout}`);
@@ -703,6 +737,12 @@ ${Object.keys(size)
703737
}
704738

705739
async getDisplayOrientation(): Promise<number> {
740+
// Return cached value if not always fetching and cache exists
741+
const shouldCache = !(this.options?.alwaysFetchScreenInfo ?? false);
742+
if (shouldCache && this.cachedOrientation !== null) {
743+
return this.cachedOrientation;
744+
}
745+
706746
const adb = await this.getAdb();
707747
let orientation = 0;
708748

@@ -740,6 +780,11 @@ ${Object.keys(size)
740780
}
741781
}
742782

783+
// Cache the result if caching is enabled
784+
if (shouldCache) {
785+
this.cachedOrientation = orientation;
786+
}
787+
743788
return orientation;
744789
}
745790

‎packages/core/src/ai-model/common.ts‎

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -693,30 +693,33 @@ export const parseActionParam = (
693693
rawParam: Record<string, any>,
694694
zodSchema: z.ZodType<any>,
695695
): Record<string, any> => {
696+
// Handle undefined or null rawParam by providing an empty object
697+
const param = rawParam ?? {};
698+
696699
// Find all locate fields in the schema
697700
const locateFields = findAllMidsceneLocatorField(zodSchema);
698701

699702
// If there are no locate fields, just do normal validation
700703
if (locateFields.length === 0) {
701-
return zodSchema.parse(rawParam);
704+
return zodSchema.parse(param);
702705
}
703706

704707
// Extract locate field values to restore later
705708
const locateFieldValues: Record<string, any> = {};
706709
for (const fieldName of locateFields) {
707-
if (fieldName in rawParam) {
708-
locateFieldValues[fieldName] = rawParam[fieldName];
710+
if (fieldName in param) {
711+
locateFieldValues[fieldName] = param[fieldName];
709712
}
710713
}
711714

712715
// Build params for validation - skip locate fields and use dummy values
713716
const paramsForValidation: Record<string, any> = {};
714-
for (const key in rawParam) {
717+
for (const key in param) {
715718
if (locateFields.includes(key)) {
716719
// Use dummy value to satisfy schema validation
717720
paramsForValidation[key] = { prompt: '_dummy_' };
718721
} else {
719-
paramsForValidation[key] = rawParam[key];
722+
paramsForValidation[key] = param[key];
720723
}
721724
}
722725

0 commit comments

Comments
 (0)