AndroidWearとAmazon API GatewayでGithub Follower数を見える化

目的

様々なサービス上のAPIで取得できる数値データ等を一元的に管理した上でAndroidWearに表示したい。
新たに利用したAPIが増えたり、変更された時にメンテナンスコストを低く、セキュリティ面を担保した上でAndroid側のプログラムを簡単な物にするために今年の7月に使えるようになったAmazon API Gatewayを利用する。(日本リージョンはまだ未対応)

関連記事

動作概要図

AndroidWearがインターネット上のデータを取得するには、親機を経由しなければならないため、親機でAmazon API Gatewayへアクセス -> Wearに取得したデータをmessageとして送信という全体像となります。

本来であればwear側から定期的にmobile側へ情報更新要求を出し、wear側へ送信や mobile側からnodeが存在すればwear側へ定期的に送信等処理を実装して完成に至ると思いますが、通信可能なWatchFace作成方法の情報が英語・日本語共に少なかったので、土台部分の実装のみひとまずデモ的に動いたので公開致します。

システム・ネットワーク図資料用 2

Amazon API Gatewayの利用

Android側のアプリケーションでHTTPリクエスト組み立てたり複数のAPI管理が面倒だな、、、と考えた時にAPIをまとめて管理出来て、Android/iOS/Javascript用のAPIアクセスライブラリ/SDKを吐き出してくれる「Amazon API Gateway」が頭をよぎりましたので利用してみました、ほんの触りでプロクシ機能しか使っておりませんが非常に短いコードで行けたので利用しない手は無いと思いました。

これまでイマイチ使い処が見えにくかった Lambda がAPI Gatewayとの組み合わせで色々使えるかなぁと感じております。下記を今後実施してみる予定、、、
API Gateway -> Lambda -> SNS -> Android端末等へ通知/Twilio 等々

Android Wearへのデータ送信

下記方法で mobile側から wearにデータを送信し WatchFaceを更新しています

mobile側処理

  • アプリ起動時にGoogleApiClientを利用可能にする
  • 通信可能な端末を getNodes で取得
  • GoogleApiClient の sendMessage で Wearにメッセージを送信

wear側処理

  • Messageを受信すると DataLayerListenerService の onMessageReceived が呼び出される
  • Preferencesに受信したメッセージ内容を書き込み
  • WatchFace の 描画部分でPreferenceより取得した値を表示

完成品

母艦でRefreshボタンを押すと、弊社の現在のgithub follower数がWatchFaceに表示されます
thumb_IMG_3281_1024

Android Studioでプロジェクトを作成

Mobile / Wear両方のチェックボックスを付けたプロジェクトを作成します。
2つのモジュールを跨ぐクラスの作成については、New Module -> Common等の名前で作成しdependenciesの設定を下記のように行います。

API_Gateway06 API_Gateway07

Amazon API Gatewayの設定

API Gateway の詳細設定は別記事としました下記を参考に下さい
Amazon API Gatewayを利用しAndroidアプリから簡単にWeb上のAPIを叩く方法

下記GitHub APIを API Gatewayに登録して利用しました。
https://api.github.com/users/xxxx

API_Gateway04 API_Gateway05

Android Studioに下記コードを記載

common module

WearConstants.java

package net.skyarch.common;

public class WearConstants {
    public static final String PREFS = "net.skyarch.test";
    public static final String PREFS_KEY_MESSAGE_TEXT = "net.skyarch.test.message";

    public static final String DATA_CHANGED_ACTION = "net.skyarch.test.data.changed";
}

mobile (スマホ/タブレット)

res/layout/activity_main.xmlにButton btRefreshを配置して下さい。

MainActivity.java

package net.skyarch.testapp;

import java.util.Collection;
import java.util.HashSet;

import android.app.Activity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.StrictMode;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;

//Wearとの通信に必要
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.api.GoogleApiClient.ConnectionCallbacks;
import com.google.android.gms.common.api.GoogleApiClient.OnConnectionFailedListener;
import com.google.android.gms.common.api.ResultCallback;
import com.google.android.gms.wearable.DataApi.DataItemResult;
import com.google.android.gms.wearable.MessageApi;
import com.google.android.gms.wearable.MessageEvent;
import com.google.android.gms.wearable.Node;
import com.google.android.gms.wearable.NodeApi;
import com.google.android.gms.wearable.Wearable;

//MobileとWearで共通で利用するクラス
import net.skyarch.common.WearConstants;

//Amazon API Gatewayの利用に必要
import com.amazonaws.mobileconnectors.apigateway.ApiClientFactory;
import net.skyarch.api.SkyarchKPIClient;

public class MainActivity extends Activity implements OnClickListener,
        ConnectionCallbacks, ResultCallback<DataItemResult>,
        OnConnectionFailedListener {

    private final String TAG = "Mobile";

    //Google Play Service
    private GoogleApiClient mGoogleApiClient;

    //Wear Node
    private Collection<String> mNodes;

    //Button
    private Button _btRefresh;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);

        //GoogleApiClientの作成
        mGoogleApiClient = new GoogleApiClient.Builder(this)
                .addApi(Wearable.API)
                .addConnectionCallbacks(this)
                .addOnConnectionFailedListener(this)
                .build();
        mGoogleApiClient.connect();

        //Refreshボタン
        _btRefresh = (Button) findViewById(R.id.btRefresh);
        _btRefresh.setOnClickListener(this);
        _btRefresh.setEnabled(true);
    }

    @Override
    public void onClick(View v) {
        Log.d(TAG, "Clicked");

        switch (v.getId()) {
            case R.id.btRefresh:
                Log.d(TAG, "Refresh Button Pushed");

                new AsyncTask<Void, Void, Void>() {
                    @Override
                    protected Void doInBackground(Void... params) {

                        //Githubから follower数を取得
                        ApiClientFactory factory = new ApiClientFactory();
                        final SkyarchKPIClient client = factory.build(SkyarchKPIClient.class);
                        int followerCount = client.githubGet().getFollowers();

                        Log.d(TAG, "git followers" + Integer.toString(followerCount));

                        //Wear Nodeを取得
                        mNodes = getNodes();

                        //メッセージをWearに送信
                        sendMessageToWear(mNodes,
                                WearConstants.DATA_CHANGED_ACTION,
                                Integer.toString(followerCount));

                        return null;
                    }
                }.execute();
                break;
        }
    }

    @Override
    public void onConnected(Bundle connectionHint) {
        Log.d(TAG, "onConnected:");
    }

    @Override
    public void onConnectionSuspended(int cause) {
        Log.d(TAG, "onConnectionSuspended:");
    }

    @Override
    public void onConnectionFailed(ConnectionResult result) {
        Log.d(TAG, "onConnectionFailed:");
    }

    @Override
    public void onResult(DataItemResult result) {
        Log.d(TAG, "Result:" + result.getStatus().isSuccess());
    }

    /**
     * Wear nodeを取得
     *
     * @return WearNode
     */
    private Collection<String> getNodes() {

        HashSet<String> results = new HashSet<String>();
        NodeApi.GetConnectedNodesResult nodes = Wearable.NodeApi
                .getConnectedNodes(mGoogleApiClient).await();

        String nodesStr = "";
        for (Node node : nodes.getNodes()) {
            Log.d(TAG, "node.getId():" + node.getId());
            nodesStr += node.getId() + ",";
            results.add(node.getId());
        }

        final String nodesTmp = nodesStr;
        runOnUiThread(new Runnable() {
            public void run() {
                //UIの更新
            }
        });
        return results;
    }

    /**
     * Wearにメッセージを送信
     */
    public void sendMessageToWear(Collection<String> nodes,
                                  String action, String message) {

        for (String node : nodes) {
            Wearable.MessageApi.addListener(mGoogleApiClient, new MessageApi.MessageListener() {

                @Override
                public void onMessageReceived(MessageEvent messageEvent) {
                    final String data = new String(messageEvent.getData());

                    Log.d(TAG, "Message received: " + messageEvent);
                    Log.d(TAG, "Data: " + data);

                    runOnUiThread(new Runnable() {
                        public void run() {
                            //UIの更新
                        }
                    });
                }
            });

            MessageApi.SendMessageResult result = Wearable.MessageApi
                    .sendMessage(mGoogleApiClient, node, action,
                            message.getBytes()).await();

            if (!result.getStatus().isSuccess()) {
                Log.d(TAG, "ERROR: failed to send Message: " + result.getStatus());
                runOnUiThread(new Runnable() {
                    public void run() {
                        //UIの更新
                    }
                });
            } else {
                Log.d(TAG, "result.getStatus():" + result.getStatus());
                runOnUiThread(new Runnable() {
                    public void run() {
                        //UIの更新
                    }
                });
            }
        }
    }
}

Manifest

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="net.skyarch.testapp" >

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >

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

        <meta-data android:name="com.google.android.gms.version"
            android:value="@integer/google_play_services_version"/>

        <activity
            android:name=".MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

build.gradle

...
dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    wearApp project(':wear')
    compile 'com.android.support:appcompat-v7:23.0.1'
    compile 'com.google.android.gms:play-services:7.8.0'
    compile project(':common')
}

wear (スマートウォッチ)

res/drawable に test.png を入れて下さい

DataLayerListenerService.java

package net.skyarch.testapp;

import android.content.Intent;
import android.util.Log;

import com.google.android.gms.wearable.MessageEvent;
import com.google.android.gms.wearable.Node;
import com.google.android.gms.wearable.WearableListenerService;

import net.skyarch.common.WearConstants;

public class DataLayerListenerService extends WearableListenerService {
    private static final String TAG = "DataLayerService";

    @Override
    public void onMessageReceived(MessageEvent messageEvent) {
        super.onMessageReceived(messageEvent);

        String strMessage = new String(messageEvent.getData());

        Log.d(TAG, "onMessageReceived");
        Log.d(TAG, strMessage);

        if (strMessage != null) {
            getBaseContext().getSharedPreferences(WearConstants.PREFS, MODE_PRIVATE).edit().putString(WearConstants.PREFS_KEY_MESSAGE_TEXT,
                    strMessage).commit();
            getBaseContext().sendBroadcast(new Intent(WearConstants.DATA_CHANGED_ACTION));
        }
    }

    @Override
    public void onPeerConnected(Node peer) {}

    @Override
    public void onPeerDisconnected(Node peer){}

}

TestWatch.java

package net.skyarch.testapp;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.Typeface;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.wearable.watchface.CanvasWatchFaceService;
import android.support.wearable.watchface.WatchFaceStyle;
import android.text.format.Time;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.WindowInsets;

import net.skyarch.common.WearConstants;

import java.lang.ref.WeakReference;
import java.text.DateFormat;
import java.util.Date;
import java.util.TimeZone;
import java.util.concurrent.TimeUnit;

/**
 * Digital watch face with seconds. In ambient mode, the seconds aren't displayed. On devices with
 * low-bit ambient mode, the text is drawn without anti-aliasing in ambient mode.
 */
public class TestWatch extends CanvasWatchFaceService {
    private static final String TAG = "TestWatch";

    private static final Typeface NORMAL_TYPEFACE =
            Typeface.create(Typeface.SANS_SERIF, Typeface.NORMAL);

    /**
     * Update rate in milliseconds for interactive mode. We update once a second since seconds are
     * displayed in interactive mode.
     */
    private static final long INTERACTIVE_UPDATE_RATE_MS = TimeUnit.SECONDS.toMillis(1);

    /**
     * Handler message id for updating the time periodically in interactive mode.
     */
    private static final int MSG_UPDATE_TIME = 0;

    @Override
    public Engine onCreateEngine() {
        return new Engine();
    }

    private class Engine extends CanvasWatchFaceService.Engine {
        final Handler mUpdateTimeHandler = new EngineHandler(this);

        final BroadcastReceiver mTimeZoneReceiver = new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                mTime.clear(intent.getStringExtra("time-zone"));
                mTime.setToNow();
            }
        };

        boolean mRegisteredTimeZoneReceiver = false;

        Paint mBackgroundPaint;

        //背景に表示するpng
        Bitmap mBackgroundBitmap;

        boolean mAmbient;

        Time mTime;

        //日付
        Paint mTextPaintDate;
        float mXOffsetDate;
        float mYOffsetDate;

        //時刻
        Paint mTextPaintTime;
        float mXOffsetTime;
        float mXOffsetTimeAmbient;
        float mYOffsetTime;

        //mobileアプリからのMessage
        Paint mTextPaintMessage;
        float mXOffsetMessage;
        float mYOffsetMessage;

        /**
         * Whether the display supports fewer bits for each color in ambient mode. When true, we
         * disable anti-aliasing in ambient mode.
         */
        boolean mLowBitAmbient;

        @Override
        public void onCreate(SurfaceHolder holder) {
            super.onCreate(holder);

            setWatchFaceStyle(new WatchFaceStyle.Builder(TestWatch.this)
                    .setCardPeekMode(WatchFaceStyle.PEEK_MODE_VARIABLE)
                    .setBackgroundVisibility(WatchFaceStyle.BACKGROUND_VISIBILITY_INTERRUPTIVE)
                    .setShowSystemUiTime(false)
                    .build());
            Resources resources = TestWatch.this.getResources();
            mYOffsetTime = resources.getDimension(R.dimen.digital_y_offset_time);

            mBackgroundPaint = new Paint();
            mBackgroundPaint.setColor(resources.getColor(R.color.digital_background));

            Drawable backgroundDrawable = resources.getDrawable(R.drawable.test, null);
            mBackgroundBitmap = ((BitmapDrawable) backgroundDrawable).getBitmap();

            //Date
            mTextPaintDate = new Paint();
            mTextPaintDate = createTextPaint(resources.getColor(R.color.digital_text_date));

            //Time
            mTextPaintTime = new Paint();
            mTextPaintTime = createTextPaint(resources.getColor(R.color.digital_text_time));

            //Message
            mTextPaintMessage = new Paint();
            mTextPaintMessage = createTextPaint(resources.getColor(R.color.digital_text_message));

            mTime = new Time();
        }

        @Override
        public void onDestroy() {
            mUpdateTimeHandler.removeMessages(MSG_UPDATE_TIME);
            super.onDestroy();
        }

        private Paint createTextPaint(int textColor) {
            Paint paint = new Paint();
            paint.setColor(textColor);
            paint.setTypeface(NORMAL_TYPEFACE);
            paint.setAntiAlias(true);
            return paint;
        }

        @Override
        public void onVisibilityChanged(boolean visible) {
            super.onVisibilityChanged(visible);

            if (visible) {
                registerReceiver();

                // Update time zone in case it changed while we weren't visible.
                mTime.clear(TimeZone.getDefault().getID());
                mTime.setToNow();
            } else {
                unregisterReceiver();
            }

            // Whether the timer should be running depends on whether we're visible (as well as
            // whether we're in ambient mode), so we may need to start or stop the timer.
            updateTimer();
        }

        private void registerReceiver() {
            if (mRegisteredTimeZoneReceiver) {
                return;
            }
            mRegisteredTimeZoneReceiver = true;
            IntentFilter filter = new IntentFilter(Intent.ACTION_TIMEZONE_CHANGED);
            TestWatch.this.registerReceiver(mTimeZoneReceiver, filter);
        }

        private void unregisterReceiver() {
            if (!mRegisteredTimeZoneReceiver) {
                return;
            }
            mRegisteredTimeZoneReceiver = false;
            TestWatch.this.unregisterReceiver(mTimeZoneReceiver);
        }

        @Override
        public void onApplyWindowInsets(WindowInsets insets) {
            super.onApplyWindowInsets(insets);

            // Load resources that have alternate values for round watches.
            Resources resources = TestWatch.this.getResources();
            boolean isRound = insets.isRound();

            // for Date 時刻の上に表示
            mXOffsetDate = resources.getDimension(isRound
                    ? R.dimen.digital_x_offset_date_round : R.dimen.digital_x_offset_date);
            mYOffsetDate = resources.getDimension(R.dimen.digital_y_offset_date);
            float textSizeDate = resources.getDimension(isRound
                    ? R.dimen.digital_text_size_date_round : R.dimen.digital_text_size_date);
            mTextPaintDate.setTextSize(textSizeDate);

            // for Time
            mXOffsetTime = resources.getDimension(isRound
                    ? R.dimen.digital_x_offset_time_round : R.dimen.digital_x_offset_time);
            mXOffsetTimeAmbient = resources.getDimension(isRound
                    ? R.dimen.digital_x_offset_time_round : R.dimen.digital_x_offset_time) +
                    resources.getDimension(R.dimen.digital_x_offset_time_ambient_shift);
            mYOffsetTime = resources.getDimension(R.dimen.digital_y_offset_time);
            float textSizeTime = resources.getDimension(isRound
                    ? R.dimen.digital_text_size_time_round : R.dimen.digital_text_size_time);
            mTextPaintTime.setTextSize(textSizeTime);

            // for Message
            mXOffsetMessage = resources.getDimension(isRound
                    ? R.dimen.digital_x_offset_message_round : R.dimen.digital_x_offset_message);
            mYOffsetMessage = resources.getDimension(R.dimen.digital_y_offset_message);
            float textSizeMessage = resources.getDimension(isRound
                    ? R.dimen.digital_text_size_message_round : R.dimen.digital_text_size_message);
            mTextPaintMessage.setTextSize(textSizeMessage);
        }

        @Override
        public void onPropertiesChanged(Bundle properties) {
            super.onPropertiesChanged(properties);
            mLowBitAmbient = properties.getBoolean(PROPERTY_LOW_BIT_AMBIENT, false);
        }

        @Override
        public void onTimeTick() {
            super.onTimeTick();
            invalidate();
        }

        @Override
        public void onAmbientModeChanged(boolean inAmbientMode) {
            super.onAmbientModeChanged(inAmbientMode);
            if (mAmbient != inAmbientMode) {
                mAmbient = inAmbientMode;
                if (mLowBitAmbient) {
                    mTextPaintTime.setAntiAlias(!inAmbientMode);
                    mTextPaintDate.setAntiAlias(!inAmbientMode);
                }
                invalidate();
            }

            // Whether the timer should be running depends on whether we're visible (as well as
            // whether we're in ambient mode), so we may need to start or stop the timer.
            updateTimer();
        }

        @Override
        public void onDraw(Canvas canvas, Rect bounds) {
            //draw the background.
            canvas.drawRect(0, 0, bounds.width(), bounds.height(), mBackgroundPaint);
            canvas.drawBitmap(mBackgroundBitmap, bounds.width() / 2 - mBackgroundBitmap.getWidth() / 2, 20, null);

            //draw date
            DateFormat format = android.text.format.DateFormat.getDateFormat(getApplicationContext());
            canvas.drawText(format.format(new Date()), mXOffsetDate, mYOffsetDate, mTextPaintDate);

            //draw H:MM in ambient mode or H:MM:SS in interactive mode.
            mTime.setToNow();

            //draw time
            String strTime = mAmbient
                    ? String.format("%d:%02d", mTime.hour, mTime.minute)
                    : String.format("%d:%02d:%02d", mTime.hour, mTime.minute, mTime.second);
            if (mAmbient) {
                canvas.drawText(strTime, mXOffsetTimeAmbient, mYOffsetTime, mTextPaintTime);
            } else {
                canvas.drawText(strTime, mXOffsetTime, mYOffsetTime, mTextPaintTime);
            }

            //draw message
            String strMessage = TestWatch.this.getSharedPreferences(WearConstants.PREFS, MODE_PRIVATE)
                    .getString(WearConstants.PREFS_KEY_MESSAGE_TEXT, "REFRESH");
            canvas.drawText(strMessage, mXOffsetMessage, mYOffsetMessage, mTextPaintMessage);
        }

        /**
         * Starts the {@link #mUpdateTimeHandler} timer if it should be running and isn't currently
         * or stops it if it shouldn't be running but currently is.
         */
        private void updateTimer() {
            mUpdateTimeHandler.removeMessages(MSG_UPDATE_TIME);
            if (shouldTimerBeRunning()) {
                mUpdateTimeHandler.sendEmptyMessage(MSG_UPDATE_TIME);
            }
        }

        /**
         * Returns whether the {@link #mUpdateTimeHandler} timer should be running. The timer should
         * only run when we're visible and in interactive mode.
         */
        private boolean shouldTimerBeRunning() {
            return isVisible() && !isInAmbientMode();
        }

        /**
         * Handle updating the time periodically in interactive mode.
         */
        private void handleUpdateTimeMessage() {
            invalidate();
            if (shouldTimerBeRunning()) {
                long timeMs = System.currentTimeMillis();
                long delayMs = INTERACTIVE_UPDATE_RATE_MS
                        - (timeMs % INTERACTIVE_UPDATE_RATE_MS);
                mUpdateTimeHandler.sendEmptyMessageDelayed(MSG_UPDATE_TIME, delayMs);
            }
        }
    }

    private static class EngineHandler extends Handler {
        private final WeakReference<TestWatch.Engine> mWeakReference;

        public EngineHandler(TestWatch.Engine reference) {
            mWeakReference = new WeakReference<>(reference);
        }

        @Override
        public void handleMessage(Message msg) {
            TestWatch.Engine engine = mWeakReference.get();
            if (engine != null) {
                switch (msg.what) {
                    case MSG_UPDATE_TIME:
                        engine.handleUpdateTimeMessage();
                        break;
                }
            }
        }
    }
}

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="net.skyarch.testapp" >

    <uses-feature android:name="android.hardware.type.watch" />

    <!-- Required to act as a custom watch face. -->
    <uses-permission android:name="com.google.android.permission.PROVIDE_BACKGROUND" />
    <uses-permission android:name="android.permission.WAKE_LOCK" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:theme="@android:style/Theme.DeviceDefault" >

        <meta-data
            android:name="com.google.android.gms.version"
            android:value="@integer/google_play_services_version" />

        <service android:name=".DataLayerListenerService" >
            <intent-filter>
                <action android:name="com.google.android.gms.wearable.BIND_LISTENER" />
            </intent-filter>
        </service>

        <service
            android:name=".TestWatch"
            android:label="@string/my_digital_name"
            android:permission="android.permission.BIND_WALLPAPER" >
            <meta-data
                android:name="android.service.wallpaper"
                android:resource="@xml/watch_face" />
            <meta-data
                android:name="com.google.android.wearable.watchface.preview"
                android:resource="@drawable/preview_digital" />
            <meta-data
                android:name="com.google.android.wearable.watchface.preview_circular"
                android:resource="@drawable/preview_digital_circular" />

            <intent-filter>
                <action android:name="android.service.wallpaper.WallpaperService" />

                <category android:name="com.google.android.wearable.watchface.category.WATCH_FACE" />
            </intent-filter>
        </service>

        <meta-data
            android:name="com.google.android.gms.version"
            android:value="@integer/google_play_services_version" />
    </application>

</manifest>

res/values/strings.xml

<resources>
    <string name="app_name">TestApp</string>
    <string name="my_digital_name">SkyarchTest</string>
</resources>

res/values/dimens.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <dimen name="digital_text_size_date">10dp</dimen>
    <dimen name="digital_text_size_date_round">10dp</dimen>
    <dimen name="digital_text_size_time">20dp</dimen>
    <dimen name="digital_text_size_time_round">20dp</dimen>
    <dimen name="digital_text_size_message">40dp</dimen>
    <dimen name="digital_text_size_message_round">40dp</dimen>
    <dimen name="digital_x_offset_date">85dp</dimen>
    <dimen name="digital_x_offset_date_round">85dp</dimen>
    <dimen name="digital_y_offset_date">180dp</dimen>
    <dimen name="digital_x_offset_time">70dp</dimen>
    <dimen name="digital_x_offset_time_round">70dp</dimen>
    <dimen name="digital_x_offset_time_ambient_shift">13dp</dimen>
    <dimen name="digital_y_offset_time">200dp</dimen>
    <dimen name="digital_x_offset_message">75dp</dimen>
    <dimen name="digital_x_offset_message_round">75dp</dimen>
    <dimen name="digital_y_offset_message">130dp</dimen>
</resources>

res/values/colors.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="digital_background">#000000</color>
    <color name="digital_text_date">#ffffff</color>
    <color name="digital_text_time">#ffffff</color>
    <color name="digital_text_message">#cc0000</color>
</resources>

参考記事

Amazon API Gatewayの利用方法
http://dev.classmethod.jp/cloud/aws/api-gateway-with-android/

公式ページデータ転送方法について
https://developer.android.com/intl/ja/training/wearables/data-layer/messages.html

天気予報をAndroidWearのWatchFaceに表示
http://swarmnyc.com/whiteboard/how-to-design-and-develop-an-android-watch-face-app-wearables-overview/

Data APIを使ったデータ転送
https://sites.google.com/a/gclue.jp/android-docs/ni-yinkiwear/data-apiwo-shittadeta-zhuan-song

Android Wearのアプリの作り方
http://www.buildinsider.net/mobile/androidwear/02

ありがとうございました!

投稿者プロフィール

takashi
Japan AWS Ambassadors 2023, 2024
開発会社での ASP型WEBサービス企画 / 開発 / サーバ運用 を経て
2010年よりスカイアーチネットワークスに在籍しております

機械化/効率化/システム構築を軸に人に喜んで頂ける物作りが大好きです。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

CAPTCHA


Time limit is exhausted. Please reload CAPTCHA.

ABOUTこの記事をかいた人

Japan AWS Ambassadors 2023, 2024 開発会社での ASP型WEBサービス企画 / 開発 / サーバ運用 を経て 2010年よりスカイアーチネットワークスに在籍しております 機械化/効率化/システム構築を軸に人に喜んで頂ける物作りが大好きです。