1i7 (1i7) wrote,
1i7
1i7

Category:

Chipkit и Ётафон: дополнительно

Завершаем дружить ChipKIT и Yotaphone: в прошлый раз выбрали оборудование, научили ChipKIT и Ётафон видеть друг друга и отправлять друг другу послания. Сейчас несколько завершающих замечаний о работе с акссессуарами на Андроиде, чтобы при случае не хотеть с ними работать так, как они работать не умеют.

О логике работы с аксессуарами в Андроиде

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

Итак, после некоторого количества экспериментов, лично я для себя выделил 2 основных сценария для работы с аксессуарами из приложения:

1) Концепт акрсессуар подключен - экран открыт, аксессуар отключен - экран закрыт. Для запуска приложения нужно полностью положиться на системный диалог подключения аксесуара: пользователь должен или нажимать ОК при каждом подключении или поставить галочку "Использовать по умолчанию для этого USB-аксессуара" и тогда система будет делать это каждый раз за него автоматически. Этот сценарий в целом уже описан выше. Если пользователь нажал кнопку Отмена в диалоге подключения, то нужно считать, что это окончательное решение - при ручном запуске приложения эта ситуация распознается как "нет разрешения для доступа к аксессуару" и в этом случае нужно просто выходить из приложения. При отключении аксессуара тоже выходить из приложения. Чтобы снова получить доступ к аксессуару, просто выдернуть провод и воткнуть заново.

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

Для реализации первого подхода в системе есть все необходимое, и оно уже было показано выше.

Второй подход выглядит более традиционным и на первый взгляд наиболее логичным, т.к. похожим образом релизована работа с другими ресурсами и устройствами, которые в любой момент времени могут быть как доступны, так и не доступны (веб-сервер, подключение вай-фай, сигнал GPS) - работающие с ними приложения в этом случае просто отображают их актуальный статус и корректируют свой функционал. Однако с реализацией этого подхода на практике с текущим API Android Accessory возникает ряд проблем, часть из которых кое-как решается, а часть - нет.

Первая проблема - диалог подключения аксессуара - он будет выскакивать всегда и в любом случае до тех пор, пока пользователь не решит нажать кнопку "Использовать по умолчанию". Но в этом случае, система будет запускать новую копию приложения автоматически при подключении USB-шнура поверх уже открытой. Убрать теги, инициализирующие автозапуск из AndroiManifest.xml, тоже не лучший вариант - вместо диалога подключения будет вылезать другой диалог о неподдерживаемом аксессуаре. Поэтому аккуратно реализовать концепт "пользователь сам решит, когда запускать приложение" не получится - останется фактор назойливого диалога подключения аксессуара или второй копии приложения (хотя еще можно попробовать поиграть с флагами для экранов activity - возможо там есть что-то типа не запускать две копии одного экрана, что может сойти за очередную полумеру).

Вторая проблема - хотя в системе определено событие о подключении аксессуара UsbManager.USB_ACCESSORY_ATTACHED, внутри нашего BroadcastReceiver'а на заранее открытом экране мы его получать не сможем - система просто его не присылает, даже если оно добавлено в IntentFilter. Буквально так: выключить все кнопки на экране автоматически при отключении аксессуара мы можем, а включить их обратно при подключении аксессуара автоматически мы уже не можем - только в ручном режиме через меню или специальную кнопку "подлкючиться к аксессуару" на экране. Работать будет, но с красотой как-то так.

Третяя проблема - разработчики API для работы с аксессуарами по какой-то причине решили не использовать стандартные механизмы разграничения доступа к ресурсам устройства (когда разрешение permission на использование указанного ресурса типа выход в интернет, получение сиглана GPS или отправки СМС прописывается в манифесте приложения AndroidManifest.xml), вместо этого запрос на разрешение доступа к подключенному аксессуару выскакивает в специальном диалоге.

Accessory - request permission.png

Приложение может по своему желанию вызвать этот дилог и запросить доступ к аксессуару в том случае, если в предыдущем автоматическом диалоге пользователь нажал Отмена.



Запрос разрешения на доступ к аксессуару вручную

Всё тот же USBClientActivity.java

Добавим специальную константу для вызова запроса - её разработчики API тоже не озаботились определить в системе.


    // Да, имя этого системного акшена не определено в виде строковой константы
    // в UsbManager разработчиками андроида, сделаем это за них:
    private static final String ACTION_USB_PERMISSION = "com.google.android.DemoKit.action.USB_PERMISSION";


Возвращаемся к методу connectToAccessory - добавляем условную ветку на тот случай, когда разрешения не предоставлено. Делаем запрос через UsbManager.


    /**
     * Пробует подключиться к указанному аксессуару. Открывает канал
     * коммуникации, если все хорошо, при необходимости запрашивает разрешение
     * пользователя.
     *
     * @param accessory
     * аксессуар для подключения
     */
    private void connectToAccessory(UsbAccessory accessory) {
        if (accessory != null) {
            if (usbManager.hasPermission(accessory)) {
                debug("connectToAccessory: has permission => openAccessory");
                openAccessory(accessory);
            } else {
                if (!requestingPermission) {
                    debug("connectToAccessory: no permission => requestPermission");
                    // Нет разрешения открыть аксессуар, запросим у пользователя
                    requestingPermission = true;
                    usbManager.requestPermission(accessory, PendingIntent
                            .getBroadcast(this, 0, new Intent(
                                    ACTION_USB_PERMISSION), 0));
                } else {
                    debug("connectToAccessory: requesting permission => skip");
                }
            }
        } else {
            debug("connectToAccessory: no accessories found");
        }
    }


После этого появится дистемный диалог:

Accessory - request permission.png

Результат действия пользователя в любом случае придет в BroadcastReceiver:


private final BroadcastReceiver usbReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            if (ACTION_USB_PERMISSION.equals(action)) {
                final UsbAccessory accessory = (UsbAccessory) intent
                        .getParcelableExtra(UsbManager.EXTRA_ACCESSORY);
                if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED,
                        false)) {
                    debug("Broadcast: accessory permission granted");

                    openAccessory(accessory);
                } else {
                    debug("Broadcast: permission denied for accessory");
                }
                requestingPermission = false;
                updateViews();
            } else if (UsbManager.ACTION_USB_ACCESSORY_DETACHED.equals(action)) {
                ...
            }
        }
    };


Действие ACTION_USB_PERMISSION должно быть в IntentFilter.


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

        final IntentFilter filter = new IntentFilter(ACTION_USB_PERMISSION);
        ...
        // Судя по всему Android не позволяет принимать сообщения о событии
        // ACTION_USB_ACCESSORY_ATTACHED при помощи BroadcastReceiver,
        // поэтому будем использовать меню "Подключить аксессуар". Другой
        // вариант - закрывать активити каждый раз, когда аксессуар отключен и
        // открывать заново, когда подключен снова.
        // filter.addAction(UsbManager.ACTION_USB_ACCESSORY_ATTACHED);
        registerReceiver(usbReceiver, filter);

        ...
}


В проекте connectToAccessory вызывается в методе onResume (когда экран приложения получает фокус) и через меню "Подключиться к аксессуару" в ручном режиме. Стройной реализации всегда открытого экрана не получилось, но кое-как работает.



подсветка синтаксиса.
Tags: android, chipkit, типовые задачи
Subscribe

  • Post a new comment

    Error

    default userpic

    Your IP address will be recorded 

    When you submit the form an invisible reCAPTCHA check will be performed.
    You must follow the Privacy Policy and Google Terms of use.
  • 0 comments