{ "id": "136655", "key": "TIMOB-17705", "fields": { "issuetype": { "id": "4", "description": "An improvement or enhancement to an existing feature or task.", "name": "Improvement", "subtask": false }, "project": { "id": "10153", "key": "TIMOB", "name": "Titanium SDK/CLI", "projectCategory": { "id": "10100", "description": "Titanium and related SDKs used in application development", "name": "Client" } }, "fixVersions": [ { "id": "15972", "description": "Release 3.4.0", "name": "Release 3.4.0", "archived": false, "released": true, "releaseDate": "2014-09-28" }, { "id": "16704", "description": "Release 3.5.0", "name": "Release 3.5.0", "archived": false, "released": true, "releaseDate": "2015-01-13" } ], "resolution": { "id": "1", "description": "A fix for this issue is checked into the tree and tested.", "name": "Fixed" }, "resolutiondate": "2014-09-19T21:25:11.000+0000", "created": "2014-09-16T14:27:57.000+0000", "priority": null, "labels": [], "versions": [], "issuelinks": [ { "id": "41401", "type": { "id": "10003", "name": "Relates", "inward": "relates to", "outward": "relates to" }, "inwardIssue": { "id": "136900", "key": "TIMOB-17732", "fields": { "summary": "iOS: Push - If you send an iOS push with a badge > 0, then another push with badge = 0, the alert dialog will appear momentarily ", "status": { "description": "The issue is open and ready for the assignee to start work on it.", "name": "Open", "id": "1", "statusCategory": { "id": 2, "key": "new", "colorName": "blue-gray", "name": "To Do" } }, "priority": { "name": "Medium", "id": "3" }, "issuetype": { "id": "1", "description": "A problem which impairs or prevents the functions of the product.", "name": "Bug", "subtask": false } } } } ], "assignee": { "name": "jalter", "key": "jalter", "displayName": "Jon Alter", "active": true, "timeZone": "America/Los_Angeles" }, "updated": "2014-11-21T20:22:45.000+0000", "status": { "description": "The issue is considered finished, the resolution is correct. Issues which are closed can be reopened.", "name": "Closed", "id": "6", "statusCategory": { "id": 3, "key": "done", "colorName": "green", "name": "Done" } }, "components": [ { "id": "10206", "name": "iOS", "description": "iOS Platform" } ], "description": "Currently only actions on Local Notifications are handled. We, as Titanium Developers, can display Remote Notifications with Actions, but a click on one of the buttons is never received by the app.\r\n\r\nIn TiApp.m the following is implemented for Local Notifications\r\n{code}\r\n- (void) application:(UIApplication *)application handleActionWithIdentifier:(NSString *)identifier forLocalNotification:(UILocalNotification *)notification completionHandler:\r\n{code}\r\nSee: https://github.com/appcelerator/titanium_mobile/blob/master/iphone/Classes/TiApp.m#L441\r\n\r\nA similar AppDelegate function has to be implemented to handle Actions on Remote Notifications\r\n{code}\r\n- application:handleActionWithIdentifier:forRemoteNotification:completionHandler:\r\n{code}\r\nSee: https://developer.apple.com/library/prerelease/ios/documentation/UIKit/Reference/UIApplicationDelegate_Protocol/#//apple_ref/occ/intfm/UIApplicationDelegate/application:handleActionWithIdentifier:forRemoteNotification:completionHandler:\r\n\r\nI reckon that the implementation can be similar to \r\n{code}\r\n- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo\r\n{code}\r\nSee: https://github.com/appcelerator/titanium_mobile/blob/master/iphone/Classes/TiApp.m#L825\r\n\r\nWith in addition a key in the event details which action was triggered.", "attachment": [], "flagged": false, "summary": "iOS8: Handle action types for Remote Notifications as well", "creator": { "name": "timanrebel", "key": "timanrebel", "displayName": "Timan Rebel", "active": true, "timeZone": "Europe/London" }, "subtasks": [], "reporter": { "name": "timanrebel", "key": "timanrebel", "displayName": "Timan Rebel", "active": true, "timeZone": "Europe/London" }, "environment": null, "closedSprints": [ { "id": 219, "state": "closed", "name": "2014 Sprint 19 SDK", "startDate": "2014-09-18T21:06:27.559Z", "endDate": "2014-09-27T00:00:00.000Z", "completeDate": "2014-09-29T13:58:58.022Z", "originBoardId": 114 } ], "comment": { "comments": [ { "id": "324057", "author": { "name": "jalter", "key": "jalter", "displayName": "Jon Alter", "active": true, "timeZone": "America/Los_Angeles" }, "body": "Will fire event named 'remotenotificationaction'", "updateAuthor": { "name": "jalter", "key": "jalter", "displayName": "Jon Alter", "active": true, "timeZone": "America/Los_Angeles" }, "created": "2014-09-16T21:06:56.000+0000", "updated": "2014-09-16T21:06:56.000+0000" }, { "id": "324195", "author": { "name": "timanrebel", "key": "timanrebel", "displayName": "Timan Rebel", "active": true, "timeZone": "Europe/London" }, "body": "The local notifications trigger 'notification' or 'notificationbackground' (without the action part) and a remote notification triggers the callback registered when calling 'registerRemoteNotifications'. \r\n\r\nCan't you call the callback registered with registerRemoteNotifications or trigger 'remotenotification' and have every remote notification trigger that event as well? Now it seems inconsistent with how the rest of the notifications are handled.", "updateAuthor": { "name": "timanrebel", "key": "timanrebel", "displayName": "Timan Rebel", "active": true, "timeZone": "Europe/London" }, "created": "2014-09-17T07:50:19.000+0000", "updated": "2014-09-17T07:50:19.000+0000" }, { "id": "324466", "author": { "name": "timanrebel", "key": "timanrebel", "displayName": "Timan Rebel", "active": true, "timeZone": "Europe/London" }, "body": "I just noticed that the event for local notifications is now called localnotificationaction, so please disregard my comment.", "updateAuthor": { "name": "timanrebel", "key": "timanrebel", "displayName": "Timan Rebel", "active": true, "timeZone": "Europe/London" }, "created": "2014-09-18T14:11:51.000+0000", "updated": "2014-09-18T14:11:51.000+0000" }, { "id": "324594", "author": { "name": "jalter", "key": "jalter", "displayName": "Jon Alter", "active": true, "timeZone": "America/Los_Angeles" }, "body": "master PR: https://github.com/appcelerator/titanium_mobile/pull/6129\r\n3_4_X PR: https://github.com/appcelerator/titanium_mobile/pull/6130\r\n\r\nh6. Steps to test\r\n1. Setup push (have fun with that)\r\n2. Open the ti.cloud example app and make a new file in the `pushNotifications` folder named `test.js` with the contents below.\r\n2.5. Include the ti.cloud module\r\n3. Update table.js to include the file you just added (see table.js line 7 and 18 below)\r\n4. Run the app\r\n5. Go to `Push Notifications>Settings` and click `disabled` (if registering for push is successful, you will get an alert with the device token)\r\n6. Go back one window and click `Subscribe Token` > fill in any channel name you like and then subscribe.\r\n7. If this was successful, go to the dashboard, and send a push with one of the categories defined below.\r\n8. You should see the `remotenotificationaction` event fired when you click one of the buttons on the alert.\r\n\r\nh6. test.js\r\n{code}\r\nvar WindowManager = require('helper/WindowManager');\r\nvar Utils = require('helper/Utils');\r\nvar Cloud = require('ti.cloud');\r\nexports['Test'] = function (evt) {\r\n var rows = [\r\n {\r\n title: 'currentUserNotificationSettings',\r\n onClick: function(){\r\n // iOS 8 +\r\n var settings = Ti.App.iOS.currentUserNotificationSettings;\r\n logUserNotificationSettings(settings);\r\n }\r\n },\r\n {\r\n title: 'registerUserNotificationSettings',\r\n onClick: function(){\r\n // iOS 8 +\r\n // The following action launches the application in the foreground and requires the device to be unlocked\r\n var acceptAction = Ti.App.iOS.createUserNotificationAction({\r\n identifier: \"ACCEPT_IDENTIFIER\",\r\n title: \"Accept\",\r\n activationMode: Ti.App.iOS.USER_NOTIFICATION_ACTIVATION_MODE_FOREGROUND,\r\n destructive: false,\r\n authenticationRequired: true\r\n });\r\n var testAction = Ti.App.iOS.createUserNotificationAction({\r\n identifier: \"TEST_IDENTIFIER\",\r\n title: \"Test\",\r\n activationMode: Ti.App.iOS.USER_NOTIFICATION_ACTIVATION_MODE_FOREGROUND,\r\n destructive: false,\r\n authenticationRequired: true\r\n });\r\n \r\n var foregroundCategory = Ti.App.iOS.createUserNotificationCategory({\r\n identifier: \"FOREGROUND_CATEGORY\",\r\n // The following actions will be displayed for an alert dialog\r\n actionsForDefaultContext: [acceptAction, testAction],\r\n // The following actions will be displayed for all other notifications\r\n actionsForMinimalContext: [acceptAction, testAction]\r\n });\r\n\r\n\r\n\r\n // The following action launches the application in the foreground and requires the device to be unlocked\r\n var acceptAction = Ti.App.iOS.createUserNotificationAction({\r\n identifier: \"ACCEPT_IDENTIFIER\",\r\n title: \"Accept\",\r\n activationMode: Ti.App.iOS.USER_NOTIFICATION_ACTIVATION_MODE_FOREGROUND,\r\n destructive: false,\r\n authenticationRequired: true\r\n });\r\n \r\n \r\n // The following action will only activate the application in the background, requires the device to be unlocked, and may have a red background. \r\n var rejectAction = Ti.App.iOS.createUserNotificationAction({\r\n identifier: \"REJECT_IDENTIFIER\",\r\n title: \"Reject\",\r\n activationMode: Ti.App.iOS.USER_NOTIFICATION_ACTIVATION_MODE_BACKGROUND,\r\n destructive: true,\r\n authenticationRequired: true\r\n });\r\n\r\n\r\n var invitationCategory = Ti.App.iOS.createUserNotificationCategory({\r\n identifier: \"INVITE_CATEGORY\",\r\n // The following actions will be displayed for an alert dialog\r\n actionsForDefaultContext: [acceptAction, rejectAction],\r\n // The following actions will be displayed for all other notifications\r\n actionsForMinimalContext: [acceptAction, rejectAction]\r\n });\r\n\r\n\r\n \r\n Ti.App.iOS.registerUserNotificationSettings({\r\n types: [Ti.App.iOS.USER_NOTIFICATION_TYPE_ALERT,\r\n Ti.App.iOS.USER_NOTIFICATION_TYPE_SOUND,\r\n Ti.App.iOS.USER_NOTIFICATION_TYPE_BADGE],\r\n categories: [invitationCategory, foregroundCategory]\r\n });\r\n }\r\n },\r\n {\r\n title: 'scheduleLocalNotification',\r\n onClick: function(){\r\n Ti.App.iOS.scheduleLocalNotification({\r\n date: new Date(new Date().getTime() + 3000),\r\n alertBody: \"New content available! Download now?\",\r\n badge: 1,\r\n userInfo: {\"url\": \"http://www.download.com/resource/asset.json\"},\r\n category: \"FOREGROUND_CATEGORY\"\r\n // category: \"INVITE_CATEGORY\"\r\n });\r\n }\r\n }\r\n ];\r\n \r\n function logInApp(text) {\r\n textLog.value = textLog.value + '\\n' + text;\r\n }\r\n \r\n function logUserNotificationSettings(settings) {\r\n logInApp('UserNotificationSettings: ');\r\n logInApp('types: ' + JSON.stringify(settings.types));\r\n var categories = [];\r\n for (var i = 0, j = settings.categories.length; i < j; i++) {\r\n categories.push(settings.categories[i].identifier);\r\n }\r\n logInApp('categories: ' + JSON.stringify(categories));\r\n }\r\n \r\n Ti.App.iOS.addEventListener('usernotificationsettings', function(e) {\r\n logInApp('Event: usernotificationsettings');\r\n logUserNotificationSettings(e);\r\n });\r\n\r\n Ti.App.iOS.addEventListener('localnotificationaction', function(e) {\r\n logInApp('#### localnotificationaction: ' + JSON.stringify(e));\r\n logInApp('#### category: ' + e.category + ' identifier: ' + e.identifier);\r\n });\r\n\r\n Ti.App.iOS.addEventListener('remotenotificationaction', function(e) {\r\n logInApp('#####---##### remotenotificationaction: ' + JSON.stringify(e));\r\n logInApp('#### category: ' + e.category + ' identifier: ' + e.identifier);\r\n }); \r\n\r\n Ti.App.iOS.addEventListener('notification', function(e) {\r\n logInApp('#### notification');\r\n });\r\n \r\n ////////////////////////////////////////////////////////\r\n // UI\r\n ////////////////////////////////////////////////////////\r\n var win = Ti.UI.createWindow({\r\n backgroundColor: 'white'\r\n });\r\n // win.open();\r\n \r\n var textLog = Ti.UI.createTextArea({\r\n top: 0,\r\n height: '20%',\r\n width: '100%',\r\n borderWidth: '2',\r\n borderColor: '#000',\r\n value: 'AppLog: see device log for more info'\r\n });\r\n win.add(textLog);\r\n \r\n var tableView = Ti.UI.createTableView({\r\n top: '20%',\r\n data: rows\r\n });\r\n tableView.addEventListener('click', function(e){\r\n e.source.onClick && e.source.onClick();\r\n });\r\n win.add(tableView);\r\n\r\n return win;\r\n};\r\n{code}\r\n\r\nh6. table.js\r\n{code}\r\nvar WindowManager = require('helper/WindowManager');\r\nvar Utils = require('helper/Utils');\r\nvar Cloud = require('ti.cloud');\r\nvar PushManager = require('windows/pushNotifications/pushManager');\r\n\r\nWindowManager.include(\r\n '/windows/pushNotifications/test',\r\n '/windows/pushNotifications/query',\r\n '/windows/pushNotifications/notify',\r\n '/windows/pushNotifications/settings',\r\n '/windows/pushNotifications/subscribe',\r\n '/windows/pushNotifications/unsubscribe',\r\n '/windows/pushNotifications/notifyTokens',\r\n '/windows/pushNotifications/subscribeToken',\r\n '/windows/pushNotifications/unsubscribeToken',\r\n '/windows/pushNotifications/updateSubscription',\r\n '/windows/pushNotifications/showChannels',\r\n '/windows/pushNotifications/queryChannels',\r\n '/windows/pushNotifications/setBadge',\r\n '/windows/pushNotifications/resetBadge'\r\n);\r\nexports['Push Notifications'] = function () {\r\n var win = WindowManager.createWindow({\r\n backgroundColor: 'white'\r\n });\r\n\r\n var rows = [\r\n 'Test',\r\n 'Notify',\r\n 'Notify Tokens',\r\n 'Query Subscriptions',\r\n 'Show Channels',\r\n 'Query Channels',\r\n 'Set Badge',\r\n 'Reset Badge'\r\n ];\r\n if (Ti.Platform.name === 'iPhone OS' || Ti.Platform.name === 'android') {\r\n rows.push('Settings for This Device');\r\n rows.push('Subscribe');\r\n rows.push('Unsubscribe');\r\n rows.push('Subscribe Token');\r\n rows.push('Unsubscribe Token');\r\n rows.push('Update Subscription');\r\n }\r\n else {\r\n // Our other platforms do not support push notifications yet.\r\n }\r\n\r\n var table = Ti.UI.createTableView({\r\n backgroundColor: '#fff',\r\n top: 0,\r\n data: Utils.createRows(rows)\r\n });\r\n table.addEventListener('click', WindowManager.handleOpenWindow);\r\n win.add(table);\r\n return win;\r\n};\r\n\r\nPushManager.checkPushNotifications();\r\n{code}", "updateAuthor": { "name": "jalter", "key": "jalter", "displayName": "Jon Alter", "active": true, "timeZone": "America/Los_Angeles" }, "created": "2014-09-18T21:50:37.000+0000", "updated": "2014-09-19T21:26:29.000+0000" }, { "id": "324818", "author": { "name": "wluu", "key": "wluu", "displayName": "Wilson Luu", "active": false, "timeZone": "America/Los_Angeles" }, "body": "Closing ticket as fixed. Verified ACS iOS push (remote notification) can trigger the notification actions on device.\r\n\r\nTested on:\r\n\r\nAppcelerator Studio, build: 3.4.0.201409161950\r\nSDK build: 3.4.0.v20140919142515\r\nti.cloud: 3.2.4\r\nDashboard: preproduction (2.0.1)\r\nCLI: 3.4.0-rc4\r\nAlloy: 1.5.0-rc3\r\nXcode: 6.0.1\r\nDevices: iphone 5s (8.0)", "updateAuthor": { "name": "wluu", "key": "wluu", "displayName": "Wilson Luu", "active": false, "timeZone": "America/Los_Angeles" }, "created": "2014-09-19T23:41:54.000+0000", "updated": "2014-09-19T23:41:54.000+0000" }, { "id": "325121", "author": { "name": "pilo", "key": "pilo", "displayName": "erez pilosof", "active": true, "timeZone": "Asia/Jerusalem" }, "body": "please reopen ...\r\n\r\ndoesn't work if the app is not running !!! or not in background !!\r\n\r\nserious issue ", "updateAuthor": { "name": "pilo", "key": "pilo", "displayName": "erez pilosof", "active": true, "timeZone": "Asia/Jerusalem" }, "created": "2014-09-23T11:20:04.000+0000", "updated": "2014-09-23T11:20:04.000+0000" }, { "id": "325134", "author": { "name": "ingo", "key": "ingo", "displayName": "Ingo Muschenetz", "active": true, "timeZone": "America/Los_Angeles" }, "body": "[~pilo] Please file a set of steps to reproduce your issue and we'd be happy to take a look.", "updateAuthor": { "name": "ingo", "key": "ingo", "displayName": "Ingo Muschenetz", "active": true, "timeZone": "America/Los_Angeles" }, "created": "2014-09-23T13:53:22.000+0000", "updated": "2014-09-23T13:53:22.000+0000" }, { "id": "330244", "author": { "name": "ludolphus", "key": "ludolphus", "displayName": "Steven van Loef", "active": true, "timeZone": "Europe/Amsterdam" }, "body": "Like Perez Pilosof commented the action tapped by the user doesn't get fired when the app is not in memory (foreground or background). The steps described by Jon Alter all work except for step 8. The actions are shown on the notification center of course, but when tapping one of them it will just start the app but the 'remotenotificationaction' event is never fired.\r\n\r\nBy adding some log statements to I found that in TiApp.m\r\n\r\n{code}\r\n- (void) application:(UIApplication *)application handleActionWithIdentifier:(NSString *)identifier forRemoteNotification:(NSDictionary *)userInfo completionHandler:(void (^)())completionHandler {\r\n{code}\r\n\r\nis called before app.js gets loaded. So that's too soon to even register for the 'remotenotificationaction' event in your app.", "updateAuthor": { "name": "ludolphus", "key": "ludolphus", "displayName": "Steven van Loef", "active": true, "timeZone": "Europe/Amsterdam" }, "created": "2014-11-01T13:33:34.000+0000", "updated": "2014-11-01T13:33:34.000+0000" }, { "id": "330245", "author": { "name": "ludolphus", "key": "ludolphus", "displayName": "Steven van Loef", "active": true, "timeZone": "Europe/Amsterdam" }, "body": "A kind of dirty quick hack is to delay firing the event with this code (at about line 465 in TiApp.m of SDK 3.4.0):\r\n\r\n{code}\r\n\t\tdouble delayInSeconds = 5.0; // your mileage may vary here...\r\n\t\tdispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);\r\n\t\tdispatch_after(popTime, dispatch_get_main_queue(), ^(void){\r\n\t\t //code to be executed on the main queue after delay\r\n\t\t [[NSNotificationCenter defaultCenter] postNotificationName:kTiRemoteNotificationAction object:event userInfo:nil];\r\n\t\t [event autorelease];\r\n\t\t completionHandler();\r\n\t\t});\r\n{code}\r\n", "updateAuthor": { "name": "ludolphus", "key": "ludolphus", "displayName": "Steven van Loef", "active": true, "timeZone": "Europe/Amsterdam" }, "created": "2014-11-01T13:41:17.000+0000", "updated": "2014-11-01T13:41:17.000+0000" } ], "maxResults": 10, "total": 10, "startAt": 0 } } }