From 0637829174a284bc67ac17e89d760216c04e6fb2 Mon Sep 17 00:00:00 2001 From: timo <44401485+Timo972@users.noreply.github.com> Date: Thu, 15 Oct 2026 12:04:40 +0200 Subject: [PATCH] fix: present the broadcast picker in portrait on phones lying on their side The picker host window used a plain view controller, so the runner followed the device: on a phone lying on its side it came up in landscape, and SpringBoard presented the Control Center broadcast sheet in landscape to match. The confirmation tap then landed outside the sheet's "Start Broadcast" button, dismissing the sheet instead, so replayd never started a session and every start timed out with 408 (DRO-3369). Pin the picker host to portrait (portrait-only root view controller, scene geometry request on iOS 16+) and wait for the scene to turn before firing the picker, so the sheet appears as on an upright phone. Co-Authored-By: Claude Opus 5.5 --- .../Utilities/FBBroadcastPickerHost.m | 47 ++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/WebDriverAgentLib/Utilities/FBBroadcastPickerHost.m b/WebDriverAgentLib/Utilities/FBBroadcastPickerHost.m index da4d5a30d..00d64c99a 100644 --- a/WebDriverAgentLib/Utilities/FBBroadcastPickerHost.m +++ b/WebDriverAgentLib/Utilities/FBBroadcastPickerHost.m @@ -14,11 +14,32 @@ #endif #import "FBLogger.h" +#import "FBRunLoopSpinner.h" static NSString *const FBBroadcastPickerHostErrorDomain = @"com.facebook.WebDriverAgent.FBBroadcastPickerHost"; #if !TARGET_OS_TV static UIWindow *pickerWindow = nil; + +// Pins the runner to portrait while it hosts the picker. Otherwise a phone lying on its side turns +// the runner, and with it the system's broadcast sheet, to landscape, and the confirmation tap +// misses the sheet's button (it lands outside the module, which dismisses it). +@interface FBBroadcastPickerViewController : UIViewController +@end + +@implementation FBBroadcastPickerViewController + +- (UIInterfaceOrientationMask)supportedInterfaceOrientations +{ + return UIInterfaceOrientationMaskPortrait; +} + +- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation +{ + return UIInterfaceOrientationPortrait; +} + +@end #endif @implementation FBBroadcastPickerHost @@ -64,7 +85,7 @@ + (BOOL)triggerPickerWithPreferredExtension:(NSString *)preferredExtension window.frame = CGRectMake(0, 0, 2, 2); window.windowLevel = UIWindowLevelNormal; window.alpha = 0.02; - window.rootViewController = [[UIViewController alloc] init]; + window.rootViewController = [[FBBroadcastPickerViewController alloc] init]; RPSystemBroadcastPickerView *pickerView = [[RPSystemBroadcastPickerView alloc] initWithFrame:CGRectMake(0, 0, 2, 2)]; pickerView.preferredExtension = preferredExtension; @@ -74,6 +95,7 @@ + (BOOL)triggerPickerWithPreferredExtension:(NSString *)preferredExtension pickerWindow = window; } [pickerWindow makeKeyAndVisible]; + [self rotatePickerSceneToPortrait]; RPSystemBroadcastPickerView *pickerView = nil; for (UIView *subview in pickerWindow.rootViewController.view.subviews) { @@ -98,6 +120,29 @@ + (BOOL)triggerPickerWithPreferredExtension:(NSString *)preferredExtension return YES; } ++ (void)rotatePickerSceneToPortrait +{ + UIWindowScene *scene = pickerWindow.windowScene; + if (nil == scene || UIInterfaceOrientationIsPortrait(scene.interfaceOrientation)) { + return; + } + if (@available(iOS 16.0, *)) { + [pickerWindow.rootViewController setNeedsUpdateOfSupportedInterfaceOrientations]; + UIWindowSceneGeometryPreferencesIOS *preferences = + [[UIWindowSceneGeometryPreferencesIOS alloc] initWithInterfaceOrientations:UIInterfaceOrientationMaskPortrait]; + [scene requestGeometryUpdateWithPreferences:preferences errorHandler:^(NSError *error) { + [FBLogger logFmt:@"Cannot rotate the broadcast picker host to portrait: %@", error.localizedDescription]; + }]; + } else { + [UIViewController attemptRotationToDeviceOrientation]; + } + // The system sheet takes the orientation the runner has when the picker fires. + BOOL portrait = [[[[FBRunLoopSpinner new] timeout:2.0] interval:0.05] spinUntilTrue:^BOOL{ + return UIInterfaceOrientationIsPortrait(scene.interfaceOrientation); + }]; + [FBLogger logFmt:@"Broadcast picker host %@ portrait", portrait ? @"rotated to" : @"did not rotate to"]; +} + + (nullable UIButton *)findButtonInView:(nullable UIView *)view { if (nil == view) {