Чак Норрис и 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