Пишем приложения для Google Glass

Содержание

Несколько дней назад я имел возможность основательно попрактиковаться в разработке приложений для Google Glass. Полученный опыт растеряется со временем, так как пока разрабатывать что-то ещё под «очки» не планирую. Чтобы поделиться пока ещё свежими впечатлениями решил написать этот топик.
Думаю, всем кто интересуется Google Glass известно, что представляет собой программная «начинка» этого гаджета. Да, это Android 4 с адаптированным launcher-ом. Да, в «очках» вполне можно запускать обычные android-приложения, установив их туда через adb. Известно вам наверняка и про Mirror API, который до недавнего времени считался единственным способом официально предоставить свой сервис пользователю Google Glass. Ниже я немного расскажу о использовании этого инструмента. Но главное, о чём хотелось бы рассказать — как писать под Google Glass полноценные android-приложения, используя пока ещё не официальный Glass Development Kit.

Итак, для начала, сделаем себе Google Glass

Если вы не попали в число избранных обладателей революционного гаджета, не отчаивайтесь. Почти настоящий Google Glass вы сможете сделать из своего android-смартфона или планшета, установив на него launcher и несколько сопутствующих apk отсюда. Вы получите полноценный интерфейс с timeline-карточками, нормально работающее распознавание голосовых команд, bluetooth, кое-как работающую камеру (удалось нормально запустить только на Nexus 7) звук и Hangouts в придачу. С навигацией как-то не сложилось, но возможно у вас получится лучше. При первом запуске launcher запросит доступ к вашему аккаунту как обычное приложение. Даём ему права и становимся почти настоящим Glass Explorer-ом. По крайней мере вы сможете себе отправлять timeline-карточки через Mirror API.

Почему Goggle даёт Mirror API только владельцам Google Glass?

Что сделает нормальный программист, получив доступ к новому инструменту? Конечно же, начнёт писать код. Затем — тестировать. А когда багов вроде бы не останется — опубликует так или иначе своё детище. Это нормально везде, только не в Google Glass. На этой платформе пользователь не переключает внимание между реальным и виртуальным миром. Google Glass в этом смысле — уникальный инструмент. Не пользующийся «очками» программист скорее всего не сможет сделать своё приложение достаточно ненавязчивым и одновременно функциональным, особенно поначалу. Пользовательский опыт Glass Explorer-а в полной мере гайдлайнами не заменяется. Вероятно для того чтобы оградить пока ещё крошечное сообщество «носителей» Google Glass от тонны неприятных и навязчивых приложений Google и «прячет» Mirror API.

Но, допустим, доступ у вас есть. Что мы можем делать с его помощью?

Публикуем и подписываемся без гарантии сроков доставки

Основная парадигма интерфейса Google Glass — это Timeline. Справа от «домашнего» экрана с часами и голосовым вводом — бесконечная лента карточек уходящих в прошлое. Все приложения, использующие Mirror API публикуют туда свои карточки в хронологическом порядке и могут подписываться на события, которые с этими карточками происходят.

События пользователь генерирует с помощью элементов меню, привязанных к карточке. Карточка может содержать как предопределённые элементы меню, например «Delete» или «Share» а также определённые приложением. Карточка может содержать вложенные карточки. Схема организации таких «пакетов» достаточно примитивна и не позволяет делать многоуровневые конструкции. Мы назначаем серии карточек один и тот же bundleId а той карточке, что должна быть «обложкой» устанавливаем isBundleCover=true. При этом меню «обложки» становится недоступным. Использовать его снова пользователь сможет только если удалит все вложенные карточки.

Карточки могут располагаться и слева от «домашнего» экрана. Это «закреплённые» карточки. Вы можете попытаться добавить такую карточку через Mirror API, установив свойство isPinned=true но у вас, скорее всего, ничего не выйдет. Mirror API всё равно свалит вашу карточку в общую ленту. Впрочем, решение есть: добавляем в опции меню с action TOGGLE_PINNED и пользователь сам, если сочтёт нужным, закрепит вашу карточку. Обновление карточки уже не будет влиять на её состояние — она так и останется закреплённой пока вы или пользователь не удалите её или пользователь не сделает ей UnPin той же опцией в меню.

Это, понятно, не всё, что вы можете делать с помощью Mirror API. Вы можете добавить пользователю «контакт» вашего приложения, давая тем самым возможность ему шарить вам фото или видео. Карточки могут включать вложения. Есть куча особенностей в формировании внешнего вида этих самых карточек. Оставлю тут только ссылки на пару полезных ресурсов, где всё это вы можете попробовать. APIs Explorer даст вам возможность тренироваться в общении с Mirror API, а playground позволит «подизайнить» карточки.

Важно же данном случае другое: вы НИКАК не сможете сделать с помощью Mirror API интерактивное приложение. Пользователь может что-то сделать в вашем “интерфейсе” но вы не можете быть уверены в том, когда это событие вам доставит Google. Вы можете что-то показать пользователю. Но вы никак не сможете предвидеть, когда пользователь получит ваше “послание”. Большинство великолепных идей приложений просто принципиально не реализуемы с помощью Mirror API. Это надо понимать. И с этим надо смириться.

Как же сделать что-то интерактивное?

И тут нам на помощь приходит Glass Development Kit. Официально он уже разрешён, хотя ещё не опубликован. Google призывает использовать обычный Android SDK. Можно и так, но не стоит забывать о весьма необычных свойствах Google Glass в плане «пользовательского ввода». У нас нет кнопок. Нет touch-панели в привычном нам смысле. То, по чему Glass Explorer-ы «тапают» и «свайпают» понимает только жесты. OnTouch на нём поймать не получится. У нас нет возможности перехватить долгое нажатие а жест сверху вниз зарезервирован и ловится в приложении как onBackPressed. Выручают, как ни странно, сенсоры. Кивок и поворот головы для этого устройства — вполне достойная замена кнопкам. С голосовым вводом, который должен заменять всё, пока не так хорошо как хотелось бы. По крайней мере у меня пока не получилось добавлять свои команды и получать события при их распознавании. Но, возможно, я недостаточно старался и у вас получится лучше.

В общем, делается это как-то так

Находим какое-нибудь нативное приложение для Google Glass, например это. Берём оттуда glasslib.jar, который, предположительно и есть подобие того, что потом будет опубликовано как GDK. Добавляем его в свой проект и получаем возможность манипулировать timline-карточками так же, как и через Mirror API. Только есть два существенных преимущества. Никаких задержек и никаких ограничений. Если вы теперь сделаете карточке isPinned(true), то она послушно станет слева от «домашнего» экрана без всякого участия пользователя. Работаем с Timline через TimlineHelper и обязательно из сервиса. Обычная схема такая: у приложения есть только одно Activity, которое стартует Service при старте и завершается. Также не помешает подписаться на событие загрузки устройства и из BroadcastReceiver-а опять-таки поднимать наш сервис. В Service проверяем есть ли у пользователя карточка нашего приложения (для этого хорошо бы хранить её Id в SharedPreferences) удаляем старую и добавляем новую, опять же сохраняем её Id.

import android.app.Service;
import android.content.ContentResolver;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.IBinder;
import android.preference.PreferenceManager;
import com.google.glass.location.GlassLocationManager;
import com.google.glass.timeline.TimelineHelper;
import com.google.glass.timeline.TimelineProvider;
import com.google.glass.util.SettingsSecure;
import com.google.googlex.glass.common.proto.MenuItem;
import com.google.googlex.glass.common.proto.MenuValue;
import com.google.googlex.glass.common.proto.TimelineItem;

import java.util.UUID;

public class GlassService extends Service {

    private static final String HOME_CARD = "home_card";

    @Override
    public int onStartCommand(Intent intent, int flags, int startid){
        super.onStartCommand(intent, flags, startid);
        GlassLocationManager.init(this);
        SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
        String homeCardId = preferences.getString(HOME_CARD, null);
        TimelineHelper tlHelper = new TimelineHelper();
        ContentResolver cr = getContentResolver();
        if(homeCardId != null){
            // find and delete previous home card
            TimelineItem timelineItem = tlHelper.queryTimelineItem(cr, homeCardId);
            if (timelineItem!=null && !timelineItem.getIsDeleted()) tlHelper.deleteTimelineItem(this, timelineItem);
        }
        // create new home card
        String id = UUID.randomUUID().toString();
        MenuItem delOption = MenuItem.newBuilder().setAction(MenuItem.Action.DELETE).build();
        MenuItem customOption = MenuItem.newBuilder().addValue(MenuValue.newBuilder().setDisplayName("Custom").build()).setAction(MenuItem.Action.BROADCAST).setBroadcastAction("net.multipi.TEST_ACTION").build();
        TimelineItem.Builder builder = tlHelper.createTimelineItemBuilder(this, new SettingsSecure(cr));
        TimelineItem item = builder.setId(id).setText("Hello, world!").setIsPinned(true).addMenuItem(customOption).addMenuItem(delOption).build();
        cr.insert(TimelineProvider.TIMELINE_URI, TimelineHelper.toContentValues(item));
        preferences.edit().putString(HOME_CARD, id).commit();
        return START_NOT_STICKY;
    }

    @Override
    public IBinder onBind(Intent intent){
        return null;
    }
}

Как видно выше, наша карточка снабжена меню из двух пунктов: Delete и Custom. И если первый обрабатывает система, послушно удаляя карточку, то второй бросит нам broadcast, который мы можем поймать и обработать.
Чтобы не останавливаться на банальном «Hello, world» я сделал небольшой проект. Можете использовать его как более расширенный материал для изучения особенностей «нативной» работы с Google Glass. Ну, и, само собой, я всегда готов ответить на вопросы.

Конечно, никто не заставляет нас использовать TimeLine в качестве интерфейса для своего приложения. Мы вполне можем поднять Activity с простенькими элементами управления, научить пользователя обходиться с ними… Для графически насыщенных приложений, например игр, это вообще будет единственным выходом. Но, что касается обычных приложений, их, по-моему, стоит выполнять в «родном» стиле для этой необычной платформы. Тогда они смогут рассчитывать на гораздо более тёплый приём у пользователей.