Open zburlott4 opened 1 year ago
Hi
There are some changes going on here, one of the things though is that f.requestPermission()
is an asynchronous call. Most likely what is happening here is that the permission status is unknown at the point where you call f.browseForOpen()
so this never actually starts - are you able to check the logcat output to see if there's an exception being thrown there saying that it needs permissions to proceed?
One thing though, please don't use the documentsDirectory unless you absolutely have to!
We are currently trying to work out how to handle a mixture of the URI formats that we get back from the file selection activities using the Storage Access Framework, vs the native paths that we can get at from existing code. It seems as if you can request and receive permissions for a file via the content URI but then can't open it via its actual path... so there is still a bit of work in progress here. Essentially it's just that Android's file/storage paradigm has moved on and is not particularly compatible with the AIR idea of a 'file'....
But please let us know how you get on with the code if you switch it to something like:
var f:File = File.documentsDirectory.resolvePath("Documents");
f.addEventListener(Event.SELECT, fileSelected);
f.addEventListener(PermissionEvent.PERMISSION_STATUS, permissionStatus);
f.requestPermission();
function permissionStatus(event:PermissionEvent):void
{
trace("Permission status = " + event.toString());
f.browseForOpen("Open");
}
function fileSelected(event:Event):void
{
trace("FileSelected")
}
Also note, if you request permission for the filesystem using the Storage Access Framework, it's going to pop up a dialog asking you to select a file to open. You can get it to select a folder (to grant access to) if you add a trailing / onto the path, but it's perhaps best to to just use a blank File object here rather than setting up the path beforehand..
thanks
By following the above example, the browser window is able to open BUT putting a try catch around browseForOpen shows the following errors related to permissions
Error #3800 at MethodInfo-77036()
The permission event shows
Permission status = [PermissionEvent type ="permissionStatus" bubbles=false cancelable=false status="denied"
If I simply do
var file = new File()
Then I get permission permitted but the file browser won't open.
What I really need is a way to just get to the image directory where the images are stored and have permission to read it back. Any help on this would be appreciated.
Possibly another use bit of information.
On SDK 50.1.1.2 this functionality works using the code provided but it pulls up an older style browser picker that shows just a list of files.
In SDK 50.2.1.1 you get the permissions denied error and a browser picker that looks like an android file browser.
So what changed between the two? and why the difference in the browser picker. Preferably the android style browser picker would be best to use since its the most visually appealing and shows the image previews. Any thoughts on this would be appreciated.
The change - in behaviour, and in appearance - is because of the adoption of the Android Storage Access Framework, which is what we need to start using to get "proper" filesystem access. And when I say "proper", I mean, "according to how Android wants your application to behave".
The old 'permissions' was something where your manifest permissions (read_external_storage etc) would mean you can read all the content of the 'external storage' location (in AS3, this is File.documentsDirectory or File.userDirectory). So as long as you got Android to confirm with the user that this would be okay, you got your access.
The new StorageAccessFramework means that you need to get the user's permission a little more explicitly, either for a folder (which is how we've implemented the "File.browseForDirectory" functionality) or for a file to read or write (File.browseForOpen, File.browseForWrite).
If you just want generic access to the filesystem .. then you'll need to get the user to agree to it via the same functionality as you get from File.browseForDirectory. The goal that we had was:
The idea of getting access to specific media folders - images, videos etc - is something we may need to add with some extra APIs, but this again may depend very much on what Android version is running on the phone. We can look at what's required here and what the differences are between versions!
Hope the explanation is useful though :-)
thanks
Do you have a working example of getting permission for a directory, opening a file browser at that directory and allowing a end user to select any file in that directory for use in your program using the adoption of the newer apks.
I've tried a variety of items, using browse for directory to request permission and then calling browseforopen on that location, but it all comes back with permission denied. I don't know what file users are going to select ahead of time, at best I'll know which directory they will be choosing files from.
I second @zburlott4 request. I've been trying to save a file and have tried many different iterations that all tell me permission denied. I happen to be testing on Android 12 and Android 13. When I request permissions or call browseforsave, android displays the downloads folder even though iIm trying to save in the documents directory. It is all very confusing to me.
Yes it's a bit of a pain at the moment, there are some mixtures of things that we've found to work but it also appears to depend upon the format of the content uri or the use of native paths. There's also the issue with 'resolvePath' which is causing problems too. We're trying to get out an updated release later on which will hopefully improve things, but I'm half convinced that things we've tried and that work one day, then fail to work the next day, so we'll then have to take stock and try to cover these key use cases, and get any further Android-specific updates out as swiftly as possible to fix outstanding problems.
thanks
Okay so with the latest updates in 50.2.2.3, we are still finding that we don't have the permission to open a URI that we've just selected -> but there is an assumption in our code, based on what we were seeing originally, that the URI we get is a 'tree' URI. From what I've just been looking at, we're getting a 'document' URI being returned. So we'll have to check whether it's possible to do this with that mechanism; the code in there currently is trying to convert it from a document uri to a tree uri but that's when it's failing...
I feel your pain for bleeding edge changes from a 3rd party (google).
Not sure if this helps but I tried something slightly different (running AIR 50.2.2) today and got this:
[Fault] exception, information=PermissionError: 'Error #3800: This call requires Storage permission.' when i called browseForSave. i have read and write external storage permissions in the manifest.
after I call File resolvePath, should I replace the "file://" with "content://" in the uri? I just want to dump a temp file somewhere that has some debug information in it when running on device. I've used documentsDirectory in the past but maybe I shouldn't? I just want to plug the device into my laptop so i can copy the file over. This isn't a user feature, just a way to get some debug information (if you really want to know, i have an encrypted sqlite database and for testing/debugging, i want to see the records in that database so i want to make a copy of it unencrypted. this used to work before android made all their storage changes).
Hi
Yes in order to ensure the File.browseForXXX
functions get past the initial check, you can do:
(new File()).requestPermission();
You should perhaps do it properly with an event listener and not proceeding until you get a 'granted' response, but if you're using a new Android device, this will return straight away with a 'granted' because you're using a blank File object. This is where we're basically saying, "okay you want the permissions, it's actually going to be file-based so go ahead for now and we'll see what we end up with later". The aim is, it allows for some code that can be the same between whichever Android version you're using - so if you are on an old phone and call that, you would see the "This app wants to access your filesystem" message and the user can grant/deny the request.
The "content://" URIs should be (soon!) properly supported, but you can't just swap one to the other.
I was about to say, for temporary files you can just use File.applicationStorageDirectory - if everyone did this, it should mean there are no problems at all, because the app should always have full permissions in here even without asking for it!
But if you're trying to put things in a temp file that can then be accessed by the USB mass storage, then yes something like Documents would be best.
We're currently at the point where we've got it working a bit more: you can browse for a file and then read/write whatever you find (depending on whether you browsed for Open or Save); and once that's done you can read back the same file in the future.
But the browseForDirectory stuff isn't working properly: e.g. we can create a subfolder of a directory that the user selects, but we can't then add a file into it. And creating a file in the chosen folder is also failing! So a little more to work out still...
I have a slight concern that if we programmatically create a new folder, in a tree for which we have been granted permissions, then we won't be granted permission for anything in the new folder. Seems counter-intuitive to me so I'm hoping I'm wrong...
thanks
This is what I was doing. In the event listeners for permission status, browseForSave is failing. Yes, should be checking the actual permission granted and all that fun stuff. I'll just wait until the next release.
var f:File = File.documentsDirectory.resolvePath("Documents"); // I've tried files and directories here, all to no avail. trace(f.url); try { f.addEventListener(PermissionEvent.PERMISSION_STATUS, function (e:PermissionEvent):void { f.browseForSave("Save as"); });
f.addEventListener(Event.SELECT, function (e:Event):void { var newFile:File = e.target as File;
... do my stuff to dump data to file ....
}); f.requestPermission(); } catch (e: Error) { trace("Failed: " + e.message); }
From: Andrew Frost @.> Sent: Tuesday, April 4, 2023 12:53 PM To: airsdk/Adobe-Runtime-Support @.> Cc: [RCD] PHIT @.>; Comment @.> Subject: Re: [airsdk/Adobe-Runtime-Support] AIR 50.2 SDK File Permission Issue Between Android 12 and Android 7 (Issue #2559)
EXTERNAL: This email originated from outside of the organization. Do not click links or open attachments unless you recognize the sender and know the content is safe.
Hi
Yes in order to ensure the File.browseForXXX functions get past the initial check, you can do:
(new File()).requestPermission();
You should perhaps do it properly with an event listener and not proceeding until you get a 'granted' response, but if you're using a new Android device, this will return straight away with a 'granted' because you're using a blank File object. This is where we're basically saying, "okay you want the permissions, it's actually going to be file-based so go ahead for now and we'll see what we end up with later". The aim is, it allows for some code that can be the same between whichever Android version you're using - so if you are on an old phone and call that, you would see the "This app wants to access your filesystem" message and the user can grant/deny the request.
The "content://" URIs should be (soon!) properly supported, but you can't just swap one to the other.
I was about to say, for temporary files you can just use File.applicationStorageDirectory - if everyone did this, it should mean there are no problems at all, because the app should always have full permissions in here even without asking for it!
But if you're trying to put things in a temp file that can then be accessed by the USB mass storage, then yes something like Documents would be best.
We're currently at the point where we've got it working a bit more: you can browse for a file and then read/write whatever you find (depending on whether you browsed for Open or Save); and once that's done you can read back the same file in the future.
But the browseForDirectory stuff isn't working properly: e.g. we can create a subfolder of a directory that the user selects, but we can't then add a file into it. And creating a file in the chosen folder is also failing! So a little more to work out still...
I have a slight concern that if we programmatically create a new folder, in a tree for which we have been granted permissions, then we won't be granted permission for anything in the new folder. Seems counter-intuitive to me so I'm hoping I'm wrong...
thanks
- Reply to this email directly, view it on GitHubhttps://nam04.safelinks.protection.outlook.com/?url=https%3A%2F%2Fgithub.com%2Fairsdk%2FAdobe-Runtime-Support%2Fissues%2F2559%23issuecomment-1496299669&data=05%7C01%7Creckhoff%40rti.org%7C826fde2685c74525b71308db352d1a24%7C2ffc2ede4d4449948082487341fa43fb%7C0%7C0%7C638162240072862649%7CUnknown%7CTWFpbGZsb3d8eyJWIjoiMC4wLjAwMDAiLCJQIjoiV2luMzIiLCJBTiI6Ik1haWwiLCJXVCI6Mn0%3D%7C3000%7C%7C%7C&sdata=Spdtgty3IZX5WHdv9TKCL6Bq6i7GkkYuTcqmnIskFLY%3D&reserved=0, or unsubscribehttps://nam04.safelinks.protection.outlook.com/?url=https%3A%2F%2Fgithub.com%2Fnotifications%2Funsubscribe-auth%2FAFCUWSGK3HAYI5VOYHM2LVTW7RGYFANCNFSM6AAAAAAWNUX46Q&data=05%7C01%7Creckhoff%40rti.org%7C826fde2685c74525b71308db352d1a24%7C2ffc2ede4d4449948082487341fa43fb%7C0%7C0%7C638162240072862649%7CUnknown%7CTWFpbGZsb3d8eyJWIjoiMC4wLjAwMDAiLCJQIjoiV2luMzIiLCJBTiI6Ik1haWwiLCJXVCI6Mn0%3D%7C3000%7C%7C%7C&sdata=SlxuhTGCeXAeQ29KUWCqsmJEr1plq0GdpJNbWnRo7tQ%3D&reserved=0. You are receiving this because you commented.Message ID: @.***>
Yes, so I think it should be possible to do:
var fBlank : File = new File();
var f:File = File.documentsDirectory.resolvePath("Documents"); // I've tried files and directories here, all to no avail.
trace(f.url);
try {
fBlank.addEventListener(PermissionEvent.PERMISSION_STATUS, function (e:PermissionEvent):void {
f.browseForSave("Save as");
});
f.addEventListener(Event.SELECT, function (e:Event):void {
var newFile:File = e.target as File;
// newFile here will still be 'f'...?
... do my stuff to dump data to file ....
});
fBlank.requestPermission();
} catch (e: Error) {
trace("Failed: " + e.message);
}
i.e. just change the permission stuff to be using the new blank file. But currently (50.2.2.3) even this doesn't work because of the different styles of URLs.
I think I've worked out what's happened here: if you browse for a file, you get a 'document' type content URI, and then we can deal with it, but if you browse for a folder, you get a 'tree' type content URI, and we are struggling to mix the two. I think we had implemented the support for 'document' versions but then things stopped working when we kicked off the tests with the folder browsing and ended up with 'tree' versions, so now it works for them and not for documents. But then, we are still seeing that we get permissions for a subfolder, and can write a file to it, but then don't have permissions to read that file...
Am appreciating your patience here ....!
thanks
Hi
So, we seem to be making a bit of progress here.. below in a zip file is the "runtimeClasses.jar" that goes into the SDK under the "lib/android/lib" folder. Can you perhaps back up the one you have already and then copy this over the top?
The above code should then start working, and hopefully a lot more of these cases will work...
thanks
Unable to test this runtimeClasses.zip on SDK 5.2.2.3. App doesn't start on android. Last working SDK is 5.2.2.1.
Okay interesting -> do you have any logcat output for this? It's been working fine for me but then I've just used a different platform/device and then also got it crashing on start-up, but there was a clear JNI exception in the log...
thanks
Can you try with the files from the below please? This should help with that start-up issue as well as making file access work a bit better.. https://transfer.harman.com/message/xifMcPM14EXBZeIdZnKibj
Tried on 5.2.2.3. No change in ability to start the app. It installs, but is unable to start. Tried the 32 bit and 64 bit versions of the compile.
2023-04-06 10:23:34.608 32026-32318/? D/WindowManager: container=Task{a410c52 #278 type=standard A=10271:air.*******Mobile U=0 visible=true mode=fullscreen translucent=false sz=1} 2023-04-06 10:23:34.620 20065-20065/? A/libc: Fatal signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x58 in tid 20065 (air.*******Mobile), pid 20065 (air.*******Mobile) 2023-04-06 10:23:34.638 1117-1117/? D/SurfaceFlinger: Display 4630946823025164673 HWC layers: DEVICE | 0xb4000074c7594260 | 0100 | RGBA_8888 | 0.0 0.0 1200.0 2000.0 | 0 0 1200 2000 | Splash Screen air.*******Mobile$_32375#0 DEVICE | 0xb4000074c7dadb60 | 0100 | RGBA_8888 | 0.0 0.0 36.0 2000.0 | 1164 0 1200 2000 | StatusBar$_32375#0 DEVICE | 0xb4000074c7db2a20 | 0100 | RGBA_8888 | 0.0 0.0 72.0 2000.0 | 0 0 72 2000 | NavigationBar0$_32375#0 2023-04-06 10:23:35.022 20108-20108/? A/DEBUG: Cmdline: air.*******Mobile 2023-04-06 10:23:35.022 20108-20108/? A/DEBUG: pid: 20065, tid: 20065, name: air.*******Mobile >>> air.*******Mobile <<< 2023-04-06 10:23:35.022 20108-20108/? A/DEBUG: #00 pc 00154bf2 /data/app/~~LHtbkt7-aU8Zwarg_hyg9Q==/air.*******Mobile-rRb-f29LNxplYi1i0O8WeA==/lib/arm/libCore.so (BuildId: 6fe000f6ebbaf325a0fbb17c07f35bb6ad96a01b) 2023-04-06 10:23:35.022 20108-20108/? A/DEBUG: #01 pc 0045e20f /data/app/~~LHtbkt7-aU8Zwarg_hyg9Q==/air.*******Mobile-rRb-f29LNxplYi1i0O8WeA==/lib/arm/libCore.so (BuildId: 6fe000f6ebbaf325a0fbb17c07f35bb6ad96a01b) 2023-04-06 10:23:35.022 20108-20108/? A/DEBUG: #02 pc 0045831f /data/app/~~LHtbkt7-aU8Zwarg_hyg9Q==/air.*******Mobile-rRb-f29LNxplYi1i0O8WeA==/lib/arm/libCore.so (BuildId: 6fe000f6ebbaf325a0fbb17c07f35bb6ad96a01b) 2023-04-06 10:23:35.049 32026-20112/? W/ActivityManager: crash : air.*******Mobile,10271 2023-04-06 10:23:35.052 32026-20112/? W/ActivityTaskManager: Force finishing activity air.*******Mobile/.AIRAppEntry 2023-04-06 10:23:35.057 32026-20112/? D/MARsPolicyManager: onPackageResumedFG pkgName = air.*******Mobile, userId = 0 2023-04-06 10:23:35.070 1117-2133/? I/SurfaceFlinger: id=17995 Removed Bounds for - air.*******Mobile/air.*******Mobile.AIRAppEntry@0#0 (114) 2023-04-06 10:23:35.070 1117-2133/? I/SurfaceFlinger: id=17996 Removed SurfaceView - air.*******Mobile/air.*******Mobile.AIRAppEntry@d5498ad@0#0 (114) 2023-04-06 10:23:35.070 1117-2133/? I/SurfaceFlinger: id=17997 Removed SurfaceView - air.*******Mobile/air.*******Mobile.AIRAppEntry@d5498ad@0(BLAST)#0 (114) 2023-04-06 10:23:35.070 1117-2133/? I/SurfaceFlinger: id=17998 Removed Background for SurfaceView - air.*******Mobile/air.*******Mobile.AIRAppEntry@d5498ad@0#0 (114) 2023-04-06 10:23:35.070 1117-2133/? I/SurfaceFlinger: id=17999 Removed SurfaceView - air.*******Mobile/air.*******Mobile.AIRAppEntry@abfb4d7@0#0 (114) 2023-04-06 10:23:35.070 1117-2133/? I/SurfaceFlinger: id=18000 Removed SurfaceView - air.*******Mobile/air.*******Mobile.AIRAppEntry@abfb4d7@0(BLAST)#0 (114) 2023-04-06 10:23:35.070 1117-2133/? I/SurfaceFlinger: id=18001 Removed Background for SurfaceView - air.*******Mobile/air.*******Mobile.AIRAppEntry@abfb4d7@0#0 (114) 2023-04-06 10:23:35.102 32026-20112/? W/WindowManager: Failed to deliver inset state change to w=Window{d8f8344 u0 air.*******Mobile/air.*******Mobile.AIRAppEntry} android.os.DeadObjectException at android.os.BinderProxy.transactNative(Native Method) at android.os.BinderProxy.transact(BinderProxy.java:635) at android.view.IWindow$Stub$Proxy.insetsControlChanged(IWindow.java:770) at com.android.server.wm.WindowState.notifyInsetsControlChanged(WindowState.java:4759) at com.android.server.wm.InsetsStateController.lambda$notifyPendingInsetsControlChanged$6$InsetsStateController(InsetsStateController.java:677) at com.android.server.wm.InsetsStateController$$ExternalSyntheticLambda1.run(Unknown Source:2) at com.android.server.wm.WindowAnimator.executeAfterPrepareSurfacesRunnables(WindowAnimator.java:345) at com.android.server.wm.RootWindowContainer.performSurfacePlacementNoTrace(RootWindowContainer.java:1036) at com.android.server.wm.RootWindowContainer.performSurfacePlacement(RootWindowContainer.java:959) at com.android.server.wm.WindowSurfacePlacer.performSurfacePlacementLoop(WindowSurfacePlacer.java:189) at com.android.server.wm.WindowSurfacePlacer.performSurfacePlacement(WindowSurfacePlacer.java:130) at com.android.server.wm.WindowSurfacePlacer.performSurfacePlacement(WindowSurfacePlacer.java:119) at com.android.server.wm.WindowSurfacePlacer.continueLayout(WindowSurfacePlacer.java:101) at com.android.server.wm.ActivityTaskManagerService.continueWindowLayout(ActivityTaskManagerService.java:5288) at com.android.server.wm.ActivityRecord.finishIfPossible(ActivityRecord.java:3736) at com.android.server.wm.ActivityRecord.finishIfPossible(ActivityRecord.java:3450) at com.android.server.wm.Task.finishTopCrashedActivityLocked(Task.java:8870) at com.android.server.wm.RootWindowContainer.lambda$finishTopCrashedActivities$20(RootWindowContainer.java:2781) at com.android.server.wm.RootWindowContainer$$ExternalSyntheticLambda30.accept(Unknown Source:10) at com.android.server.wm.Task.forAllTasks(Task.java:5181) at com.android.server.wm.WindowContainer.forAllTasks(WindowContainer.java:1849) at com.android.server.wm.WindowContainer.forAllTasks(WindowContainer.java:1849) at com.android.server.wm.WindowContainer.forAllTasks(WindowContainer.java:1849) at com.android.server.wm.WindowContainer.forAllTasks(WindowContainer.java:1849) at com.android.server.wm.WindowContainer.forAllTasks(WindowContainer.java:1849) at com.android.server.wm.WindowContainer.forAllTasks(WindowContainer.java:1849) at com.android.server.wm.WindowContainer.forAllTasks(WindowContainer.java:1849) at com.android.server.wm.WindowContainer.forAllTasks(WindowContainer.java:1842) at com.android.server.wm.RootWindowContainer.finishTopCrashedActivities(RootWindowContainer.java:2780) at com.android.server.wm.ActivityTaskManagerService$LocalService.finishTopCrashedActivities(ActivityTaskManagerService.java:7625) at com.android.server.am.AppErrors.handleAppCrashLSPB(AppErrors.java:1035) at com.android.server.am.AppErrors.makeAppCrashingLocked(AppErrors.java:864) at com.android.server.am.AppErrors.crashApplicationInner(AppErrors.java:733) at com.android.server.am.AppErrors.crashApplication(AppErrors.java:584) at com.android.server.am.ActivityManagerService.handleApplicationCrashInner(ActivityManagerService.java:9735) at com.android.server.am.NativeCrashListener$NativeCrashReporter.run(NativeCrashListener.java:119) 2023-04-06 10:23:35.109 32026-32205/? I/WindowManager: WIN DEATH: Window{d8f8344 u0 air.*******Mobile/air.*******Mobile.AIRAppEntry} 2023-04-06 10:23:35.109 32026-32205/? W/InputManager-JNI: Input channel object 'd8f8344 air.*******Mobile/air.*******Mobile.AIRAppEntry (client)' was disposed without first being removed with the input manager! 2023-04-06 10:23:35.110 32026-32205/? I/WindowManager: Destroying surface Surface(name=air.*******Mobile/air.*******Mobile.AIRAppEntry$_20065)/@0xd15d30e called by com.android.server.wm.WindowStateAnimator.destroySurface:987 com.android.server.wm.WindowStateAnimator.destroySurfaceLocked:518 com.android.server.wm.WindowState.removeImmediately:2960 com.android.server.wm.WindowState.removeIfPossible:3151 com.android.server.wm.WindowState.access$300:334 com.android.server.wm.WindowState$DeathRecipient.binderDied:3725 android.os.IBinder$DeathRecipient.binderDied:320 android.os.BinderProxy.sendDeathNotice:750 2023-04-06 10:23:35.119 32026-32475/? I/ActivityManager: Process air.*******Mobile (pid 20065) has died: fg TOP (136,833) 2023-04-06 10:23:35.122 1117-1117/? I/Layer: id=17992 mRemovedFromDrawingState air.*******Mobile/air.*******Mobile.AIRAppEntry$_20065#0 (112) 2023-04-06 10:23:35.122 1117-1117/? I/Layer: id=17995 mRemovedFromDrawingState Bounds for - air.*******Mobile/air.*******Mobile.AIRAppEntry@0#0 (112) 2023-04-06 10:23:35.122 1117-1117/? I/Layer: id=17996 mRemovedFromDrawingState SurfaceView - air.*******Mobile/air.*******Mobile.AIRAppEntry@d5498ad@0#0 (112)
Hey guys, what is the status of this?
I'd like to be able to save an image from my app to the user's documentsDirectory or some other directory that the image can be viewed in the gallery.
In the most recent Air, trying something like:
savedFile = File.documentsDirectory.resolvePath('Myapp/' + filename); savedFile.addEventListener(PermissionEvent.PERMISSION_STATUS, onPermissionStatus); savedFile.requestPermission();
shows: onPermissionStatus - granted
But then when I actually try to write the file there I get: IOErrorEvent: text=Error #3001: File or directory access denied.
I've read the recent release notes, but unfortunately still can't understand how to do this. Any help would be greatly appreciated!
Hi @Ender22 - there is still a challenge with this, which is that the new Android way of accessing files isn't a very good match for how AIR assumes a file system will work. So for example, in your code snippet above, you can't use that approach to create/save a file: you will need to ask the user to select the target filename/location for the file you're creating (unless it's going into normal/private application storage i.e. using File.applicationStorageDirectory
). So you'd need to use the File.browseForSave()
mechanism.
But even this will cause a problem in your use case I think, because even if you save an image file into a public location, the Android scoped storage system means that the Gallery application will not have permission to read it. If you actually want to save media files (image/video/audio) so that they're generally accessible on the device, there's a different set of APIs needed to do this now in Android. And those ones we don't expose through the core AS3 API set - I'm not sure whether this is available via the ANE that @pol2095 had created..? It looks like it: http://pol2095.free.fr/Android-Storage-Access-Framework/docs/com/nativeExtensions/saf/Document.html#createBytesToGallery()
thanks
I share a complete example using SAF in this case :
package
{
import com.nativeExtensions.saf.Document;
import com.nativeExtensions.saf.DocumentEvent;
import flash.display.BitmapData;
import flash.display.PNGEncoderOptions;
import flash.display.Sprite;
import flash.events.MouseEvent;
import flash.net.SharedObject;
import flash.utils.ByteArray;
public class Main extends Sprite
{
[Embed(source = "./myImage.png")]
private static const Image:Class;
private var document:Document;
private var mySO:SharedObject = SharedObject.getLocal("saf");
public function Main()
{
document = new Document();
document.addEventListener(DocumentEvent.READ, onRead);
document.uri = document.documentsUri;
var sprite:Sprite = new Sprite();
sprite.graphics.beginFill(0xFF0000);
sprite.graphics.drawRect( 0, 0, 200, 200);
sprite.graphics.endFill();
sprite.addEventListener(MouseEvent.CLICK, clickHandler);
this.addChild( sprite );
}
private function clickHandler(event:MouseEvent):void
{
if( mySO.data.hasOwnProperty( "documentsUri" ) )
{
if( mySO.data.documentsUri ) document.uri = mySO.data.documentsUri;
}
if( ! document.isPersistableUri ) document.getUriFromDir(true);
else onReadChildrens();
}
private function onRead(event:DocumentEvent):void
{
mySO.data.documentsUri = event.uri;
mySO.flush();
onReadChildrens();
}
private function onReadChildrens():void
{
var bitmapData:BitmapData = new Image().bitmapData;
var bytes:ByteArray = new ByteArray();
bitmapData.encode( bitmapData.rect, new PNGEncoderOptions(true), bytes );
var filename:String = "myImage.png";
var fileUri:String = document.childExists( filename );
if( ! fileUri )
{
trace( document.createBytesFromUri( filename, bytes ) );
}
else
{
document.uri = fileUri;
trace( document.editBytesFromUri( bytes ) );
}
}
}
}
[getUriFromDir()](http://pol2095.free.fr/Android-Storage-Access-Framework/docs/com/nativeExtensions/saf/Document.html#getUriFromDir()) work like a permission, if the user select documents directory once, the access is always granted to this directory for your app. The example included too how to replace the image.
Thank you both for the info, I'll take a look~
Just in case the option helps anyone, I ended up using distriqt's CameraRollExtended ANE to save the image to the gallery.
Great, I added [createBytesToGallery()](http://pol2095.free.fr/Android-Storage-Access-Framework/docs/com/nativeExtensions/saf/Document.html#createBytesToGallery()) but it doesn't use SAF, it work like flash.media.CameraRoll. What is the difference between flash.media.CameraRoll and distriqt's CameraRollExtended ANE ?
It all depends on your use case, SAF can access to SD card, USB key, SSD disk, SAMBA, Google Drive...
Is there a working example on how to gain access to the documents or images directory on android without an ANE for use in a file upload. There have been quite a few revisions since this was posted and trying my existing code in higher revisions still don't work on android but works on windows. The code shouldn't have to change when it goes to android, thats the idea of cross platform isn't it?
Attempting to use BrowseForOpen. On Android 12 and 11 I'm able to open the file picker successfully, but on Android 12, the Event.Select event is not being fired so there is no way to determine what file was selected. The same code works on Android 7 without issue, the Event.Select event returns to my listener. Does Android 12 have issues with permissions? ` var f:File = File.documentsDirectory.resolvePath("Documents"); f.addEventListener(Event.SELECT, fileSelected); f.requestPermission(); f.browseForOpen("Open");
function fileSelected(event:Event):void { trace("FileSelected") } `