Android UsbHost Проблемы с Samsung Galaxy Tab S 10.5 и Unity

Когда приложение уже открыто и подключено USBDevice, приложение иногда может обмениваться данными с устройством, а иногда нет. Ошибка, которую я получаю, отличается каждый раз. Константа в том, что она работает, как и ожидалось, на телефоне Samsung Galaxy, Nexus 7, Nexus 10. Если приложение запускается с подключенного устройства, в большинстве случаев все работает как положено.

Манифест настроен так

<uses-feature android:name="android.hardware.usb.host" />
<uses-feature android:name="android.hardware.usb.UsbInterface" />

<activity android:label="@string/app_name"
    android:screenOrientation="fullSensor"  android:launchMode="singleTask"
    android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|uiMode|screenSize|smallestScreenSize|fontScale"
    android:name="com.realart.Beatmaker.UnityPlayerNativeActivity">

    <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>

    <!-- USB Connect List -->
    <meta-data android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
        android:resource="@xml/device_filter" />

    <intent-filter>
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />
    </intent-filter>

    <meta-data android:name="unityplayer.UnityActivity" android:value="true" />
    <meta-data android:name="unityplayer.ForwardNativeEventsToDalvik" android:value="false" />    

</activity> 

В onCreate я проверяю, было ли приложение запущено с намерения:

// Get the device that woke up the activity
UsbDevice connectedDevice = (UsbDevice)getIntent()
        .getParcelableExtra(UsbManager.EXTRA_DEVICE);

if (connectedDevice != null) {
    UsbManager manager = (UsbManager)getSystemService(Context.USB_SERVICE);
    UsbDevice device = connectedDevice;
    writeDeviceInfo(device);
    initDataTransferThread(manager, device);

}

Если приложение уже открыто, и потому launchMode является singleTaskЯ переопределил onNewIntent метод, как так

@Override
public void onNewIntent(Intent intent) {
    final String DEVICE_CONNECTED = "android.hardware.usb.action.USB_DEVICE_ATTACHED";

    String action = intent.getAction();
    if (action.equals(DEVICE_CONNECTED)) {
        Log.d(TAG, "Device connected");
        final UsbManager manager = (UsbManager)getSystemService(Context.USB_SERVICE);

        try {
            final UsbDevice connectedDevice = (UsbDevice)intent
                    .getParcelableExtra(UsbManager.EXTRA_DEVICE);

            if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
                if (connectedDevice != null) {
                    writeDeviceInfo(connectedDevice);
                    initDataTransferThread(manager, connectedDevice);
                } else {
                    Log.d(TAG, "Device null");
                }
            } else {
                Log.d(TAG, "Permission denied");
                Log.d(TAG, "Asking for permission and trying again...");
                manager.requestPermission(connectedDevice, permissionIntent);
            }
        } catch (Exception e) {
            Log.d(TAG, "Exception throw: " + e.toString());
            e.printStackTrace();
        }
    }
}

И в этом методе он почти всегда попадает в блок "Отказано в разрешении", поэтому он переходит к получателю широковещательной рассылки проверки разрешения, который я настроил следующим образом:

// Attach Events
attachReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        Log.d(TAG, "Permissions receiver hit");
        String action = intent.getAction();
        Log.d(TAG, "Action requested: " + action);
        UsbManager manager = (UsbManager)getSystemService(Context.USB_SERVICE);
        if (intent.getAction().equals("com.realart.Beatmaker.USB_PERMISSION")) {

            // Thread still running?
            if (arduino != null) {
                if (arduino.pipeline != null) {
                    Message msg = Message.obtain();
                    msg.what = Change.USB_DISCONNECTED;
                    arduino.pipeline.sendMessage(msg);
                }
                arduino = null;
            }

            synchronized (this) {
                Log.d(TAG, "Checking permissions from user...");
                UsbDevice device = (UsbDevice)intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
                boolean permissionGranted = intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false);
                Log.d(TAG, "Permissions granted: " + permissionGranted);
                if (permissionGranted) {
                    if (device != null) {
                        writeDeviceInfo(device);
                        initDataTransferThread(manager, device);
                    } else {
                        Log.d(TAG, "Device null");
                    }
                } else {
                    Log.d(TAG, "Permission denied from user");
                }
            }
        }
    }
};

permissionIntent = PendingIntent.getBroadcast(this, 0, new Intent("com.realart.Beatmaker.USB_PERMISSION"), 0);
IntentFilter attachFilter = new IntentFilter("com.realart.Beatmaker.USB_PERMISSION");
registerReceiver(attachReceiver, attachFilter);

Этот процесс, похоже, не вызывает каких-либо ошибок, и информация об устройстве записывается каждый раз. Иногда в нем перечисляются два интерфейса, иногда 0. Что странно, и я не знаю, почему он это сделал.

Вот как я инициализирую устройство:

private boolean initSerialBus() {
    try {
        // Open device
        connection = usbManager.openDevice(arduino);
        if (connection == null) {
            Log.d(TAG, "Opening device failed");
            return false;
        }

        // Get serial interface
        usbInterface = arduino.getInterface(1);
        boolean interfaceClaimed = connection.claimInterface(usbInterface, FORCE_CLAIM);
        Log.d(TAG, "USB Interface claimed: " + interfaceClaimed);
        if (!interfaceClaimed) {
            Log.d(TAG, "Trying again with kernel driver");
            interfaceClaimed = connection.claimInterface(usbInterface, false);
            if (!interfaceClaimed) {
                Log.d(TAG, "Unable to claim interface.");
                return false;
            }
        }

        // Arduino USB serial converter setup
        Log.d(TAG, "Data Transfter setup");

        // Line state
        int lineState = connection.controlTransfer(0x21, 0x22, 0, 0, null, 0, 0);
        if (lineState < 0) {
            Log.d(TAG, "Line state control transfer failed.");
            Log.d(TAG, "Return value: " + lineState);
            releaseUsbResources();
            arduinoState.onArduinoError(new Exception("Control frame transfer error"));
            return false;
        }
        Log.d(TAG, "Line state control frame: OK return: " + lineState);

        // Line encoding (9600 Baud)
        final byte[] lineEncoding = {
                (byte) 0x80, 0x25, 0x00, 0x00, 0x00, 0x00, 0x08
        };
        int lineEncode = connection.controlTransfer(0x21, 0x20, 0, 0, lineEncoding, 7, 0);
        if (lineEncode < 0) {
            Log.d(TAG, "Line encoding control transfer failed.");
            Log.d(TAG, "Return value: " + lineEncode);
            releaseUsbResources();
            arduinoState.onArduinoError(new Exception("Control frame transfer error"));
            return false;
        }
        Log.d(TAG, "Line encoding control frame: OK return: " + lineEncode);

        // I/O Endpoints
        for (int i = 0; i < usbInterface.getEndpointCount(); i++) {
            UsbEndpoint endpoint = usbInterface.getEndpoint(i);
            if (endpoint.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK) {
                if (endpoint.getDirection() == UsbConstants.USB_DIR_IN) {
                    arduinoIn = usbInterface.getEndpoint(i);
                } else {
                    arduinoOut = usbInterface.getEndpoint(i);
                }
            }
        }

        // Ensure we found the endpoints
        if (arduinoIn == null || arduinoOut == null) {
            releaseUsbResources();
            arduinoState.onArduinoError(new Exception("No usb endpoints found"));
            return false;
        }
        Log.d(TAG, "I/O Enpoints found");
        arduinoConnected = true;
    } catch (Exception e) {
        e.printStackTrace();
    }
    return arduinoConnected;
}

Это обычный вывод logcat:

V/UsbHostManager( 2733): USB HOST UEVENT: {SUBSYSTEM=host_notify, STATE=ADD, DEVNAME=usb_otg, DEVPATH=/devices/virtual/host_notify/usb_otg, SEQNUM=18860, ACTION=change}
D/UsbHostManager( 2733): turnOnLcd :: 
D/UsbHostNotification( 2733): setUsbObserverNotification : cancel id = 998563545, device = UsbDevices
D/UsbHostNotification( 2733): setUsbObserverNotification : notify id = 998563545, device = UsbDevices, title = USB connector connected.
D/UsbHostNotification( 2733): send the timeout : current  1413981920804, vailed = -2999, displayed = 1413981920803
D/UsbHostNotification( 2733): setUsbObserverNotification : cancel id = 998563545, device = UsbDevices
D/UsbHostManager( 2733): usbDeviceAdded : device :: /dev/bus/usb/002/077 [2341h:003dh] [02h,00h,00h] (CDC Control)
D/UsbHostManager( 2733): usbDeviceAdded : interface :: /dev/bus/usb/002/077 [2341h:003dh] [02h,02h,01h] (CDC Control)
D/UsbHostManager( 2733): usbDeviceAdded : interface :: /dev/bus/usb/002/077 [2341h:003dh] [0ah,00h,00h] (CDC Data)
D/UsbSettingsManager( 2733): deviceAttached: /dev/bus/usb/002/077 def package com.realart.Beatmaker
D/UsbSettingsManager( 2733): deviceAttached, send intent Intent { act=android.hardware.usb.action.USB_DEVICE_ATTACHED flg=0x10000000 (has extras) }
D/UsbSettingsManager( 2733): deviceAttached, call to resolveActivity started, Intent { act=android.hardware.usb.action.USB_DEVICE_ATTACHED flg=0x10000000 (has extras) }
D/UsbSettingsManager( 2733): resolveActivity : matches count = 1, defaultPackage = com.realart.Beatmaker
D/UsbSettingsManager( 2733): resolveActivity : defaultRI = ResolveInfo{425e9ad0 com.realart.Beatmaker/.UnityPlayerNativeActivity m=0x108000}
D/UsbSettingsManager( 2733): grantDevicePermission: UsbDevice[mName=/dev/bus/usb/002/077,mVendorId=9025,mProductId=61,mClass=2,mSubclass=0,mProtocol=0,mInterfaces=[Landroid.hardware.usb.UsbInterface;@42d33850] for 10212
D/UsbSettingsManager( 2733): grantDevicePermission: mDevicePermissionMap put UsbDevice[mName=/dev/bus/usb/002/077,mVendorId=9025,mProductId=61,mClass=2,mSubclass=0,mProtocol=0,mInterfaces=[Landroid.hardware.usb.UsbInterface;@42d33850] for {}
D/UsbSettingsManager( 2733): resolveActivity : permissionsGranted to ResolveInfo{425e9ad0 com.realart.Beatmaker/.UnityPlayerNativeActivity m=0x108000}
D/UsbSettingsManager( 2733): deviceAttached, call to resolveActivity ended, Intent { act=android.hardware.usb.action.USB_DEVICE_ATTACHED flg=0x10000000 cmp=com.realart.Beatmaker/.UnityPlayerNativeActivity (has extras) }
D/Unity   (30871): Device connected
D/Unity   (30871): Permission denied
D/Unity   (30871): Asking for permission and trying again...
D/UsbSettingsManager( 2733): requestPermission:/dev/bus/usb/002/077 ,pi PendingIntent{42169a08: PendingIntentRecord{42c42748 com.realart.Beatmaker broadcastIntent}}
D/UsbSettingsManager( 2733): Request Permission for Device vendorId: 9025, productId: 61, package: com.realart.Beatmaker
D/UsbSettingsManager( 2733): hasPermission++
D/UsbSettingsManager( 2733): hasPermission: UsbDevice[mName=/dev/bus/usb/002/077,mVendorId=9025,mProductId=61,mClass=2,mSubclass=0,mProtocol=0,mInterfaces=[Landroid.os.Parcelable;@4360cc38]for {10212=true}
D/UsbSettingsManager( 2733): requestPermission:/dev/bus/usb/002/077 has permissions
D/Unity   (30871): Permissions receiver hit
D/Unity   (30871): Action requested: com.realart.Beatmaker.USB_PERMISSION
D/Unity   (30871): Checking permissions from user...
D/Unity   (30871): Permissions granted: true
D/Unity   (30871): Device name: /dev/bus/usb/002/077
D/Unity   (30871): Vender id: 9025
D/Unity   (30871): Product id: 61
D/Unity   (30871): Class: class android.hardware.usb.UsbDevice
D/Unity   (30871): Sub-class: 0
D/Unity   (30871): Protocol: 0
D/Unity   (30871): Num Interfaces: 2
D/Unity   (30871): Device hash: 263427216
D/Arduino (30871): USB Interface claimed: false
D/Arduino (30871): Trying again with kernel driver
D/Arduino (30871): Unable to claim interface.
D/UsbSettingsManager( 2733): checkPermission: /dev/bus/usb/002/077
D/UsbSettingsManager( 2733): hasPermission++
D/UsbSettingsManager( 2733): hasPermission: UsbDevice[mName=/dev/bus/usb/002/077,mVendorId=9025,mProductId=61,mClass=2,mSubclass=0,mProtocol=0,mInterfaces=[Landroid.hardware.usb.UsbInterface;@42d33850]for {10212=true}

Кажется, есть заметное время ожидания после того, как следующие строки напечатаны в logcat:

D/UsbHostNotification( 2733): send the timeout : current  1413981920804, vailed = -2999, displayed = 1413981920803
D/UsbHostNotification( 2733): setUsbObserverNotification : cancel id = 998563545, device = UsbDevices

Если остальная часть приложения работает достаточно быстро, проблема не возникает, но если она зависает на этом шаге в течение нескольких секунд, каждый раз происходит сбой. Но не всегда в одном и том же месте. Иногда при попытке запросить интерфейс, иногда при отправке элемента управления, а иногда нет сообщения об ошибке, но данные не принимаются.

Когда приложение запускается, вывод logcat выглядит так:

--------- beginning of /dev/log/system
D/UsbHostNotification( 2733): setUsbObserverNotification : cancel id = 998563545, device = UsbDevices
--------- beginning of /dev/log/main
V/UsbHostManager( 2733): USB HOST UEVENT: {SUBSYSTEM=host_notify, STATE=ADD, DEVNAME=usb_otg, DEVPATH=/devices/virtual/host_notify/usb_otg, SEQNUM=19659, ACTION=change}
D/UsbHostManager( 2733): turnOnLcd :: 
D/UsbHostNotification( 2733): setUsbObserverNotification : cancel id = 998563545, device = UsbDevices
D/UsbHostNotification( 2733): setUsbObserverNotification : notify id = 998563545, device = UsbDevices, title = USB connector connected.
D/UsbHostNotification( 2733): send the timeout : current  1413982261198, vailed = -2991, displayed = 1413982261189
D/UsbHostManager( 2733): usbDeviceAdded : device :: /dev/bus/usb/002/081 [2341h:003dh] [02h,00h,00h] (CDC Control)
D/UsbHostManager( 2733): usbDeviceAdded : interface :: /dev/bus/usb/002/081 [2341h:003dh] [02h,02h,01h] (CDC Control)
D/UsbHostManager( 2733): usbDeviceAdded : interface :: /dev/bus/usb/002/081 [2341h:003dh] [0ah,00h,00h] (CDC Data)
D/UsbSettingsManager( 2733): deviceAttached: /dev/bus/usb/002/081 def package com.realart.Beatmaker
D/UsbSettingsManager( 2733): deviceAttached, send intent Intent { act=android.hardware.usb.action.USB_DEVICE_ATTACHED flg=0x10000000 (has extras) }
D/UsbSettingsManager( 2733): deviceAttached, call to resolveActivity started, Intent { act=android.hardware.usb.action.USB_DEVICE_ATTACHED flg=0x10000000 (has extras) }
D/UsbSettingsManager( 2733): resolveActivity : matches count = 1, defaultPackage = com.realart.Beatmaker
D/UsbSettingsManager( 2733): resolveActivity : defaultRI = ResolveInfo{42458358 com.realart.Beatmaker/.UnityPlayerNativeActivity m=0x108000}
D/UsbSettingsManager( 2733): grantDevicePermission: UsbDevice[mName=/dev/bus/usb/002/081,mVendorId=9025,mProductId=61,mClass=2,mSubclass=0,mProtocol=0,mInterfaces=[Landroid.hardware.usb.UsbInterface;@4259eeb8] for 10212
D/UsbSettingsManager( 2733): grantDevicePermission: mDevicePermissionMap put UsbDevice[mName=/dev/bus/usb/002/081,mVendorId=9025,mProductId=61,mClass=2,mSubclass=0,mProtocol=0,mInterfaces=[Landroid.hardware.usb.UsbInterface;@4259eeb8] for {}
D/UsbSettingsManager( 2733): resolveActivity : permissionsGranted to ResolveInfo{42458358 com.realart.Beatmaker/.UnityPlayerNativeActivity m=0x108000}
D/UsbSettingsManager( 2733): deviceAttached, call to resolveActivity ended, Intent { act=android.hardware.usb.action.USB_DEVICE_ATTACHED flg=0x10000000 cmp=com.realart.Beatmaker/.UnityPlayerNativeActivity (has extras) }
D/UsbHostManager( 2733): onUEvent(device) :: action = add, devtype = usb_interface, device = null, product = 2341/3d/1, type = 2/0/0, interface = 2/2/1, devpath = /devices/platform/exynos-dwc3.0/exynos-xhci.0/usb2/2-1/2-1:1.0
D/UsbHostManager( 2733): onUEvent(device) :: action = add, devtype = usb_interface, device = null, product = 2341/3d/1, type = 2/0/0, interface = 10/0/0, devpath = /devices/platform/exynos-dwc3.0/exynos-xhci.0/usb2/2-1/2-1:1.1
D/Unity   (31983): Device name: /dev/bus/usb/002/081
D/Unity   (31983): Vender id: 9025
D/Unity   (31983): Product id: 61
D/Unity   (31983): Class: class android.hardware.usb.UsbDevice
D/Unity   (31983): Sub-class: 0
D/Unity   (31983): Protocol: 0
D/Unity   (31983): Num Interfaces: 2
D/Unity   (31983): Device hash: 263427241
D/Arduino (31983): USB Interface claimed: true
D/Arduino (31983): Data Transfter setup
D/Arduino (31983): Line state control frame: OK return: 0
D/Arduino (31983): Line encoding control frame: OK return: 7
D/Arduino (31983): I/O Enpoints found
D/Arduino (31983): Arduino bus initialized
D/UsbSettingsManager( 2733): checkPermission: /dev/bus/usb/002/081
D/UsbSettingsManager( 2733): hasPermission++
D/UsbSettingsManager( 2733): hasPermission: UsbDevice[mName=/dev/bus/usb/002/081,mVendorId=9025,mProductId=61,mClass=2,mSubclass=0,mProtocol=0,mInterfaces=[Landroid.hardware.usb.UsbInterface;@4259eeb8]for {10212=true}
D/Arduino (31983): Receiver thread started
D/Unity   (31983): onArduinoReady
D/Arduino (31983): resetArduino
V/Unity   (31983): sending message to unity = reset
V/Unity   (31983): sending message to unity = onArduinoReady

Когда приложение запущено, устройство подключено, а onNewIntent метод вызван, блок запрещенных разрешений срабатывает каждый раз, но подсказка с запросом разрешений никогда не отображается?

У кого-нибудь есть идеи, почему это поведение так странно с этим планшетом?

Модель: SM-T800

Версия для Android: 4.4.2

Версия ядра: 3.4.39-2010469

Номер сборки: KOT49H.T800XXU1ANFB

1 ответ

Решение

В случае, если кто-то сталкивается с этой же проблемой, основной раздел приложения был создан с помощью Unity, и это было состояние гонки между порожденным потоком, который отреагировал на намерение, и Unity при получении интерфейса UsbInterface. При загрузке, поскольку Unity еще не был инициализирован, порожденный поток смог захватить интерфейс в 99% случаев, но после того, как Unity уже был создан, Untiy через JNI смог запросить интерфейс первым. Я не совсем уверен, почему он требовал его в качестве датчика на этом планшете, а не другие, но, вероятно, как-то связано с Exynos 5 Octa 5420 Чипсет и как Unity решает что-то, это сенсор.

Другие вопросы по тегам