Android USB Accessory模式无法使用libusb

时间:2017-07-31 15:41:57

标签: java android c++ usb libusb

我们希望克服的基本问题是通过USB从作为主机和电源的外部源(PC)传输数据到Android设备。由于PC将能够供电,并使产品的最终消费者更容易,我们希望避免OTG电缆,因此在附件模式下运行Android设备。

我设法让Android设备进入附件模式并识别出有USB主机连接到它,甚至能够读取主机的控制数据。但是,我似乎无法弄清楚实际的批量数据传输。

除了简单地设置初始连接之外,官方的android文档似乎在这个主题上提供的很少。

我尽可能多地从关于这个主题的几个来源,主要来自here,以及其他地方,在网上挽救尽可能多的代码。

在Android代码中,usbThread开始运行,甚至可以访问附件并正确打印出其制造商,型号等。但是,从inputStream读取始终会导致IOError。

在主机PC上的c ++代码中,一切都顺利运行并连接到transferTest,其中报告第一次批量传输超时和零字节被传输。

我在运行c ++代码之前总是杀掉ADB。 android文档说要发送一个set_configuration(1),我试图无济于事。他们还谈到ADB有第二个接口,我在输出中没有看到。我确实看到了另一种接口配置,但设置和使用它根本不起作用。

是否需要进行其他设置?我错过了一些基本的USB协议吗? Android代码应该只对inputStream进行连续轮询吗?配件模式是否仍然支持这个时代?

相关代码如下所示

MainActivity.java

//<company> contains actual reversed Internet domain name
private static final String ACTION_USB_PERMISSION = "<company>.USB_PERMISSION"; 
UsbManager usbManager;
usbThread thread;
boolean usbStarted = false;
boolean resumeRan = false;
boolean threadCreated = false;
UsbAccessory accessory;
ParcelFileDescriptor fileDescriptor;
FileInputStream inputStream;
FileOutputStream outputStream;

private void openAccessory() {
    Log.d(TAG, "openAccessory: " + accessory);
    fileDescriptor = usbManager.openAccessory(accessory);
    if (fileDescriptor != null)
    {
        usbStarted = true;
        FileDescriptor fd = fileDescriptor.getFileDescriptor();
        inputStream = new FileInputStream(fd);
        outputStream = new FileOutputStream(fd);
        thread = new usbThread();
        thread.start();
    }
}

private final BroadcastReceiver permissionReceiver = new BroadcastReceiver()
{
    public void onReceive(Context context, Intent intent)
    {
        String action = intent.getAction();
        if (ACTION_USB_PERMISSION.equals(action))
        {
            Log.d(TAG, "Asking for usb permission");
            synchronized (this)
            {
                if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false))
                {
                    if((!usbStarted) && resumeRan)
                    {
                        resumeRan = false;
                        Log.d(TAG, "Model: " + accessory.getModel());
                        openAccessory();
                    }
                }
                else
                {
                    Log.d(TAG, "permission denied for device ");
                }
            }
        }
    }
};

Handler usbHandler = new Handler()
{
    @Override
    public void handleMessage(Message msg)
    {
        byte res = msg.getData().getByte("val");
        Log.d(TAG, "Byte " + Byte.toString(res));
    }
};

private class usbThread extends Thread
{
    boolean bueno = true;
    usbThread()
    {
        if(!threadCreated)
        {
            threadCreated = true;
        }
        else
        {
            bueno = false;
            Log.d(TAG, "Tried to open second thread");
        }
    }
    public void run()
    {
        while(usbStarted && bueno)
        {
            byte[] buffer = new byte[10];
            int  ret = 0;
            try
            {
                int av = inputStream.available();
                ret = inputStream.read(buffer, 0, av);
            } catch (IOException e)
            {
                e.printStackTrace();
                //Log.d("MMI_IO_READ", "IOException on buffer read.");
            }

            if(ret > 0)
            {
                //Log.d("MMI_IO_READ", "Ret = " +ret);
                for(int i = 0; i < ret; i++)
                {
                    Message m = usbHandler.obtainMessage();
                    Bundle b = new Bundle();
                    b.putByte("val", buffer[i]);
                    m.setData(b);
                    usbHandler.sendMessage(m);
                }
            }
        }
        Log.d(TAG, "Thread Shutdown");
    }
}

@Override
protected void onCreate(Bundle savedInstanceState)
{
    super.onCreate(savedInstanceState);

    usbManager = (UsbManager) getSystemService(Context.USB_SERVICE);
    IntentFilter filter = new IntentFilter(UsbManager.ACTION_USB_DEVICE_DETACHED);
    registerReceiver(mUsbDetachReceiver , filter);
    filter = new IntentFilter(ACTION_USB_PERMISSION);
    registerReceiver(permissionReceiver, filter);
}

@Override
protected void onResume()
{
    super.onResume();

    UsbAccessory[] accessoryList = usbManager.getAccessoryList();
    if (accessoryList != null)
    {
        if(!usbStarted)
        {
            Log.d(TAG, "Resume, usb not started");
            accessory = accessoryList[0];
            Log.d(TAG, "Manufacturer " + accessory.getManufacturer());
            PendingIntent mPermissionIntent = PendingIntent.getBroadcast(getApplicationContext(), 0, new Intent(ACTION_USB_PERMISSION), 0);
            resumeRan = true;
            usbManager.requestPermission(accessory, mPermissionIntent);
        }
    }
}

请注意,正如this thread等建议,以及我自己的经验,USB_ACCESSORY_ATTACH意图永远不会触发。因此,USB检测依赖于一个不稳定的onResume检查。

HostUSB.cpp

#include <stdio.h>
#include <libusb-1.0/libusb.h>
#include <string.h>
#include <unistd.h>
#include <inttypes.h>
#include <iostream>


#define ACCESSORY_VID 0x18d1
#define ACCESSORY_PID 0x2D00
#define ACCESSORY_ADB_PID 0x2D01
#define INTERFACE 0


const char* manufacturer = "PCHost";
const char* modelName = "PCHost1";
const char* description = "Description";
const char* version = "1.0";
const char* uri = "http://example.com";
const char* serialNumber = "666";

//static

static void error(int code)
{
    fprintf(stdout, "%s\n", libusb_strerror((libusb_error)code));
}

static int shutdown(libusb_device_handle *handle)
{
    if(handle)
    {
        libusb_release_interface(handle, INTERFACE);
        libusb_close(handle);
    }
    libusb_exit(NULL);
    return 0;
}


static int init()
{
    libusb_init(NULL);
    libusb_set_debug(NULL, 3);  //Verbose debugging level

    return 0;
}

// Send AOA specified introdction control information.
static int androidIntroduction(libusb_device_handle *handle)
{
    unsigned char ioBuffer[2];
    int devVersion;
    int response;
    response = libusb_control_transfer(
        handle, //handle
        0xC0, //bmRequestType
        51, //bRequest
        0, //wValue
        0, //wIndex
        ioBuffer, //data
        2, //wLength
        100 //timeout
    );
    fprintf(stdout,"Sent getProtocol\n");

    if(response < 0)
        {error(response);return -1;}

    fprintf(stdout,"Response \n");

    devVersion = ioBuffer[1] << 8 | ioBuffer[0];
    if(!(devVersion == 1 || devVersion==2))
        return -1;
    fprintf(stdout,"Version Code Device: %d\n", devVersion);

    usleep(1000);//sometimes hangs on the next transfer :(

    response = libusb_control_transfer(handle,0x40,52,0,0,(unsigned char*)manufacturer,strlen(manufacturer)+1,0);
    if(response < 0)
        {error(response);return -1;}
    response = libusb_control_transfer(handle,0x40,52,0,1,(unsigned char*)modelName,strlen(modelName)+1,0);
    if(response < 0)
        {error(response);return -1;}
    response = libusb_control_transfer(handle,0x40,52,0,2,(unsigned char*)description,strlen(description)+1,0);
    if(response < 0)
        {error(response);return -1;}
    response = libusb_control_transfer(handle,0x40,52,0,3,(unsigned char*)version,strlen(version)+1,0);
    if(response < 0)
        {error(response);return -1;}
    response = libusb_control_transfer(handle,0x40,52,0,4,(unsigned char*)uri,strlen(uri)+1,0);
    if(response < 0)
        {error(response);return -1;}
    response = libusb_control_transfer(handle,0x40,52,0,5,(unsigned char*)serialNumber,strlen(serialNumber)+1,0);
    if(response < 0)
        {error(response);return -1;}

    fprintf(stdout,"Accessory Identification sent\n");
    return 1;
}

// Send introduction information to given handle, then try to put it into
// accessory mode and catch it once it reconnects.
static libusb_device_handle* setupAccessory(libusb_device_handle *handle)
    {
    int response;
    response = androidIntroduction(handle);
    if(response < 0)
        return NULL;
    response = libusb_control_transfer(handle,0x40,53,0,0,NULL,0,0);
    if(response < 0){error(response);return NULL;}

    fprintf(stdout,"Attempted to put device into accessory mode\n");

    libusb_device_handle *androidHandle;

    int tries = 4;
    while(true)
    {
        tries--;
        if((androidHandle = libusb_open_device_with_vid_pid(NULL, ACCESSORY_VID, ACCESSORY_ADB_PID)) == NULL)
        {
            if((androidHandle = libusb_open_device_with_vid_pid(NULL, ACCESSORY_VID, ACCESSORY_PID)) == NULL)
            {
                if(tries < 0)
                {
                    std::cout << "Could not..." << '\n';
                    return NULL;
                }
            }
            else
            {
                break;
            }
        }else
        {
            break;
        }
        usleep(1000000);
    }

    return androidHandle;
}

//Find the first Bulk OUT Enpoint of the given device. 
uint8_t findBulkOut (libusb_device *device)
{
    libusb_config_descriptor *con_desc;
    libusb_get_active_config_descriptor(device, &con_desc);
    const libusb_interface *interfaceList = con_desc->interface;
    uint16_t numInterface = con_desc->bNumInterfaces;
    for(int j = 0; j<numInterface; j++)
    {
        libusb_interface interface = interfaceList[j];
        const libusb_interface_descriptor *intDescList = interface.altsetting;
        int numAlt = interface.num_altsetting;
        for(int p = 0; p < numAlt; p++)
        {
            libusb_interface_descriptor intDesc = intDescList[p];
            uint8_t numEnd = intDesc.bNumEndpoints;
            const libusb_endpoint_descriptor *ends = intDesc.endpoint;
            for(int k = 0; k < numEnd; k++)
            {
                libusb_endpoint_descriptor endpoint = ends[k];
                uint8_t type = 0x03 & endpoint.bmAttributes;
                uint8_t address = endpoint.bEndpointAddress;
                switch (type) {
                    case LIBUSB_TRANSFER_TYPE_CONTROL:
                        break;
                    case LIBUSB_TRANSFER_TYPE_ISOCHRONOUS:
                        break;
                    case LIBUSB_TRANSFER_TYPE_BULK:
                        if(!(address & LIBUSB_ENDPOINT_IN)) //LIBUSB_ENPOINT_OUT is simply 0000, can't AND that...
                            {
                                return address;
                            }
                        break;
                    case LIBUSB_TRANSFER_TYPE_INTERRUPT:
                        break;
                    case LIBUSB_TRANSFER_TYPE_BULK_STREAM:
                        break;
                }
            }
        }
    }
}

//Basically findBulkOut, but with output for EndPoints.
void printEnds (libusb_device *device)
{
    libusb_config_descriptor *con_desc;
    libusb_get_active_config_descriptor(device, &con_desc);
    const libusb_interface *interfaceList = con_desc->interface;
    uint16_t numInterface = con_desc->bNumInterfaces;
    for(int j = 0; j<numInterface; j++)
    {
        libusb_interface interface = interfaceList[j];
        const libusb_interface_descriptor *intDescList = interface.altsetting;
        int numAlt = interface.num_altsetting;
        for(int p = 0; p < numAlt; p++)
        {
            libusb_interface_descriptor intDesc = intDescList[p];
            uint8_t numEnd = intDesc.bNumEndpoints;
            const libusb_endpoint_descriptor *ends = intDesc.endpoint;
            fprintf(stdout, "Interface %d. altSetting %d. Num of endpoints: %d\n", p, intDesc.bInterfaceNumber, numEnd);
            for(int k = 0; k < numEnd; k++)
            {
                libusb_endpoint_descriptor endpoint = ends[k];
                uint8_t type = 0x03 & endpoint.bmAttributes;
                uint8_t address = endpoint.bEndpointAddress;
                fprintf(stdout, "Endpoint type ");
                switch (type) {
                    case LIBUSB_TRANSFER_TYPE_CONTROL:
                        std::cout << "Control";
                        break;
                    case LIBUSB_TRANSFER_TYPE_ISOCHRONOUS:
                        std::cout << "Isochronus";
                        break;
                    case LIBUSB_TRANSFER_TYPE_BULK:
                        std::cout << "Bulk";
                        break;
                    case LIBUSB_TRANSFER_TYPE_INTERRUPT:
                        std::cout << "Interupt";
                        break;
                    case LIBUSB_TRANSFER_TYPE_BULK_STREAM:
                        std::cout << "Bulk Stream";
                        break;
                }
                std::cout << " ";
                std::cout << (address & LIBUSB_ENDPOINT_IN ? "IN" : "OUT");
                std::cout << '\n';
                fprintf(stdout, "Address %04X\n", address);
            }
        }
    }
}

// Go through all connected devices. If they are do not have Google PID
// and VID, try to find out if they are Android devices. If they (most likely)
// are, try to put them in accessory mode. If successful, return that handle
libusb_device_handle* getAndroidHandle()
{
    libusb_device **list;
    ssize_t cnt = libusb_get_device_list(NULL, &list);
    ssize_t i = 0;
    int err = 0;
    if (cnt < 0)
        error(0);
    for (i = 0; i < cnt; i++)
    {
        fprintf(stdout,"\nAttempted index %d\n", (int)i);
        libusb_device *device = list[i];
        libusb_device_descriptor dev_desc;
        libusb_get_device_descriptor(device, &dev_desc);
        uint16_t VID = dev_desc.idVendor;
        uint16_t PID = dev_desc.idProduct;
        fprintf(stdout, "VID: %04X. PID: %04X\n", VID, PID);

        libusb_device_handle *handle;
        int response = libusb_open(device, &handle);
        if(response < 0)
            {error(response);continue;}
        libusb_set_auto_detach_kernel_driver(handle, 1);

        libusb_device_handle *androidHandle;
        if(VID == ACCESSORY_VID && (PID == ACCESSORY_PID || PID == ACCESSORY_ADB_PID))
        {
            int r = androidIntroduction(handle);
            if (r != 1)
                continue;
            androidHandle = handle;
        }
        else
        {
             androidHandle = setupAccessory(handle);
             libusb_close(handle);

        }
        if (androidHandle)
        {
            libusb_free_device_list(list, 1);
            std::cout << "\n\nAndroid Found:" << '\n';
            printEnds(libusb_get_device(androidHandle));
            return androidHandle;
        }
    }
    libusb_free_device_list(list, 1);
    return NULL;
}

//Try to send data.
static int transferTest(libusb_device_handle *handle)
{
    const static int PACKET_BULK_LEN=64;
    const static int TIMEOUT=5000;
    int r,i;
    int transferred;
    usleep(1000000); //1s

    //libusb_set_configuration(handle, 1);
    r = libusb_claim_interface(handle, INTERFACE);
    if(r < 0)
        {error(r); return -1;}
    fprintf(stdout, "Interface claimed, ready to transfer data\n");
    // TEST BULK IN/OUT
    usleep(100000);// 0.1s
    uint8_t outAddress = findBulkOut(libusb_get_device(handle));
    fprintf(stdout, "Trying to write to %04X\n", outAddress );
    char answer[PACKET_BULK_LEN];
    char question[PACKET_BULK_LEN];
    for (i=0;i<PACKET_BULK_LEN; i++) question[i]=i;

    // ***TIMES OUT HERE***
    r = libusb_bulk_transfer(handle, outAddress, (unsigned char*)question, PACKET_BULK_LEN,
                             &transferred,TIMEOUT);
    if (r < 0)
    {
        fprintf(stderr, "Bulk write error %d\n", r);
        error(r);
        fprintf(stderr, "Number of bytes written %d\n", transferred);
        return r;
    }
    fprintf(stdout, "Wrote %d bytes", transferred);

    /*r = libusb_bulk_transfer(handle, ENDPOINT_BULK_IN, (unsigned char*)answer,PACKET_BULK_LEN,
                             &transferred, TIMEOUT);
    if (r < 0)
    {
        fprintf(stderr, "Bulk read error %d\n", r);
        error(r);
        return r;
    }
    fprintf(stdout, "Read %d bytes", r);

    if (transferred < PACKET_BULK_LEN)
    {
        fprintf(stderr, "Bulk transfer short read (%d)\n", r);
        error(r);
        return -1;
    }
    printf("Bulk Transfer Loop Test Result:\n");
    //     for (i=0;i< PACKET_BULK_LEN;i++) printf("%i, %i,\n ",question[i],answer[i]);
    for(i = 0;i < PACKET_BULK_LEN; i++)
    {
        if(i%8 == 0)
            printf("\n");
        printf("%02x, %02x; ",question[i],answer[i]);
    }
    printf("\n\n");
    */return 0;
}

int main (int argc, char *argv[])
{
    fprintf(stdout, "OUT flag %04X IN flag %04X\n", LIBUSB_ENDPOINT_OUT, LIBUSB_ENDPOINT_IN);
    fprintf(stdout, "Shifted in %04X\n", LIBUSB_ENDPOINT_IN >> 7);
    if(init() < 0)
        return -1;

    libusb_device_handle *handle = getAndroidHandle();
    if(!handle)
    {
        fprintf(stdout, "\nError setting up accessory\n");
        shutdown(NULL);
        return -1;
    };
    if(transferTest(handle) < 0)
    {
        fprintf(stdout, "\nError in transferTest\n");
        shutdown(handle);
        return -1;
    }
    shutdown(handle);
    fprintf(stdout, "\nFinished\n");
    return 0;
}

预先感谢您的回复! :)

修改

似乎是inputStream.available(),它是主要的罪魁祸首,似乎没有正常工作(我从一些随机的谷歌搜索中发现)。无论正确设置如何,它都会抛出IOException。我还取消了libusb_set_configuration的注释,并在确保它正常工作后设置了一点延迟。

旁注:我没有杀死ADB服务器以使USB连接正常工作。如果设备将自身引入0x2D01作为PID,则ADB日志记录工作正常!

2 个答案:

答案 0 :(得分:1)

这是我的Java代码,它可以帮助你。

如果在AOA模式之后添加libusb_set_configuartion,上面的C ++代码也可以工作。

爪哇

import android.app.PendingIntent;
import android.hardware.usb.UsbAccessory;
import android.hardware.usb.UsbManager;


import android.os.Bundle;
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.ParcelFileDescriptor;
import android.util.Log;


import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
//import java.util.logging.Logger;


public class MainActivity extends Activity implements Runnable
{

    private static final String ACTION_USB_PERMISSION = "com.examples.accessory.controller.action.USB_PERMISSION";
    private final String TAG = "_ITE";

    private UsbManager mUsbManager;
    private PendingIntent mPermissionIntent;
    private boolean mPermissionRequestPending;

    UsbAccessory mAccessory;
    ParcelFileDescriptor mFileDescriptor;
    FileInputStream mInputStream;
    FileOutputStream mOutputStream;


    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);

        mUsbManager = (UsbManager)getSystemService(Context.USB_SERVICE);
        mPermissionIntent = PendingIntent.getBroadcast(this, 0, new Intent(ACTION_USB_PERMISSION), 0);
        IntentFilter filter = new IntentFilter(ACTION_USB_PERMISSION);
        filter.addAction(UsbManager.ACTION_USB_ACCESSORY_DETACHED);
        registerReceiver(mUsbReceiver, filter);

        setContentView(R.layout.activity_main);

        //enableControls(false);
    }

    @Override
    public void onResume()
    {
        super.onResume();

        Intent intent = getIntent();
        if (mInputStream != null && mOutputStream != null) {
            return;
        }

        UsbAccessory[] accessories = mUsbManager.getAccessoryList();
        UsbAccessory accessory = (accessories == null ? null : accessories[0]);
        if (accessory != null)
        {
            if (mUsbManager.hasPermission(accessory))
            {
                openAccessory(accessory);
            }
            else
                {
                synchronized (mUsbReceiver)
                {
                    if (!mPermissionRequestPending)
                    {
                        mUsbManager.requestPermission(accessory,mPermissionIntent);
                        mPermissionRequestPending = true;
                    }
                }
            }
        }
        else
            {
            Log.d(TAG, "mAccessory is null");
        }
    }

    @Override
    public void onPause() {
        super.onPause();
        closeAccessory();
    }

    @Override
    public void onDestroy() {
        unregisterReceiver(mUsbReceiver);
        super.onDestroy();
    }



    private void openAccessory(UsbAccessory accessory)
    {
        mFileDescriptor = mUsbManager.openAccessory(accessory);
        if (mFileDescriptor != null) {
            mAccessory = accessory;
            FileDescriptor fd = mFileDescriptor.getFileDescriptor();
            mInputStream = new FileInputStream(fd);
            mOutputStream = new FileOutputStream(fd);
            Thread thread = new Thread(null, this, "AccessoryController");
            thread.start();
            Log.d(TAG, "accessory opened");
            //enableControls(true);
        } else {
            Log.d(TAG, "accessory open fail");
        }
    }

    private void closeAccessory() {
        //enableControls(false);

        try {
            if (mFileDescriptor != null) {
                mFileDescriptor.close();
            }
        } catch (IOException e) {
        } finally {
            mFileDescriptor = null;
            mAccessory = null;
        }
    }

    /*
     * This receiver monitors for the event of a user granting permission to use
     * the attached accessory.  If the user has checked to always allow, this will
     * be generated following attachment without further user interaction.
     */
    private final BroadcastReceiver mUsbReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            if (ACTION_USB_PERMISSION.equals(action)) {
                synchronized (this) {
                    UsbAccessory accessory = (UsbAccessory) intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY);
                    if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
                        openAccessory(accessory);
                    } else {
                        Log.d(TAG, "permission denied for accessory "+ accessory);
                    }
                    mPermissionRequestPending = false;
                }
            } else if (UsbManager.ACTION_USB_ACCESSORY_DETACHED.equals(action)) {
                UsbAccessory accessory = (UsbAccessory) intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY);
                if (accessory != null && accessory.equals(mAccessory)) {
                    closeAccessory();
                }
            }
        }
    };

    /*
     * Runnable block that will poll the accessory data stream
     * for regular updates, posting each message it finds to a
     * Handler.  This is run on a spawned background thread.
     */
    public void run() {
        int ret = 0;
        byte[] buffer = new byte[16384];
        int i;

        while (ret >= 0)
        {
            try
            {
                ret = mInputStream.read(buffer);
            } catch (IOException e) {
                break;
            }




        }
    }


}

清单

<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/AppTheme">
        <uses-library android:name="com.android.future.usb.accessory" />
        <activity android:name=".MainActivity">
            <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_ACCESSORY_ATTACHED" />
                <action android:name="android.hardware.usb.action.USB_ACCESSORY_DETACHED" />
            </intent-filter>

            <meta-data
                android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED"
                android:resource="@xml/accessory_filter" >
            </meta-data>
        </activity>
    </application>

accessory_filter.xml

<?xml version="1.0" encoding="utf-8"?>

<resources>    
    <usb-accessory model="YourModel" manufacturer="yourname" version="0.0.1"/>

</resources>

答案 1 :(得分:0)

这不是代码问题,您无法在没有OTG的情况下批量传输数据。获得10/15美元的OTG适配器,然后您可以这样做,但这取决于设备及其使用年限。通常,它们不会在没有adb和OTG适配器/电缆的情况下充当主机。我从来没有听说过另一种方式,我认为你最终会得到一个无效的代码,但如果你确实找到了办法,请告诉我。

相关问题