Чак Норрис и Google Glass — что общего, а вот…

в 5:00, , рубрики: android, asynctask, Glass Development Kit, Google Glass, java, json, Программирование, метки: , , , , ,

Недавно мне (несказанно поперло) выпал шанс купить Google Glass по Explorers Program. Стоило только заказать, как на следующее утро почтальон разбудил меня стуком в деверь — вам посылка. Не умываясь и не чистя зубы…

Под вечер возникла возможность попрограммировать. Изучив пару-тройку примеров и настроив окружение — я взялся за дело. Возникла идея написать приложение которое будет вытаскивать шутку про Чака Норриса из веб-сервиса, парсить JSON, и читать вслух.


Итак начнем.

Голосовая активация:

Наша шутилка будет активироваться голосовой командой. Для этого определяем сервис в манифесте:

        <service
                android:name="com.chucknorris.JokeService"
                android:icon="@drawable/icon"
                android:label="@string/app_name"
                android:enabled="true"
                android:exported="true">
            <intent-filter>
                <action android:name="com.google.android.glass.action.VOICE_TRIGGER"/>
            </intent-filter>
            <meta-data
                    android:name="com.google.android.glass.VoiceTrigger"
                    android:resource="@xml/voice_trigger_start"/>
        </service>

Ключевым элементом здесь, в отличие от обычных андроид приложений, является VoiceTrigger.

xml/voice_trigger_start выглядит просто:

<?xml version="1.0" encoding="utf-8"?>
<trigger keyword="@string/chuck_norris_joke" />

Ну и наконец, строка, которая задает фразу, по которой в свою очередь наш сервис запустится:

<string name="chuck_norris_joke">say Chuck Norris joke</string>

Примечание: согласно правилам, которые Google выдвигает по отношению к активационным фразам — они должны начинаться с глагола. Мне лично это правило не очень нравится, но сапожник не вправе судить выше сапога. Просто уж больно много фраз будет начинаться с «show», «get», «say», «start»
Более подробно здесь

Теперь, когда мы определили VoiceTrigger, наш сервис будет стартовать, если мы скажем «ok glass, say Chuck Norris joke»
К слову, Activity тоже можно запустить с помощью голосовой команды. Кусочек манифеста будет таковым:

       <activity
         android:name="com.google.android.glass.sample.waveform.WaveformActivity" >
            <intent-filter>
                <action android:name="com.google.android.glass.action.VOICE_TRIGGER" />
            </intent-filter>
            <meta-data android:name="com.google.android.glass.VoiceTrigger"
                android:resource="@xml/voice_trigger_start" />
        </activity>
Начинка сервиса

Очень хочется рассказать о Card и LiveCard, но как-нибудь в другой раз. Наша шутилка не будет показывать ничего, просто будет шутить вслух.

Раз шутку нужно вытащить из интернет-ресурса, то не забудем задекларировать сие намерение в AndroidManifest.xml:

<uses-permission android:name="android.permission.INTERNET"/>

В UI Thread лазить в интернет воспрещается, заведем для этой цели AsyncTask

doInBackground выполняет запрос на веб сервис:

        HttpClient client = new DefaultHttpClient();
        HttpGet getRequest = new HttpGet();

        try {
            // construct a URI object
            getRequest.setURI(new URI(urls[0]));
        } catch (URISyntaxException e) {
            Log.e("URISyntaxException", e.toString());
        }

        // buffer reader to read the response
        BufferedReader in = null;
        // the service response
        HttpResponse response = null;
        try {
            // execute the request
            response = client.execute(getRequest);
        } catch (ClientProtocolException e) {
            Log.e("ClientProtocolException", e.toString());
        } catch (IOException e) {
            Log.e("IO exception", e.toString());
        }

        try {
            in = new BufferedReader(new InputStreamReader(response.getEntity().getContent()));
        } catch (IllegalStateException e) {
            Log.e("IllegalStateException", e.toString());
        } catch (IOException e) {
            Log.e("IO exception", e.toString());
        }
        StringBuffer buff = new StringBuffer("");
        String line = "";
        try {
            while ((line = in.readLine()) != null) {
                buff.append(line);
            }
        } catch (IOException e) {
            Log.e("IO exception", e.toString());
            return e.getMessage();
        }

        try {
            in.close();
        } catch (IOException e) {
            Log.e("IO exception", e.toString());
        }

затем парсит JSON:

        String joke = "";
        try {
            JSONObject jObject = new JSONObject(buff.toString());
            joke = jObject.getJSONObject("value").getString("joke");
        } catch (JSONException e) {
            Log.e("JSON exception", e.toString());
        }

ok glass скажи шось

AsyncTask предоставляет метод onPostExecute. Стало быть, когда запрос выполнен и шутка получена, вытащена из JSON оболочки — самое время произнести ее вслух. Android любезно помогает нам не только распознать речь но и произнести текст с помощью TextToSpeech.

TextToSpeech инициализируем в сервисе и передаем в AsyncTask через параметр конструктора.

        tts = new TextToSpeech(this, new TextToSpeech.OnInitListener(){
            @Override
            public void onInit(int i) {
                  //TODO по-хорошему надо бы подождать инициализации
            }
        });

в onPostExecute вызываем новый метод readOutLoud

    protected void onPostExecute(String joke) {
        if (exception != null) {
            return;
        }
        readOutLoud(joke);
    }

чтение шутки вслух:

    private void readOutLoud(String joke) {
        tts.speak(joke, TextToSpeech.QUEUE_FLUSH, null);
    }
Отладка и загладка

Теперь осталось подключить очки через USB, собрать apk, align, sign, deploy (IDE все сделает за нас). Никакой Activity запускать не нужно.

итак, барабанная дробь
— «ok glass, say Chuck Norris Joke»
— «Chuck Norris can read from an input stream.»

Вместо послесловия

Пользуясь случаем, хочу поблагодарить www.icndb.com/. Я думаю надо бы сделать похожий сервис с шутками про Штирлица. Если будет время — займусь.

Насчет голосового воспроизведения

наверняка есть дополнительные настройки скорости чтения и/или голоса. На OS X они есть.

Возвращаясь к пункту «а что показывать»

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

    private void publishJokeCard(String joke) {
        Card card = new Card(ctx);
        card.setText(joke);
        card.addImage(R.drawable.chuck);

        TimelineManager.from(ctx).insert(card);
    }

Немного больше о карточках — в следующий раз, надо немного разобраться. Пока писал одно приложение — задеплоил на очки — а там уже новая версия SDK. Благо кто-то на форуме поделился опытом, что вышло обновление день назад (на тот момент). Как выглядят карточки можно посмотреть здесь

Картинки

Иконку подготовить не трудно — основное требование — белая, на прозрачном фоне и 50х50 пикселей.

Картинка для карточки — ну в принципе любая, но лучше — где-то треть размера ширины карточки и полный размер высоты карточки.
На последок, пожалуй скажу, что не все шутки одинаково приличны. Чтоб не ввести пользователя в конфуз — лучше добавить к URL "?type=nerdy".

Код

Если кому интересно, код целиком
Официальные примеры здесь

Автор: httpavel

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js