Pull to refresh

Пишем приложения для Sony SmartWatch и SmartWatch 2

Reading time 15 min
Views 32K
Sony SmartWatch – достаточно интересный девайс своего времени, разработку под который почему-то обошли стороной на хабре. Ну раз так – давайте исправлять! В качестве примера мы разработаем простое приложение для управлением любым аудио-плеером.

Статья предназначена для тех, кто уже хотя бы минимально знает, с какой стороны держать инструменты для разработки под Android, а так же тех, кто видел те самые часы или читал про них обзоры, и, соответственно, представляет их функционал. Разрабатывать будем сразу под первую и вторую версии SmartWatch.



Установка необходимых библиотек


Запускаем Android SDK Manager и идём в меню Tools -> Manage Add-on Sites



На вкладке User Defined Sites добавляем адрес с SDK под часы:
dl-developer.sonymobile.com/sdk_manager/Sony-Add-on-SDK.xml

На самом деле, данный SDK поддерживает не только часы, но и некоторые другие хитрые устройства от Sony, такие как например Smart Headset… Но нам пока интересны только часы.



И теперь выбираем новые, появившиеся в списке пакеты и устанавливаем их:



Кроме собственно необходимых библиотек, после установки обязательно загляните в папку [директория Android SDK]/sdk/add-ons/addon-sony_add-on_sdk_2_1-sony-16/samples. Там есть примеры использованию абсолютно всех возможностей часиков, мы поговорим только об избранных.

Эмулятор часов


В принципе, разрабатывать под реальные часы гораздо проще и удобнее, но тем не менее, вместе с SDK идёт и эмулятор. Для его использования пойдём в AVD Manager и создадим одно из появившихся в списке новых устройство от Sony, например, Xperia T. Главное, что бы в качестве параметра Target был выбран Sony Add-on SDK.



Теперь, если запустить такое устройство на эмуляцию, то в списке приложений на эмулируемом устройстве можно найти Accessory emulator



Который эмулирует необходимые нам часики (и не только, как уже упоминалось выше).



План проекта


Ну а теперь, что именно мы будем разрабатывать? Как мне кажется, делать всякие hello word скучно, так что напишем приложение для управления плеером! Любым плеером на телефоне. Вот это подходящий масштаб действий. ;)

  • Приложение будет управляться жестами и кликами. Жест справа-налево и обратно – это следующий/предыдущий трек, вверх/вниз – громче/тише. Клик в центре – поставить на паузу/продолжить воспроизведение.
  • Кроме самого экрана приложения реализуем виджет (для часов), который по клику будет вызывать основное окно программы.
  • Сделаем заготовку для экрана настроек приложения – ну просто про запас.
  • Поддерживать оно должно обе версии SmartWatch (первую и вторую, как подсказываем Кэп).


Подключаем библиотеки к проекту в IntelliJ IDEA


Поскольку я использую IntelliJ IDEA, то и пример приводить на ней. Для начала – создадим проект, в качестве версии SDK выбираем вариант от Sony.



Кроме того, для работы мы подключим к проекту пару модулей из той самой папки samples– в частности SmartExtensions/SmartExtensionAPI и SmartExtensions/SmartExtensionUtils. Вторую, теоретически, можно не подключать, и написать всё её содержимое с нуля, но мы, адепты тёмной стороны силы, ценим эффективность и удобство, а желание писать с нуля то, что уже существует нам чуждо. Инструкции по самому подключению я убрал под спойлер, благо там всё просто.

Подключаем библиотеки.
Идём в File -> Project Structure, там – на закладку Modules, кликаем по “плюсику” и выбираем Import Module



Находим папку SmartExtensionAPI:



Дальше ОК и Next->Next->Next до победного конца, как в старые добрые времена.
После чего подключаем к основному проекту добавленный модуль.





Аналогичным образом подключаем и SmartExtensionUtils.


Настраиваем базовые классы и параметры


Начнём с манифеста.

AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="com.smartwatch_habra_demo">
    <uses-sdk android:minSdkVersion="9"
              android:targetSdkVersion="16"/>
    <uses-permission android:name="com.sonyericsson.extras.liveware.aef.EXTENSION_PERMISSION" />

    <application android:label="Демо-приложения для часов для хабра" android:icon="@drawable/icon">

        <activity
                android:name="DemoConfigActivity"
                android:label="Экран с настройками" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
            </intent-filter>
        </activity>

        <service android:name="DemoReceiverService" />

        <receiver
                android:name="DemoExtensionReceiver"
                android:permission="com.sonyericsson.extras.liveware.aef.HOSTAPP_PERMISSION" >
            <intent-filter>

                <!-- Generic extension intents. -->
                <action android:name="com.sonyericsson.extras.liveware.aef.registration.EXTENSION_REGISTER_REQUEST" />
                <action android:name="com.sonyericsson.extras.liveware.aef.registration.ACCESSORY_CONNECTION" />
                <action android:name="android.intent.action.LOCALE_CHANGED" />

                <!-- Notification intents -->
                <action android:name="com.sonyericsson.extras.liveware.aef.notification.VIEW_EVENT_DETAIL" />
                <action android:name="com.sonyericsson.extras.liveware.aef.notification.REFRESH_REQUEST" />

                <!-- Widget intents -->
                <action android:name="com.sonyericsson.extras.aef.widget.START_REFRESH_IMAGE_REQUEST" />
                <action android:name="com.sonyericsson.extras.aef.widget.STOP_REFRESH_IMAGE_REQUEST" />
                <action android:name="com.sonyericsson.extras.aef.widget.ONTOUCH" />
                <action android:name="com.sonyericsson.extras.liveware.extension.util.widget.scheduled.refresh" />

                <!-- Control intents -->
                <action android:name="com.sonyericsson.extras.aef.control.START" />
                <action android:name="com.sonyericsson.extras.aef.control.STOP" />
                <action android:name="com.sonyericsson.extras.aef.control.PAUSE" />
                <action android:name="com.sonyericsson.extras.aef.control.RESUME" />
                <action android:name="com.sonyericsson.extras.aef.control.ERROR" />
                <action android:name="com.sonyericsson.extras.aef.control.KEY_EVENT" />
                <action android:name="com.sonyericsson.extras.aef.control.TOUCH_EVENT" />
                <action android:name="com.sonyericsson.extras.aef.control.SWIPE_EVENT" />
                <action android:name="com.sonyericsson.extras.aef.control.OBJECT_CLICK_EVENT" />
                <action android:name="com.sonyericsson.extras.aef.control.MENU_ITEM_SELECTED" />
            </intent-filter>
        </receiver>
    </application>
</manifest>


Суть происходящего такова: мы создаём в приложении класс, который будет принимать события от часов, передавать их в сервис обработки, который и будет производить некие осмысленные действия. Единственная activity нам нужна для окна настроек, если же таковое нам не нужно – можно было бы выкинуть её совсем.

Класс-receiver совсем простой:
DemoExtensionReceiver.java
public class DemoExtensionReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(final Context context, final Intent intent) {
        intent.setClass(context, DemoReceiverService.class);
        context.startService(intent);
    }
}


Ну а теперь перейдём к самому сервису:
DemoReceiverService.java
public class DemoReceiverService extends ExtensionService {

    public static final String EXTENSION_KEY = "com.smartwatch_habra_demo"; //todo не смог найти в документации подробностей о применимости, так что просто копипастим из примеров по шаблону "пакет.приложения"

    public DemoReceiverService() {
        super(EXTENSION_KEY);
    }

    @Override
    protected RegistrationInformation getRegistrationInformation() {
        return new DemoRegistrationInformation(this);
    }

    @Override
    protected boolean keepRunningWhenConnected() {//нам не нужно постоянно держать сервис работающим
        return false;
    }

    @Override
    public WidgetExtension createWidgetExtension(String hostAppPackageName) { //возвращаем объект виджета
        return new DemoWidget(this,hostAppPackageName);
    }

    @Override
    public ControlExtension createControlExtension(String hostAppPackageName) {//возвращаем объект основной программы
        boolean IsSmartWatch2= DeviceInfoHelper.isSmartWatch2ApiAndScreenDetected(
                this, hostAppPackageName);
        if (IsSmartWatch2){
            return new DemoControl2(this,hostAppPackageName);
        }else{
            return new DemoControl(this,hostAppPackageName);
        }
    }
}


Достаточно лаконично, правда? Ключевые моменты поясняются комментариями, вопросов вроде не должно возникнуть. ControlExtension нам нужен для обработки и рисования основного приложения на часах, WidgetExtension – для тех же целей, но уже для виджета.

А вот RegistrationInformation – это информация для регистрации нашего расширения в программе управления часами так сказать.
DemoRegistrationInformation.java
public class DemoRegistrationInformation extends RegistrationInformation {
    public static final int WIDGET_WIDTH_SMARTWATCH = 128;
    public static final int WIDGET_HEIGHT_SMARTWATCH = 110;

    public static final int CONTROL_WIDTH_SMARTWATCH = 128;
    public static final int CONTROL_HEIGHT_SMARTWATCH = 128;
    public static final int CONTROL_WIDTH_SMARTWATCH_2 = 220;
    public static final int CONTROL_HEIGHT_SMARTWATCH_2 = 176;

    Context mContext;

    protected DemoRegistrationInformation(Context context) {
        if (context == null) {
            throw new IllegalArgumentException("context == null");
        }
        mContext = context;
    }

    @Override
    public ContentValues getExtensionRegistrationConfiguration() {
        String iconHostapp = ExtensionUtils.getUriString(mContext, R.drawable.icon);

        ContentValues values = new ContentValues();
        values.put(Registration.ExtensionColumns.CONFIGURATION_ACTIVITY,DemoConfigActivity.class.getName()); //активити, которое будет отображаться в меню "настройки расширения". Если оно нам не нужно - убираем параметр совсем.
        values.put(Registration.ExtensionColumns.CONFIGURATION_TEXT,"Настройки демо-расширения");//а это текст, отображащийся в качестве пункта меню программы управления часами. Если оно нам не нужно - убираем параметр совсем.

        values.put(Registration.ExtensionColumns.NAME, "Хабра-демо-расширение");//имя, отображаемое в списке приложений внутри программы управления часами
        values.put(Registration.ExtensionColumns.EXTENSION_KEY,DemoReceiverService.EXTENSION_KEY); //уникальный ключ расширения

        values.put(Registration.ExtensionColumns.HOST_APP_ICON_URI, iconHostapp); //иконка в списке приложений в телефоне
        values.put(Registration.ExtensionColumns.EXTENSION_ICON_URI, iconHostapp); //иконка в списке приложений на самих часах, в идеале 48x48
        values.put(Registration.ExtensionColumns.NOTIFICATION_API_VERSION,getRequiredNotificationApiVersion());//нужная версия механизма уведомлений
        values.put(Registration.ExtensionColumns.PACKAGE_NAME, mContext.getPackageName());

        return values;
    }

    @Override
    public int getRequiredNotificationApiVersion() { //нам не нужно управление нотификациями
        return 0;
    }

    @Override
    public int getRequiredSensorApiVersion() { //нам не нужна инфа с сенсоров вроде акселерометра
        return 0;
    }

    //---------------------------------------------
    //всё что нужно для поддержки виджета
    //---------------------------------------------

    @Override
    public boolean isWidgetSizeSupported(final int width, final int height) {
        return (width == WIDGET_WIDTH_SMARTWATCH && height == WIDGET_HEIGHT_SMARTWATCH);
    }

    @Override
    public int getRequiredWidgetApiVersion() { //для поддержки первых часов
        return 1;
    }

    //---------------------------------------------
    //всё что нужно для поддержки контроллера
    //---------------------------------------------

    @Override
    public int getRequiredControlApiVersion() { //для поддержки первых часов
        return 1;
    }

    @Override
    public int getTargetControlApiVersion() { //для поддержки второй версии часов
        return 2;
    }

    @Override
    public boolean isDisplaySizeSupported(int width, int height) {
        return (width == CONTROL_WIDTH_SMARTWATCH_2 && height == CONTROL_HEIGHT_SMARTWATCH_2)
                || (width == CONTROL_WIDTH_SMARTWATCH && height == CONTROL_HEIGHT_SMARTWATCH);
    }
}


Здесь стоит остановиться поподробнее. Дело в том, что скачанное нами API от Sony – универсальное для целой пачки устройств от Sony, и никто не мешает нам написать приложение (расширение), которое может запуститься на всех этих устройствах разом. Или только на избранных из них.

Раз такое дело, нам надо сообщить, какие размеры экранов и версии API для сенсоров, виджетов и т.п. нам нужно поддержать. Нам нужно указать:
  • Поддержка разных сенсоров (акселерометров и т.п.) – getRequiredSensorApiVersion. Нам оно не надо совсем, так что версия API = 0.
  • Нотификации (Notification) — всплывающие сообщения-уведомления; нам они тоже не нужны. Так что в getRequiredNotificationApiVersion снова 0.
  • “Контроллер” – это то самое “обычное окно программы на часах”. Для него нам нужно определить версию. Кроме того, нам придётся указать поддерживаемые размеры экранов первых и вторых часов, и только их, никакие иные устройства нам не нужны. Поэтому передаём:
    • getRequiredControlApiVersion – версию 1 (для поддержки первой версии часов). Если бы передали 2 – поддерживались бы только Smartwatch 2, на первых бы не запустилось.
    • getTargetControlApiVersion – целевая версия API, здесь 2 для опять же поддержки Smartwatch 2
    • isDisplaySizeSupported – получаем размеры экрана устройства и определяем, хотим ли мы запускаться на нём или нет.

  • “Виджет” (Widget) – это изображение в списке виджетов. Аналогично, нужно указать требуемую версию и размеры экрана. Важный момент: вторая версия часов виджеты не поддерживает. Увы.

Плюс пачка параметров в getExtensionRegistrationConfiguration, но там всё понятно из комментариев.

Основное окно программы


Здесь важно осознать следующим момент. На часы в первой версии часов мы можем отправлять только изображения. Картинки. Всё. Ничего больше. Иным способом рисовать мы не можем. Во второй версии появились расширенные контроллеры, но мы-то изначально пишем для поддержки обоих версий, так что только изображения.

Если же вы хотите использовать для рендера возможности Layout, например, отрендерить компоненты – без проблем, но координаты кликов и прочее взаимодействие придётся обрабатывать вручную. Безрадостная перспектива… Но тем не менее. Вот так будет выглядеть наша картинка:



А вот так — код, который за всё ответит:
DemoControl.java
public class DemoControl extends ControlExtension {

    static final Rect buttonStopPlaySmartWatch = new Rect(43, 42, 85, 88);

    public DemoControl(Context context, String hostAppPackageName) {
        super(context, hostAppPackageName);
    }

    @Override
    public void onTouch(final ControlTouchEvent event) {//реакция на клики
        if (event.getAction() == Control.Intents.CLICK_TYPE_SHORT) {
            if (buttonStopPlaySmartWatch.contains(event.getX(), event.getY())){
                MusicBackgroundControlWrapper.TogglePausePlay(mContext);
            }
        }
    }

    @Override
    public void onSwipe(int direction) {//реакция на жесты
        if (direction== Control.Intents.SWIPE_DIRECTION_UP){
            MusicBackgroundControlWrapper.VolumeUp(mContext);
        }
        if (direction==Control.Intents.SWIPE_DIRECTION_DOWN){
            MusicBackgroundControlWrapper.VolumeDown(mContext);
        }
        if (direction==Control.Intents.SWIPE_DIRECTION_LEFT){
            MusicBackgroundControlWrapper.Next(mContext);
        }
        if (direction==Control.Intents.SWIPE_DIRECTION_RIGHT){
            MusicBackgroundControlWrapper.Prev(mContext);
        }
    }

    @Override
    public void onResume() {//рисуем изображение
        Bitmap mPicture = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.control_picture);
        showBitmap(mPicture);
    }
}


Назначение событий onSwipe и onTouch говорят сами за себя, onResume вызывается каждый раз, как оно программы будет видно, например, часы вышли из спячки или была выбрана иконка приложения. В принципе, этого достаточно для большинства взаимодействий с приложением.

MusicBackgroundControlWrapper – это небольшой самописный класс, предназначенный для управления плеером с использованием эмуляции нажатий мультимедийных клавиш. Нормально работает не со всеми плеерами и телефонами, но там где работает – работает на ура. Если знаете лучший способ (с поддержкой Android 2.3 и выше!) – поделитесь пожалуйста в комментариях.

MusicBackgroundControlWrapper.java
public class MusicBackgroundControlWrapper {
    public static void KeyPressDownAndUp(int key,Context context){
        long eventtime = SystemClock.uptimeMillis() - 1;

        Intent downIntent = new Intent(Intent.ACTION_MEDIA_BUTTON, null);
        KeyEvent downEvent = new KeyEvent(eventtime, eventtime,
                KeyEvent.ACTION_DOWN, key, 0);
        downIntent.putExtra(Intent.EXTRA_KEY_EVENT, downEvent);
        context.sendOrderedBroadcast(downIntent, null);

        eventtime++;
        Intent upIntent = new Intent(Intent.ACTION_MEDIA_BUTTON, null);
        KeyEvent upEvent = new KeyEvent(eventtime, eventtime,
                KeyEvent.ACTION_UP, key, 0);
        upIntent.putExtra(Intent.EXTRA_KEY_EVENT, upEvent);
        context.sendOrderedBroadcast(upIntent, null);
    }

    public static void VolumeUp(Context context){
        AudioManager audioManager =(AudioManager)context.getSystemService(Context.AUDIO_SERVICE);

        int max=audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
        int current=audioManager.getStreamVolume(AudioManager.STREAM_MUSIC);

        if (current<max){
            current++;
        }

        audioManager.setStreamVolume(AudioManager.STREAM_MUSIC,
                current,0);
    }

    public static void VolumeDown(Context context){
        AudioManager audioManager =(AudioManager)context.getSystemService(Context.AUDIO_SERVICE);

        int current=audioManager.getStreamVolume(AudioManager.STREAM_MUSIC);

        if (current>0){
            current--;
        }

        audioManager.setStreamVolume(AudioManager.STREAM_MUSIC,
                current,0);
    }

    public static void TogglePausePlay(Context context){
        KeyPressDownAndUp(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE,context);
    }

    public static void Next(Context context){
        KeyPressDownAndUp(KeyEvent.KEYCODE_MEDIA_NEXT, context);
    }

    public static void Prev(Context context){
        KeyPressDownAndUp(KeyEvent.KEYCODE_MEDIA_PREVIOUS, context);
    }
}


Для поддержки второй версии часов мы унаследуем DemoControl2 от DemoControl, с парой изменений – в onResume() будем передавать другое изображение, а в onTouch – проверять иные координаты.

DemoControl2.java
public class DemoControl2 extends DemoControl {
    static final Rect buttonStopPlaySmartWatch2 = new Rect(59, 52, 167, 122);

    public DemoControl2(Context context, String hostAppPackageName) {
        super(context, hostAppPackageName);
    }

    @Override
    public void onTouch(final ControlTouchEvent event) {//реакция на клики
        if (event.getAction() == Control.Intents.CLICK_TYPE_SHORT) {
            if (buttonStopPlaySmartWatch2.contains(event.getX(), event.getY())){
                MusicBackgroundControlWrapper.TogglePausePlay(mContext);
            }
        }
    }

    @Override
    public void onResume() {//рисуем изображение
        Bitmap mPicture = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.control_picture2);
        showBitmap(mPicture);
    }
}


Виджет


Итак, виджет. Каноничный виджет имеет разрешение 92x92 пикселя для первой версии часов и не поддерживаются в принципе для второй. Можно растянуть его и на бОльшие разрешения (вплоть до 128x110), но он тогда будет выбиваться из стилистики и закрывать стандартные элементы управления и индикации.

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

DemoWidget.java
public class DemoWidget extends WidgetExtension {

    public DemoWidget(Context context, String hostAppPackageName) {
        super(context, hostAppPackageName);
    }

    @Override
    public void onStartRefresh() { //Когда виджет становится видимым и/или обновляется.
        showBitmap(new DemoWidgetImage(mContext).getBitmap());
    }

    @Override
    public void onStopRefresh() { //Когда виджет перестаёт быть видимым. Нам ничего не нужно делать, мы и так не обновляем его и не анимируем.
    }

    @Override
    public void onTouch(final int type, final int x, final int y) {
        if (!SmartWatchConst.ACTIVE_WIDGET_TOUCH_AREA.contains(x, y)) { //если кликнули вне иконки приложения - ничего не делаем
            return;
        }

        //по клику (быстрому или долгому) запускаем основное окно программы
        if (type == Widget.Intents.EVENT_TYPE_SHORT_TAP || type==Widget.Intents.EVENT_TYPE_LONG_TAP) {
            Intent intent = new Intent(Control.Intents.CONTROL_START_REQUEST_INTENT);
            intent.putExtra(Control.Intents.EXTRA_AEA_PACKAGE_NAME, mContext.getPackageName());
            intent.setPackage(mHostAppPackageName);
            mContext.sendBroadcast(intent, Registration.HOSTAPP_PERMISSION);
        }
    }
}


Хотя есть там и интересный момент. В комплекте с API, среди утилит есть класс специально для виджетов, самостоятельно рендерящий Layout в картинку. Грех такой возможностью не воспользоваться, хотя бы и в целях обучения. Рендерить будем через класс DemoWidgetImage.

DemoWidgetImage.java
public class DemoWidgetImage extends SmartWatchWidgetImage {

    public DemoWidgetImage(Context context) {
        super(context);
        setInnerLayoutResourceId(R.layout.music_widget_image);
    }

    @Override
    protected void applyInnerLayout(LinearLayout innerLayout) {
        //даже если ничего не делаем с содержимым - переопределить обязаны. Угу.
    }
}


Окно настроек


Ну тут нужно совсем минимум. Поскольку в классе DemoRegistrationInformation мы уже прописали имя активити, то тут нам сейчас остаётся только заполнить её ну хоть чем-то. Даже комментировать не буду. Просто код.

DemoConfigActivity.java
public class DemoConfigActivity extends Activity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.config);
    }
}

config.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:orientation="vertical"
              android:layout_width="match_parent"
              android:layout_height="match_parent">

    <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textAppearance="?android:attr/textAppearanceMedium"
            android:text="Демонстрационный текст"
            android:id="@+id/textView" android:layout_gravity="center_horizontal"/>
</LinearLayout>


Как опубликовать приложение в Google Play


Что бы ваше приложение находилось утилитой управления часами в магазине приложений – нужно добавить в текст описания программы на Google Play:
  • Для поддержки SmartWatch — “LiveWare extension for SmartWatch”
  • Для поддержки SmartWatch 2 – “Smart Connect extension for SmartWatch 2”
  • Если нужны оба – добавляем соответственно обе строки.

Что характерно, установить приложение сможет и человек, у которого самих часов нет. Установить, не запустить и влепить минимальную оценку, да. Привыкайте, это мир Google Play! Но нам ведь не важна оценка, нам важно, что мир становится чуточку лучше, верно…?

Что ещё можно доделать в приложении-примере


  • Окно настроек (сделать, например инвертирование жестов).
  • Более корректный и универсальный способ управления плеерами. В Android 4.4 уже реализован нужный API (Remote controllers кажется называется), а вот для более старых – проблема.
  • Сделать (придумать, найти) автоматический расчет координат для объектов, находящихся на вьюшке. Что бы руками не считать каждый раз, вдруг Sony создаст третьи часы с третьим разрешением.


Результат нашей работы






Исходный код примера из статьи


github.com/Newbilius/smartwatch_habra_demo

Источник в лице сайта Sony


developer.sonymobile.com/knowledge-base/sony-add-on-sdk

И повторюсь, если возникли вопросы по другим фичам часов – смотрите папку examples (полный путь был приведён выше), там есть примеры использования абсолютно всех датчиков и возможностей. Цель этой статьи – дать вам возможность совершить “быстрый старт” и заинтересовать, надеюсь, у меня это получилось сделать.

P.S. Если вам нужно готовое приложение, описанное в этой статье, но нет желание заниматься разработкой – в Google Play уже есть такое, и даже более функциональные — но или платные, или не совсем такие или с подобными же недостатками.

P.P.S. У меня нет на руках второй версии часов, так что всё что о них написано — это информация из примеров или документации, плюс проверка на эмуляторе, на реальных часах второй версии не проверял. Первая же версия часов есть, там всё точно и проверено.
Tags:
Hubs:
+19
Comments 13
Comments Comments 13

Articles