$30 off During Our Annual Pro Sale. View Details »

Walking with Awareness API

Naoki Ishii
January 11, 2018

Walking with Awareness API

Awareness APIを使ってみました。
https://developers.google.com/awareness/

Naoki Ishii

January 11, 2018
Tweet

More Decks by Naoki Ishii

Other Decks in Technology

Transcript

  1. Walking with
    Awareness API

    View Slide

  2. 自己紹介
    ▣ 石井直貴
    ▣ NAVITIME 2010〜
    ▣ Androidアプリ開発
    ▣ NAVITIME, 乗換NAVITIME

    View Slide

  3. 今日話すこと
    ▣ Awareness API概要
    ▣ 既存機能を置き換えてみた
    ▣ まとめ

    View Slide

  4. Awareness API
    概要

    View Slide

  5. ユーザーの周囲の状況に反応する機能
    + + +

    View Slide

  6. Awareness APIとは
    ▣ Google I/O 2016で発表
    ▣ GooglePlayServices
    ▣ ユーザーの周囲の状況
    (Context)に合わせた機能
    を1つのAPIで簡単に提供で
    きる
    ▣ Contextは7種類

    View Slide

  7. Contextのタイプ
    Time 時刻
    Location 位置情報
    Place Place情報、お店の種別など
    Activity ユーザーのアクティビティ情報( 歩いている、走っている、自
    転車に乗っている等)
    Beacons ビーコン
    Headphones ヘッドホン
    Weather 天気

    View Slide

  8. 特徴
    簡単 高精度 省電力

    View Slide

  9. 2種類のAPI
    Fence API
    ユーザーの周囲の状況に反応できるような Fenceを登録
    (WeatherとPlacesはFenceを作成できない)
    Snapshot API
    呼び出し時点での状況を取得

    View Slide

  10. Fence API - 登録
    val duringWalkFence: AwarenessFence
    = DetectedActivityFence.during(DetectedActivityFence.ON_FOOT)
    val headphoneFence: AwarenessFence
    = HeadphoneFence.during(HeadphoneState.PLUGGED_IN)
    Awareness.getFenceClient(this)
    .updateFences(
    FenceUpdateRequest.Builder()
    .addFence(COMBINED_FENCE_KEY, combinedFence,
    FenceReceiver.createPendingIntent(this))
    .build())
    .addOnSuccessListener {
    Log.d(TAG, "Fence was successfully registered.")
    }
    .addOnFailureListener {
    Log.d(TAG, "Fence could not be registered.")
    }
    val combinedFence: AwarenessFence
    = AwarenessFence.and(duringWalkFence, headphoneFence)

    View Slide

  11. Fence API - Receiver
    override fun onReceive(context: Context?, intent: Intent?) {
    val fenceState = FenceState.extract(intent)
    if (fenceState.fenceKey == COMBINED_FENCE_KEY
    && fenceState.currentState == FenceState.TRUE) {
    // Walking with headphone
    showNotification(context)
    }
    }
    FenceState = TRUE, FALSE, UNKNOWN
    FenceStateに変更があった時に broadcast通知がくる
    (Fence条件を満たした時だけではない )
    初回登録時 : UNKNOWN → FALSE or TRUE
    条件を満たした時 : FALSE → TRUE
    条件を満たさなくなった時 : TRUE → FALSE

    View Slide

  12. Snapshot API
    Awareness.getSnapshotClient(this).weather
    .addOnSuccessListener { weatherResponse ->
    Log.d(TAG, "current weather is ${weatherResponse.weather}")
    }
    Awareness.getSnapshotClient(this).places
    .addOnSuccessListener { placesResponse ->
    placesResponse.placeLikelihoods.forEach {
    Log.d(TAG, "place found : ${it.place.name}")
    }
    }

    View Slide

  13. 発表以降のアップデート
    2017/2 version10.2, 11.0
    Timeのアップデート
    ● Time Instant(時間の瞬間)の追加
    ○ Sunrise, Sunset
    ○ Fence API, aroundTimeInstantでFenceを登録可能
    ● Time Interval(時間の間隔)にsemantic timeの追加
    ○ 時間帯 : MORNING, AFTERNOON, NIGHT..
    ○ 曜日: WEEKDAY, WEEKEND, HOLIDAY
    ○ Fence API, inTimeIntervalでFenceを登録可能
    ○ Snapshot API, getTimeIntervalsで今のIntervalを取得可能

    View Slide

  14. 発表以降のアップデート
    2017/2 version11.4.0〜
    ▣ Google Api Clientの使用方法変更
    □ GoogleApiClient経由でのGooglePlayサービス接続の手
    動管理が不要に
    ・参考 : 新しい Location API で負荷を軽減
      https://developers-jp.googleblog.com/2017/07/reduce-friction-with-new-location-apis.html
    ▣ FenceApi/SnapshotApiがdeprecatedに
    ▣ FenceClient/SnapshotClientを使う

    View Slide

  15. NAVITIMEアプリ
    事例

    View Slide

  16. ひと駅歩こう
    ● 混雑緩和機能の1つ
    ● 朝、定期券区間の1駅前で降りて歩くとポイントが貯ま

    View Slide

  17. 課題
    機能を開始させるトリガーの処理が煩雑
    ▣ 複数APIの組み合わせ
    □ AlarmManager, GeoFence, GoogleFit
    ▣ 定期実行

    View Slide

  18. 課題
    バッテリー消費を(そんなに)意識できていない
    Google I/O 2016
    Introducing the Awareness API, an easy way to make your apps context aware より
    GeoFenceとActivityRecgnitionの組み合わせ
    どちらを先に起動させたほうがバッテリー消費が少
    ないかは難しい問題

    View Slide

  19. Awareness APIで置き換える
    ● 7〜10時に、
    ● 渋谷駅周辺で、
    ● 歩いていること
    をトリガーにできれば良い。
    → TimeFence
    → LocationFence
    → DetectedActivityFence

    View Slide

  20. AwarenessAPIを使った実装
    val morningFence: AwarenessFence = TimeFence.inDailyInterval(TimeZone.getDefault(),
    TimeUnit.HOURS.toMillis(7), TimeUnit.HOURS.toMillis(10))
    val stationFence: AwarenessFence = LocationFence.`in`(station.lat, station.lon,
    config.geoFenceRadius.toDouble(), 0L)
    val duringWalkFence: AwarenessFence
    = DetectedActivityFence.during(DetectedActivityFence.ON_FOOT)
    val combinedFence: AwarenessFence
    = AwarenessFence.and(morningFence, stationFence, duringWalkFence)
    Awareness.getFenceClient(this)
    .updateFences(FenceUpdateRequest.Builder()
    .addFence(COMBINED_FENCE_KEY, combinedFence,
    AwarenessCombinedReceiver.createPendingIntent(this))
    .build())

    View Slide

  21. 登録されているFenceの確認
    ▣ adbコマンドで確認できない
    ▣ FenceClient.queryFences
    ▣ FenceKeyとFenceStateを取得できる
    (Fence毎の具体的な設定は取得できない)
    Awareness.getFenceClient(this)
    .queryFences(FenceQueryRequest.all())
    .addOnSuccessListener { queryResponse ->
    queryResponse.fenceStateMap.fenceKeys.forEach { fenceKey ->
    val state = queryResponse.fenceStateMap.getFenceState(fenceKey)
    Log.d(TAG, "fenceKey/state = $fenceKey/$state")
    }
    }

    View Slide

  22. 登録されているFenceの確認
    ▣ 開発用メニューに表示
    ▣ 合成条件と個別の条件を表示

    View Slide

  23. Awareness APIで置き換える
    ▣ できた
    ▣ 簡単
    ▣ 既存と比較して精度
    □ 特に問題なし
    ▣ 既存と比較してバッテリー消費
    □ 調べきれませんでした。。

    View Slide

  24. 電車 → 徒歩の検知
    ▣ 「ジオフェンス内で歩いていたら」
    はできた
    ▣ 「ジオフェンス内で電車から降りて歩き
    始めた」
    ができるとなお良い

    View Slide

  25. 電車 → 徒歩の検知 ①
    val morningFence: AwarenessFence = TimeFence.inDailyInterval(TimeZone.getDefault(),
    TimeUnit.HOURS.toMillis(7), TimeUnit.HOURS.toMillis(10))
    val stationFence: AwarenessFence = LocationFence.`in`(station.lat, station.lon,
    config.geoFenceRadius.toDouble(), 0L)
    val stopVehicleFence: AwarenessFence
    = DetectedActivityFence.stopping(DetectedActivityFence.IN_VEHICLE)
    val startWalkingFence: AwarenessFence
    = DetectedActivityFence.starting(DetectedActivityFence.ON_FOOT)
    val combinedFence: AwarenessFence
    = AwarenessFence.and(morningFence, stationFence, stopVehicleFence, startWalkingFence)
    失敗
    DetectedActivityFence.stopping/startingは瞬間的なイベントのため、
    すぐにFALSEになってしまう

    View Slide

  26. 電車 → 徒歩の検知 ②
    val morningFence: AwarenessFence = TimeFence.inDailyInterval(TimeZone.getDefault(),
    TimeUnit.HOURS.toMillis(7), TimeUnit.HOURS.toMillis(23))
    val stationFence: AwarenessFence = LocationFence.`in`(station.lat, station.lon,
    config.geoFenceRadius.toDouble(), 0L)
    val duringWalkingFence: AwarenessFence
    = DetectedActivityFence.during(DetectedActivityFence.ON_FOOT)
    val combinedFence: AwarenessFence
    = AwarenessFence.and(morningFence, stationFence, duringWalkingFence)
    val stopVehicleFence: AwarenessFence
    = DetectedActivityFence.stopping(DetectedActivityFence.IN_VEHICLE)
    朝、駅周辺、歩いていたら
    の合成Fence
    乗り物から降りた
    のFence

    View Slide

  27. 電車 → 徒歩の検知 ②
    Awareness.getFenceClient(this)
    .updateFences(FenceUpdateRequest.Builder()
    .addFence(COMBINED_FENCE_KEY, combinedFence,
    AwarenessFenceReceiver.createPendingIntent(this))
    .addFence(STOP_VEHICLE_FENCE_KEY, stopVehicleFence,
    AwarenessFenceReceiver.createPendingIntent(this))
    .build())
    2つのFenceを登録

    View Slide

  28. override fun onReceive(context: Context?, intent: Intent?) {
    val fenceState = FenceState.extract(intent)
    if (fenceState.fenceKey == AwarenessOneWalkService.COMBINED_FENCE_KEY
    && fenceState.currentState == FenceState.TRUE) {
    Awareness.getFenceClient(context).queryFences(
    FenceQueryRequest.forFences(AwarenessOneWalkService.STOP_VEHICLE_FENCE_KEY))
    .addOnSuccessListener { fenceQueryResponse ->
    val stopVehicleState = fenceQueryResponse.fenceStateMap
    .getFenceState(AwarenessOneWalkService.STOP_VEHICLE_FENCE_KEY)
    val diffSeconds = TimeUnit.MILLISECONDS.toSeconds(
    System.currentTimeMillis() - stopVehicleState.lastFenceUpdateTimeMillis)
    if (stopVehicleState.currentState == FenceState.FALSE
    && stopVehicleState.previousState == FenceState.TRUE
    && diffSeconds <= 60) {
    // train to walk
    }
    }
    }
    }
    「朝、駅周辺、歩いている」という通知を受けたあとに、
    「乗り物から降りた」という Fenceの状態を取ってきて、 60秒以内にそのイベントが起こっていたら、乗り物から徒歩
    への遷移が起こった

    View Slide

  29. 電車 → 徒歩の検知 ②
    ▣ できた
    ▣ ちょっと複雑
    ▣ Awareness APIという1つのAPIで色々なContextを
    取り扱えるため、試行錯誤がしやすい

    View Slide

  30. まとめ
    ▣ 周囲の状況を複数組み合わせた処理が楽にできる
    ▣ 条件を変更して試行錯誤が容易
    ▣ バッテリー消費の最適化もよしなにやってくれる
    ▣ Fence設定の確認が少ししづらい
    ▣ 便利!
    ▣ 追記:
    □ 動作確認は実際に歩いて行いました
    □ AwarenessAPI自体の挙動確認は、ヘッドホンのイベントを使うと
    楽です(プラグの抜き差しでイベントが発生するため)

    View Slide