7
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

qnoteAdvent Calendar 2015

Day 13

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

Last updated at Posted at 2015-12-12

#スマホ側の設定

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

7
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?