USB-хост Android 3.1 - BroadcastReceiver не получает USB_DEVICE_ATTACHED

Я работал с описанием и примерами для USB-хоста на developer.android.com, чтобы обнаружить подключенные и отключенные USB-устройства.

Если я использую фильтр намерений в файле манифеста для запуска приложения, когда устройство подключено, оно работает отлично: подключается, устройство обнаруживается, android запрашивает разрешение на запуск приложения, информация об устройстве отображается в таблице.

Разрабатываемое мной приложение не должно запускаться / завершаться, только если устройство подключено / отключено (например, в целях управления данными). Также я не хочу, чтобы открытое диалоговое окно появлялось, если приложение уже запущено. Поэтому я решил не начинать действие напрямую, если устройство подключено, а зарегистрировать BroadcastReceiver, который (позднее) должен уведомлять об активности, если устройство подключено / отключено. Этот приемник распознает только действие отсоединения, но не действие присоединения.

Я пропускаю разрешение или атрибут данных или что-то подобное? Учебник и примеры ничего не говорят о дополнительных необходимых атрибутах.

Вот файл манифеста:

<?xml version="1.0" encoding="utf-8"?>
<manifest 
  xmlns:android="http://schemas.android.com/apk/res/android"
  package="de.visira.smartfdr"
  android:versionCode="1"
  android:versionName="1.0">

<uses-sdk android:minSdkVersion="12" />
<uses-feature android:name="android.hardware.usb.host" />

<application android:icon="@drawable/icon" android:label="@string/app_name">


    <receiver android:name=".usb.Detector">
        <intent-filter>
            <action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
            <action android:name="android.hardware.usb.action.USB_DEVICE_DETACHED" />
        </intent-filter>

        <meta-data android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
            android:resource="@xml/device_filter" />
        <meta-data android:name="android.hardware.usb.action.USB_DEVICE_DETACHED"
            android:resource="@xml/device_filter" />
    </receiver>
</application>

И получатель:

public class FDRDetector extends BroadcastReceiver{

@Override
public void onReceive(Context context, Intent intent) {
    String action = intent.getAction();

    Toast.makeText(context, "Action: " + action, 3).show();
            // pops up only if action == DETACHED
}

Я не понимаю, почему тот же фильтр намерений работает, если я использую их в действии, но не в том случае, если они применяются к получателю? Даже если я настрою приемник и отфильтрую код, вложения не распознаются.

Моя рабочая среда: IDE: Eclipse 3.7 с плагином Android

Устройство: Acer Iconia Tab A500

Android: 3.1

заранее спасибо

3 ответа

Ага! Я понял. У меня была точно такая же проблема.

Суть этого в том, что если ваше приложение запускается автоматически при подключении устройства (с использованием файла манифеста), то, похоже, система Android получает намерение ACTION_USB_DEVICE_ATTACHED, а затем, поскольку оно знает, что ваше приложение хочет работать в этой ситуации, он на самом деле отправляет вашему приложению намерение android.intent.action.MAIN. Он никогда не отправляет действие ACTION_USB_DEVICE_ATTACHED вашему приложению, поскольку думает, что оно уже знает, что ваше приложение хочет сделать в этой ситуации.

Я только что определила проблему, и я думаю, что у меня есть решение, но я могу сказать вам, что я нашел:

Даже если ваше приложение работает и на переднем плане, когда вы подключаете USB-устройство и система Android получает намерение ACTION_USB_DEVICE_ATTACHED, оно будет вызывать onResume() в вашей активности.

К сожалению, вы не можете просто сделать это:

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

    Intent intent = getIntent();
    Log.d(TAG, "intent: " + intent);
    String action = intent.getAction();

    if (UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(action)) {
        //do something
    } 
}

Потому что намерение вернется как android.intent.action.MAIN, а не ACTION_USB_DEVICE_ATTACHED.

Досадно, что вы также получаете android.intent.action.MAIN, если просто выходите из приложения, но не отключаете USB. Я полагаю, что перевод устройства в режим сна и его повторное включение сделают то же самое.

Итак, из того, что я обнаружил, вы не можете получить намерение напрямую, но похоже, что вы можете рассчитывать на вызов onResume() при подключении USB-устройства, поэтому решение состоит в том, чтобы просто проверить, является ли USB подключен каждый раз, когда вы получаете onResume. Вы также можете установить флаг, когда USB отключен, потому что, конечно, намерение отключить USB срабатывает очень хорошо.

В общем, ваш приемник вещания может выглядеть так:

// BroadcastReceiver when remove the device USB plug from a USB port  
BroadcastReceiver mUsbReceiver = new BroadcastReceiver() {
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
         if (UsbManager.ACTION_USB_DEVICE_DETACHED.equals(action)) {                
        usbConnected=false;             
        }
    }
};

У вас будет это внутри onCreate:

  // listen for new devices
 IntentFilter filter = new IntentFilter();
 filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);
 registerReceiver(mUsbReceiver, 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" />

У вас будет файл device_filter.xml в папке /res/xml/, который выглядит следующим образом:

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

<resources>
    <usb-device vendor-id="1027" product-id="24577" />  
    <usb-device vendor-id="1118" product-id="688" /> 
</resources>

(конечно, с любыми идентификаторами поставщиков и идентификаторами продуктов, которые вам нужны)

И тогда ваш onCreate выглядит примерно так:

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

    Intent intent = getIntent();
    Log.d(TAG, "intent: " + intent);
    String action = intent.getAction();


    if (usbConnected==false ) {
        //check to see if USB is now connected
    } 
}

У меня нет специального кода для проверки, подключен ли USB, так как я на самом деле еще не углубился в это. Я использую библиотеку, которая просто подключится, если сможет, поэтому для своего приложения я могу просто запустить этот цикл, и я в порядке.

Также, вероятно, важно установить режим запуска вашей активности в манифесте на "singleTask", чтобы предотвратить его повторный запуск, когда он уже запущен, или же подключение к USB-устройству просто запустит второй экземпляр вашего приложения!

Так что весь мой тег активности в моем манифесте выглядит так:

    <activity
        android:label="@string/app_name"
        android:name="com.awitness.common.TorqueTablet"
        android:theme="@android:style/Theme.Holo.NoActionBar.Fullscreen"
        android:screenOrientation="landscape"
        android:configChanges="orientation|keyboardHidden" 
        android:launchMode="singleTask"
        >
        <intent-filter >
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.HOME"/>
        <category android:name="android.intent.category.DEFAULT" />
        <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>

Во всяком случае, я надеюсь, что это кому-то поможет! Я был удивлен, что не смог найти решение для этого уже!

Просто чтобы продолжить из проницательного комментария @Gusdor (+1): я осуществил проверку в onNewIntent() что, как указывает @Gusdor, вызывается, когда ваша деятельность launchMode устанавливается как singleTask или же singleTop, Затем вместо проверки булевых флагов, как предполагает принятый ответ, просто передайте намерение вашему USB-приемнику, используя LocalBroadcastManager, Например,

@Override
protected void onNewIntent(Intent intent) {
    super.onNewIntent(intent);
    if (UsbManager.ACTION_USB_ACCESSORY_ATTACHED.equals(intent.getAction())) {
        LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
    }
}

Затем, где бы вы ни регистрировали свой существующий (системный) широковещательный приемник USB, просто зарегистрируйте того же получателя в экземпляре локального широковещательного менеджера, т.е.

@Override
protected void onResume() {
    super.onResume();
    myContext.registerReceiver(myUsbBroadcastReceiver, myIntent); // system receiver
    LocalBroadcastManager.getInstance(myContext).registerReceiver(myUsbBroadcastReceiver, intent); // local receiver
}

@Override
protected void onPause() {
    super.onResume();
    myContext.unregisterReceiver(myUsbBroadcastReceiver); // system receiver
    LocalBroadcastManager.getInstance(myContext).unregisterReceiver(myUsbBroadcastReceiver); // local receiver
}

Вы можете отправить другую системную трансляцию, а не локальную трансляцию, но я не думаю, что вы сможете использовать действие UsbManager.ACTION_USB_ACCESSORY_ATTACHED (система воспримет это как потенциальную угрозу безопасности), поэтому вам придется определить собственное действие. Ничего страшного, но зачем беспокоиться, тем более, что нет никаких накладных расходов IPC с местными трансляциями.

Создание в приложении широковещательного приемника, а не манифеста, позволяет приложению обрабатывать только отдельные события во время его работы. Таким образом, отдельные события отправляются только в приложение, которое работает в данный момент, и не передаются всем приложениям.

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