Slide 1

Slide 1 text

Walking with Awareness API

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

Awareness API 概要

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

特徴 簡単 高精度 省電力

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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)

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

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}") } }

Slide 13

Slide 13 text

発表以降のアップデート 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を取得可能

Slide 14

Slide 14 text

発表以降のアップデート 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を使う

Slide 15

Slide 15 text

NAVITIMEアプリ 事例

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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())

Slide 21

Slide 21 text

登録されている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") } }

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

電車 → 徒歩の検知 ① 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になってしまう

Slide 26

Slide 26 text

電車 → 徒歩の検知 ② 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

Slide 27

Slide 27 text

電車 → 徒歩の検知 ② 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を登録

Slide 28

Slide 28 text

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秒以内にそのイベントが起こっていたら、乗り物から徒歩 への遷移が起こった

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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