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 с местными трансляциями.
Создание в приложении широковещательного приемника, а не манифеста, позволяет приложению обрабатывать только отдельные события во время его работы. Таким образом, отдельные события отправляются только в приложение, которое работает в данный момент, и не передаются всем приложениям.