読者です 読者をやめる 読者になる 読者になる

遥かへのスピードランナー

シリコンバレーでAndroidアプリの開発してます。コンピュータービジョン・3D・アルゴリズム界隈にもたまに出現します。

スマホから送信したNotificationでAndroid Wear上のActivityを開く方法

android android-wear

最近Android Wearをいじっているのでその話題。スマホからWearに対して送信したNotificationで、Android Wear上のActivityを開く方法について書きます。

Google カメラアプリの挙動

先日、Googleのカメラアプリがアップデートされて、Wearからシャッターを切れるようになりました。この挙動がわりと面白くて、スマホ側でカメラアプリを起動するとWearに通知が表示され、その通知をクリックすると、Wear上でシャッターを押すための専用Activityが起動される流れになっています。

f:id:thorikawa:20140712163747p:plain

この挙動を実装しようとすると、なかなか一筋縄ではいきません。現状のNotification APIの仕様ではスマホ側から発行したNotificationは、スマホ側のActivityしか開くことができないからです。

これを解決する方法の一つが、スマホからWearへの通知をNotification APIではなくData APIなどのデータ送受信APIで行い、Wear側でNotification APIを使って通知を発行するという方法です。Googleのカメラアプリもこの方法を使っているようです。

f:id:thorikawa:20140712165848p:plain

実装方法

実際のコードは以下のようになります。(プロジェクト全体はthorikawa/WearNotificationSample · GitHubにあげてあります)


スマホ側では、Data APIを利用してデータ変更を行います。

    PutDataMapRequest dataMapRequest = PutDataMapRequest.create(Constants.NOTIFICATION_PATH);
    dataMapRequest.getDataMap().putString(Constants.NOTIFICATION_TITLE, "This is the title");
    dataMapRequest.getDataMap().putString(Constants.NOTIFICATION_CONTENT, "This is a notification with some text.");
    // Set timestamp so that it always trigger onDataChanged event
    dataMapRequest.getDataMap().putLong(Constants.NOTIFICATION_TIME, System.currentTimeMillis());
    PutDataRequest putDataRequest = dataMapRequest.asPutDataRequest();
    PendingResult<DataApi.DataItemResult> pendingResult = Wearable.DataApi.putDataItem(mGoogleApiClient, putDataRequest);

一方、Wear側ではデータの変更イベントをServiceで受け取るようにします。

AndrodManifest.xmlでデータ変更時に発行されるActionを受け取るServiceを定義します。

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

このServiceはWearableListenerServiceを継承しており、onDataChangedでスマホ側からのデータ変更を検知し、実際にWear上で表示されるNotificationを発行します。その際にaddActionやsetContentIntentなどで、Wear上のActivityに対するPendingIntentを設定してやれば、NotificationをクリックしてWear上でActivityを開くことができます。

public class NotificationUpdateService extends WearableListenerService {
...
    @Override
    public void onDataChanged(DataEventBuffer dataEvents) {
        for (DataEvent dataEvent : dataEvents) {
            if (dataEvent.getType() == DataEvent.TYPE_CHANGED) {
                if (Constants.NOTIFICATION_PATH.equals(dataEvent.getDataItem().getUri().getPath())) {
                    DataMapItem dataMapItem = DataMapItem.fromDataItem(dataEvent.getDataItem());
                    String title = dataMapItem.getDataMap().getString(Constants.NOTIFICATION_TITLE);
                    String content = dataMapItem.getDataMap().getString(Constants.NOTIFICATION_CONTENT);
                    sendNotification(title, content);
                }
            }
        }
    }

    private void sendNotification(String title, String content) {

        // this intent will open the activity when the user taps the "open" action on the notification
        Intent viewIntent = new Intent(this, MyActivity.class);
        PendingIntent pendingViewIntent = PendingIntent.getActivity(this, 0, viewIntent, 0);

        NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
                .setSmallIcon(R.drawable.ic_launcher)
                .setContentTitle(title)
                .setContentText(content)
                .addAction(R.drawable.ic_launcher, "Open", pendingViewIntent)
                .setLocalOnly(true)
                .extend(new NotificationCompat.WearableExtender().setContentAction(0).setHintHideIcon(true));

        Notification notification = builder.build();

        NotificationManagerCompat notificationManagerCompat = NotificationManagerCompat.from(this);
        notificationManagerCompat.notify(notificationId++, notification);
    }
...
}

まとめ

このような感じで、Wearで利用できるAPIは限られていますが、組み合わせによってはまだまだ面白い使い方ができそうです。