Skip to content

Commit 17c36b5

Browse files
committed
Improve Android inspector and stream pacing
1 parent 5055a02 commit 17c36b5

9 files changed

Lines changed: 221 additions & 35 deletions

File tree

client/src/api/types.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,15 +160,23 @@ export interface AccessibilityNode {
160160
AXUniqueId?: string | null;
161161
AXValue?: string | null;
162162
alpha?: number | null;
163+
androidClass?: string | null;
164+
androidPackage?: string | null;
165+
androidResourceId?: string | null;
163166
backgroundColor?: Record<string, unknown> | null;
164167
bounds?: AccessibilityFrame | null;
168+
checkable?: boolean | null;
169+
checked?: boolean | null;
165170
className?: string | null;
171+
clickable?: boolean | null;
166172
children?: AccessibilityNode[];
167173
control?: Record<string, unknown> | null;
168174
content_required?: boolean | null;
169175
custom_actions?: string[] | null;
170176
debugDescription?: string | null;
171177
enabled?: boolean | null;
178+
focusable?: boolean | null;
179+
focused?: boolean | null;
172180
frame?: AccessibilityFrame | null;
173181
frameInScreen?: AccessibilityFrame | null;
174182
help?: string | null;
@@ -177,14 +185,18 @@ export interface AccessibilityNode {
177185
isHidden?: boolean | null;
178186
isOpaque?: boolean | null;
179187
isUserInteractionEnabled?: boolean | null;
188+
longClickable?: boolean | null;
180189
moduleName?: string | null;
181190
nativeScript?: Record<string, unknown> | null;
191+
password?: boolean | null;
182192
pid?: number | null;
183193
placeholder?: string | null;
184194
reactNative?: Record<string, unknown> | null;
185195
role?: string | null;
186196
role_description?: string | null;
187197
scroll?: Record<string, unknown> | null;
198+
scrollable?: boolean | null;
199+
selected?: boolean | null;
188200
source?:
189201
| "native-ax"
190202
| "in-app-inspector"
@@ -210,6 +222,7 @@ export interface AccessibilityNode {
210222
}
211223

212224
export type AccessibilitySource =
225+
| "android-uiautomator"
213226
| "native-ax"
214227
| "in-app-inspector"
215228
| "nativescript"

client/src/app/AppShell.tsx

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1257,6 +1257,7 @@ export function AppShell({
12571257
viewportChromeProfile,
12581258
chromeScreenRect,
12591259
);
1260+
const isAndroidViewport = isAndroidSimulator(selectedSimulator);
12601261
const chromeScreenStyle =
12611262
viewportChromeProfile && chromeScreenRect
12621263
? ({
@@ -1287,15 +1288,19 @@ export function AppShell({
12871288
} satisfies CSSProperties)
12881289
: null;
12891290
const screenOnlyStyle =
1290-
!viewportChromeProfile && chromeProfile && chromeProfile.screenWidth > 0
1291+
!viewportChromeProfile && isAndroidViewport
12911292
? ({
1292-
borderRadius: `${Math.min(
1293-
chromeProfile.cornerRadius *
1294-
(DEVICE_SCREEN_WIDTH / chromeProfile.screenWidth),
1295-
DEVICE_SCREEN_WIDTH / 2,
1296-
)}px`,
1293+
borderRadius: "10px",
12971294
} satisfies CSSProperties)
1298-
: null;
1295+
: !viewportChromeProfile && chromeProfile && chromeProfile.screenWidth > 0
1296+
? ({
1297+
borderRadius: `${Math.min(
1298+
chromeProfile.cornerRadius *
1299+
(DEVICE_SCREEN_WIDTH / chromeProfile.screenWidth),
1300+
DEVICE_SCREEN_WIDTH / 2,
1301+
)}px`,
1302+
} satisfies CSSProperties)
1303+
: null;
12991304
const viewportScreenStyle = chromeScreenStyle ?? screenOnlyStyle;
13001305
const shellStyle = viewportChromeProfile
13011306
? {
@@ -1998,6 +2003,7 @@ export function AppShell({
19982003
outerCanvasRef={handleOuterCanvasRef}
19992004
rotationQuarterTurns={rotationQuarterTurns}
20002005
screenAspect={screenAspect}
2006+
screenClassName={isAndroidViewport ? "android-screen" : undefined}
20012007
selectedSimulator={selectedSimulator}
20022008
shellStyle={shellStyle}
20032009
streamCanvasRef={handleStreamCanvasRef}
@@ -2149,6 +2155,14 @@ function normalizeMaxEdge(
21492155
: fallback;
21502156
}
21512157

2158+
function isAndroidSimulator(simulator: SimulatorMetadata | null): boolean {
2159+
return Boolean(
2160+
simulator?.platform === "android-emulator" ||
2161+
simulator?.deviceTypeIdentifier === "android-emulator" ||
2162+
simulator?.udid.startsWith("android:"),
2163+
);
2164+
}
2165+
21522166
function streamConfigsEqual(left: StreamConfig, right: StreamConfig): boolean {
21532167
return (
21542168
left.encoder === right.encoder &&

client/src/app/uiState.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ const ACCESSIBILITY_SOURCE_ORDER: AccessibilitySource[] = [
3636
"react-native",
3737
"swiftui",
3838
"in-app-inspector",
39+
"android-uiautomator",
3940
"native-ax",
4041
];
4142

@@ -160,6 +161,7 @@ export function isAccessibilitySource(
160161
value === "react-native" ||
161162
value === "swiftui" ||
162163
value === "in-app-inspector" ||
164+
value === "android-uiautomator" ||
163165
value === "native-ax"
164166
);
165167
}

client/src/features/accessibility/AccessibilityInspector.tsx

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -458,16 +458,23 @@ function NodeDetails({
458458
node: AccessibilityNode;
459459
selectedSimulator: SimulatorMetadata | null;
460460
}) {
461+
const isAndroid = isAndroidSimulator(selectedSimulator);
461462
const details = [
462463
["Type", accessibilityKind(node)],
463464
["Label", primaryAccessibilityText(node)],
464465
["Source", sourceLocationText(node)],
465-
["Identifier", accessibilityIdentifier(node)],
466+
[
467+
isAndroid ? "Resource ID" : "Identifier",
468+
isAndroid
469+
? (node.androidResourceId ?? "")
470+
: accessibilityIdentifier(node),
471+
],
466472
["Inspector ID", node.inspectorId ?? ""],
467473
["Module", node.moduleName ?? ""],
468474
["NativeScript", nativeScriptDescription(node.nativeScript)],
469475
["React Native", reactNativeDescription(node.reactNative)],
470-
["UIKit Class", node.className ?? ""],
476+
[isAndroid ? "Android Class" : "UIKit Class", node.className ?? ""],
477+
["Package", isAndroid ? (node.androidPackage ?? "") : ""],
471478
["Last JS", lastUIKitScriptText(node)],
472479
["Value", node.AXValue ?? ""],
473480
["Role", node.role ?? ""],
@@ -476,6 +483,15 @@ function NodeDetails({
476483
["SwiftUI", swiftUIDescription(node.swiftUI)],
477484
["Enabled", node.enabled == null ? "" : node.enabled ? "true" : "false"],
478485
["Hidden", node.isHidden == null ? "" : node.isHidden ? "true" : "false"],
486+
["Clickable", boolDetail(isAndroid, node.clickable)],
487+
["Long Clickable", boolDetail(isAndroid, node.longClickable)],
488+
["Focusable", boolDetail(isAndroid, node.focusable)],
489+
["Focused", boolDetail(isAndroid, node.focused)],
490+
["Scrollable", boolDetail(isAndroid, node.scrollable)],
491+
["Checkable", boolDetail(isAndroid, node.checkable)],
492+
["Checked", boolDetail(isAndroid, node.checked)],
493+
["Selected", boolDetail(isAndroid, node.selected)],
494+
["Password", boolDetail(isAndroid, node.password)],
479495
["Alpha", node.alpha == null ? "" : String(round(node.alpha))],
480496
["Frame", validFrame(node.frame) ? frameText(node.frame) : ""],
481497
["PID", node.pid == null ? "" : String(node.pid)],
@@ -497,6 +513,18 @@ function NodeDetails({
497513
);
498514
}
499515

516+
function isAndroidSimulator(simulator: SimulatorMetadata | null): boolean {
517+
return Boolean(
518+
simulator?.platform === "android-emulator" ||
519+
simulator?.deviceTypeIdentifier === "android-emulator" ||
520+
simulator?.udid.startsWith("android:"),
521+
);
522+
}
523+
524+
function boolDetail(include: boolean, value: boolean | null | undefined) {
525+
return include && value != null ? (value ? "true" : "false") : "";
526+
}
527+
500528
function UIKitScriptEditor({
501529
node,
502530
selectedSimulator,
@@ -729,6 +757,7 @@ const HIERARCHY_SOURCE_ORDER: AccessibilitySource[] = [
729757
"react-native",
730758
"swiftui",
731759
"in-app-inspector",
760+
"android-uiautomator",
732761
"native-ax",
733762
];
734763

@@ -759,6 +788,9 @@ function sourceLabel(source: AccessibilitySource): string {
759788
if (source === "swiftui") {
760789
return "SwiftUI";
761790
}
791+
if (source === "android-uiautomator") {
792+
return "Android";
793+
}
762794
return source === "in-app-inspector" ? "UIKit" : "Native AX";
763795
}
764796

client/src/features/viewport/DeviceChrome.tsx

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ interface DeviceChromeProps {
4242
onStartPanning: (event: React.PointerEvent<HTMLElement>) => void;
4343
rotationQuarterTurns: number;
4444
screenAspect: string;
45+
screenClassName?: string;
4546
shellStyle: CSSProperties | null;
4647
simulatorName: string;
4748
streamBackend: string;
@@ -80,6 +81,7 @@ export function DeviceChrome({
8081
onStartPanning,
8182
rotationQuarterTurns,
8283
screenAspect,
84+
screenClassName,
8385
shellStyle,
8486
simulatorName,
8587
streamBackend,
@@ -138,6 +140,7 @@ export function DeviceChrome({
138140
onPickerSelect={onPickerSelect}
139141
rotationQuarterTurns={rotationQuarterTurns}
140142
simulatorName={simulatorName}
143+
screenClassName={screenClassName}
141144
streamBackend={streamBackend}
142145
streamCanvasRef={streamCanvasRef}
143146
streamCanvasKey={streamCanvasKey}
@@ -179,6 +182,7 @@ export function DeviceChrome({
179182
onPickerSelect={onPickerSelect}
180183
rotationQuarterTurns={rotationQuarterTurns}
181184
simulatorName={simulatorName}
185+
screenClassName={screenClassName}
182186
streamBackend={streamBackend}
183187
streamCanvasRef={streamCanvasRef}
184188
streamCanvasKey={streamCanvasKey}
@@ -418,6 +422,7 @@ interface ScreenLayerProps {
418422
onPickerHover: (id: string | null) => void;
419423
onPickerSelect: (id: string) => void;
420424
rotationQuarterTurns: number;
425+
screenClassName?: string;
421426
simulatorName: string;
422427
streamBackend: string;
423428
streamCanvasRef: Ref<HTMLCanvasElement | null>;
@@ -445,6 +450,7 @@ function ScreenLayer({
445450
onPickerHover,
446451
onPickerSelect,
447452
rotationQuarterTurns,
453+
screenClassName,
448454
simulatorName,
449455
streamBackend,
450456
streamCanvasRef,
@@ -456,7 +462,13 @@ function ScreenLayer({
456462
}: ScreenLayerProps) {
457463
return (
458464
<div
459-
className={`device-screen ${useChromeProfile ? "chrome-screen" : ""}`}
465+
className={[
466+
"device-screen",
467+
useChromeProfile ? "chrome-screen" : "",
468+
screenClassName ?? "",
469+
]
470+
.filter(Boolean)
471+
.join(" ")}
460472
onPointerCancel={onScreenPointerCancel}
461473
onPointerDown={onScreenPointerDown}
462474
onPointerMove={onScreenPointerMove}

client/src/features/viewport/SimulatorViewport.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ interface SimulatorViewportProps {
5656
outerCanvasRef: Ref<HTMLDivElement | null>;
5757
rotationQuarterTurns: number;
5858
screenAspect: string;
59+
screenClassName?: string;
5960
selectedSimulator: SimulatorMetadata | null;
6061
shellStyle: CSSProperties | null;
6162
streamBackend: string;
@@ -112,6 +113,7 @@ export function SimulatorViewport({
112113
outerCanvasRef,
113114
rotationQuarterTurns,
114115
screenAspect,
116+
screenClassName,
115117
selectedSimulator,
116118
shellStyle,
117119
streamBackend,
@@ -197,6 +199,7 @@ export function SimulatorViewport({
197199
onStartPanning={onStartPanning}
198200
rotationQuarterTurns={rotationQuarterTurns}
199201
screenAspect={screenAspect}
202+
screenClassName={screenClassName}
200203
shellStyle={shellStyle}
201204
simulatorName={selectedSimulator.name}
202205
streamBackend={streamBackend}

client/src/styles/components.css

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -636,6 +636,12 @@
636636
color: color-mix(in srgb, #d7ba7d 82%, var(--text));
637637
}
638638

639+
.hierarchy-source-pill.source-android-uiautomator {
640+
border-color: color-mix(in srgb, #7fd97f 55%, var(--border));
641+
background: color-mix(in srgb, #7fd97f 13%, transparent);
642+
color: color-mix(in srgb, #7fd97f 82%, var(--text));
643+
}
644+
639645
.hierarchy-source-pill.active {
640646
gap: 5px;
641647
padding-inline: 7px 8px;
@@ -1491,6 +1497,11 @@
14911497
border-radius: 0;
14921498
}
14931499

1500+
.device-screen.android-screen {
1501+
background: transparent;
1502+
border-radius: 10px;
1503+
}
1504+
14941505
.stream-canvas {
14951506
position: absolute;
14961507
inset: 0;

0 commit comments

Comments
 (0)