{ "id": "173832", "key": "TIMOB-27201", "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": "21052", "description": "", "name": "Release 9.3.0", "archived": false, "released": true, "releaseDate": "2020-12-14" } ], "resolution": { "id": "1", "description": "A fix for this issue is checked into the tree and tested.", "name": "Fixed" }, "resolutiondate": "2020-10-22T22:24:23.000+0000", "created": "2019-07-01T23:34:37.000+0000", "priority": { "name": "Medium", "id": "3" }, "labels": [ "android", "blob", "content", "file", "url" ], "versions": [], "issuelinks": [ { "id": "57856", "type": { "id": "10003", "name": "Relates", "inward": "relates to", "outward": "relates to" }, "outwardIssue": { "id": "174238", "key": "TIMOB-27458", "fields": { "summary": "Parity: Ti.Filesystem.getFile inconsistent in support of various URIs across platforms", "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": "High", "id": "2" }, "issuetype": { "id": "1", "description": "A problem which impairs or prevents the functions of the product.", "name": "Bug", "subtask": false } } } } ], "assignee": { "name": "jquick", "key": "jquick", "displayName": "Joshua Quick", "active": true, "timeZone": "America/Los_Angeles" }, "updated": "2020-10-22T22:24:23.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": "10202", "name": "Android", "description": "Android Platform" } ], "description": "*Summary:*\r\nWhen passing an Android \"content://\" URL to the {{Ti.Filesystem.getFile()}} method, the returned {{File}} object does not support the following methods/properties. They will return failure results and log a \"Method is not supported\" warning.\r\n* [File.createdAt()|https://docs.appcelerator.com/platform/latest/#!/api/Titanium.Filesystem.File-method-createdAt]\r\n* [File.deleteFile()|https://docs.appcelerator.com/platform/latest/#!/api/Titanium.Filesystem.File-method-deleteFile]\r\n* [File.exists()|https://docs.appcelerator.com/platform/latest/#!/api/Titanium.Filesystem.File-method-exists]\r\n* [File.extension()|https://docs.appcelerator.com/platform/latest/#!/api/Titanium.Filesystem.File-method-extension]\r\n* [File.modifiedAt()|https://docs.appcelerator.com/platform/latest/#!/api/Titanium.Filesystem.File-method-modifiedAt]\r\n* [File.read()|https://docs.appcelerator.com/platform/latest/#!/api/Titanium.Filesystem.File-method-read]\r\n* [File.readonly|https://docs.appcelerator.com/platform/latest/#!/api/Titanium.Filesystem.File-property-readonly]\r\n* [File.write()|https://docs.appcelerator.com/platform/latest/#!/api/Titanium.Filesystem.File-method-write]\r\n* [File.writable|https://docs.appcelerator.com/platform/latest/#!/api/Titanium.Filesystem.File-property-writable]\r\n\r\nWe should add the above methods for consistency.\r\n\r\n*Note:*\r\nThe {{File}} object already correctly implements all other methods and properties for a \"content://\" URL such as the {{copy()}} method and \"nativePath\" property. Adding the above methods will help make the interface more complete and avoid confusion.\r\n\r\n*Implementation:*\r\nWe need to add the above methods to our {{TitaniumBlob}} Java class.\r\nhttps://github.com/appcelerator/titanium_mobile/blob/master/android/titanium/src/java/org/appcelerator/titanium/io/TitaniumBlob.java\r\n\r\nWe can test the inclusion of these methods with the attached [^FileContentUrlTest.js] file. This test script assumes its was created via a Classic Default project template.\r\n", "attachment": [ { "id": "66738", "filename": "FileContentUrlTest.js", "author": { "name": "jquick", "key": "jquick", "displayName": "Joshua Quick", "active": true, "timeZone": "America/Los_Angeles" }, "created": "2019-07-02T00:17:46.000+0000", "size": 1275, "mimeType": "application/x-javascript" } ], "flagged": false, "summary": "Android: Update \"Ti.Filesystem.File\" to support unimplemented APIs when wrapping a \"content://\" URL", "creator": { "name": "jquick", "key": "jquick", "displayName": "Joshua Quick", "active": true, "timeZone": "America/Los_Angeles" }, "subtasks": [], "reporter": { "name": "jquick", "key": "jquick", "displayName": "Joshua Quick", "active": true, "timeZone": "America/Los_Angeles" }, "environment": null, "comment": { "comments": [ { "id": "449815", "author": { "name": "hknoechel", "key": "hansknoechel", "displayName": "Hans Knöchel", "active": true, "timeZone": "Europe/Berlin" }, "body": "It seems like it's also not possible to handle content URL's right after granting permissions:\r\n{code}\r\n[ERROR] TitaniumBlob: java.lang.SecurityException: Permission Denial: opening provider org.chromium.chrome.browser.util.ChromeFileProvider from ProcessRecord{329dacf 19524:io.lambus.app/u0a90} (pid=19524, uid=10090) that is not exported from UID 10049\r\n[ERROR] TitaniumBlob: \tat android.os.Parcel.createException(Parcel.java:1942)\r\n[ERROR] TitaniumBlob: \tat android.os.Parcel.readException(Parcel.java:1910)\r\n[ERROR] TitaniumBlob: \tat android.os.Parcel.readException(Parcel.java:1860)\r\n[ERROR] TitaniumBlob: \tat android.app.IActivityManager$Stub$Proxy.getContentProvider(IActivityManager.java:4181)\r\n[ERROR] TitaniumBlob: \tat android.app.ActivityThread.acquireProvider(ActivityThread.java:5970)\r\n[ERROR] TitaniumBlob: \tat android.app.ContextImpl$ApplicationContentResolver.acquireUnstableProvider(ContextImpl.java:2592)\r\n[ERROR] TitaniumBlob: \tat android.content.ContentResolver.acquireUnstableProvider(ContentResolver.java:1828)\r\n[ERROR] TitaniumBlob: \tat android.content.ContentResolver.query(ContentResolver.java:786)\r\n[ERROR] TitaniumBlob: \tat android.content.ContentResolver.query(ContentResolver.java:752)\r\n[ERROR] TitaniumBlob: \tat android.content.ContentResolver.query(ContentResolver.java:710)\r\n[ERROR] TitaniumBlob: \tat org.appcelerator.titanium.io.TitaniumBlob.init(TitaniumBlob.java:105)\r\n[ERROR] TitaniumBlob: \tat org.appcelerator.titanium.io.TitaniumBlob.(TitaniumBlob.java:41)\r\n[ERROR] TitaniumBlob: \tat org.appcelerator.titanium.io.TiFileFactory.createTitaniumFile(TiFileFactory.java:112)\r\n[ERROR] TitaniumBlob: \tat org.appcelerator.titanium.util.TiUrl.resolve(TiUrl.java:256)\r\n[ERROR] TitaniumBlob: \tat org.appcelerator.kroll.KrollProxy.resolveUrl(KrollProxy.java:1385)\r\n[ERROR] TitaniumBlob: \tat org.appcelerator.titanium.TiFileProxy.(TiFileProxy.java:81)\r\n[ERROR] TitaniumBlob: \tat ti.modules.titanium.filesystem.FileProxy.(FileProxy.java:18)\r\n{code}\r\n\r\nWe use the following to grant permissions before accessing the URL, but it only works at the second attempt and crashes before:\r\n{code:js}\r\nTi.Android.requestPermissions([ 'android.permission.READ_EXTERNAL_STORAGE', 'android.permission.WRITE_EXTERNAL_STORAGE' ], event => {\r\n resolve(event.success);\r\n});\r\n{code}\r\n\r\n-And as usual, no workaround applicable :(-\r\n\r\n*EDIT*: Workaround found: {{intent.addFlags(Titanium.Android.FLAG_GRANT_READ_URI_PERMISSION | Titanium.Android.FLAG_GRANT_WRITE_URI_PERMISSION);}} + the {{com.google.android.apps.photos.permission.GOOGLE_PHOTOS}} permission fixed it.\r\n\r\nNo idea why exactly that would work better.", "updateAuthor": { "name": "hknoechel", "key": "hansknoechel", "displayName": "Hans Knöchel", "active": true, "timeZone": "Europe/Berlin" }, "created": "2019-07-15T18:22:06.000+0000", "updated": "2019-07-15T19:06:17.000+0000" }, { "id": "449821", "author": { "name": "jquick", "key": "jquick", "displayName": "Joshua Quick", "active": true, "timeZone": "America/Los_Angeles" }, "body": "[~hknoechel], the external storage permissions only applies to direct file system access. It has no impact on content URLs because you do not have direct file access to another app's sandboxed files. Instead, a {{ContentProvider}} provides access to files via inter-process communications. So, an app has to grant temporary access to the content URL's file via the intent's {{FLAG_GRANT_READ_URI_PERMISSION}} as you have noted. This is the correct native behavior.\r\n\r\nGoogle documents this below. Also note that the {{FLAG_GRANT_READ_URI_PERMISSION}} flag only grants *+temporary+* permission. The permission will be lost after rebooting the device.\r\nhttps://developer.android.com/guide/topics/providers/content-provider-creating\r\n\r\nAlso, would you mind next time writing this up as a separate \"story\" ticket? Because what you've posted has nothing to do with this ticket and I don't want to add any confusion to it.\r\n(Titanium is handling intents correctly. It just sounds like more examples are needed.)", "updateAuthor": { "name": "jquick", "key": "jquick", "displayName": "Joshua Quick", "active": true, "timeZone": "America/Los_Angeles" }, "created": "2019-07-15T19:30:52.000+0000", "updated": "2019-07-15T19:30:52.000+0000" }, { "id": "450302", "author": { "name": "mpoole_tp", "key": "mpoole_tp", "displayName": "Matt Poole", "active": true, "timeZone": "America/New_York" }, "body": "@jquick This would be great for consistency as you say. If I am looking to do now what read() would offer, what would be a recommended workaround? \r\n\r\nNamely the user is selecting a video from their gallery and I want to upload that to our server. In our current app flow, we queue up the request and asynchronously look to access the content/file at the time of upload. Usually, as you say, that is by doing getFile(nativePath) and read() to load the Blob data to then give to the HttpClient instance.\r\n\r\nSince I can't do that for content:// URLs now, I'm struggling on the correct roundabout way of accomplishing the same thing.\r\n\r\nAny help is appreciated.", "updateAuthor": { "name": "mpoole_tp", "key": "mpoole_tp", "displayName": "Matt Poole", "active": true, "timeZone": "America/New_York" }, "created": "2019-08-06T22:39:42.000+0000", "updated": "2019-08-06T22:39:42.000+0000" }, { "id": "450303", "author": { "name": "jquick", "key": "jquick", "displayName": "Joshua Quick", "active": true, "timeZone": "America/Los_Angeles" }, "body": "[~mpoole_tp],\r\n\r\nThe {{HTTPClient.send()}} method supports a {{File}} object. So, you don't need to do a {{File.read()}} call and send its blob.\r\n\r\nAlternatively, since you're selecting the video via {{Ti.Media.openPhotoGallery()}}, the {{e.media}} object that references the selected video is a {{Blob}} object. You can simply send that via {{HTTPClient}} as well. This is probably the most optimized way of doing it. Have a look at the test code in the link below. It plays an {{e.media}} video references via {{VideoPlayer}}. In your case, you'll want to send it via {{HTTPClient}} instead.\r\nhttps://github.com/appcelerator/titanium_mobile/pull/9223\r\n\r\nAlso note that a {{File}} object referencing a \"content://\" URL still supports the {{open()}} and {{copy()}} methods. So, you have other options as well. For example, for photo selection, I would normally tell devs to {{copy()}} the photo to their app's own sandbox. Partly because \"content://\" URL access is typically temporary and access will be revoked after rebooting the device.\r\n\r\nI hope this helps.", "updateAuthor": { "name": "jquick", "key": "jquick", "displayName": "Joshua Quick", "active": true, "timeZone": "America/Los_Angeles" }, "created": "2019-08-06T23:03:06.000+0000", "updated": "2019-08-06T23:04:09.000+0000" }, { "id": "450322", "author": { "name": "mpoole_tp", "key": "mpoole_tp", "displayName": "Matt Poole", "active": true, "timeZone": "America/New_York" }, "body": "Thanks. Yeah, I was able to confirm that if I use the e.media immediately I'm able to post it fine.\r\n\r\nWhat my issue within my current code structure seems to be is that I can't use the content:// url to re-load it instead (i.e. ignore the initial e.media). At least not by doing Ti.Filesystem.getFile(nativePath), because of what you're saying in this ticket. Trying to get it to load as a stream and copy it at that point seems to not be working for me as well..\r\n\r\nI think if I make a copy of the initial e.media into an app directory, though, I probably will be ok and that's my best way forward if I don't want to immediately upload it.\r\n\r\nthanks for your quick response.", "updateAuthor": { "name": "mpoole_tp", "key": "mpoole_tp", "displayName": "Matt Poole", "active": true, "timeZone": "America/New_York" }, "created": "2019-08-07T21:57:22.000+0000", "updated": "2019-08-07T21:57:22.000+0000" }, { "id": "457111", "author": { "name": "jquick", "key": "jquick", "displayName": "Joshua Quick", "active": true, "timeZone": "America/Los_Angeles" }, "body": "PR (master): https://github.com/appcelerator/titanium_mobile/pull/12143", "updateAuthor": { "name": "jquick", "key": "jquick", "displayName": "Joshua Quick", "active": true, "timeZone": "America/Los_Angeles" }, "created": "2020-10-01T07:41:34.000+0000", "updated": "2020-10-01T07:41:34.000+0000" }, { "id": "457205", "author": { "name": "ssaddique", "key": "ssaddique", "displayName": "Sohail Saddique", "active": true, "timeZone": "Europe/London" }, "body": "FR Passed for this ticket.", "updateAuthor": { "name": "ssaddique", "key": "ssaddique", "displayName": "Sohail Saddique", "active": true, "timeZone": "Europe/London" }, "created": "2020-10-08T10:00:01.000+0000", "updated": "2020-10-08T10:00:01.000+0000" }, { "id": "457317", "author": { "name": "lchoudhary", "key": "lchoudhary", "displayName": "Lokesh Choudhary", "active": true, "timeZone": "America/Los_Angeles" }, "body": "Verified the fix with SDK 9.3.0.v20201022111908.\r\nClosing.", "updateAuthor": { "name": "lchoudhary", "key": "lchoudhary", "displayName": "Lokesh Choudhary", "active": true, "timeZone": "America/Los_Angeles" }, "created": "2020-10-22T22:24:17.000+0000", "updated": "2020-10-22T22:24:17.000+0000" } ], "maxResults": 8, "total": 8, "startAt": 0 } } }