Edited at
qnoteDay 13

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

More than 3 years have passed since last update.


スマホ側の設定


Activityの実装


  • Android Wearと連携を行う際にGoogleApiClientというものを使います。

    まずその設定方法から

    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()を実装する必要があります。

    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を実装しておきます


    public GoogleApiClient getGoogleApiClient() {

return mGoogleApiClient;
}


Fragment実装


  • 適当にボタンをおいたFragmentを用意して、OnClickListenerを設定します

  • Activityに直接ボタンをおいても問題ありません。何となくFragmentを使いました。

    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()を実装します。何かしらデータの書き込みが行われた際に呼ばれます。

    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;
}
}
}


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

@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の値が取れます。


  • そのデータを元にカードを作成します。

                ....

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の表示サイズを指定します。

    値としては以下のものが存在します。

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にセットします。

                ....

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として登録します。


    <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の中身です。

    @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番目をそのまま使います。

  • 実際にボタンを押してメッセージを送ってみます。

    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に表示します。


        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の用途と合ってないような気がしますが、動作確認としては思った通りの動きをしてくれて良かったです。


次は画像の表示などを試してみたいと思います。