Недавно мне (несказанно поперло) выпал шанс купить 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