Skip to content

Commit 7352d32

Browse files
committed
fix: preserve Android system dialog snapshots
1 parent 17a4d6f commit 7352d32

3 files changed

Lines changed: 72 additions & 0 deletions

File tree

src/platforms/android/__tests__/snapshot-content-recovery.test.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,47 @@ test('rejects a status-bar-only capture without window metadata', () => {
159159
expect(decision?.diagnostics.helperNonSystemMeaningfulNodeCount).toBe(0);
160160
});
161161

162+
test('keeps a recognized system dialog without window metadata', () => {
163+
const decision = classifyAndroidHelperContentRecovery(
164+
helperXml([
165+
node({
166+
text: "Demo isn't responding",
167+
resourceId: 'android:id/alertTitle',
168+
packageName: 'com.android.systemui',
169+
className: 'android.widget.TextView',
170+
}),
171+
node({
172+
text: 'Do you want to close it?',
173+
resourceId: 'android:id/message',
174+
packageName: 'com.android.systemui',
175+
className: 'android.widget.TextView',
176+
}),
177+
node({
178+
text: 'Close app',
179+
resourceId: 'android:id/button2',
180+
packageName: 'com.android.systemui',
181+
className: 'android.widget.Button',
182+
}),
183+
node({
184+
text: 'Wait',
185+
resourceId: 'android:id/button1',
186+
packageName: 'com.android.systemui',
187+
className: 'android.widget.Button',
188+
}),
189+
]),
190+
{
191+
backend: 'android-helper',
192+
nodeCount: 4,
193+
rootPresent: true,
194+
windowCount: 1,
195+
captureMode: 'interactive-windows',
196+
},
197+
{ foregroundAppPackage: 'com.pagerviewexample' },
198+
);
199+
200+
expect(decision).toBeUndefined();
201+
});
202+
162203
test('rejects system chrome inherited under an empty application window', () => {
163204
const decision = classifyAndroidHelperContentRecovery(
164205
helperXml([

src/platforms/android/alert-detection.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,15 @@ const ACCEPT_LABEL_PATTERN =
4242
const DISMISS_LABEL_PATTERN =
4343
/^(?:cancel|deny|don.t allow|dont allow|not now|no|dismiss|close|close app|later|skip)$/i;
4444

45+
export function classifyAndroidAlertIdentifier(
46+
identifier: string | null,
47+
): 'button' | 'content' | undefined {
48+
if (identifier === null) return undefined;
49+
if (ANDROID_ALERT_BUTTON_ID_PATTERN.test(identifier)) return 'button';
50+
if (ANDROID_ALERT_ID_PATTERN.test(identifier)) return 'content';
51+
return undefined;
52+
}
53+
4554
export function findAndroidAlertCandidate(nodes: RawSnapshotNode[]): AndroidAlertCandidate | null {
4655
const candidate = findAndroidAlertNodes(nodes);
4756
const candidateNodes = candidate.nodes;

src/platforms/android/snapshot-content-recovery.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { AndroidSnapshotBackendMetadata } from './snapshot-types.ts';
22
import { isAndroidInputMethodOwnedNode } from '../../contracts/android-input-ownership.ts';
3+
import { classifyAndroidAlertIdentifier } from './alert-detection.ts';
34
import { androidUiNodes, type AndroidUiNodeMetadata } from './ui-hierarchy.ts';
45

56
const ANDROID_WINDOW_TYPE_APPLICATION = 1;
@@ -48,6 +49,7 @@ export function classifyAndroidHelperContentRecovery(
4849
);
4950
}
5051

52+
if (hasRecognizedAndroidAlertSurface(summary)) return undefined;
5153
if (isForegroundAppContentHiddenByInputMethod(summary)) return undefined;
5254
if (isForegroundAppContentPoor(summary)) {
5355
return buildRecoveryDecision(
@@ -143,6 +145,10 @@ function isSystemWindowOnly(summary: AndroidHelperXmlSummary): boolean {
143145
);
144146
}
145147

148+
function hasRecognizedAndroidAlertSurface(summary: AndroidHelperXmlSummary): boolean {
149+
return summary.hasAlertButtonIdentifier && summary.hasAlertContentIdentifier;
150+
}
151+
146152
function buildRecoveryDecision(
147153
summary: AndroidHelperXmlSummary,
148154
metadata: AndroidSnapshotBackendMetadata,
@@ -165,6 +171,8 @@ type AndroidHelperXmlSummary = {
165171
applicationMeaningfulNodeCount: number;
166172
nonSystemMeaningfulNodeCount: number;
167173
inputMethodMeaningfulNodeCount: number;
174+
hasAlertButtonIdentifier: boolean;
175+
hasAlertContentIdentifier: boolean;
168176
foregroundAppPackage?: string;
169177
foregroundAppMeaningfulNodeCount?: number;
170178
windowTypes: number[];
@@ -200,6 +208,8 @@ function createAndroidHelperXmlSummaryState(
200208
applicationMeaningfulNodeCount: 0,
201209
nonSystemMeaningfulNodeCount: 0,
202210
inputMethodMeaningfulNodeCount: 0,
211+
hasAlertButtonIdentifier: false,
212+
hasAlertContentIdentifier: false,
203213
...(foregroundAppPackage !== undefined
204214
? { foregroundAppPackage, foregroundAppMeaningfulNodeCount: 0 }
205215
: {}),
@@ -213,10 +223,20 @@ function recordAndroidHelperSummaryNode(
213223
): void {
214224
summary.nodeCount += 1;
215225
if (node.packageName === ANDROID_SYSTEM_UI_PACKAGE) summary.systemUiNodeCount += 1;
226+
if (node.visibleToUser !== false) recordAndroidAlertIdentifier(summary, node.resourceId);
216227
recordAndroidHelperWindowNode(summary, node);
217228
recordAndroidHelperMeaningfulNode(summary, node);
218229
}
219230

231+
function recordAndroidAlertIdentifier(
232+
summary: AndroidHelperXmlSummaryState,
233+
resourceId: string | null,
234+
): void {
235+
const kind = classifyAndroidAlertIdentifier(resourceId);
236+
if (kind === 'button') summary.hasAlertButtonIdentifier = true;
237+
if (kind === 'content') summary.hasAlertContentIdentifier = true;
238+
}
239+
220240
function recordAndroidHelperWindowNode(
221241
summary: AndroidHelperXmlSummaryState,
222242
node: AndroidUiNodeMetadata,
@@ -275,6 +295,8 @@ function finalizeAndroidHelperXmlSummary(
275295
applicationMeaningfulNodeCount: summary.applicationMeaningfulNodeCount,
276296
nonSystemMeaningfulNodeCount: summary.nonSystemMeaningfulNodeCount,
277297
inputMethodMeaningfulNodeCount: summary.inputMethodMeaningfulNodeCount,
298+
hasAlertButtonIdentifier: summary.hasAlertButtonIdentifier,
299+
hasAlertContentIdentifier: summary.hasAlertContentIdentifier,
278300
...(summary.foregroundAppPackage !== undefined
279301
? {
280302
foregroundAppPackage: summary.foregroundAppPackage,

0 commit comments

Comments
 (0)