libgphoto2
libgphoto2 copied to clipboard
Support for non-rooted Android devices
Hi @msmeissn @ndim !
The current implementation of the IO library usb1 supports only rooted Android devices. Means we can build gphoto2, libgphoto2, libgphoto2_port, camlibs, iolibs for Android without problems, but it will work only on rooted Android devices! Problem is similar to when we want to use any USB device in Linux without udev. It works only when process trying to access a USB device runs as root. However on Android the situation is bit different, because there is no udev!
On Android the application (written in Java or Kotlin language) when detects an attached USB device, might ask a permission for it. This happens in form of a popup dialog (managed by the Android OS) and when one chooses "Yes", permission is granted on the device path (for example /dev/bus/usb/001/002) for the running application.
Once the permission is granted on USB device (dev_path), the application can open the device and get its native file descriptor (FD)! At this point might come our beloved native library - libgphoto2: We should somehow pass the already available native FD through libgphoto2_port->usb1 to libusb1.0. Fortunately the latest 2021-june, v1.0.24 of libusb1.0 has builtin support for passing native FD to the library functions.
Initialization step for Android in libusb1.0 is following:
set_the_native_Descriptor(int fileDescriptor) {
libusb_context *ctx;
libusb_device_handle *devh;
libusb_set_option(&ctx, LIBUSB_OPTION_WEAK_AUTHORITY, NULL); // important for Android
libusb_init(&ctx);
libusb_wrap_sys_device(NULL, (intptr_t)fileDescriptor, &devh);
// From this point you can regulary use all LibUsb functions as usual.
}
How to distinguish in code, when we are on Android? That's easy:
#ifdef __ANDROID__
More info about the theory and libusb1.0 new feature:
- https://developer.android.com/guide/topics/connectivity/usb/host
- https://github.com/libusb/libusb/wiki/Android
It would be great to somehow implement such support in either libgphoto2(_port) at the gp_camera_init function and/or in the IOLIB:usb1. Once it is implemented, we can build nice gphoto2 GUI applications for Android devices.
BR,
Ladislav
Please submit a pull request.
Please submit a pull request.
I really would like to do that but unfortunately I am not fully familiar (yet) with the current libgphoto2 code and it's not 100% clear for me, where and what to change.
@msmeissn @ndim @hfiguiere Hi!
Are there any news on this from your side?
BR,
Ladislav
Are there any news on this from your side?
Did we miss your pull request?
Are there any news on this from your side?
Did we miss your pull request?
This is more like a feature request, since I really don't know, what and where exactly to change in the existing code. That's why also the opening post.
I was able to make some progress and identify the needed changes to libgphoto2:
- Set the option LIBUSB_OPTION_NO_DEVICE_DISCOVERY before calling libusb_init
- Call libusb_wrap_sys_device using a provided file descriptor instead of libusb_open
The second change is a bit challenging. libgphoto2's USB port driver in a couple of places calls load_devicelist which creates the structure that includes things like the device descriptor. But since we cannot use device discovery due to Android USB permissions, that just returns an empty list. What worked was creating a "fake" device list with a single entry if a USB file descriptor is given. When constructing the entry, we can call libusb_wrap_sys_device to get the device, and libusb_get_device_descriptor to get the device descriptor.
I have a commit that illustrates the above changes: https://github.com/fahrion2600/libgphoto2/commit/1e2b170ec176bc9c2dacfdffd09ce91e597eee8d
It's not ready for a PR yet, since the way I am passing in the file descriptor is not ideal (environment variable). It should ideally be passed in via the port string (something like "usbfd:123"), I think.
Hope this information helps someone!
@fahrion2600
Good idea, I mean passing FD as an environment variable. Even better is the port string. Hope it will work!
BTW, what about keeping the original USB port driver untouched and creating a new USB/Android port driver?
BTW, what about keeping the original USB port driver untouched and creating a new USB/Android port driver?
Certainly possible, although I would have to learn more about the build process before attempting to do so :sweat_smile: I'm not sure how to get the USB path (to get the FD) in all the places, so a separate driver might be a way around that.
Right now, the environment variable method is good enough for me as I work on the Android app side. I have a few notes that might save someone some time:
- Bundling the libgphoto2/gphoto2 binaries is challenging. You can't zip them up and extract them somewhere because newer Android versions prevent running executables from most places. Instead, you have to bundle them in app/jniLibs/[architecture] and use android:extractNativeLibs="true" to have the OS extract it into the app's native library directory.
- Even then, subdirectories are not supported. I tested and you can throw libgphoto2.so, camlibs, iolibs into the same native libs directory, set IOLIBS and CAMLIBS to point to it, and it will work (albeit with symbol warnings).
- Only .so files get bundled, so you have to rename gphoto2 to gphoto2.so. You can successfully exec this from the Android app. But that won't work if you want to use USB, because...
- File descriptors are not inherited with Runtime.exec, so you can't pass a FD to gphoto2. The only option is to call libgphoto2 functions via JNI/JNA.
- HOME is unset, so the settings file is not read properly. You can set it to the app's files directory and precreate the settings file.
As you can see, there are a lot of pitfalls to avoid :sweat_smile: The code is still very unfinished, but I finally got it to the point where it can do a very simple trigger capture over USB without root (phone itself is rooted, but not the app).
@fahrion2600
Wait, what do you mean by HOME is not set? The $HOME enironment variable? This one always worked for me. I am unzipping all the binaries including .so files (without renaming them) from assets to the $HOME/whateverfolder location and just setting permissions on executables. Tested on Android 9/10/11. It works. I can call any native executable from my Android test app.
Yes, for some reason the $HOME environment variable was not set when running my app. Doesn't matter too much to me, as it's just an extra call to programmatically set it.
Regarding extracting and running from $HOME, I previously tried that, but it didn't work on my phone (Pixel 2 running Android 11). I believe this is the cause: https://developer.android.com/about/versions/10/behavior-changes-10#execute-permission
Based on the above link, I guess the difference in behavior could have to do with the target API version of the app.
i think its best to use the existing libusb1 driver, we can surely keep the change limited.
environment variable or port string ... usually the port string is used, e.g. we can use something like usb:fd=X