Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 84c001dc authored by Febin Thattil's avatar Febin Thattil
Browse files

[AOA FFS Implementation] Added support for streaming APIs

Adds native callbacks for streaming operations. This will enable the USB
Accessory mode to work seamlessly for the user, without relying on the
kernel driver, when using new streaming APIs.

Adds ZLP implementation as well.

Bug: 407985367
Bug: 422766393
Flag: android.hardware.usb.flags.enable_aoa_userspace_implementation
Test: Tested by manually connecting to a usb device in accessory mode, tested with the CTS Verifier app.
Change-Id: Ifc91e599e3e6310a65999121d40bec5b07c3a122
parent f6613ab4
Loading
Loading
Loading
Loading
+13 −0
Original line number Diff line number Diff line
@@ -47,6 +47,19 @@ interface IUsbManager
     */
    ParcelFileDescriptor openAccessory(in UsbAccessory accessory);

    /* Returns a file descriptor for reading from the USB accessory.
     * This file descriptor can be used with standard Java file operations.
     */
    ParcelFileDescriptor openAccessoryForInputStream(in UsbAccessory accessory);

    /* Returns a file descriptor for writing to the USB accessory.
     * This file descriptor can be used with standard Java file operations.
     */
    ParcelFileDescriptor openAccessoryForOutputStream(in UsbAccessory accessory);

    /* Returns the max packet size of the USB accessory.*/
    int getMaxPacketSize(in UsbAccessory accessory);

    /* Sets the default package for a USB device
     * (or clears it if the package name is null)
     */
+61 −33
Original line number Diff line number Diff line
@@ -48,6 +48,8 @@ import android.hardware.usb.gadget.UsbSpeed;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
import android.system.ErrnoException;
import android.system.Os;
import android.os.ParcelFileDescriptor;
import android.os.Process;
import android.os.RemoteException;
@@ -60,6 +62,7 @@ import com.android.internal.annotations.GuardedBy;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InterruptedIOException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
@@ -959,35 +962,9 @@ public class UsbManager {
            /* TODO(b/377850642) : Ensure the stream is closed even if client does not
            explicitly close the stream to avoid corrupt FDs*/
            super.close();
            if (!android.hardware.usb.flags.Flags.enableAoaUserspaceImplementation()) {
                closeHandleForAccessory(mAccessory, true);
            }


        @Override
        public int read() throws IOException {
            final int result = super.read();
            checkError(result);
            return result;
        }

        @Override
        public int read(byte[] b) throws IOException {
            final int result = super.read(b);
            checkError(result);
            return result;
        }

        @Override
        public int read(byte[] b, int off, int len) throws IOException {
            final int result = super.read(b, off, len);
            checkError(result);
            return result;
        }

        private void checkError(int result) throws IOException {
            if (result == -1 && mPfd.canDetectErrors()) {
                mPfd.checkError();
            }
        }
    }

@@ -998,10 +975,35 @@ public class UsbManager {
     */
    private class AccessoryAutoCloseOutputStream extends FileOutputStream {
        private final UsbAccessory mAccessory;
        private final int mMaxPacketSize;
        private final ParcelFileDescriptor mPfd;

        AccessoryAutoCloseOutputStream(
                UsbAccessory accessory, ParcelFileDescriptor pfd, int maxPacketSize) {
            super(pfd.getFileDescriptor());
            mMaxPacketSize = maxPacketSize;
            mAccessory = accessory;
            mPfd = pfd;
        }

        AccessoryAutoCloseOutputStream(UsbAccessory accessory, ParcelFileDescriptor pfd) {
            super(pfd.getFileDescriptor());
            mMaxPacketSize = -1;
            mAccessory = accessory;
            mPfd = pfd;
        }

        @Override
        public void write(byte[] b, int off, int len) throws IOException {
            super.write(b, off, len);

            if (!android.hardware.usb.flags.Flags.enableAoaUserspaceImplementation()) {
                return;
            }
            // Check if a ZLP is needed for this specific write operation
            if (len > 0 && (len % mMaxPacketSize == 0)) {
                sendZlp();
            }
        }

        @Override
@@ -1009,10 +1011,26 @@ public class UsbManager {
            /* TODO(b/377850642) : Ensure the stream is closed even if client does not
            explicitly close the stream to avoid corrupt FDs*/
            super.close();
            if (!android.hardware.usb.flags.Flags.enableAoaUserspaceImplementation()) {
                closeHandleForAccessory(mAccessory, false);
            }
        }

        /** Sends a Zero-Length Packet. This is done by writing a 0-byte array. */
        private void sendZlp() {
            byte[] emptyBuffer = new byte[0]; // Or any buffer, as count will be 0
            // This should make a write(2) syscall with count = 0
            try {
                // TODO: febinthattil - Try this with native code
                Os.write(mPfd.getFileDescriptor(), emptyBuffer, 0, 0);
            } catch (ErrnoException e) {
                Log.e(TAG, "ZLP failed to send.", e);
            } catch (InterruptedIOException e) {
                Log.e(TAG, "ZLP failed to send.", e);
            }
        }
    }

    /**
     * Holds file descriptor and marks whether input and output streams have been opened for it.
     */
@@ -1179,8 +1197,12 @@ public class UsbManager {
    @RequiresFeature(PackageManager.FEATURE_USB_ACCESSORY)
    public @NonNull InputStream openAccessoryInputStream(@NonNull UsbAccessory accessory) {
        try {
            return new AccessoryAutoCloseInputStream(accessory,
                    openHandleForAccessory(accessory, true).getPfd());
            if (android.hardware.usb.flags.Flags.enableAoaUserspaceImplementation()) {
                return new AccessoryAutoCloseInputStream(
                        accessory, mService.openAccessoryForInputStream(accessory));
            }
            return new AccessoryAutoCloseInputStream(
                    accessory, openHandleForAccessory(accessory, true).getPfd());

        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
@@ -1199,6 +1221,12 @@ public class UsbManager {
    @RequiresFeature(PackageManager.FEATURE_USB_ACCESSORY)
    public @NonNull OutputStream openAccessoryOutputStream(@NonNull UsbAccessory accessory) {
        try {
            if (android.hardware.usb.flags.Flags.enableAoaUserspaceImplementation()) {
                return new AccessoryAutoCloseOutputStream(
                        accessory,
                        mService.openAccessoryForOutputStream(accessory),
                        mService.getMaxPacketSize(accessory));
            }
            return new AccessoryAutoCloseOutputStream(accessory,
                    openHandleForAccessory(accessory, false).getPfd());
        } catch (RemoteException e) {
+61 −0
Original line number Diff line number Diff line
@@ -42,6 +42,8 @@
#define FFS_VENDOR_CTRL_REQUEST_EP0 "/dev/usb-ffs/ctrl/ep0"

#define FFS_ACCESSORY_EP0 "/dev/usb-ffs/aoa/ep0"
#define FFS_ACCESSORY_EP1 "/dev/usb-ffs/aoa/ep1"
#define FFS_ACCESSORY_EP2 "/dev/usb-ffs/aoa/ep2"

namespace {
struct func_desc {
@@ -669,6 +671,11 @@ public:
        return mAccessoryFields.maxPacketSize;
    }

    void setMaxPacketSize(int maxPacketSize) {
        std::lock_guard<std::mutex> lock(mAccessoryFieldsMutex);
        mAccessoryFields.maxPacketSize = maxPacketSize;
    }

    ~NativeVendorControlRequestMonitorThread() {
        stop();
        close(mShutdownPipefd[0]);
@@ -705,6 +712,16 @@ static void set_accessory_string_from_ffs(JNIEnv *env, jobjectArray strArray, in
    }
}

static int get_max_packet_size(int ffs_fd) {
    struct usb_endpoint_descriptor desc{};
    if (ioctl(ffs_fd, FUNCTIONFS_ENDPOINT_DESC, reinterpret_cast<unsigned long>(&desc))) {
        ALOGE("Could not get FFS bulk-in descriptor");
        return 512;
    } else {
        return desc.wMaxPacketSize;
    }
}

static jobjectArray android_server_UsbDeviceManager_getAccessoryStrings(JNIEnv *env,
                                                                        jobject /* thiz */)
{
@@ -743,6 +760,11 @@ static jobjectArray android_server_UsbDeviceManager_getAccessoryStringsFromFfs(J
    return strArray;
}

static jint android_server_UsbDeviceManager_getMaxPacketSize(JNIEnv * /* env */,
                                                             jobject /* thiz */) {
    return static_cast<jint>(sVendorControlRequestMonitorThread->getMaxPacketSize());
}

static jobject android_server_UsbDeviceManager_openAccessory(JNIEnv *env, jobject /* thiz */)
{
    int fd = open(DRIVER_NAME, O_RDWR);
@@ -759,6 +781,40 @@ static jobject android_server_UsbDeviceManager_openAccessory(JNIEnv *env, jobjec
        gParcelFileDescriptorOffsets.mConstructor, fileDescriptor);
}

static jobject android_server_UsbDeviceManager_openAccessoryForInputStream(JNIEnv *env,
                                                                           jobject /* thiz */) {
    int readFd = open(FFS_ACCESSORY_EP1, O_RDONLY);
    if (readFd < 0) {
        ALOGE("could not open %s", FFS_ACCESSORY_EP1);
        return nullptr;
    }
    jobject readFileDescriptor = jniCreateFileDescriptor(env, readFd);
    if (readFileDescriptor == nullptr) {
        close(readFd);
        return nullptr;
    }
    return env->NewObject(gParcelFileDescriptorOffsets.mClass,
                          gParcelFileDescriptorOffsets.mConstructor, readFileDescriptor);
}

static jobject android_server_UsbDeviceManager_openAccessoryForOutputStream(JNIEnv *env,
                                                                            jobject /* thiz */) {
    int writeFd = open(FFS_ACCESSORY_EP2, O_WRONLY);

    if (writeFd < 0) {
        ALOGE("could not open %s", FFS_ACCESSORY_EP2);
        return nullptr;
    }
    sVendorControlRequestMonitorThread->setMaxPacketSize(get_max_packet_size(writeFd));
    jobject writeFileDescriptor = jniCreateFileDescriptor(env, writeFd);
    if (writeFileDescriptor == nullptr) {
        close(writeFd);
        return nullptr;
    }
    return env->NewObject(gParcelFileDescriptorOffsets.mClass,
                          gParcelFileDescriptorOffsets.mConstructor, writeFileDescriptor);
}

static jboolean android_server_UsbDeviceManager_isStartRequested(JNIEnv* /* env */,
                                                                 jobject /* thiz */)
{
@@ -896,8 +952,13 @@ static const JNINativeMethod method_table[] = {
         (void *)android_server_UsbDeviceManager_getAccessoryStrings},
        {"nativeGetAccessoryStringsFromFfs", "()[Ljava/lang/String;",
         (void *)android_server_UsbDeviceManager_getAccessoryStringsFromFfs},
        {"nativeGetMaxPacketSize", "()I", (void *)android_server_UsbDeviceManager_getMaxPacketSize},
        {"nativeOpenAccessory", "()Landroid/os/ParcelFileDescriptor;",
         (void *)android_server_UsbDeviceManager_openAccessory},
        {"nativeOpenAccessoryForInputStream", "()Landroid/os/ParcelFileDescriptor;",
         (void *)android_server_UsbDeviceManager_openAccessoryForInputStream},
        {"nativeOpenAccessoryForOutputStream", "()Landroid/os/ParcelFileDescriptor;",
         (void *)android_server_UsbDeviceManager_openAccessoryForOutputStream},
        {"nativeIsStartRequested", "()Z", (void *)android_server_UsbDeviceManager_isStartRequested},
        {"nativeOpenControl", "(Ljava/lang/String;)Ljava/io/FileDescriptor;",
         (void *)android_server_UsbDeviceManager_openControl},
+66 −0
Original line number Diff line number Diff line
@@ -2607,6 +2607,66 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser
        return nativeOpenAccessory();
    }

    /**
     * opens the currently attached USB accessory to read from.
     *
     * @param accessory accessory to be opened.
     * @param permissions UsbUserPermissionManager to check permissions.
     * @param pid Pid of the caller
     * @param uid Uid of the caller
     */
    public ParcelFileDescriptor openAccessoryForInputStream(
            UsbAccessory accessory, UsbUserPermissionManager permissions, int pid, int uid) {
        UsbAccessory currentAccessory = mHandler.getCurrentAccessory();
        if (currentAccessory == null) {
            throw new IllegalArgumentException("no accessory attached");
        }
        if (!currentAccessory.equals(accessory)) {
            String error =
                    accessory.toString() + " does not match current accessory " + currentAccessory;
            throw new IllegalArgumentException(error);
        }
        permissions.checkPermission(accessory, pid, uid);
        return nativeOpenAccessoryForInputStream();
    }

    /**
     * opens the currently attached USB accessory to write to.
     *
     * @param accessory accessory to be opened.
     * @param permissions UsbUserPermissionManager to check permissions.
     * @param pid Pid of the caller
     * @param uid Uid of the caller
     */
    public ParcelFileDescriptor openAccessoryForOutputStream(
            UsbAccessory accessory, UsbUserPermissionManager permissions, int pid, int uid) {
        UsbAccessory currentAccessory = mHandler.getCurrentAccessory();
        if (currentAccessory == null) {
            throw new IllegalArgumentException("no accessory attached");
        }
        if (!currentAccessory.equals(accessory)) {
            String error =
                    accessory.toString() + " does not match current accessory " + currentAccessory;
            throw new IllegalArgumentException(error);
        }
        permissions.checkPermission(accessory, pid, uid);
        return nativeOpenAccessoryForOutputStream();
    }

    public int getMaxPacketSize(UsbAccessory accessory) {
        UsbAccessory currentAccessory = mHandler.getCurrentAccessory();
        if (currentAccessory == null) {
            throw new IllegalArgumentException("no accessory attached");
        }
        if (!currentAccessory.equals(accessory)) {
            String error =
                    accessory.toString() + " does not match current accessory " + currentAccessory;
            throw new IllegalArgumentException(error);
        }

        return nativeGetMaxPacketSize();
    }

    public long getCurrentFunctions() {
        return mHandler.getEnabledFunctions();
    }
@@ -2769,8 +2829,14 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser

    private native String[] nativeGetAccessoryStringsFromFfs();

    private native int nativeGetMaxPacketSize();

    private native ParcelFileDescriptor nativeOpenAccessory();

    private native ParcelFileDescriptor nativeOpenAccessoryForInputStream();

    private native ParcelFileDescriptor nativeOpenAccessoryForOutputStream();

    private native String nativeWaitAndGetProperty(String propName);

    private native FileDescriptor nativeOpenControl(String usbFunction);
+82 −0
Original line number Diff line number Diff line
@@ -404,6 +404,88 @@ public class UsbService extends IUsbManager.Stub {
        return null;
    }

    /* opens the currently attached USB accessory to read from (device mode) */
    @Override
    public ParcelFileDescriptor openAccessoryForInputStream(UsbAccessory accessory) {
        if (mDeviceManager != null) {
            int uid = Binder.getCallingUid();
            int pid = Binder.getCallingPid();
            int user = UserHandle.getUserId(uid);

            final long ident = clearCallingIdentity();
            try {
                synchronized (mLock) {
                    if (mUserManager.isSameProfileGroup(user, mCurrentUserId)) {
                        return mDeviceManager.openAccessoryForInputStream(
                                accessory, getPermissionsForUser(user), pid, uid);
                    } else {
                        Slog.w(TAG, "Cannot open " + accessory + " for user " + user
                                        + " as user is not active.");
                    }
                }
            } finally {
                restoreCallingIdentity(ident);
            }
        }

        return null;
    }

    /* opens the currently attached USB accessory to write to (device mode) */
    @Override
    public ParcelFileDescriptor openAccessoryForOutputStream(UsbAccessory accessory) {
        if (mDeviceManager != null) {
            int uid = Binder.getCallingUid();
            int pid = Binder.getCallingPid();
            int user = UserHandle.getUserId(uid);

            final long ident = clearCallingIdentity();
            try {
                synchronized (mLock) {
                    if (mUserManager.isSameProfileGroup(user, mCurrentUserId)) {
                        return mDeviceManager.openAccessoryForOutputStream(
                                accessory, getPermissionsForUser(user), pid, uid);
                    } else {
                        Slog.w(
                                TAG,
                                "Cannot open " + accessory + " for user " + user
                                        + " as user is not active.");
                    }
                }
            } finally {
                restoreCallingIdentity(ident);
            }
        }

        return null;
    }

    /* Gets the currently attached USB accessory max packet size (device mode) */
    @Override
    public int getMaxPacketSize(UsbAccessory accessory) {
        Preconditions.checkNotNull(mDeviceManager, "DeviceManager must not be null");
        int uid = Binder.getCallingUid();
        int user = UserHandle.getUserId(uid);

        final long ident = clearCallingIdentity();
        try {
            synchronized (mLock) {
                int maxPacketSize = -1;
                if (mUserManager.isSameProfileGroup(user, mCurrentUserId)) {
                    maxPacketSize = mDeviceManager.getMaxPacketSize(accessory);
                } else {
                    Slog.w(
                            TAG,
                            "Cannot open " + accessory + " for user " + user
                                    + " as user is not active.");
                }
                return maxPacketSize;
            }
        } finally {
            restoreCallingIdentity(ident);
        }
    }

    @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_MTP)
    /* Returns a dup of the control file descriptor for the given function. */
    @Override