Android Wear でActivity付きのカードを表示する

#スマホ側の設定
## Activityの実装
- Android Wearと連携を行う際にGoogleApiClientというものを使います。  
まずその設定方法から

```java
    private GoogleApiClient mGoogleApiClient;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ActionFragment fragment = ActionFragment.newInstance();
        if (savedInstanceState == null) {
            getSupportFragmentManager().beginTransaction()
                    .add(R.id.container, fragment)
                    .commit();
        }

        mGoogleApiClient = new GoogleApiClient.Builder(this)
                .addApi(Wearable.API)
                .addConnectionCallbacks(new GoogleApiClient.ConnectionCallbacks() {
                    @Override
                    public void onConnected(Bundle bundle) {
                        Log.d(LOG_TAG, "Google Api Client connected");
                        Wearable.MessageApi.addListener(mGoogleApiClient,MainActivity.this);
                    }

                    @Override
                    public void onConnectionSuspended(int cause) {
                        // 一時的に切断された際に行いたい処理を記述する
                    }
                }).build();
        mGoogleApiClient.connect();
    }
```

- 適当なFragmentを表示させています。
- mGoogleApiClientはクラスのプロパティとして保持しておいて下さい。  
後で使いまわします。
- addApi(Wearable.API)とすることでスマホ、AndroidWear間のAPIが使えるようになります。
- GoogleApiClientの接続は非同期処理なので、GoogleApiClient.ConnectionCallbacks()をaddConnectionCallbacks()に設定して接続が終わったのを取れるようにします。  
- 接続が終わるとonConnected()が呼ばれますので、そこでAndroidWearからのメッセージを取れるようにListenerをセットします。  
- 今回はActivityにMessageApi.MessageListenerをimplementsしました。  
そうするとonMessageReceived()を実装する必要があります。

```java
    private static final String CLICK_PATH = "/click/wearable";

    @Override
    public void onMessageReceived(MessageEvent messageEvent) {
        if (messageEvent.getPath().equals(CLICK_PATH)) {
            Log.d(LOG_TAG,"mobile onMessageReceived");
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    Toast.makeText(MainActivity.this, "Watchでボタンが押されたよ", Toast.LENGTH_SHORT).show();
                }
            });
        }
    }
```

- これでAndroidWearで送られたメッセージを受け取れるようになります。
- log出しでは味気ないので、トーストを出すようにしました。
- 別スレッドでメッセージが来るのでrunOnUiThread()内で行います。
- if文で確認しているCLICK_PATHとしているのはスマホとAndroidWearがそれぞれ持つ共通pathです。
"/"から始まれば何でも良いので、今回は/click/wearableとています。

- 最後にmGoogleApiClientをFragment内で取れるようにgetterを実装しておきます

```java
    public GoogleApiClient getGoogleApiClient() {
        return mGoogleApiClient;
    }
```

## Fragment実装

- 適当にボタンをおいたFragmentを用意して、OnClickListenerを設定します
- Activityに直接ボタンをおいても問題ありません。何となくFragmentを使いました。

```java
    private static final String COUNT_KEY = "COUNT_KEY";
    private static final String COUNT_PATH = "/count";
    private int mCount = 0;

    @Override
    public void onClick(View v) {
        PutDataMapRequest dataMap = PutDataMapRequest.create(COUNT_PATH);
        dataMap.getDataMap().putInt(COUNT_KEY, ++mCount);
        PutDataRequest request = dataMap.asPutDataRequest();
        PendingResult<DataApi.DataItemResult> pendingResult = Wearable.DataApi
                .putDataItem(((MainActivity) getActivity()).getGoogleApiClient(), request);
        pendingResult.setResultCallback(new ResultCallback<DataApi.DataItemResult>() {
            @Override
            public void onResult(DataApi.DataItemResult dataItemResult) {
                Log.d(LOG_TAG, "count updated:" + mCount);
            }
        });
    }
```

- ボタンを押したら、AndroidWearへインクリメントしたintの値を送るようにします。
- 送ると書きましたが、COUNT_PATHにスマホ・AndroidWear共通の領域にデータを置くイメージです。CLICK_PATHと同じく"/"で始まる必要があります。
- COUNT_KEYをkeyとしてスマホ・AndroidWearどちらからも読み書き可能です。
- pendingResult.setResultCallback()で設定したCallbackはスマホ・AndroidWearどちらであっても書き込み完了時に呼ばれます。

#AndroidWear側の設定

## WearableListenerServiceの実装
- まずWearableListenerServiceをextendsしたクラスを作成します。  
DataLayerListenerServiceとしています。
- onDataChanged()を実装します。何かしらデータの書き込みが行われた際に呼ばれます。

```java
    private static final String COUNT_KEY = "COUNT_KEY";
    private static final String COUNT_PATH = "/count";

    @Override
    public void onDataChanged(DataEventBuffer dataEvents) {
        for (DataEvent event : dataEvents) {
            DataItem dataItem = event.getDataItem();
            if (COUNT_PATH.equals(dataItem.getUri().getPath())) {
                DataMap dataMap = DataMapItem.fromDataItem(dataItem).getDataMap();
                int count = dataMap.getInt(COUNT_KEY);

                Intent intent = new Intent(this, NotificationEmbeddedActivity.class);
                intent.putExtra(NotificationEmbeddedActivity.EXTRA_KEY_COUNT, count);
                PendingIntent pendingIntent =
                        PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);

                Notification secondPage = new Notification.Builder(this)
                        .setSmallIcon(R.mipmap.ic_launcher)
                        .extend(
                                new Notification.WearableExtender()
                                        .setCustomSizePreset(Notification.WearableExtender.SIZE_FULL_SCREEN)
                                        .setDisplayIntent(pendingIntent))
                        .build();

                Notification notification = new Notification.Builder(this)
                        .setSmallIcon(R.mipmap.ic_launcher)
                        .setContentText("メッセージ")
                        .setContentTitle("タイトル")
                        .extend(
                                new Notification.WearableExtender()
                                        .addPage(secondPage)
                                        .setHintHideIcon(true)
                        )
                        .build();

                NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
                notificationManager.notify(1000, notification);

                break;
            }
        }
    }
```

### 今回の肝の部分になりますので、分解して解説します。

```java
@Override
    public void onDataChanged(DataEventBuffer dataEvents) {
        for (DataEvent event : dataEvents) {
            DataItem dataItem = event.getDataItem();
            if (COUNT_PATH.equals(dataItem.getUri().getPath())) {
                DataMap dataMap = DataMapItem.fromDataItem(dataItem).getDataMap();
                int count = dataMap.getInt(COUNT_KEY);
                ....

                ....
                break;
            }
        }
    }
```

- onDataChanged()は複数の書き込みイベントが取れる可能性があるので、スマホ側で指定した共通のpathを使って目的の領域への書き込みか確認を行います。
- 目的の書き込みだった場合は、これまたスマホ側で指定したkeyを使って目的のデータを取得します。
- これでスマホでインクリメントしたintの値が取れます。  
- そのデータを元にカードを作成します。

```java
                ....
                Intent intent = new Intent(this, NotificationEmbeddedActivity.class);
                intent.putExtra(NotificationEmbeddedActivity.EXTRA_KEY_COUNT, count);
                PendingIntent pendingIntent =
                        PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);

                Notification secondPage = new Notification.Builder(this)
                        .setSmallIcon(R.mipmap.ic_launcher)
                        .extend(
                                new Notification.WearableExtender()
                                        .setCustomSizePreset(Notification.WearableExtender.SIZE_FULL_SCREEN)
                                        .setDisplayIntent(pendingIntent))
                        .build();
                ....

```

- 表示したいActivityとデータをセットしたIntentを元にpendingIntentに作成します。
- そして、Notificationを作成します。
  ここで通常addAction()でPendingIntentを指定しますが、extend()でAndroidWear用の処理が入ります。  
Notification.WearableExtender生成し、setDisplayIntent()にPendingIntentをセットすることでActivityが表示されるようになります。
- setCustomSizePreset()という設定もあって、これは表示するActivityの表示サイズを指定します。  
値としては以下のものが存在します。

```java
Notification.WearableExtender.SIZE_DEFAULT
Notification.WearableExtender.SIZE_FULL_SCREEN
Notification.WearableExtender.SIZE_LARGE
Notification.WearableExtender.SIZE_MEDIUM
Notification.WearableExtender.SIZE_SMALL
Notification.WearableExtender.SIZE_XSMALL
```

- 作成したNotificationを実際に送るNotificationにセットします。

```java
                ....
                Notification notification = new Notification.Builder(this)
                        .setSmallIcon(R.mipmap.ic_launcher)
                        .setContentText("メッセージ")
                        .setContentTitle("タイトル")
                        .extend(
                                new Notification.WearableExtender()
                                        .addPage(secondPage)
                                        .setHintHideIcon(true)
                        )
                        .build();

                NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
                notificationManager.notify(1000, notification);
                ....
```

- ここでもextend()に生成したNotification.WearableExtenderを指定します。
- 先ほどのsecondPageはaddPage()で指定しています。
複数のNotificationを指定する場合は、NotificationのListを生成しaddPagesで指定できます。
- そして、Notificationを投げます。ここで投げたものはスマホ側には表示されません。
- ここまでがカード表示までの処理です。

- そして、onDataChanged()が呼ばれるようにするためにDataLayerListenerServiceをAndroidManifest.xmlにてserviceとして登録します。

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

## NotificationEmbeddedActivityの実装

- DataLayerListenerServiceで表示するActivityとして指定しているNotificationEmbeddedActivityを実装します。
- スマホのActivityの内容と同じくmGoogleApiClientを生成します。
- ほとんどスマホ側と同じですが、AndroidWearからはメッセージを送るのでNodeApiと言うものでスマホのidを取得する必要があります。  
以下はmGoogleApiClient生成のaddConnectionCallbacks()で設定したCallBackの中身です。

```java
    @Override
    public void onConnected(Bundle bundle) {
        Wearable.NodeApi.getConnectedNodes(mGoogleApiClient).setResultCallback(new ResultCallback<NodeApi.GetConnectedNodesResult>() {
            @Override
            public void onResult(NodeApi.GetConnectedNodesResult getConnectedNodesResult) {
                if (getConnectedNodesResult.getStatus().isSuccess() && getConnectedNodesResult.getNodes().size() > 0) {
                    mNodeId = getConnectedNodesResult.getNodes().get(0).getId();
                }
            }
        });
    }

    @Override
    public void onConnectionSuspended(int cause) {
        // 一時的に切断された際に行いたい処理を記述する
    }
```

- String型のmNodeIdというクラスのプロパティを用意し、取得したidを保持します。
- 複数Nodeが取れる可能性がありますが、今回はスマホとしか繋いでいないので配列の0番目をそのまま使います。
- 実際にボタンを押してメッセージを送ってみます。

```java
    private static final String CLICK_PATH = "/click/wearable";

    @Override
    public void onClick(View v) {
        Wearable.MessageApi.sendMessage(mGoogleApiClient,mNodeId,CLICK_PATH,null);
    }
```

- スマホ側でも設定したCLICK_PATHと先ほど取得したNodeIdを使ってsendMessage()を行います。  
 第四引数にbyteの配列を入れることでデータも送ることができます。今回は送りません。
- これでスマホ側のonMessageReceived()が呼ばれトーストが表示されます。

- 最後にIntentに設定したデータを取得してTextViewに表示します。

```java
        Intent intent = getIntent();
        int count = intent.getIntExtra(EXTRA_KEY_COUNT, -1);
        if (count >= 0) {
            textView.setText(String.format("count is %d", count));
        } else {
            textView.setText(String.format("Hello world!"));
        }
```

##まとめ
これでスマホ側のボタンを押せば、AndroidWear側にカードが現れ、左にスワイプすることでActivityが表示し、画面上にカウントが出ています。
またAndroidWear側のボタンを押すとスマホ側にトーストが表示ができるようになりました。
提供されているAPIの用途と合ってないような気がしますが、動作確認としては思った通りの動きをしてくれて良かったです。  
次は画像の表示などを試してみたいと思います。