{ "id": "172180", "key": "TIMOB-26370", "fields": { "issuetype": { "id": "1", "description": "A problem which impairs or prevents the functions of the product.", "name": "Bug", "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": "19882", "name": "Release 8.0.0", "archived": false, "released": true, "releaseDate": "2019-03-14" } ], "resolution": { "id": "1", "description": "A fix for this issue is checked into the tree and tested.", "name": "Fixed" }, "resolutiondate": "2018-11-19T23:17:29.000+0000", "created": "2018-09-05T20:22:16.000+0000", "priority": { "name": "Critical", "id": "1" }, "labels": [ "android", "api", "camera", "roll", "save" ], "versions": [], "issuelinks": [], "assignee": { "name": "ybanev", "key": "ybanev", "displayName": "Yordan Banev", "active": true, "timeZone": "Europe/Athens" }, "updated": "2018-11-26T20:10:27.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": [], "description": "When downloading any image, saving to temp folder and then attempting to save the image to the Android Camera Roll, results in error (saving to Camera Roll) \"TiMedia: (main) [526999,526999] Failed to create external storage directory\". Works as expected on Android API levels 25 and below. Fails on API levels 26, 27, 28. Tested on emulators API 25-28 and devices API 21 and API 28.\r\n\r\n*Sample Test Code:*\r\n\r\n{code:js}\r\nvar checkCameraPermissions = function(callback) {\r\n if (Ti.Media.hasCameraPermissions()) {\r\n Ti.UI.createAlertDialog({ cancel: 0, buttonNames: ['OK'], title:'Permissions', message:'Already had permissions. Good to go.'}).show();\r\n callback(true);\r\n } else { \r\n Ti.Media.requestCameraPermissions(function(e) {\r\n if (e.success === true) {\r\n Ti.UI.createAlertDialog({ cancel: 0, buttonNames: ['OK'], title:'Permissions', message:'User granted permissions. Good to go.'}).show();\r\n callback(true);\r\n } else {\r\n Ti.UI.createAlertDialog({ cancel: 0, buttonNames: ['OK'], title:'Permissions', message:'User did not granted permissions. Bad day.'}).show();\r\n callback(false);\r\n }\r\n });\r\n }\r\n};\r\n \r\nvar win = Ti.UI.createWindow({\r\n exitOnClose: true,\r\n fullscreen: false,\r\n title: 'Test'\r\n});\r\n\r\nvar button = Titanium.UI.createButton({\r\n title: 'Test DL Img & Save to Camera Roll'\r\n}); \r\nbutton.addEventListener('click', function(e) {\r\n checkCameraPermissions(function(returnedData) { \r\n if (returnedData == true) {\r\n var xhrImageGet = Ti.Network.createHTTPClient({\r\n onerror : function(xhrErr) {\r\n Ti.UI.createAlertDialog({ cancel: 0, buttonNames: ['OK'], title:'Error', message:'Unable to download the remote image. ' + xhrErr.error}).show();\r\n },\r\n onload : function() {\r\n try {\r\n var image_to_save = this.responseData; \r\n var picture = Titanium.Filesystem.getFile(Titanium.Filesystem.tempDirectory, 'icon-arrowFeature.png');\r\n picture.write(image_to_save); \r\n Ti.Media.saveToPhotoGallery(picture.read(), {\r\n success:function(){\r\n Ti.UI.createAlertDialog({ cancel: 0, buttonNames: ['OK'], title:'Image Saved', message:'icon-arrowFeature.png has been saved to your photo gallery.'}).show();\r\n },\r\n error:function(err){\r\n Ti.UI.createAlertDialog({ cancel: 0, buttonNames: ['OK'], title:'Error Saving to Gallery', message:err.error}).show();\r\n// FAILS RIGHT HERE WITH NO ERROR MESSAGE RETURNED. HOWEVER CONSOLE SHOWS THE ERROR TiMedia: (main) [526999,526999] Failed to create external storage directory.\r\n }\r\n }); \r\n image_to_save = null;\r\n }\r\n catch(saveErr) {\r\n Ti.UI.createAlertDialog({ cancel: 0, buttonNames: ['OK'], title:'Error', message:'Unable to save the file to your device. ' + saveErr.error}).show();\r\n }\r\n }\r\n }); \r\n xhrImageGet.open(\"GET\", 'https://s3.amazonaws.com/www.appcelerator.com.images/icon-arrowFeature.png');\r\n xhrImageGet.send(); \r\n } else {\r\n Ti.UI.createAlertDialog({ cancel: 0, buttonNames: ['OK'], title:'Permission Not Granted', message:'You have not granted the app the needed permissions to access your camera and/or device storage.'}).show();\r\n } \r\n }); \r\n});\r\n\r\nwin.add(button); \r\nwin.open();\r\n{code}\r\n\r\n*Test App tiapp.xml:*\r\n{code:xml}\r\n\r\n\r\n com.walkthelot.testsavetogallery\r\n TestSaveToGallery\r\n 1.0\r\n Ken\r\n \r\n undefined\r\n 2018 by Ken\r\n appicon.png\r\n false\r\n false\r\n true\r\n a17321c0-c4e2-4542-9222-e3f5731f8fb5\r\n dp\r\n true\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n true\r\n false\r\n false\r\n \r\n 7.3.1.GA\r\n\r\n{code}\r\nI've dug and dug and can not find anything that I may be missing.", "attachment": [ { "id": "65498", "filename": "Screenshot_20180906-165427.jpg", "author": { "name": "rmitro", "key": "rmitro", "displayName": "Rakhi Mitro", "active": false, "timeZone": "America/Los_Angeles" }, "created": "2018-09-06T10:55:12.000+0000", "size": 79182, "mimeType": "image/jpeg" } ], "flagged": false, "summary": "Android: \"Failed to create external storage directory\" when download image and save to camera roll", "creator": { "name": "ken@walkthelot.com", "key": "ken@walkthelot.com", "displayName": "Ken Rucker", "active": true, "timeZone": "America/New_York" }, "subtasks": [], "reporter": { "name": "ken@walkthelot.com", "key": "ken@walkthelot.com", "displayName": "Ken Rucker", "active": true, "timeZone": "America/New_York" }, "environment": "TI SDK 7.3.1 (fails below as well). MacOS. Android API 26, 27 & 28", "comment": { "comments": [ { "id": "441376", "author": { "name": "rmitro", "key": "rmitro", "displayName": "Rakhi Mitro", "active": false, "timeZone": "America/Los_Angeles" }, "body": "Hello,\r\n\r\nTested this issue and able to reproduce on SDK 7.3.0.GA, android 8(Huawei y9 2018) device.\r\n\r\n*Test Environemnt:*\r\n{code}\r\n\r\nAppcelerator Command-Line Interface, version 7.0.5\r\nOperating System\r\n Name = Mac OS X\r\n Version = 10.13.6\r\n Architecture = 64bit\r\n # CPUs = 4\r\n Memory = 8589934592\r\nNode.js\r\n Node.js Version = 8.9.1\r\n npm Version = 5.5.1\r\nTitanium CLI\r\n CLI Version = 5.1.1\r\nTitanium SDK\r\n SDK Version = 7.3.0.GA\r\n\r\nDevice: Android 8(Huawei y9 2018) device, Android 5 device\r\n {code}\r\n\r\n*Test code:* Sample code provided above\r\n\r\n*Test Steps:*\r\n1. Create a classic project .\r\n2. Paste the sample code and run. \r\n3. Click on *Test DL Img & Save to Camera Roll* button. Checking the permissions. And receive error “*Error Saving to Gallery*\" by the app. Attached the screenshot.\r\n\r\n\r\n*Console logs:*\r\n\r\n{code}\r\n[WARN] : libEGL: EGLNativeWindowType 0x7330b73010 disconnect failed\r\n[ERROR] : TiMedia: (main) [14437,14437] Failed to create external storage directory.\r\n[INFO] : zygote64: Do partial code cache collection, code=30KB, data=29KB\r\n[INFO] : zygote64: After code cache collection, code=30KB, data=29KB\r\n[INFO] : zygote64: Increasing code cache capacity to 128KB\r\n[INFO] : PressGestureDetector: HiTouch restricted: AboardArea.\r\n[INFO] : APSAnalyticsRunnable: Analytics Started\r\n[INFO] : APSAnalyticsRunnable: Stopping Service\r\n{code}", "updateAuthor": { "name": "sdarda", "key": "sdarda", "displayName": "Sharif AbuDarda", "active": false, "timeZone": "Asia/Dhaka" }, "created": "2018-09-06T10:53:42.000+0000", "updated": "2018-09-07T13:02:17.000+0000" }, { "id": "442281", "author": { "name": "Pietro", "key": "pietro", "displayName": "Pietro Granati", "active": true, "timeZone": "Europe/Rome" }, "body": "Same problem here! \r\nTested on LG and Huawei with android 8 and not working, on LG with android 6 no problem", "updateAuthor": { "name": "Pietro", "key": "pietro", "displayName": "Pietro Granati", "active": true, "timeZone": "Europe/Rome" }, "created": "2018-10-04T10:40:36.000+0000", "updated": "2018-10-04T10:40:36.000+0000" }, { "id": "442327", "author": { "name": "Pietro", "key": "pietro", "displayName": "Pietro Granati", "active": true, "timeZone": "Europe/Rome" }, "body": "Any news? This bug is HUGE!", "updateAuthor": { "name": "Pietro", "key": "pietro", "displayName": "Pietro Granati", "active": true, "timeZone": "Europe/Rome" }, "created": "2018-10-05T08:32:40.000+0000", "updated": "2018-10-05T08:32:40.000+0000" }, { "id": "442329", "author": { "name": "ybanev", "key": "ybanev", "displayName": "Yordan Banev", "active": true, "timeZone": "Europe/Athens" }, "body": "I think this may be due to not providing WRITE_EXTERNAL_STORAGE permission at runtime. I will give the code a go and get back here when I have some results.", "updateAuthor": { "name": "ybanev", "key": "ybanev", "displayName": "Yordan Banev", "active": true, "timeZone": "Europe/Athens" }, "created": "2018-10-05T09:05:20.000+0000", "updated": "2018-10-05T09:05:20.000+0000" }, { "id": "442330", "author": { "name": "michael", "key": "michael", "displayName": "Michael Gangolf", "active": true, "timeZone": "Europe/Berlin" }, "body": "[~ybanev]\r\nyou are right. Requesting the runtime permission:\r\n{code}\r\nvar permissions = ['android.permission.WRITE_EXTERNAL_STORAGE'...];\r\nTi.Android.requestPermissions(permissions, function(e) {...})\r\n{code}\r\n\r\nworks on Android 8. Without that I'll get the error described above", "updateAuthor": { "name": "michael", "key": "michael", "displayName": "Michael Gangolf", "active": true, "timeZone": "Europe/Berlin" }, "created": "2018-10-05T09:23:59.000+0000", "updated": "2018-10-05T09:23:59.000+0000" }, { "id": "442331", "author": { "name": "Pietro", "key": "pietro", "displayName": "Pietro Granati", "active": true, "timeZone": "Europe/Rome" }, "body": "Yeah while searching I was trying and seems to work", "updateAuthor": { "name": "Pietro", "key": "pietro", "displayName": "Pietro Granati", "active": true, "timeZone": "Europe/Rome" }, "created": "2018-10-05T09:26:03.000+0000", "updated": "2018-10-05T09:26:03.000+0000" }, { "id": "442332", "author": { "name": "ybanev", "key": "ybanev", "displayName": "Yordan Banev", "active": true, "timeZone": "Europe/Athens" }, "body": "Here is the reason why this problem occurred:\r\nhttps://developer.android.com/about/versions/oreo/android-8.0-changes#rmp\r\nWhat happens is that on Android API levels below 26 the WRITE_EXTERNAL_STORAGE was automatically granted if the user grants the READ_EXTERNAL_STORAGE permission since they are in the same permission group. The latter is requested by calling the {{Ti.Media.requestCameraPermissions}} method. They fixed that behavior in Android Oreo (see the link above).\r\nThe workaround provided by [~michael] is the best way to go for now. ", "updateAuthor": { "name": "ybanev", "key": "ybanev", "displayName": "Yordan Banev", "active": true, "timeZone": "Europe/Athens" }, "created": "2018-10-05T10:00:21.000+0000", "updated": "2018-10-05T10:00:21.000+0000" }, { "id": "442529", "author": { "name": "michael", "key": "michael", "displayName": "Michael Gangolf", "active": true, "timeZone": "Europe/Berlin" }, "body": "I can't update the Wiki (permission denied), but the examples there should be updated to the latest permissions too:\r\nhttps://docs.appcelerator.com/platform/latest/#!/guide/Camera_and_Photo_Gallery_APIs\r\n", "updateAuthor": { "name": "michael", "key": "michael", "displayName": "Michael Gangolf", "active": true, "timeZone": "Europe/Berlin" }, "created": "2018-10-11T14:31:27.000+0000", "updated": "2018-10-11T14:31:27.000+0000" }, { "id": "442530", "author": { "name": "jda", "key": "jda", "displayName": "John Dalsgaard", "active": true, "timeZone": "Europe/Berlin" }, "body": "Agree with Michael\r\n\r\nThe documentation should mention that for iOS you need to add these permissions in tiapp.xml:\r\n\r\n{code:java}\r\n NSCameraUsageDescription\r\n We need access to take a photo\r\n NSPhotoLibraryUsageDescription\r\n We need access to save a photo\r\n NSPhotoLibraryAddUsageDescription\r\n We need access to save a photo\r\n{code}\r\n\r\nand the showCamera() method could be adapted to something like this:\r\n\r\n{code:java}\r\n // check if we already have permissions to capture media\r\n var permissionsToRequest = [\"android.permission.CAMERA\",\"android.permission.WRITE_EXTERNAL_STORAGE\"];\r\n if ((OS_ANDROID && !Ti.Android.hasPermission(permissionsToRequest)) || (OS_IOS && !Ti.Media.hasCameraPermissions())) {\r\n // request permissions to capture media\r\n if(OS_ANDROID) {\r\n\t Ti.Android.requestPermissions(permissionsToRequest,function (e) {\r\n\t // success! display the camera\r\n\t if (e.success) {\r\n\t camera();\r\n\t } else {\r\n\t callback(new Error('could not obtain camera permissions!'), null);\r\n\t }\r\n\t });\r\n } else {\r\n\t Ti.Media.requestCameraPermissions(function (e) {\r\n\t // success! display the camera\r\n\t if (e.success) {\r\n\t camera();\r\n\t } else {\r\n\t callback(new Error('could not obtain camera permissions!'), null);\r\n\t }\r\n\t });\r\n }\r\n } else {\r\n camera();\r\n }\r\n{code}\r\n\r\n... or some cleaner code ;-) ", "updateAuthor": { "name": "jda", "key": "jda", "displayName": "John Dalsgaard", "active": true, "timeZone": "Europe/Berlin" }, "created": "2018-10-11T15:00:54.000+0000", "updated": "2018-10-11T15:00:54.000+0000" }, { "id": "442702", "author": { "name": "ybanev", "key": "ybanev", "displayName": "Yordan Banev", "active": true, "timeZone": "Europe/Athens" }, "body": "[~michael], [~jda] Thank you for the contribution! Scheduling to have the proposed changes/improvements included in 8.0.0.", "updateAuthor": { "name": "ybanev", "key": "ybanev", "displayName": "Yordan Banev", "active": true, "timeZone": "Europe/Athens" }, "created": "2018-10-17T15:14:47.000+0000", "updated": "2018-10-17T15:14:47.000+0000" }, { "id": "443421", "author": { "name": "ybanev", "key": "ybanev", "displayName": "Yordan Banev", "active": true, "timeZone": "Europe/Athens" }, "body": "PR: https://github.com/appcelerator/titanium_mobile/pull/10443", "updateAuthor": { "name": "ybanev", "key": "ybanev", "displayName": "Yordan Banev", "active": true, "timeZone": "Europe/Athens" }, "created": "2018-11-08T16:28:46.000+0000", "updated": "2018-11-08T16:28:46.000+0000" }, { "id": "443760", "author": { "name": "kmahalingam", "key": "kmahalingam", "displayName": "Keerthi Mahalingam", "active": false, "timeZone": "America/Los_Angeles" }, "body": "FR Passed. PR merged", "updateAuthor": { "name": "kmahalingam", "key": "kmahalingam", "displayName": "Keerthi Mahalingam", "active": false, "timeZone": "America/Los_Angeles" }, "created": "2018-11-19T23:14:36.000+0000", "updated": "2018-11-19T23:14:36.000+0000" }, { "id": "443894", "author": { "name": "kmahalingam", "key": "kmahalingam", "displayName": "Keerthi Mahalingam", "active": false, "timeZone": "America/Los_Angeles" }, "body": "Verified the fix on SDK 8.0.0.v20181126083929 .Image get saved.Closing\r\nTest Environment:\r\n Name = Mac OS X\r\n Version = 10.13.6\r\n Architecture = 64bit\r\n Memory = 17179869184\r\nNode.js\r\n Node.js Version = 8.12.0\r\n npm Version = 6.4.1\r\nTitanium CLI\r\n CLI Version = 5.1.1\r\nTitanium SDK\r\n SDK Version = 8.0.0.v20181120090229\r\nDevice =Pixel android 9,samsung s5 android 6\r\nEmulator =nexsus 6p android 8\r\n", "updateAuthor": { "name": "kmahalingam", "key": "kmahalingam", "displayName": "Keerthi Mahalingam", "active": false, "timeZone": "America/Los_Angeles" }, "created": "2018-11-26T20:10:22.000+0000", "updated": "2018-11-26T20:10:22.000+0000" } ], "maxResults": 16, "total": 16, "startAt": 0 } } }