[iOS] Add support for Siri shortcuts

This is mostly implemented in the app, with the needed support in the SDK. Since
the app needs to donate intents and deal with creating NSUserActivity objects it
doesn't feel right to do this in a library. Instead, we donate the intents from
the app, but the SDK is ready to extract conference URLs from any intent which
was registered as a conference activity.

This also opens the door for eventually adding Handoff support.
This commit is contained in:
Saúl Ibarra Corretgé 2018-10-18 15:54:26 +02:00 committed by Zoltan Bettenbuk
parent 4898f81596
commit 889644f7bd
9 changed files with 89 additions and 3 deletions

View File

@ -7,5 +7,7 @@
<string>applinks:beta.meet.jit.si</string>
<string>applinks:meet.jit.si</string>
</array>
<key>com.apple.developer.siri</key>
<true/>
</dict>
</plist>

View File

@ -43,6 +43,7 @@
0B412F1D1EDEE6E800B1A0A6 /* ViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = "<group>"; };
0B412F1E1EDEE6E800B1A0A6 /* ViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = "<group>"; };
0B412F201EDEE95300B1A0A6 /* Main.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = Main.storyboard; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
0BBD021F212EB69D00CCB19F /* Types.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Types.h; sourceTree = "<group>"; };
0BD6B4361EF82A6B00D1F4CD /* WebRTC.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WebRTC.framework; path = "../../node_modules/react-native-webrtc/ios/WebRTC.framework"; sourceTree = "<group>"; };
13B07F961A680F5B00A75B9A /* jitsi-meet.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "jitsi-meet.app"; sourceTree = BUILT_PRODUCTS_DIR; };
13B07FAF1A68108700A75B9A /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; };
@ -96,6 +97,7 @@
13B07FB11A68108700A75B9A /* LaunchScreen.xib */,
13B07FB71A68108700A75B9A /* main.m */,
0B412F201EDEE95300B1A0A6 /* Main.storyboard */,
0BBD021F212EB69D00CCB19F /* Types.h */,
0B412F1D1EDEE6E800B1A0A6 /* ViewController.h */,
0B412F1E1EDEE6E800B1A0A6 /* ViewController.m */,
);
@ -173,6 +175,9 @@
com.apple.SafariKeychain = {
enabled = 1;
};
com.apple.Siri = {
enabled = 1;
};
};
};
};

View File

@ -17,6 +17,7 @@
#import "AppDelegate.h"
#import "FIRUtilities.h"
#import "Types.h"
#import <JitsiMeet/JitsiMeet.h>
@ -37,6 +38,10 @@
[Fabric with:@[[Crashlytics class]]];
}
// Set the conference activity type defined in this application.
// This cannot be defined by the SDK.
JitsiMeetView.conferenceActivityType = JitsiMeetConferenceActivityType;
return [JitsiMeetView application:application
didFinishLaunchingWithOptions:launchOptions];
}

View File

@ -97,5 +97,9 @@
<false/>
<key>firebase_crashlytics_collection_enabled</key>
<string>false</string>
<key>NSUserActivityTypes</key>
<array>
<string>org.jitsi.JitsiMeet.ios.conference</string>
</array>
</dict>
</plist>

7
ios/app/src/Types.h Normal file
View File

@ -0,0 +1,7 @@
#import <Foundation/Foundation.h>
// This must match what's defined in the NSUserActivityTypes array in the
// Info.plist file.
static NSString *const JitsiMeetConferenceActivityType
= @"org.jitsi.JitsiMeet.ios.conference";

View File

@ -14,8 +14,16 @@
* limitations under the License.
*/
#import <Availability.h>
#import <CoreSpotlight/CoreSpotlight.h>
#import <MobileCoreServices/MobileCoreServices.h>
#import "Types.h"
#import "ViewController.h"
// Needed for NSUserActivity suggestedInvocationPhrase
@import Intents;
/**
* The query to perform through JMAddPeopleController when the InviteButton is
* tapped in order to exercise the public API of the feature invite. If nil, the
@ -33,11 +41,10 @@ static NSString * const ADD_PEOPLE_CONTROLLER_QUERY = nil;
[super viewDidLoad];
JitsiMeetView *view = (JitsiMeetView *) self.view;
view.delegate = self;
#ifdef DEBUG
view.delegate = self;
// inviteController
JMInviteController *inviteController = view.inviteController;
inviteController.delegate = self;
@ -56,12 +63,13 @@ static NSString * const ADD_PEOPLE_CONTROLLER_QUERY = nil;
[view loadURL:nil];
}
#if DEBUG
// JitsiMeetViewDelegate
- (void)_onJitsiMeetViewDelegateEvent:(NSString *)name
withData:(NSDictionary *)data {
#if DEBUG
NSLog(
@"[%s:%d] JitsiMeetViewDelegate %@ %@",
__FILE__, __LINE__, name, data);
@ -70,6 +78,7 @@ static NSString * const ADD_PEOPLE_CONTROLLER_QUERY = nil;
[NSThread isMainThread],
@"JitsiMeetViewDelegate %@ method invoked on a non-main thread",
name);
#endif
}
- (void)conferenceFailed:(NSDictionary *)data {
@ -78,6 +87,36 @@ static NSString * const ADD_PEOPLE_CONTROLLER_QUERY = nil;
- (void)conferenceJoined:(NSDictionary *)data {
[self _onJitsiMeetViewDelegateEvent:@"CONFERENCE_JOINED" withData:data];
// Register a NSUserActivity for this conference so it can be invoked as a
// Siri shortcut. This is only supported in iOS >= 12.
#ifdef __IPHONE_12_0
if (@available(iOS 12.0, *)) {
NSUserActivity *userActivity
= [[NSUserActivity alloc] initWithActivityType:JitsiMeetConferenceActivityType];
NSString *urlStr = data[@"url"];
NSURL *url = [NSURL URLWithString:urlStr];
NSString *conference = [url.pathComponents lastObject];
userActivity.title = [NSString stringWithFormat:@"Join %@", conference];
userActivity.suggestedInvocationPhrase = @"Join my Jitsi meeting";
userActivity.userInfo = @{@"url": urlStr};
[userActivity setEligibleForSearch:YES];
[userActivity setEligibleForPrediction:YES];
[userActivity setPersistentIdentifier:urlStr];
// Subtitle
CSSearchableItemAttributeSet *attributes
= [[CSSearchableItemAttributeSet alloc] initWithItemContentType:(NSString *)kUTTypeItem];
attributes.contentDescription = urlStr;
userActivity.contentAttributeSet = attributes;
self.userActivity = userActivity;
[userActivity becomeCurrent];
}
#endif
}
- (void)conferenceLeft:(NSDictionary *)data {
@ -96,6 +135,8 @@ static NSString * const ADD_PEOPLE_CONTROLLER_QUERY = nil;
[self _onJitsiMeetViewDelegateEvent:@"LOAD_CONFIG_ERROR" withData:data];
}
#if DEBUG
// JMInviteControllerDelegate
- (void)beginAddPeople:(JMAddPeopleController *)addPeopleController {

View File

@ -18,6 +18,7 @@
@interface JitsiMeetView ()
+ (NSDictionary *)conferenceURLFromUserActivity:(NSUserActivity *)userActivity;
+ (instancetype)viewForExternalAPIScope:(NSString *)externalAPIScope;
@end

View File

@ -22,6 +22,8 @@
@interface JitsiMeetView : UIView
@property (class, copy, nonatomic, nullable) NSString *conferenceActivityType;
@property (copy, nonatomic, nullable) NSURL *defaultURL;
@property (nonatomic, nullable, weak) id<JitsiMeetViewDelegate> delegate;

View File

@ -88,6 +88,8 @@ void registerFatalErrorHandler() {
@dynamic pictureInPictureEnabled;
static NSString *_conferenceActivityType;
static RCTBridgeWrapper *bridgeWrapper;
/**
@ -277,6 +279,16 @@ static NSMapTable<NSString *, JitsiMeetView *> *views;
[self loadURLObject:urlString ? @{ @"url": urlString } : nil];
}
#pragma conferenceActivityType getter / setter
+ (NSString *)conferenceActivityType {
return _conferenceActivityType;
}
+ (void) setConferenceActivityType:(NSString *)conferenceActivityType {
_conferenceActivityType = conferenceActivityType;
}
#pragma pictureInPictureEnabled getter / setter
- (void) setPictureInPictureEnabled:(BOOL)pictureInPictureEnabled {
@ -358,6 +370,13 @@ static NSMapTable<NSString *, JitsiMeetView *> *views;
@"url": url
};
}
} else if (_conferenceActivityType && [activityType isEqualToString:_conferenceActivityType]) {
// App was started by continuing a registered NSUserActivity (SiriKit, Handoff, ...)
NSString *url;
if ((url = userActivity.userInfo[@"url"])) {
return @{ @"url" : url };
}
}
return nil;