Open gogins opened 10 months ago
Did Hunkins get his Chromebook into Developer Mode?
https://chromeos.dev/en/android-environment/developer-options
What I gather so far is that this problem arises from Google pushing almost all storage into the cloud (e.g. Google Drive). It may be that the developer options and/or the Android settings can be tweaked to provide access to the file system.
Is this related? https://issuetracker.google.com/issues/206884707?pli=1
Describes "side-loading:" https://www.xda-developers.com/how-sideload-apps-chromebook/.
Does Art Hunkins do side loading of the Csound for Android app? If so, does he use developer mode, or does he use ADB?
If with ADB, then in the terminal window, one can use adb to query for permissions and to grant permissions, e.g.
grant package_name permission | Grant a permission to an app. On devices running Android 6.0 (API level 23) and higher, the permission can be any permission declared in the app manifest. On devices running Android 5.1 (API level 22) and lower, must be an optional permission defined by the app.
Possibly, the Chromebook is overriding the app manifest's grant of all files access; it may be possible to restore all files access in this way.
I tried saving Music/xanadu.csd
as Music/xanadu.csound
; I could open it and save it from the app, but it wouldn't play.
Art: Focus on loading and saving from and to the Music folder.
From https://developer.android.com/training/data-storage/manage-all-files:
When an app has the MANAGE_EXTERNAL_STORAGE permission, it can access these additional files and directories using either the MediaStore API or direct file paths. When you use the Storage Access Framework, however, you can only access a file or directory if you can do so without having the MANAGE_EXTERNAL_STORAGE permission.
If this is the case with the Csound for Android app, that might well explain Art's problems.
The implication is that if I use the MediaStore API, access with MANAGE_EXTERNAL_STORAGE permission works for all MediaStore files that would work without MANAGE_EXTERNAL_STORAGE permission.
A number of questions arise:
This appears to be an attempt to deal with this situation: https://github.com/SNNafi/media_store_plus.
I have two physical devices:
This should help me clear this up.
Whoa! Maybe this will work for all required access within a subset of directories:
https://developer.android.com/reference/android/provider/MediaStore.Files
I'll hold off on trying this as it would complicate my life....
I just don't get Art's problems unless he hasn't actually granted permissions.
Notes for my zoom with Art....
Agenda:
Save as...
tries to save to the Chromebook filesystem. There's an option to save to My Files
but that includes only Downloads
that one can't read from.Possibly, I ignore the external storage directory in the app and start from as high up in the filesystem directory as I can.
Takeaways:
Got the Chromebook, set it up for myself, following instructions here.
It looks like there might be another way to do this, and that is to set up the Chromebook in Chrome developer mode, which allows direct setup of .apk files not from the Play Store. But for now I am continuing with the Linux-based side loading.
The request for permission to access all files did appear when I started the Csound for Androd app, as it does when installing on a phone, so that's not a cause of this problem.
I ran Csound Koan I, and saved it to Downloads as i.csd
.
I quit the app, started it again, and loaded i.csd
. But it would not run.
I then saved i.csd
to Music, and opened it from Play files, Music. It would not run.
I have found a workaround (not a solution, a workaround).
After you save or create a new piece, use the Files app on the Chromebook to copy that file to the Linux files
directory. You can then open that file from the Csound for Android app, and it will play.
It's clear that, in spite of enabling "All files" permissions in ChromeOS, that permission is not actually granted, and the app is continuing to use the MediaStore system for reading and writing files, which is what is causing these problems. I suppose the MediaStore system knows nothing about the Linux files
directory, and that's why this workaround works.
I will see if the permission can be granted using adb, or if there's some change I can make in the Csound for Android app's code to enable access to Linux files
.
In the CsoundApplication manifest I changed
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
to
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" tools:ignore="ScopedStorage" />
However, this did not help.
When I open a Linux terminal and run the adb shell, I can't find android.permission.MANAGE_EXTERNAL_STORAGE
. That's interesting!
I issued the command in the adb shell: pm grant com.csounds.Csound6.CsoundAppActivity android.permission.MANAGE_EXTERNAL_STORAGE
and it appeared to complete without error.
However the permission is still restricted.
Here at last an answer (in the adb shell):
pm grant com.csounds.Csound6 android.permission.MANAGE_EXTERNAL_STORAGE
Exception occurred while executing 'grant':
java.lang.SecurityException: Permission android.permission.MANAGE_EXTERNAL_STORAGE requested by com.csounds.Csound6 is not a changeable permission type
at com.android.server.pm.permission.BasePermission.enforceDeclaredUsedAndRuntimeOrDevelopment(BasePermission.java:453)
at com.android.server.pm.permission.PermissionManagerService.grantRuntimePermissionInternal(PermissionManagerService.java:1458)
at com.android.server.pm.permission.PermissionManagerService.grantRuntimePermission(PermissionManagerService.java:1411)
at com.android.server.pm.PackageManagerShellCommand.runGrantRevokePermission(PackageManagerShellCommand.java:2300)
at com.android.server.pm.PackageManagerShellCommand.onCommand(PackageManagerShellCommand.java:251)
at android.os.BasicShellCommandHandler.exec(BasicShellCommandHandler.java:98)
at android.os.ShellCommand.exec(ShellCommand.java:44)
at com.android.server.pm.PackageManagerService.onShellCommand(PackageManagerService.java:21831)
at android.os.Binder.shellCommand(Binder.java:929)
at android.os.Binder.onTransact(Binder.java:813)
at android.content.pm.IPackageManager$Stub.onTransact(IPackageManager.java:4668)
at com.android.server.pm.PackageManagerService.onTransact(PackageManagerService.java:4325)
at android.os.Binder.execTransactInternal(Binder.java:1159)
at android.os.Binder.execTransact(Binder.java:1123)
255|nautilus_cheets:/ $
Art Hunkins tried developer mode for the device, I tried the Linux development environment on the device, and the results appear to be the same.
So, there seems to be a difference between Android running on a Chromebook, and Android running a whole device, with respect to this permission.
Right now, there is no way to do what Art wants except the workaround I discovered: Save a new piece to My Files
, use the Files app to copy the piece to Linux files
, open the piece from Linux files
, and it will run.
I will try chasing this a bit further.
Linux files
.It turns out that any pieces that are copied to, saved into, and loaded from the My Files, Play files, Documents directory will play. Not so, they open as Audio:nnn
.
I believe that this is caused by the Csound for Android app using the MediaStore system to load and save files. The MediaStore system allocates different types of files to different directories. Not so.
If this holds up, then I will try removing the All Files permission from the app, and re-installing it on various devices. If it's still possible to load and save pieces from the Documents directory, I can re-submit the app to the Google Play store. Not yet!
However: As Art said, Save as... to one of the accessible directories will result in a playable piece in the app.
I'll have to read the code again....
To do:
https://medium.com/@wanxiao1994/android-storage-permission-adaptation-and-reading-writing-media-files-6b85121c9461 https://developer.android.com/training/data-storage/shared/media This is the Bible here: https://developer.android.com/about/versions/11/privacy/storage and https://developer.android.com/training/data-storage/use-cases
I think that Google has all apps from Android 11 on actually contact Google to find out if an app has MANAGE_EXTERNAL_STORAGE permission (or maybe there's a magic number in the .apk or something). That means I can't rely on granting this permission to work. Right now, doing so works on my phone and tablet, but not on my Chromebook.
My app uses the Storage Access Framework but, for current Android, this can't provide enough scope for storage.
This is key:
When an app has the MANAGE_EXTERNAL_STORAGE permission, it can access these additional files and directories using either the MediaStore API or direct file paths. When you use the Storage Access Framework, however, you can only access a file or directory if you can do so without having the MANAGE_EXTERNAL_STORAGE permission.
This is an unbelievable mess.
I'm throwing in the towel. I will re-target the maximum SDK level to 29, which is the highest level that still supports our desired behavior. That means the app can't get into the Play Store, but it should work on Chromebooks, phones, and tablets. I'm trying this now. But this move will not work from Android 14 on.
If there's a way forward after Android 14, either Google will introduce new policies/APIs, or I will have to try to make MediaStore.Files work for all needs.
Guess what! Android Studio won't let me set the maximum SDK level to 29.
Back to square one. I have installed Linux and enabled adb debugging on the Chromebook, and created an adb connection to my MacBook. Android Studio can now debug an app on the Chromebook. And look what I get for the file URI:
content://com.android.providers.media.documents/document/audio%3A371
I wonder if I get the filepath from this URI, if I can then go ahead and open the file as a .csd. No surprise, doesn't look that easy. But, what's the URI on my phone? Well, a similar URI, a .csd file in Documents anyway:
content://com.android.externalstorage.documents/document/primary%3Ai.saved.csd
.
The first has a provider android.providers.media.documents
and the second has android.externalstorage.documents
. And I think the first form is caused by MANAGE_EXTERNAL_STORAGE not being enable-able on the Chromebook.
So, Art's workaround works because Save as... creates a file URI. I don't think that there is a way to do this programmatically that would not already involve getting the filepath from the content URI, and I suspect that's not possible.
It's possible to root the Android system on a Chromebook perhaps, see https://www.xda-developers.com/root-android-subsystem-chromebooks/.
I've put my Chromebook into developer mode, and installed the Linux shell. To debug an Android app running on the Chromebook from my MacBook:
michaelgogins@Michaels-MacBook-Pro ~/csound-android % ~/Library/Android/sdk/platform-tools/adb connect 192.168.201.101
The Chromebook should then appear as a debuggable device in Android Studio on the MacBook.
I will try MediaStore.getDocumentUri
on all of my devices and compare results and permissions.
content://com.android.externalstorage.documents/document/primary%3Acsd%2Fsaved.i.csd
Uri yields anything usable as a filepath./external_primary/file/20967
.When the piece is an HTML file, then without a real filepath, it doesn't seem possible to derive a base URL.
For a .csd piece, it's a bit mysterious now for me how this works.
Possibly, I could refactor the code to render pieces only in internal storage, but then how would a user get a rendered soundfile out of the device? Or get samples into the device?
So, let's see what happens to the Uri when I do the Save as... workaround on the Chromebook.
I think this means the following things:
MANAGE_EXTERNAL_STORAGE
on the Chromebook, we may be stuck with Art's workaround.MANAGE_EXTERNAL_STORAGE
permission by refactoring the code may be impossible, and if not impossible, would require a lot of new work.On the Chromebook, the URI for rendering a .csd file is e.g.: /document/audio:228
. The URI for rendering an .html file is e.g.: /document/document:332
. So, there is a way to tell if a piece is a .csd piece (/document/audio:
) or an .html piece (/document/document
).
However, the .html pieces run into this problem:
2023-11-12 18:37:19.916 767-1290 MediaProvider com.android.providers.media.module W Using FUSE for /storage/emulated/0/Documents/Gogins/minimal.html
2023-11-12 18:37:19.992 4971-4971 Csound: com.csounds.Csound6 D baseUrlString: file:/storage/emulated/0/
This causes the base URL for the .html page to be incorrect, so required resources are not loaded and there is an exception in running the piece. In the above, the base URL really should be something like /storage/emulated/0/Documents/Gogins
.
I have pushed a partial solution for Chromebooks, Csound will now run off a content URI. Still not clear if .html pieces can be gotten to work.
What does the base URL look like on my smartphone? For Scrims:
baseUrlString: file:/storage/emulated/0/examples/Gogins/
This might be relevant: https://android-developers.googleblog.com/2023/11/increasing-trust-for-embedded-media.html
.
I don't currently see a solution to the base URL problem. The whole point of the content URI idea is to make a flat table of resources, and the whole point of a Web server/Web browser system is to root a hierarchical tree of resources at the base URL.
The Save as... workaround does not work for .html pieces, for the same reason.
Possible solution to .html pieces on Chromebooks: Add a Setting field for the base URL.
Android 13 on phone loading example:
2023-11-14 09:24:59.292 31888-31888 System.err com.csounds.Csound6 W at com.csounds.Csound6.CsoundAppActivity.getFilePathFromContentUri(CsoundAppActivity.java:645)
2023-11-14 09:25:05.639 31888-31888 Csound com.csounds.Csound6 D getFilePathFromContentUri: filepath:
2023-11-14 09:25:54.285 31888-31888 Csound com.csounds.Csound6 D uriToFilepath: filepath: /root/storage/emulated/0/Music/examples/Gogins/scrims.html
2023-11-14 09:26:14.022 31888-31888 Csound com.csounds.Csound6 D baseUrlString from filepath: file:/storage/emulated/0/Music/examples/Gogins
Loading from storage:
2023-11-14 09:30:06.620 31888-31888 Csound com.csounds.Csound6 D getFilePathFromContentUri: filepath: primary:Music/Csound6AndroidExamples/Gogins/Message.html
2023-11-14 09:30:06.621 31888-31888 Csound com.csounds.Csound6 D uriToFilepath: filepath: /storage/emulated/0/Music/Csound6AndroidExamples/Gogins/Message.html
2023-11-14 09:30:06.622 31888-31888 Csound com.csounds.Csound6 D baseUrlString from filepath: file:/storage/emulated/0/Music/Csound6AndroidExamples/Gogins/
Android 10 on tablet loading example:
2023-11-14 09:39:32.247 25682-25682 System.err com.csounds.Csound6 W at com.csounds.Csound6.CsoundAppActivity.getFilePathFromContentUri(CsoundAppActivity.java:645)
2023-11-14 09:39:32.278 25682-25682 Csound com.csounds.Csound6 D getFilePathFromContentUri: filepath:
2023-11-14 09:39:32.278 25682-25682 Csound com.csounds.Csound6 D uriToFilepath: filepath: /root/storage/emulated/0/Music/examples/Gogins/message.html
2023-11-14 09:39:32.289 25682-25682 Csound com.csounds.Csound6 D baseUrlString from filepath: file:/storage/emulated/0/Music/examples/Gogins
Loading from from storage:
2023-11-14 09:40:46.836 25682-25682 Csound com.csounds.Csound6 D getFilePathFromContentUri: filepath: primary:Music/examples/Gogins/message.html
2023-11-14 09:40:46.838 25682-25682 Csound com.csounds.Csound6 D uriToFilepath: filepath: /storage/emulated/0/Music/examples/Gogins/message.html
2023-11-14 09:40:46.841 25682-25682 Csound com.csounds.Csound6 D baseUrlString from filepath: file:/storage/emulated/0/Music/examples/Gogins/
Android 11 on Chromebook loading example:
2023-11-14 09:44:52.009 11857-11857 System.err com.csounds.Csound6 W at com.csounds.Csound6.CsoundAppActivity.getFilePathFromContentUri(CsoundAppActivity.java:645)
2023-11-14 09:44:52.061 11857-11857 Csound com.csounds.Csound6 D getFilePathFromContentUri: filepath:
2023-11-14 09:44:52.062 11857-11857 Csound com.csounds.Csound6 D uriToFilepath: filepath: /root/storage/emulated/0/Music/examples/Gogins/message.html
2023-11-14 09:44:52.088 11857-11857 Csound com.csounds.Csound6 D baseUrlString from filepath: file:/storage/emulated/0/Music/examples/Gogins
Loading from storage:
2023-11-14 09:46:41.390 11857-11857 Csound com.csounds.Csound6 D getFilePathFromContentUri: filepath: document:135
2023-11-14 09:46:41.393 11857-11857 Csound com.csounds.Csound6 D uriToFilepath: filepath: /storage/emulated/0/135
2023-11-14 09:46:41.397 11857-11857 Csound com.csounds.Csound6 D baseUrlString from filepath: file:/storage/emulated/0/
On the Chromebook, running the HTML examples works. But running the same example that is loaded from storage does not work (though it does on the other devices). This appears to be caused by a truncated base URL.
I'm stepping through the loadWebView function and examining all URIs, URLs, and filepaths to see if something is hiding in there that I did not notice before. No help.
I'm stepping through the code that loads the sample, my assumption is that it does the equivalent of Save as... First, the piece is copied from the assets directory using a complete filepath built by adding the asset filepath to the external storage directory path. Then, the app loads the piece as a text file in the same way as for all other pieces. After that, the app creates the URI for the piece thusly:
csound_uri = FileProvider.getUriForFile(getApplicationContext(), BuildConfig.APPLICATION_ID, file);
Here, csound_uri
has the complete and correct filepath /root/storage/emulated/0/Music/examples/Gogins/scrims.html
.
Here we go again: https://developer.android.com/reference/androidx/core/content/FileProvider.
As far as I know, I have done these things. But they're complex enough that I might have made a mistake.
I have not been able to figure out how to get the base URL from a content URI other than the one available after copying an asset into the examples directory.
Google's content URI idea is fine for loading and saving files, copying files, and sharing files, but it is not designed for files that need to know about other files or filepaths, which a Web browser does need to know.
Review of the code, stepping through the code, review of Android documentation, and googling forums has not told me how to do this.
One of these things might work and would be acceptable:
Bottom line: without MANAGE_EXTERNAL_FILES permission on Android 11 or later, I seem to absolutely need one of the following:
I tried denying the All Files permission on my chromebook, then granting it again. No help.
I did get the "display id" out of the ContextResolver, which is the filename, i.e. the title of the piece.
I think there is only one possible solution here, and that is to set up a special directory in a known location, then put all files in there in a flat directory structure, and simply stick the display id onto the path of that special directory to get the filepath.
This from Art Hunkins: