diff --git a/README.md b/README.md index 42eb97f..d6911e6 100644 --- a/README.md +++ b/README.md @@ -150,11 +150,20 @@ function setupOpenwith() { if (intent.items.length > 0) { cordova.openwith.load(intent.items[0], function(data, item) { + // data is a long base64 string with the content of the file console.log("the item weights " + data.length + " bytes"); uploadToServer(item); + + // "exit" when done. + // Note that there is no need to wait for the upload to finish, + // the app can continue while in background. + if (intent.exit) { cordova.openwith.exit(); } }); } + else { + if (intent.exit) { cordova.openwith.exit(); } + } } } ``` @@ -225,6 +234,18 @@ When data has been successfully loaded, `loadSuccessCallback` will be called. It Called when data can't be loaded. +### cordova.openwith.exit() + +Attempt to return the the calling app when sharing is done. Your app will be backgrounded, +it should be able to finish the upload. + +On iOS, this call might have no effects. The plugin needs to recognize the app +you are sharing from in order to send you back to it. The user can still select the +"Back-to-app" button visible on the top left. Make sure your UI shows the user +that he can now safely go back to what he was doing. + +On Android, the app will be backgrounded no matter what. + ## Contribute Contributions in the form of GitHub pull requests are welcome. Please adhere to the following guidelines: diff --git a/hooks/iosAddTarget.js b/hooks/iosAddTarget.js index edcdbb8..36f2c71 100755 --- a/hooks/iosAddTarget.js +++ b/hooks/iosAddTarget.js @@ -234,14 +234,14 @@ module.exports = function (context) { }); // Find if the project already contains the target and group - var target = pbxProject.pbxTargetByName('ShareExtension'); + var target = pbxProject.pbxTargetByName('ShareExt'); if (target) { - console.log(' ShareExtension target already exists.'); + console.log(' ShareExt target already exists.'); } if (!target) { // Add PBXNativeTarget to the project - target = pbxProject.addTarget('ShareExtension', 'app_extension', 'ShareExtension'); + target = pbxProject.addTarget('ShareExt', 'app_extension', 'ShareExtension'); // Add a new PBXSourcesBuildPhase for our ShareViewController // (we can't add it to the existing one because an extension is kind of an extra app) diff --git a/src/android/cc/fovea/openwith/OpenWithPlugin.java b/src/android/cc/fovea/openwith/OpenWithPlugin.java index aba186d..d5fd30d 100644 --- a/src/android/cc/fovea/openwith/OpenWithPlugin.java +++ b/src/android/cc/fovea/openwith/OpenWithPlugin.java @@ -102,6 +102,9 @@ else if ("setLogger".equals(action)) { else if ("load".equals(action)) { return load(data, callbackContext); } + else if ("exit".equals(action)) { + return exit(data, callbackContext); + } log(DEBUG, "execute() did not recognize this action: " + action); return false; } @@ -135,6 +138,18 @@ public boolean init(final JSONArray data, final CallbackContext context) { return PluginResultSender.ok(context); } + // Exit after processing + public boolean exit(final JSONArray data, final CallbackContext context) { + log(DEBUG, "exit() " + data); + if (data.length() != 0) { + log(WARN, "exit() -> invalidAction"); + return false; + } + cordova.getActivity().moveTaskToBack(true); + log(DEBUG, "exit() -> ok"); + return PluginResultSender.ok(context); + } + public boolean setHandler(final JSONArray data, final CallbackContext context) { log(DEBUG, "setHandler() " + data); if (data.length() != 0) { diff --git a/src/ios/OpenWithPlugin.m b/src/ios/OpenWithPlugin.m index 7290754..bd1ef93 100644 --- a/src/ios/OpenWithPlugin.m +++ b/src/ios/OpenWithPlugin.m @@ -70,12 +70,14 @@ @interface OpenWithPlugin : CDVPlugin { NSString* _handlerCallback; NSUserDefaults *_userDefaults; int _verbosityLevel; + NSString *_backURL; } @property (nonatomic,retain) NSString* loggerCallback; @property (nonatomic,retain) NSString* handlerCallback; @property (nonatomic) int verbosityLevel; @property (nonatomic,retain) NSUserDefaults *userDefaults; +@property (nonatomic,retain) NSString *backURL; @end /* @@ -88,6 +90,7 @@ @implementation OpenWithPlugin @synthesize handlerCallback = _handlerCallback; @synthesize verbosityLevel = _verbosityLevel; @synthesize userDefaults = _userDefaults; +@synthesize backURL = _backURL; // // Retrieve launchOptions @@ -227,6 +230,7 @@ - (void) checkForFileToShare { NSData *data = dict[@"data"]; NSString *text = dict[@"text"]; NSString *name = dict[@"name"]; + self.backURL = dict[@"backURL"]; NSString *type = [self mimeTypeFromUti:dict[@"uti"]]; if (![data isKindOfClass:NSData.class] || ![text isKindOfClass:NSString.class]) { [self debug:@"[checkForFileToShare] Data content is invalid"]; @@ -237,6 +241,9 @@ - (void) checkForFileToShare { utis = @[]; } + // TODO: add the backURL to the shared intent, put it aside in the plugin + // TODO: implement cordova.openwith.exit(intent), will check if backURL is set + // Send to javascript [self debug:[NSString stringWithFormat: @"[checkForFileToShare] Sharing text \"%@\" and a %d bytes image", @@ -277,5 +284,19 @@ - (void) load:(CDVInvokedUrlCommand*)command { [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; } +// Exit after sharing +- (void) exit:(CDVInvokedUrlCommand*)command { + [self debug:[NSString stringWithFormat:@"[exit] %@", self.backURL]]; + if (self.backURL != nil) { + UIApplication *app = [UIApplication sharedApplication]; + NSURL *url = [NSURL URLWithString:self.backURL]; + if ([app canOpenURL:url]) { + [app openURL:url]; + } + } + CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; + [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; +} + @end // vim: ts=4:sw=4:et diff --git a/src/ios/ShareExtension/ShareViewController.m b/src/ios/ShareExtension/ShareViewController.m index 625151b..49bb6a9 100644 --- a/src/ios/ShareExtension/ShareViewController.m +++ b/src/ios/ShareExtension/ShareViewController.m @@ -34,9 +34,11 @@ @interface ShareViewController : SLComposeServiceViewController { int _verbosityLevel; NSUserDefaults *_userDefaults; + NSString *_backURL; } @property (nonatomic) int verbosityLevel; @property (nonatomic,retain) NSUserDefaults *userDefaults; +@property (nonatomic,retain) NSString *backURL; @end /* @@ -52,6 +54,7 @@ @implementation ShareViewController @synthesize verbosityLevel = _verbosityLevel; @synthesize userDefaults = _userDefaults; +@synthesize backURL = _backURL; - (void) log:(int)level message:(NSString*)message { if (level >= self.verbosityLevel) { @@ -69,11 +72,11 @@ - (void) setup { [self debug:@"[setup]"]; } -- (BOOL)isContentValid { +- (BOOL) isContentValid { return YES; } -- (void)openURL:(nonnull NSURL *)url { +- (void) openURL:(nonnull NSURL *)url { SEL selector = NSSelectorFromString(@"openURL:options:completionHandler:"); @@ -101,7 +104,7 @@ - (void)openURL:(nonnull NSURL *)url { } } -- (void)didSelectPost { +- (void) didSelectPost { [self setup]; [self debug:@"[didSelectPost]"]; @@ -134,9 +137,9 @@ - (void)didSelectPost { else { uti = SHAREEXT_UNIFORM_TYPE_IDENTIFIER; } - NSDictionary *dict = @{ @"text" : self.contentText, + @"backURL": self.backURL, @"data" : data, @"uti": uti, @"utis": itemProvider.registeredTypeIdentifiers, @@ -173,9 +176,63 @@ - (void)didSelectPost { [self.extensionContext completeRequestReturningItems:@[] completionHandler:nil]; } -- (NSArray *)configurationItems { +- (NSArray*) configurationItems { // To add configuration options via table cells at the bottom of the sheet, return an array of SLComposeSheetConfigurationItem here. return @[]; } +- (NSString*) backURLFromBundleID: (NSString*)bundleId { + if (bundleId == nil) return nil; + // App Store - com.apple.AppStore + if ([bundleId isEqualToString:@"com.apple.AppStore"]) return @"itms-apps://"; + // Calculator - com.apple.calculator + // Calendar - com.apple.mobilecal + // Camera - com.apple.camera + // Clock - com.apple.mobiletimer + // Compass - com.apple.compass + // Contacts - com.apple.MobileAddressBook + // FaceTime - com.apple.facetime + // Find Friends - com.apple.mobileme.fmf1 + // Find iPhone - com.apple.mobileme.fmip1 + // Game Center - com.apple.gamecenter + // Health - com.apple.Health + // iBooks - com.apple.iBooks + // iTunes Store - com.apple.MobileStore + // Mail - com.apple.mobilemail - message:// + if ([bundleId isEqualToString:@"com.apple.mobilemail"]) return @"message://"; + // Maps - com.apple.Maps - maps:// + if ([bundleId isEqualToString:@"com.apple.Maps"]) return @"maps://"; + // Messages - com.apple.MobileSMS + // Music - com.apple.Music + // News - com.apple.news - applenews:// + if ([bundleId isEqualToString:@"com.apple.news"]) return @"applenews://"; + // Notes - com.apple.mobilenotes - mobilenotes:// + if ([bundleId isEqualToString:@"com.apple.mobilenotes"]) return @"mobilenotes://"; + // Phone - com.apple.mobilephone + // Photos - com.apple.mobileslideshow + if ([bundleId isEqualToString:@"com.apple.mobileslideshow"]) return @"photos-redirect://"; + // Podcasts - com.apple.podcasts + // Reminders - com.apple.reminders - x-apple-reminder:// + if ([bundleId isEqualToString:@"com.apple.reminders"]) return @"x-apple-reminder://"; + // Safari - com.apple.mobilesafari + // Settings - com.apple.Preferences + // Stocks - com.apple.stocks + // Tips - com.apple.tips + // Videos - com.apple.videos - videos:// + if ([bundleId isEqualToString:@"com.apple.videos"]) return @"videos://"; + // Voice Memos - com.apple.VoiceMemos - voicememos:// + if ([bundleId isEqualToString:@"com.apple.VoiceMemos"]) return @"voicememos://"; + // Wallet - com.apple.Passbook + // Watch - com.apple.Bridge + // Weather - com.apple.weather + return nil; +} + +// This is called at the point where the Post dialog is about to be shown. +// We use it to store the _hostBundleID +- (void) willMoveToParentViewController: (UIViewController*)parent { + NSString *hostBundleID = [parent valueForKey:(@"_hostBundleID")]; + self.backURL = [self backURLFromBundleID:hostBundleID]; +} + @end diff --git a/www/openwith.js b/www/openwith.js index 0fa2c04..a1c7cd1 100644 --- a/www/openwith.js +++ b/www/openwith.js @@ -172,6 +172,11 @@ function initOpenwithPlugin (root) { } } + openwith.exit = function () { + log(DEBUG, 'exit()') + cordova.exec(null, null, PLUGIN_NAME, 'exit', []) + } + var onNewIntent = function (intent) { log(DEBUG, 'onNewIntent(' + intent.action + ')') // process the new intent