serialport / serialport-rs

A cross-platform serial port library in Rust. Provides a blocking I/O interface and port enumeration including USB device information.
Other
496 stars 120 forks source link

Opening Port on Android "Error opening port Permission denied" #42

Open glenngartner opened 2 years ago

glenngartner commented 2 years ago

I'm using this lib on Android, when when I pass a port name from Java to this lib, I receive "Error opening port Permission denied". Just wondering to what degree Android is supported (I know device enumeration isn't, that's why I'm passing available devices from the Java layer). Some specifics:

Android SDK / NDK targets:

    defaultConfig {
        applicationId "..."
        minSdk 27
        targetSdk 30
        versionCode 1
        versionName "1.0"
        ndkVersion = "23.1.7779620"
    }

Android Manifest, giving access to certain devices

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="xxx.xxx.xxx">
    <uses-feature android:name =" android.hardware.usb.host"/>
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/xxx">
        <activity
            android:name=".MainActivity"
            android:exported="true"
            android:directBootAware="true"
            >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
            <intent-filter>
                <action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
            </intent-filter>
            <meta-data
                android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
                android:resource="@xml/device_filter" />
        </activity>
    </application>

</manifest>

Device filters, which when connected, prompt the user for permission to access the particular USB device (which, in my case, is a 232 to USB FTDI chip (vendorId: 0403, Product Id: 6001):

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <!-- 0x0403 / 0x60??: FTDI -->
    <usb-device vendor-id="1027" product-id="24577" /> <!-- 0x6001: FT232R -->
    <usb-device vendor-id="1027" product-id="24592" /> <!-- 0x6010: FT2232H -->
    <usb-device vendor-id="1027" product-id="24593" /> <!-- 0x6011: FT4232H -->
    <usb-device vendor-id="1027" product-id="24596" /> <!-- 0x6014: FT232H -->
    <usb-device vendor-id="1027" product-id="24597" /> <!-- 0x6015: FT230X, FT231X, FT234XD -->

    <!-- 0x10C4 / 0xEA??: Silabs CP210x -->
    <usb-device vendor-id="4292" product-id="60000" /> <!-- 0xea60: CP2102 and other CP210x single port devices -->
    <usb-device vendor-id="4292" product-id="60016" /> <!-- 0xea70: CP2105 -->
    <usb-device vendor-id="4292" product-id="60017" /> <!-- 0xea71: CP2108 -->

    <!-- 0x067B / 0x23?3: Prolific PL2303x -->
    <usb-device vendor-id="1659" product-id="8963" /> <!-- 0x2303: PL2303HX, HXD, TA, ... -->
    <usb-device vendor-id="1659" product-id="9123" /> <!-- 0x23a3: PL2303GC -->
    <usb-device vendor-id="1659" product-id="9139" /> <!-- 0x23b3: PL2303GB -->
    <usb-device vendor-id="1659" product-id="9155" /> <!-- 0x23c3: PL2303GT -->
    <usb-device vendor-id="1659" product-id="9171" /> <!-- 0x23d3: PL2303GL -->
    <usb-device vendor-id="1659" product-id="9187" /> <!-- 0x23e3: PL2303GE -->
    <usb-device vendor-id="1659" product-id="9203" /> <!-- 0x23f3: PL2303GS -->

    <!-- 0x1a86 / 0x?523: Qinheng CH34x -->
    <usb-device vendor-id="6790" product-id="21795" /> <!-- 0x5523: CH341A -->
    <usb-device vendor-id="6790" product-id="29987" /> <!-- 0x7523: CH340 -->

    <!-- CDC driver -->
    <usb-device vendor-id="9025" />                   <!-- 0x2341 / ......: Arduino -->
    <usb-device vendor-id="5824" product-id="1155" /> <!-- 0x16C0 / 0x0483: Teensyduino  -->
    <usb-device vendor-id="1003" product-id="8260" /> <!-- 0x03EB / 0x2044: Atmel Lufa -->
    <usb-device vendor-id="7855" product-id="4"    /> <!-- 0x1eaf / 0x0004: Leaflabs Maple -->
    <usb-device vendor-id="3368" product-id="516"  /> <!-- 0x0d28 / 0x0204: ARM mbed -->
    <usb-device vendor-id="1155" product-id="22336" /><!-- 0x0483 / 0x5740: ST CDC -->
    <usb-device vendor-id="11914" product-id="5"   /> <!-- 0x2E8A: Raspberry Pi Pico Micropython -->
</resources>

When the device is connected, Android OS prompts me to open the device in my app. When it's first connected, it asks me for permission to access it. That permission is granted, saved, and seems to persist (see below):

When app starts MainActivity.onCreate()

or when a device is connected, the user is prompted for permission to access it. The user gives permission. If I enumerate the devices in Java, with something like this, and in this enumeration, I check if the USB service / manager has permission to open the device (it does), so I proceed to open it via java, then rust lib. I've thought to myself, well, what if I don't open the device in Java, but only in rust, and perhaps that's why I don't have permission? Nope. Whether I open the device in java, or not, I'm still not permitted to access the device from rust.

 val usbManager: UsbManager = getSystemService(Context.USB_SERVICE) as UsbManager
        for ((path, device) in usbManager.deviceList) {
            Log.w("SERIALPORT::", "Found device at: $path")
            if (usbManager.hasPermission(device)) { // <-- app has permission to access device, here
                Log.w("SERIALPORT::", "USB Manager has permission to open device")
                val connection = usbManager.openDevice(device);
                val endpt = device.getInterface(0).getEndpoint(0);
                connection.claimInterface(device.getInterface(0), true);
                StringFromJNI(path)
            } else {
                Log.w("SERIALPORT::", "USB Manager does not have permission to open device $device")
            }
        }

simple rust lib code

Finally, the rust function that receives the string from java, prints some helpful logs, to prove the communication between layers is functional (because debugging is a bit of a pain), and attempts to open the port_name provided. When this code is run from a Linux machine, the device is opened just fine. When opened from an Android app, I don't have permissions to open the port. In all cases, with just 1 usb devices connected to the Android phone, the path is "/dev/bus/usb/002/002", FWIW

#[no_mangle]
pub extern "system" fn Java_com_sevenTowers_sensor_1android_1native_MainActivity_00024Companion_StringFromJNI(env: JNIEnv, class: JClass, input: JString) -> jstring {
    main();
    debug!("Rust heard a message");
    let input: String = env.get_string(input).expect("Couldn't get java string").into();
    let output = env.new_string(format!("Rust received: {}", input)).expect("Couldn't get Java string");
    let port = sensor_lib::sensors::open_port_by_name(input.as_str());
    match port {
        Some(ttyp_port) => debug!("TTY Port opened {:?}", ttyp_port),
        None => error!("TTY Port not opened")
    }
    output.into_inner()
}
// in mod sensor_lib::sensors
    pub fn open_port_by_name(port_name: &str) -> Option<TTYPort> {
        debug!("Opening port: {}", port_name);
        let port = serialport::new(port_name, 9600);
        let result = TTYPort::open(&port);
        match result {
            Ok(tty_port) => {
                debug!("Opened COM port {}", port_name);
                Some(tty_port)
            }
            Err(why) => {
                error!("Error opening port {}", why);
                None
            }
        }
    }

AVC prompts

Sometimes (but not every time) I'll see LogCat print a AVC permissions issue, like this:

type=1400 audit(0.0:2315): avc: denied { search } for name="usb" dev="tmpfs" ino=608 scontext=u:r:untrusted_app:s0:c236,c256,c512,c768 tcontext=u:object_r:usb_device:s0 tclass=dir permissive=0 app=com.sevenTowers.sensor_android_native

SELinux permissions

I understand, from Google knowledge, that the above comes from the SELinux layer, basically saying I don't have permission to access this device. My USB Service says I have permission to access to the USB, but perhaps the path for the usb is permitted, and the serial protocol requires something different. This is where I'm unsure.

My question:

Does my android device have to be rooted for this library to work? It's not, currently. I'd prefer it not have to be. I know there are java libraries for accessing serial devices on Android specifically (like here https://github.com/mik3y/usb-serial-for-android), because device drivers on stock (unrooted) Android need to be provided on the app, because they're not provided by the OS. I can integrate an entirely different serial lib for Android, and abstract the parsing of each sensor in rust by passing buffers from the above lib into rust, but I'd prefer to use one lib across devices, if possible.

I've seen the clever folks over at libusb resolve this issue for unrooted Android devices with instructions like this: https://github.com/libusb/libusb/blob/master/android/README (if you're still reading, it begins at line 35) where they request permission at the Java layer, and pass the fileDescriptor from java to the libusb native code. I'm not that familiar with this level of detail yet, but perhaps there's something to it. Or, perhaps that's just a USB interop thing, not related to serial devices. This is where I can use some help.

My goal: open a serial port from Android using this lib, parse and handle all the data in rust, as I'm doing with my desktop apps, without having to write too much more interop in Java, so this works on android.

Thanks!

AlonSpinner commented 4 months ago

Here for the same issue!

have you made any progress @jessebraham ?

dgramop commented 1 month ago

My understanding is that serial ports, as we know them from UNIX, are not available to Android apps (at this time). However USB is available if you get permissions and pass the file descriptor down into native code.

The reason the Kotlin drivers work is because they're really USB Serial drivers implemented in userspace, since the kernelspace drivers expose /dev/ serial ports (apps don't have permissions to these).

To get access to USB serial ports on an unrooted android, we'd need a rust implementation of USB serial in userspace. This would not give you access to native serial ports on android however, for example if you have some direct serial interface on inside your android phone.

Enumerating USB devices (and therefore USB serial ports) would still not be possible from Rust, since you must use android's APIs to do this.