Titanium JIRA Archive
Titanium SDK/CLI (TIMOB)

[TIMOB-27201] Android: Update "Ti.Filesystem.File" to support unimplemented APIs when wrapping a "content://" URL

GitHub Issuen/a
TypeImprovement
PriorityMedium
StatusClosed
ResolutionFixed
Resolution Date2020-10-22T22:24:23.000+0000
Affected Version/sn/a
Fix Version/sRelease 9.3.0
ComponentsAndroid
Labelsandroid, blob, content, file, url
ReporterJoshua Quick
AssigneeJoshua Quick
Created2019-07-01T23:34:37.000+0000
Updated2020-10-22T22:24:23.000+0000

Description

*Summary:* When 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. * [File.createdAt()](https://docs.appcelerator.com/platform/latest/#!/api/Titanium.Filesystem.File-method-createdAt) * [File.deleteFile()](https://docs.appcelerator.com/platform/latest/#!/api/Titanium.Filesystem.File-method-deleteFile) * [File.exists()](https://docs.appcelerator.com/platform/latest/#!/api/Titanium.Filesystem.File-method-exists) * [File.extension()](https://docs.appcelerator.com/platform/latest/#!/api/Titanium.Filesystem.File-method-extension) * [File.modifiedAt()](https://docs.appcelerator.com/platform/latest/#!/api/Titanium.Filesystem.File-method-modifiedAt) * [File.read()](https://docs.appcelerator.com/platform/latest/#!/api/Titanium.Filesystem.File-method-read) * [File.readonly](https://docs.appcelerator.com/platform/latest/#!/api/Titanium.Filesystem.File-property-readonly) * [File.write()](https://docs.appcelerator.com/platform/latest/#!/api/Titanium.Filesystem.File-method-write) * [File.writable](https://docs.appcelerator.com/platform/latest/#!/api/Titanium.Filesystem.File-property-writable) We should add the above methods for consistency. *Note:* The 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. *Implementation:* We need to add the above methods to our TitaniumBlob Java class. https://github.com/appcelerator/titanium_mobile/blob/master/android/titanium/src/java/org/appcelerator/titanium/io/TitaniumBlob.java We 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.

Attachments

FileDateSize
FileContentUrlTest.js2019-07-02T00:17:46.000+00001275

Comments

  1. Hans Knöchel 2019-07-15

    It seems like it's also not possible to handle content URL's right after granting permissions:
       [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
       [ERROR] TitaniumBlob: 	at android.os.Parcel.createException(Parcel.java:1942)
       [ERROR] TitaniumBlob: 	at android.os.Parcel.readException(Parcel.java:1910)
       [ERROR] TitaniumBlob: 	at android.os.Parcel.readException(Parcel.java:1860)
       [ERROR] TitaniumBlob: 	at android.app.IActivityManager$Stub$Proxy.getContentProvider(IActivityManager.java:4181)
       [ERROR] TitaniumBlob: 	at android.app.ActivityThread.acquireProvider(ActivityThread.java:5970)
       [ERROR] TitaniumBlob: 	at android.app.ContextImpl$ApplicationContentResolver.acquireUnstableProvider(ContextImpl.java:2592)
       [ERROR] TitaniumBlob: 	at android.content.ContentResolver.acquireUnstableProvider(ContentResolver.java:1828)
       [ERROR] TitaniumBlob: 	at android.content.ContentResolver.query(ContentResolver.java:786)
       [ERROR] TitaniumBlob: 	at android.content.ContentResolver.query(ContentResolver.java:752)
       [ERROR] TitaniumBlob: 	at android.content.ContentResolver.query(ContentResolver.java:710)
       [ERROR] TitaniumBlob: 	at org.appcelerator.titanium.io.TitaniumBlob.init(TitaniumBlob.java:105)
       [ERROR] TitaniumBlob: 	at org.appcelerator.titanium.io.TitaniumBlob.<init>(TitaniumBlob.java:41)
       [ERROR] TitaniumBlob: 	at org.appcelerator.titanium.io.TiFileFactory.createTitaniumFile(TiFileFactory.java:112)
       [ERROR] TitaniumBlob: 	at org.appcelerator.titanium.util.TiUrl.resolve(TiUrl.java:256)
       [ERROR] TitaniumBlob: 	at org.appcelerator.kroll.KrollProxy.resolveUrl(KrollProxy.java:1385)
       [ERROR] TitaniumBlob: 	at org.appcelerator.titanium.TiFileProxy.<init>(TiFileProxy.java:81)
       [ERROR] TitaniumBlob: 	at ti.modules.titanium.filesystem.FileProxy.<init>(FileProxy.java:18)
       
    We use the following to grant permissions before accessing the URL, but it only works at the second attempt and crashes before:
       Ti.Android.requestPermissions([ 'android.permission.READ_EXTERNAL_STORAGE', 'android.permission.WRITE_EXTERNAL_STORAGE' ], event => {
           resolve(event.success);
       });
       
    -And as usual, no workaround applicable :(- *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. No idea why exactly that would work better.
  2. Joshua Quick 2019-07-15

    [~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. Google 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. https://developer.android.com/guide/topics/providers/content-provider-creating Also, 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. (Titanium is handling intents correctly. It just sounds like more examples are needed.)
  3. Matt Poole 2019-08-06

    @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? Namely 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. Since I can't do that for content:// URLs now, I'm struggling on the correct roundabout way of accomplishing the same thing. Any help is appreciated.
  4. Joshua Quick 2019-08-06

    [~mpoole_tp], The HTTPClient.send() method supports a File object. So, you don't need to do a File.read() call and send its blob. Alternatively, 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. https://github.com/appcelerator/titanium_mobile/pull/9223 Also 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. I hope this helps.
  5. Matt Poole 2019-08-07

    Thanks. Yeah, I was able to confirm that if I use the e.media immediately I'm able to post it fine. What 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.. I 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. thanks for your quick response.
  6. Joshua Quick 2020-10-01

    PR (master): https://github.com/appcelerator/titanium_mobile/pull/12143
  7. Sohail Saddique 2020-10-08

    FR Passed for this ticket.
  8. Lokesh Choudhary 2020-10-22

    Verified the fix with SDK 9.3.0.v20201022111908. Closing.

JSON Source