diff --git a/android/app/src/main/java/org/jitsi/meet/MainActivity.java b/android/app/src/main/java/org/jitsi/meet/MainActivity.java index 6cac62615b..686ebf43bf 100644 --- a/android/app/src/main/java/org/jitsi/meet/MainActivity.java +++ b/android/app/src/main/java/org/jitsi/meet/MainActivity.java @@ -115,8 +115,6 @@ public class MainActivity extends JitsiMeetActivity { inviteController.setListener(new InviteControllerListener() { public void beginAddPeople( AddPeopleController addPeopleController) { - UiThreadUtil.assertOnUiThread(); - onInviteControllerBeginAddPeople( inviteController, addPeopleController); diff --git a/android/sdk/src/main/java/org/jitsi/meet/sdk/ExternalAPIModule.java b/android/sdk/src/main/java/org/jitsi/meet/sdk/ExternalAPIModule.java index 15c8da8560..49888d0a05 100644 --- a/android/sdk/src/main/java/org/jitsi/meet/sdk/ExternalAPIModule.java +++ b/android/sdk/src/main/java/org/jitsi/meet/sdk/ExternalAPIModule.java @@ -140,7 +140,8 @@ class ExternalAPIModule extends ReactContextBaseJavaModule { } /** - * Dispatches an event that occurred on JavaScript to the view's listener. + * Dispatches an event that occurred on the JavaScript side of the SDK to + * the specified {@link JitsiMeetView}'s listener. * * @param name The name of the event. * @param data The details/specifics of the event to send determined @@ -151,50 +152,81 @@ class ExternalAPIModule extends ReactContextBaseJavaModule { public void sendEvent(final String name, final ReadableMap data, final String scope) { - UiThreadUtil.runOnUiThread(new Runnable() { - @Override - public void run() { - // The JavaScript App needs to provide uniquely identifying - // information to the native ExternalAPI module so that the - // latter may match the former to the native JitsiMeetView which - // hosts it. - JitsiMeetView view - = JitsiMeetView.findViewByExternalAPIScope(scope); - - if (view == null) { - return; + // The JavaScript App needs to provide uniquely identifying information + // to the native ExternalAPI module so that the latter may match the + // former to the native JitsiMeetView which hosts it. + JitsiMeetView view = JitsiMeetView.findViewByExternalAPIScope(scope); + + if (view == null) { + return; + } + + // XXX The JitsiMeetView property URL was introduced in order to address + // an exception in the Picture-in-Picture functionality which arose + // because of delays related to bridging between JavaScript and Java. To + // reduce these delays do not wait for the call to be transfered to the + // UI thread. + maybeSetViewURL(name, data, view); + + // Make sure JitsiMeetView's listener is invoked on the UI thread. It + // was requested by SDK consumers. + if (UiThreadUtil.isOnUiThread()) { + sendEventOnUiThread(name, data, scope); + } else { + UiThreadUtil.runOnUiThread(new Runnable() { + @Override + public void run() { + sendEventOnUiThread(name, data, scope); } + }); + } + } + + /** + * Dispatches an event that occurred on the JavaScript side of the SDK to + * the specified {@link JitsiMeetView}'s listener on the UI thread. + * + * @param name The name of the event. + * @param data The details/specifics of the event to send determined + * by/associated with the specified {@code name}. + * @param scope + */ + private void sendEventOnUiThread(final String name, + final ReadableMap data, + final String scope) { + // The JavaScript App needs to provide uniquely identifying information + // to the native ExternalAPI module so that the latter may match the + // former to the native JitsiMeetView which hosts it. + JitsiMeetView view = JitsiMeetView.findViewByExternalAPIScope(scope); - maybeSetViewURL(name, data, view); + if (view == null) { + return; + } - JitsiMeetViewListener listener = view.getListener(); + JitsiMeetViewListener listener = view.getListener(); - if (listener == null) { - return; - } + if (listener == null) { + return; + } - Method method = JITSI_MEET_VIEW_LISTENER_METHODS.get(name); - - if (method != null) { - try { - method.invoke(listener, toHashMap(data)); - } catch (IllegalAccessException e) { - // FIXME There was a multicatch for - // IllegalAccessException and InvocationTargetException, - // but Android Studio complained with: "Multi-catch with - // these reflection exceptions requires API level 19 - // (current min is 16) because they get compiled to the - // common but new super type - // ReflectiveOperationException. As a workaround either - // create individual catch statements, or - // catch Exception." - throw new RuntimeException(e); - } catch (InvocationTargetException e) { - throw new RuntimeException(e); - } - } + Method method = JITSI_MEET_VIEW_LISTENER_METHODS.get(name); + + if (method != null) { + try { + method.invoke(listener, toHashMap(data)); + } catch (IllegalAccessException e) { + // FIXME There was a multicatch for IllegalAccessException and + // InvocationTargetException, but Android Studio complained + // with: "Multi-catch with these reflection exceptions requires + // API level 19 (current min is 16) because they get compiled to + // the common but new super type ReflectiveOperationException. + // As a workaround either create individual catch statements, or + // catch Exception." + throw new RuntimeException(e); + } catch (InvocationTargetException e) { + throw new RuntimeException(e); } - }); + } } /** diff --git a/android/sdk/src/main/java/org/jitsi/meet/sdk/invite/InviteModule.java b/android/sdk/src/main/java/org/jitsi/meet/sdk/invite/InviteModule.java index 311f82b726..edb435d4b8 100644 --- a/android/sdk/src/main/java/org/jitsi/meet/sdk/invite/InviteModule.java +++ b/android/sdk/src/main/java/org/jitsi/meet/sdk/invite/InviteModule.java @@ -44,17 +44,24 @@ public class InviteModule extends ReactContextBaseJavaModule { */ @ReactMethod public void beginAddPeople(final String externalAPIScope) { - UiThreadUtil.runOnUiThread(new Runnable() { - @Override - public void run() { - InviteController inviteController - = findInviteControllerByExternalAPIScope(externalAPIScope); - - if (inviteController != null) { - inviteController.beginAddPeople(getReactApplicationContext()); + // Make sure InviteControllerListener (like all other listeners of the + // SDK) is invoked on the UI thread. It was requested by SDK consumers. + if (!UiThreadUtil.isOnUiThread()) { + UiThreadUtil.runOnUiThread(new Runnable() { + @Override + public void run() { + beginAddPeople(externalAPIScope); } - } - }); + }); + return; + } + + InviteController inviteController + = findInviteControllerByExternalAPIScope(externalAPIScope); + + if (inviteController != null) { + inviteController.beginAddPeople(getReactApplicationContext()); + } } private InviteController findInviteControllerByExternalAPIScope( @@ -81,23 +88,34 @@ public class InviteModule extends ReactContextBaseJavaModule { final String externalAPIScope, final String addPeopleControllerScope, final ReadableArray failedInvitees) { - UiThreadUtil.runOnUiThread(new Runnable() { - @Override - public void run() { - InviteController inviteController - = findInviteControllerByExternalAPIScope(externalAPIScope); - - if (inviteController == null) { - Log.w( - "InviteModule", - "Invite settled, but failed to find active controller to notify"); - } else { - inviteController.inviteSettled( + // Make sure AddPeopleControllerListener (like all other listeners of + // the SDK) is invoked on the UI thread. It was requested by SDK + // consumers. + if (!UiThreadUtil.isOnUiThread()) { + UiThreadUtil.runOnUiThread(new Runnable() { + @Override + public void run() { + inviteSettled( + externalAPIScope, addPeopleControllerScope, failedInvitees); } - } - }); + }); + return; + } + + InviteController inviteController + = findInviteControllerByExternalAPIScope(externalAPIScope); + + if (inviteController == null) { + Log.w( + "InviteModule", + "Invite settled, but failed to find active controller to notify"); + } else { + inviteController.inviteSettled( + addPeopleControllerScope, + failedInvitees); + } } /** @@ -113,23 +131,35 @@ public class InviteModule extends ReactContextBaseJavaModule { final String addPeopleControllerScope, final String query, final ReadableArray results) { - UiThreadUtil.runOnUiThread(new Runnable() { - @Override - public void run() { - InviteController inviteController - = findInviteControllerByExternalAPIScope(externalAPIScope); - - if (inviteController == null) { - Log.w( - "InviteModule", - "Received results, but failed to find active controller to send results back"); - } else { - inviteController.receivedResultsForQuery( + // Make sure AddPeopleControllerListener (like all other listeners of + // the SDK) is invoked on the UI thread. It was requested by SDK + // consumers. + if (!UiThreadUtil.isOnUiThread()) { + UiThreadUtil.runOnUiThread(new Runnable() { + @Override + public void run() { + receivedResults( + externalAPIScope, addPeopleControllerScope, query, results); } - } - }); + }); + return; + } + + InviteController inviteController + = findInviteControllerByExternalAPIScope(externalAPIScope); + + if (inviteController == null) { + Log.w( + "InviteModule", + "Received results, but failed to find active controller to send results back"); + } else { + inviteController.receivedResultsForQuery( + addPeopleControllerScope, + query, + results); + } } } diff --git a/ios/app/src/ViewController.m b/ios/app/src/ViewController.m index cb8a38227d..601ea0be0f 100644 --- a/ios/app/src/ViewController.m +++ b/ios/app/src/ViewController.m @@ -14,8 +14,6 @@ * limitations under the License. */ -#import - #import "ViewController.h" /** @@ -62,37 +60,40 @@ static NSString * const ADD_PEOPLE_CONTROLLER_QUERY = nil; // JitsiMeetViewDelegate -void _onJitsiMeetViewDelegateEvent(NSString *name, NSDictionary *data) { +- (void)_onJitsiMeetViewDelegateEvent:(NSString *)name + withData:(NSDictionary *)data { NSLog( @"[%s:%d] JitsiMeetViewDelegate %@ %@", __FILE__, __LINE__, name, data); - assert([NSThread isMainThread] - && "Delegate method called in a non-main thread"); + NSAssert( + [NSThread isMainThread], + @"JitsiMeetViewDelegate %@ method invoked on a non-main thread", + name); } - (void)conferenceFailed:(NSDictionary *)data { - _onJitsiMeetViewDelegateEvent(@"CONFERENCE_FAILED", data); + [self _onJitsiMeetViewDelegateEvent:@"CONFERENCE_FAILED" withData:data]; } - (void)conferenceJoined:(NSDictionary *)data { - _onJitsiMeetViewDelegateEvent(@"CONFERENCE_JOINED", data); + [self _onJitsiMeetViewDelegateEvent:@"CONFERENCE_JOINED" withData:data]; } - (void)conferenceLeft:(NSDictionary *)data { - _onJitsiMeetViewDelegateEvent(@"CONFERENCE_LEFT", data); + [self _onJitsiMeetViewDelegateEvent:@"CONFERENCE_LEFT" withData:data]; } - (void)conferenceWillJoin:(NSDictionary *)data { - _onJitsiMeetViewDelegateEvent(@"CONFERENCE_WILL_JOIN", data); + [self _onJitsiMeetViewDelegateEvent:@"CONFERENCE_WILL_JOIN" withData:data]; } - (void)conferenceWillLeave:(NSDictionary *)data { - _onJitsiMeetViewDelegateEvent(@"CONFERENCE_WILL_LEAVE", data); + [self _onJitsiMeetViewDelegateEvent:@"CONFERENCE_WILL_LEAVE" withData:data]; } - (void)loadConfigError:(NSDictionary *)data { - _onJitsiMeetViewDelegateEvent(@"LOAD_CONFIG_ERROR", data); + [self _onJitsiMeetViewDelegateEvent:@"LOAD_CONFIG_ERROR" withData:data]; } // JMInviteControllerDelegate @@ -102,8 +103,9 @@ void _onJitsiMeetViewDelegateEvent(NSString *name, NSDictionary *data) { @"[%s:%d] JMInviteControllerDelegate %s", __FILE__, __LINE__, __FUNCTION__); - assert([NSThread isMainThread] - && "Delegate method called in a non-main thread"); + NSAssert( + [NSThread isMainThread], + @"JMInviteControllerDelegate beginAddPeople: invoked on a non-main thread"); NSString *query = ADD_PEOPLE_CONTROLLER_QUERY; JitsiMeetView *view = (JitsiMeetView *) self.view; @@ -127,8 +129,9 @@ void _onJitsiMeetViewDelegateEvent(NSString *name, NSDictionary *data) { - (void)addPeopleController:(JMAddPeopleController * _Nonnull)controller didReceiveResults:(NSArray * _Nonnull)results forQuery:(NSString * _Nonnull)query { - assert([NSThread isMainThread] - && "Delegate method called in a non-main thread"); + NSAssert( + [NSThread isMainThread], + @"JMAddPeopleControllerDelegate addPeopleController:didReceiveResults:forQuery: invoked on a non-main thread"); NSUInteger count = results.count; @@ -162,8 +165,9 @@ void _onJitsiMeetViewDelegateEvent(NSString *name, NSDictionary *data) { - (void) inviteSettled:(NSArray * _Nonnull)failedInvitees fromSearchController:(JMAddPeopleController * _Nonnull)addPeopleController { - assert([NSThread isMainThread] - && "Delegate method called in a non-main thread"); + NSAssert( + [NSThread isMainThread], + @"JMAddPeopleControllerDelegate inviteSettled:fromSearchController: invoked on a non-main thread"); // XXX Explicitly invoke endAddPeople on addPeopleController; otherwise, it // is going to be memory-leaked in the associated JMInviteController and no diff --git a/ios/sdk/src/ExternalAPI.m b/ios/sdk/src/ExternalAPI.m index 5c9cc4c393..cee9bcf3f2 100644 --- a/ios/sdk/src/ExternalAPI.m +++ b/ios/sdk/src/ExternalAPI.m @@ -26,7 +26,7 @@ RCT_EXPORT_MODULE(); /** - * Make sure all methods in this module are called in the main (i.e. UI) thread. + * Make sure all methods in this module are invoked on the main/UI thread. */ - (dispatch_queue_t)methodQueue { return dispatch_get_main_queue(); diff --git a/ios/sdk/src/invite/Invite.m b/ios/sdk/src/invite/Invite.m index 752583b44c..cba2a64e3c 100644 --- a/ios/sdk/src/invite/Invite.m +++ b/ios/sdk/src/invite/Invite.m @@ -33,6 +33,13 @@ static NSString * const PerformQueryEmitterEvent RCT_EXPORT_MODULE(); +/** + * Make sure all methods in this module are invoked on the main/UI thread. + */ +- (dispatch_queue_t)methodQueue { + return dispatch_get_main_queue(); +} + - (NSArray *)supportedEvents { return @[ InviteEmitterEvent, @@ -40,13 +47,6 @@ RCT_EXPORT_MODULE(); ]; } -/** - * Make sure all methods in this module are called in the main (i.e. UI) thread. - */ -- (dispatch_queue_t)methodQueue { - return dispatch_get_main_queue(); -} - /** * Initiates the process to add people. This involves calling a delegate method * in the JMInviteControllerDelegate so the native host application can start