2017年12月8日金曜日

Google Assistant, Actions on Google の Google I/O 2017 セッションまとめ

こんにちは、やんざむです。このエントリは Actions on Google Advent Calendar 2017 の8日目です。

Google I/O 2017 の Actions on Google のセッション を見て Bagel のサンプルを試してみたりしていたのですが、しばらく遊ばないでいたら API.AI の名前が変わって DialogFlow になっていました。Google Home が日本でも発売され、いまこそ絶好の Actions on Google 始めどきですね。

最初、11月に新しく増えた機能のことを書こうかと思っていたのですが、Google I/O のセッションのまとめが下書きのまま放置されていたので、この機会に書き上げました!どうぞ!


* 注意: セッション中の API.AI は DialogFlow に置き換えてあります。
* 注意: Google I/O 2017 時点の情報なので、もしかしたら現在変わっている部分があるかもしれません。

Building apps for the Google Assistant



Google Assistant でも ecosystem を作る
ecosystem のキモは Actions on Google という platform
  • 1. Actions Console でプロジェクトを作る
  • 2. DialogFlow を使って自然言語を処理する
  • 3. webhook につなぐ
  • 4. web simulator かデバイスでテストする
  • 5. mobile phone 向けに拡張する
  • 6. Transactions API と integrate する

ユーザーが Google Home に「Ok Googke, talk to Google IO 17」と話す
  ↓
  • Assistant で text に transcribe され
  • NLP(自然言語処理)され
  • Knowledge Graph を使った Ranking を適用し
  • ユーザーのコンテキストを使ってユーザーのクエリを解釈し
  • I/O アプリが適切なサービスだと判別する
  • Assistant は I/Oアプリの DialogFlow agent を起動し、DialogFlow agent は対応する intent (定義済み)がどれか理解する
  • DialogFlow はビジネスロジックを実行するために web hook を呼び出し結果を受け取る
  • 会話のレスポンスを公式化
    • DialogFlow から Assistant に送り返され、ユーザーに出力される
Actions on Google Console
  • Google I/O 2017 で公開
  • metadata と directory listing, ブランディング情報の設定
  • Analytics
  • Simulator
DialogFlow
  • natural language tool
1. Define Intents and Entities
  • intents と entities を定義する
  • intents
    • user query のモデル
    • 構造化された方法でユーザーが何をしたいかを理解できる
  • entities
    • 文字列の構造化されたパケット
    • ユーザーのリクエストから意味を取り出す
2. Writing responses

応答を書く

3. Use Training to improve

基本的なモデルを設定(ユーザーの会話例を入力)したら、DialogFlow で training を使って会話モデルを改善する

「What Android sessions are there?」

list session intent にマッピング

webhook
ListSessions(session_tags.TRACK_ANDROID) const App = require('actions-on-google').ApiAiApp; function listTopicsIntent(app) { GoogleIOAPI.getCategories().then( categories => app.ask("The topics covered are: ${topics}. What do you want to learn?") ) } exports.myApp = function(request, response) { var app = new App({ request, response }); app.handleRequest(listTopicsIntent); }; npm install actions-on-google 4. Web Simulator でテスト

5. mobile 向けに拡張する

API.AI ではビルドインの作成機能があり、 cards, chips, carousels, lists を簡単に定義できる

webhook に request を投げて動的な response を生成することも可能 // screen があるかチェック if(app.hasSuraceCapability(app.SurfaceCapabilities.SCREEN_OUTPUT)) {} app.ask(app.getIncomingRichResponse() .addBasicCard(app.buildBasicCard(sessionData.desc) .setTitle(sessionData.title) .addButton('Google IO Schedule', sessionData.url)) ); } 6 Transaction API
  • Google I/O 2017 で developer preview としてリリース
  • 購入とオーダーが可能
    • seamless, build-in experience を提供
    • payments と identity information の共有
    • receipts の発行による reengagement
1. Basket または shopping card を作成

配送料を決めたり、サービス可能かどうかを判定するために delivery address などが必要になることがあり、これらをリスエストする API が用意されている

2. オーダーの設定が終わったらユーザーに propose して authorization を取得する必要がある
  • propose order API を使う
  • ユーザーに購入を聞いて approve してもらう
3. approve されたらオーダーを confirm する必要がある
  • order が active かどうか知らせ、レシートを送るため
  • confirm order API を使う
4. user's identity
  • OAuth 2 Web server と統合する機能を提供
  • seamless log in flow をユーザーに提供
let order = app.buildOrder() .setCart(app.buildCart() .addLineItems(app.buildLineItem(session.title)) .setNotes(session.description)) .setTotalPrice(app.Transactions.PriceType.ACTUAL, 'USD', 0) app.askForTransactionDescription(order); let orderUpdate = app.buildOrderUpdate(orderId) .setOrderState(app.Transactions.OrderState.CONFIRMED, "Booked!") .setReceipt("io-seat-order-" + orderId) app.tell(app.buildRichResponse() .addSimpleResponse("Thanks! Here's your receipt.") .addOrderUpdate(orderUpdate)); 自分の Assistant App へ繋げるには
  • ソシャルメディアを通してシェアする
  • 自分のサイトやアプリを通してプロモートする
  • Assistant app へのトラフィックが増えるようプレスを奨励する
ユーザーがどのように App を見つけるか
  • 会話中に
    • Explicit triggering(暗黙的なトリガー)
    • Implicit triggering(明示的なトリガー)
  • Assistant Directory
  • シェアされたリンクから




Bringing the Google Assistant to any device



Google Assistant SDK
  • I/Oの3週間前にリリース
  • gRPC API
  • Docs and samples
  • Tools
AIY Voice Kit Google Assistant SDK
  • Hotword support ("Ok, Google ...")
  • Timers and alarms
  • Enhanced device control (Coming soon)
Hotword Library を試すための構成例
  • Respberry Pi 3B
  • One or optimally Two microphone
  • Speacker
  • Internet Connection
Library Calls start() set_microphone_mute(boole is_muted) start_interaction() # To initiate with button instead of hotword Library Events
  • ON_START_FINISHED
  • ON_CONVERSATION_TURN_STARTED
  • ON_CONVERSATION_TURN_TIMEOUT
  • ON_END_OF_UTTERANCE
  • ON_RECOGNIZING_SPEECH_FINISHED
  • ON_RESPONDING_STARTED
  • ON_RESPONDING_FINISHED
  • ON_NO_RESPONSE
  • ON_INTERACTION_FINISHED
  • ON_ALERT_STARTED
  • ON_ALERT_FINISHED
  • ON_ASSISTANT_ERROR
  • ON_MUTED_CHANGE
gRPC API
  • Client/server API - Remote Procedure Call
  • Minimal Processing
  • Minimal Power reqirements
  • Runs on most platforms, OS's Programming Languages
  • Open-Source Sample Code
gRPC API - Bindings for nearly any platform

Runs on nearly any platform with a TCP/IP socket.
  • Bi-directional streaming:
    • streams audio up and streams audio down - fo minimal latency
  • Robust transport over HTTPS/2
  • Open protobuf description on Github
  • gRPC is free and open-source
    • Platforms: Linux, MacOS, Windows, Android, iOS
    • Languages: C/C++/C#, Go, Python, Node.js, Ruby, and more
    • Open toolchain to generate bindings in any language
phone に 「tell my oven to set the temperature to 425」(オーブンを220℃にセットして)と言うより、直接オーブンまで行ってボタンを押して「set the temperature to 425」(220℃にセット)と言うほうがいい

Connect channel_factory = ChannelFactory(api_host, credentials) service = EmbeddedAssistantStub(channel_factory.make_channel()) response_stream = service.Converse(request_stream(), DEADLINE_SECS) return handle_response_stream(response_stream) Configure def _create_config_request(self): audio_in_config = embedded_assistant_pb2.AudioInConfig( encoding='LINEAR16', sample_rate_hertz=16000, ) audio_out_config = embedded_assistant_pb2.AudioOutConfig( encoding='OPUS_IN_OGG', sample_rate_hertz=22050, ) converse_config = embedded_assistant_pb2.ConverseConfig( audio_in_config=audio_in_config, audio_out_config=audio_out_config, ) return embedded_assistant_pb2.ConverseRequest(config=converse_config) Send Config and Streaming Audio def request_stream(self): yield self._create_config_request() while True: data = self._audio_queue.get() if not data: return yield self._create_audio_request(data) Receive and Process Streaming Results def _handle_response_stream(self, response_stream): for resp in response_stream: self._response_audio += resp.audio_out.audio_data if resp.event_type =- ConverseResponse.END_OF_UTTERANCE: self._end_audio_request() if resp.result.mucrophone_mode: _dialog_follow_on = (resp.result.microphone_mode == ConverseResult.DIALOG_FOLLOW_ON) Neural Beamforming
  • One, or optimally Two microphones
  • Server-side Neural Beamforming
    • Farfield
    • Noise robust
    • Machine Learning
  • Minimal client-side processing and cost
Add the Assistant to your Device



Building Rich Cross-Platform Conversational UX with API.AI (Google I/O '17)



DialogFlow(Google I/O 2017 時点)
  • 150K+ developers
  • 14 languages
  • 32 platform SDKs and integrations
2 main concepts
  • Intents : サポートしたい action の種類
  • Entities : Intents で使われる objects
pre-build Intents
  • Default Fallback Intent : システムが理解できない場合 Fallback Intent がトリガーされる
  • Default Welcome Intent : agent が開始されるときにトリガーされる
chatbase.com
  • analytics service
  • request early access
What's next



Defining Multimodal Interactions: One Size Does Not Fit All (Google I/O '17)

このセッションは音声レスポンスの Good, NG 例が紹介されていてよかったです。TV と Auto の話もあります。



マルチモダリティ (multimodality) に影響する要因
  • 動き (Motion)
  • 環境 (Environment)
  • 近さ (Proximity)
  • 音声機能 (Audio capability)
  • 視覚的機能 (Visual capability)
Actions on Google Home Guidelines
  • 読まない、聞く
  • 情報の過負荷を避ける
  • 質問に答える
Guidelines for Phone
  • あるモードが終わったら、別のがそれを引き継ぐ
  • 一番強いモードに最適化するが、両方を許可する
  • 各モードの強みを活かす
  • モダリティ間に冗長性を持たせる
Take-Aways (TV, Auto)
  • 各モードの強みと弱みを知る
  • 一番強いモードに最適化するが、両方を許可する
  • 読まない、聞く
  • 短く、良いものにする
Future Considerations
    - ユーザーの環境と条件への自動的な適応 - 複数の Surface (Home, Phone ...) 体験の関連 - より複雑なマルチモーダルインタラクション




Finding the Right Voice Interactions for Your App (Google I/O '17)

会話デザインの話です。例がたくさん出てきてとてもよかったです。



なぜ音声なのか?
  • 速度
  • 簡易・簡潔
  • 普遍性 (Ubiquity)
15 * 24 を計算するのに phone でやるなら何回タップする? phone を取り出してアプリを選択して....
音声は究極的に便利

会話デザインの戦略
  • 1. ユーザーと関連する日常の言語を使う
  • 2. 簡単に答えられる質問をする
  • 3. 簡単に再呼び出しできるような情報構造
技術的に可能なこと
  • ユーザーが言ったことを認識する
  • ユーザーが意図していることを理解する
認識エラーはかなり下がっている
Keynoteでは deep learning の適用によって 4.9% になってことが発表された
一方で、ユーザーが意図していることが何かを"理解"することはより難しい

例1) What's the weather in Springfield?

Springfield はメジャーな街の名前、あちこちにある
ユーザーはどこの Springfield のことを言っている?
正しく理解するにはコンテキストを適用しないといけない
例えば、ユーザーはミズーリ州の Springfield に住んでいるとか
でもこれは正しくない可能性がある
もし Springfield に住んでいるならこの聞き方はおかしい
what's the weather here とか what's the weather like today のように聞くだろう
他の Springfield のことを意味している可能性がある

例2) Play Yesterday.

Yesterday は Song? Movie? Playlist? Audiobook? game の可能性もある
同じようにコンテキストとかユーザーについて知っていることを適用して、song を探しているだろうとする

でもここで別の問題が出てくる
Yeasterday は
Original version by The Beatles?
Cover version by Boyz II Men?
Some other cover version ?
他にもいろいろある、どれかなんてわからない


いくつか戦略がある
acknowledge ambiguity あいまいさを認めよう

会話デザインの戦略
  • 1. あいまいさを認め、ユーザーに聞く
  • 2. ユーザーの選択を覚えておいて、次回に活用する
あいまいなときはユーザーに聞こう
余計なステップではと思うかもしれないが、間違いを犯すよりはステップを経るほうがよりよい

可能ならユーザーの選択を覚えておいて次回に生かす

Users in context
  • 手が離せない (Hands-busy)
  • 目が離せない (Eyes-busy)
  • マルチタスク中
  • プライベートな場所にいる
  • 家族と共有している場所にいる
voice は hiro になる
  • 教える必要がない (Instant experts)
  • 高い予想
  • 許容誤差が小さい
ユーザーが無意味なことを言わない限り、エラーは常にシステム側の問題

会話デザインの戦略
  • 1. 例外に対する戦略を開発するのに時間をかける
  • 2. ユーザーが元のフロー (track) にすごく簡単に戻ってこれるようにする
  • 3. 日常の会話で使用するテクニックを活用する
日付を聞いて 4/14 と答えられたとき、もし年も指定してほしいなら、エラーにするのではなく 4/14 を受け付けた上で年を聞く

機会を活用する例

ユーザーの視点
  • 自分の質問に答えてほしい
  • 何かするのを助けてほしい
  • トラック上にキープしてほしい
  • やることを教えてほしい
開発者の視点
  • 素早い回答
  • ひっかかりのないやりとり
  • 関連する提案
例)Financial

"What's my current balance?"
"How much do I owe?"
"What was my last transaction?"
"I need to make a payment"
"Transfer $100 to Joe's account"
"Whan does my policy expire?"

例) Retail

"Is my order on the way?"
"What's your return policy?"
"Do you do same-day delivery?"
"Do you carry milk?"
"Any headphones on sale?"
"I need to re-order cat food"

例)Healthcare
"Is my prescription ready?"
"Can I get a flu shot?"
"Any appointments available?"
"I need to schedule a follow up"
"Leave a note for my doctor"
"Refill my prescription"

例)Fun & Wellness

"What's my horoscope?"
"Got any good dad jokes?"
"Read me a story"
"Let's play a game"
"I need to meditate"

g.co/dev/ActionsDesign



Home Automation with the Google Assistant (Google I/O '17)



Vision : Google Assistant は IoTデバイスとのインテリジェントなやりとりを促進する中心的存在になる

Conversations > Commands
コマンドを覚えるのではなく、人に話すように操作できる

Home Graph
家のなかの device の contextual data を保存し Google Assistant に提供する

Structure
  • Address
  • Managers
  • Rooms
    • Labels
    • Devices
  • Labels
  • Devices
    • Type
    • Traits
    • Attributes
    • Labels
    • State
「dim the lights in the living room a little bit」 と言われたとき、Home Graph ではこうする
  • living room に light はある? - ある
  • light は点いてる ? - 点いてる
  • 現在の明るさは? - 50%
  • little bit ってどのくらい? - デフォルトの設定による、little bit や a lot を support してるかも、いずれにしろ値を決める、例えば 3%
  • Google Assistant に最終的なデータ(47%にする)を渡す
  • Google Assistant が light の明るさを変更する
Smart Home Apps
  • Actions on Google を使って build
  • device を直接 Google Graph に登録
Smart Home partners start with hardware
  • パートナーはクラウドサービスの薄いレイヤーを追加する
  • Google が言語の理解、Home Graph、各種デバイスの詳細なハンドリングを提供する


2つのフローがある

device 登録フロー
  • 1. アプリを登録すると、Smart Home Apps の一覧で出てくるようになる
  • 2. ユーザーがそれをクリックしたら、アプリの OAuth registration を呼び出す
  • 3. 戻ってきたら device に関するリクエストを送るので、ユーザーが持っている全てのデバイスの情報を JSON で返す
  • 4. 状態は Home Graph で持つのでアプリ側で持つ必要はない
実行フロー
  • 1. ユーザーの音声入力を解析し、結果を単純な JSON にして Agent に送る
  • 2. Agent が IoT デバイスを操作し、結果を返す

基本的な device type を用意しており、より多くの device type が今後数週間から数ヶ月で作られる

これらの device type は traits で構成される
traits は実際に行われる機能
例えば最も簡単な trait は on/off





Getting Your Assistant App Discovered (Google I/O '17)

Assistant App をユーザーに見つけてもらうようにするには、という話



g.co./dev/ActionsDesign/

Checklist: Your calls to action
  • ブランド認定を行う : ウェブサイトやアプリ (soon) と関連づける
  • 有益なディレクトリリストを作成する : 実行可能な会話の例を出す
  • フレーズを追加する
  • 人々がまた使いたいと思うアプリを作る!




In Conversation, There Are No Errors (Google I/O '17)

14:15 からのデモがよいです。エラー時に何が起こるかがよくわかります。



エラーを防ぐために質問を工夫する

DialogFlow では agent は intent で構成される
intent はユーザー特定の入力とコンテキストに対応する

Repeat - 「Pardon?」
Help - 「I'm not sure.」
Quit - 「I have to go!」

なんどもエラーのやり取りを繰り返すとユーザーはすぐに離脱してしまう
エラーを防ぐように準備すること

I don't know は通常は Help に割り当てられるべきだが、予想ゲームをしているときは give up と解釈するべき

Conversation Helpers
  • AskForSignIn
  • AskForOption
  • AskForDateTime
  • AskForConfirmation
  • Transactions related system intents (delivery address, payments)
No Input時のレスポンスのパターンは複数用意する
DialogFlow 以外にも sdk で複数指定することもできる

必要に応じて default fallback intent を書き換える
より context に沿った内容に

fallback intent に到達した回数を数えて、prompt の内容を変える
もし回数が閾値(ユーザーペルソナによる)を超えたら会話を終了する

ペルソナを作るべき、すごく役に立つ

コンテキストを維持して流れを強化する
  • ランダムな prompt のリストを作る
  • 連結を駆使して多くの選択肢を用意する
  • 動的な値を活用する
  • 必要に応じてトラッキングとスキップを活用し、繰り返しを避ける
  • エラーの数や種類を覚えておいて prompt を調整する
DialogFlow の Training 機能をチェックする
handle できなかった query を intent に割り当てることができる

number genie のサンプルをチェックする



Transactions with the Google Assistant (Google I/O '17)

Google Assistant (Actions on Google) で Transaction を行う話です。途中でコードサンプルがたくさんでてきます。



優秀な Assistant というのは情報を与えるだけではなく、get things done を助ける
そのために ecosystem のパートナーが必要だった

Assistant での transaction とは何か?
food delivery やコンサートチケットを購入できる
美容院やレストラン、ヨガクラスなど、予定の予約ができる

なぜこれが重要なのか
  • ユーザーにとって良いから
    • 情報と行動のギャップを埋める
  • 開発者にとって良いから
    • 新しいユーザーが見つけられる
    • 既存のユーザーを re-engage できる
    • Actions on Google app でマネタイズできる
購入は敷居が高い
ユーザーが脱落するハードルがたくさんある
2つの大きなポイントが payments と identity

お店に行って買うとする
ほしいものが売り切れていたり、少し前にセールしていたかもしれない

アプリで買うとする
アプリをダウンロードしないといけない
支払い情報、住所な電話番号なども入力しないといけない
最後にカートに入れて支払いをする
悪くないけど、これをアプリごとにやるとしたら大変
デバイスを変更したらその度にダウンロードしてサインインしないといけない

Google はすでにユーザーの payment と identity 情報を持っているので、その情報を渡すことで transaction を容易にすることができる
ハードルが下がるということは、より多くの購入につながる

3つの主な機能
  • 1. payments
  • 2. identity
  • 3. Re-engagement

Payments

1/3のユーザーがクレジットカードの入力中に購入を諦める

Google Play で買い物をしたことがあるユーザーならクレジットカード情報が Google Account に登録されている
Google は Assistant での transaction 中に payment 情報を安全に渡すことができる

処理の流れ(以下でのアプリは自分の Assistant app のこと)
  • 1. 特定のユーザーに特定の額のチャージが必要なことをアプリから Google に伝える
  • 2. Google はアプリの payment prosessor's key を使って生の credit card 情報を暗号化する(payment prosessor's key はアプリ specific)。これにより暗号化された payment credential が生成される
  • 3. Google は暗号化された payment credential をアプリに渡す
  • 4. アプリは通常の debit card やクレジットカードのように credential に対して課金する
Google-facilitated payments
  • Convenient
    • ユーザーは single tap で支払いできる
  • Free
    • Google はこれに関して手数料を取らない
    • アプリは payment processor に対する通常の processing fee だけ払えばよい
  • Lightweight
    • payment credential が必要なのは runtime 時だけ
    • 通常の credit card と同じように課金できる
Payment processors
  • Supported
    • stripe
    • Braintree
    • vantiv
  • Coming soon(in the next several months)
    • ACI UNIVERSAL PAYMENTS
    • adyen
    • First Data
    • worldpay
Google-facilitated payments は完全に optional
もし顧客がすでに支払い方法を持っている場合(例えば会員カードやポイント)、それらを使うこともできる


Identity

54%のユーザーがアカウント登録処理で離脱する
ログイン情報を忘れたユーザーの90%が離脱する


Assistant App でログインまたはアカウントの作成が必要になった場合、Google はユーザーの identity 情報をもっているので、それを使って簡単にログイン・アカウントの作成を行うことができる Google アカウントの選択と確認の2タップで終わる
eye-free, hands-free なシナリオでも可能、Google Home は Link されているユーザーがいればそれを使うので Google アカウントの選択をスキップできる

ユーザーが Google アカウントを選択したら、Assistant App に identity token を送る
token にはユーザー名やメールアドレスなどの情報が含まれる
アプリはこれらの情報を使って対応するアカウントが存在しているかチェックする
アカウントがない場合、ユーザーの Google identity を使って作成することができる
Google Sign in と同じような処理 アカウントがすでにある場合、アカウント作成プロセスを完了するために追加の OAuth 2 script を許可するか、任意でユーザーに聞くことができる


Re-engagement

transaction はユーザーが支払ったときに終わるわけではない
ユーザーはオーダーに対して質問があるかもしれない
今どこにある?現在のステータスは?配送される前に他のものを追加できる?
ユーザーはオーダーに対して何か行動したいかもしれない
アップグレードしたい、ドライバーと話したい、金曜日に変更したい

Google Assistant が全ての transaction 履歴を一箇所で管理する
ユーザーの follow-up アクションへの導線をわかりやすくできる
オーダーの update を提供できる
ユーザーはそのためのアプリをいれたり、メールを探さなくてよくなる

Transaction Helpers
  • buildCart()
  • askForDeliveryAddress()
  • getDeliveryAddress()
  • buildOrder()
  • askForTransactionDecision()
  • getTransactionDecision()
  • buildOrderUpdate()
  • getUser()
  • askForSignIn()
  • getSignInStatus()




2017年11月23日木曜日

KOIN 使ってみた

KOIN は Android 向けのシンプルな Dependency Injection フレームワークです。Kotlin の機能を使って DI を実現しています(proxy/CGLib なし、コード生成なし、introspection(リフレクションとかバイドコードいじりとか)なし)。

使い方

interface Heater interface Pump class ElectricHeater : Heater class Thermosiphon(private val heater: Heater) : Pump class CoffeeMaker(val heater:Heater, val pump:Pump)

1. dependency

implementation 'org.koin:koin-android:0.6.0' testImplementation 'org.koin:koin-test:0.6.0'

2. AndroidModule を継承したクラスを用意し、context() メソッドを実装する

この Context は Android の Context ではなくて org.koin.dsl.context.Context です。 Context の applicationContext() を使って構成します。 class DripCoffeeModule : AndroidModule() { override fun context(): Context { return applicationContext { provide { ElectricHeater() } bind Heater::class provide { Thermosiphon(get()) } bind Pump::class provide { CoffeeMaker(get(), get()) } } } } applicationContext() は name として Scope.ROOT を指定した Context を生成します。
fun applicationContext(init: Context.() -> Unit) = Context(Scope.ROOT, koinContext).apply(init) provide はデフォルトでは singleton になります。singleton にしない場合は isSingleton に false を指定するか、provideFactory を使います。 provide(isSingleton = false) { CoffeeMaker(get(), get()) } provideFactory { CoffeeMaker(get(), get()) } provide で name を指定することもできます。name を変えることで、同じ型を返す provide を複数定義できます。 provide("Coffee") { CoffeeMaker(get(), get()) } provide("Coffee2") { CoffeeMaker(get(), get()) } context() で sub context を作ることができます。Context には Scope 名を指定することができます。 class DripCoffeeModule : AndroidModule() { override fun context(): Context { return applicationContext { context("MainActivity") { provide { ElectricHeater() } bind Heater::class provide { Thermosiphon(get()) } bind Pump::class provide { CoffeeMaker(get(), get()) } } } } } provide するインスタンスを生成するときに Android の Context が必要な場合、androidApplication で Application インスタンスを取得することができます。 provide { ElectricHeater(androidApplication) } bind Heater::class

3. アプリケーションクラスの onCreate() で startKoin() を呼ぶ

class MyApplication : Application() { override fun onCreate() { super.onCreate() startKoin(this, listOf(DripCoffeeModule())) } } startKoin() は android.app.Application の拡張関数として定義されています。

4. inject

class MainActivity : Activity() { val maker by inject<CoffeeMaker>() } inject() inline fun <reified T> ComponentCallbacks.inject(name: String = "") = lazy { (StandAloneContext.koinContext as KoinContext).get<T>(name) } なので以下と同じ、つまり lazy です。 class MainActivity : Activity() { val maker by lazy { (StandAloneContext.koinContext as KoinContext).get<CoffeeMaker>() } } lazy が嫌なら onCreate() で (StandAloneContext.koinContext as KoinContext).get() を自分で呼んで代入すればできますが、全部自動で一括でとなるとやはりアノテーションなどが必要ですね。 class MainActivity : Activity() { private lateinit var maker: CoffeeMaker override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) maker = (StandAloneContext.koinContext as KoinContext).get() } }

provide で name を指定した場合、inject にその name を指定して取得できます。 class MainActivity : Activity() { val maker by inject<CoffeeMaker>("Coffee") } Scope 名を指定してインスタンスを解放することができます。 override fun onDestroy() { super.onDestroy() releaseContext("MainActivity") } ContextAwareActivity を継承して Scope 名を指定すると同じことができます。ContextDropMethod を指定しないときのデフォルトは ContextDropMethod.onPause(onPause() で releaseContext() される)です。 class TestActivity : ContextAwareActivity("MainActivity", ContextDropMethod.OnDestroy) { val maker by inject<CoffeeMaker>() }


おまけ、Daggerの場合

kapt 'com.google.dagger:dagger-compiler:2.11' implementation 'com.google.dagger:dagger:2.11' interface Heater interface Pump class ElectricHeater : Heater class Thermosiphon @Inject constructor(private val heater: Heater) : Pump class CoffeeMaker @Inject constructor(val heater: Heater, val pump: Pump) @Module class DripCoffeeModule { @Provides fun provideHeater(): Heater { return ElectricHeater() } @Provides fun providePump(pump: Thermosiphon): Pump { return pump } } @Component(modules = arrayOf(DripCoffeeModule::class)) interface CoffeeShop { fun maker(): CoffeeMaker } val coffeeShop = DaggerCoffeeShop.builder() .dripCoffeeModule(DripCoffeeModule()) .build() val maker = coffeeShop.maker() assertThat(maker.heater).isNotNull() assertThat(maker.pump).isNotNull()


2017年11月19日日曜日

Android Things をやってみよう - InputDriver 編

User-Space Drivers : input

InputDriver を使うと、タッチイベントやキーイベントをシステムに流すことができます。

これにより、例えば GPIO から入力があったときに Space キーや Enter キーなどの KeyEvent を流すことで、KeyEvent を処理する機能やライブラリをそのまま流用できます。

InputDriver を使うときは com.google.android.things.permission.MANAGE_INPUT_DRIVERS パーミッションが必要です。 <uses-permission android:name="com.google.android.things.permission.MANAGE_INPUT_DRIVERS" />

InputDriver.Builder を使って InputDriver を生成します。

UserDriverManagerregisterInputDriver() で InputDriver を登録し、最後に unregisterInputDriver() で登録を解除します。

InputDriver にキーイベントを流すときは InputDrivder.emit() に KeyEvent を渡します。 class InputDriverActivity : Activity() { private var gpio: Gpio? = null private var inputDriver: InputDriver? = null private val callback: GpioCallback = object : GpioCallback() { override fun onGpioEdge(gpio: Gpio): Boolean { val action = if (gpio.value) KeyEvent.ACTION_DOWN else KeyEvent.ACTION_UP // ドライバーに Enter キーイベントを流す inputDriver?.emit(arrayOf(KeyEvent(action, KeyEvent.KEYCODE_ENTER))) return true } } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val pioService = PeripheralManagerService() try { gpio = pioService.openGpio("GPIO_174").apply { setDirection(Gpio.DIRECTION_IN) setEdgeTriggerType(Gpio.EDGE_BOTH) setActiveType(Gpio.ACTIVE_LOW) registerGpioCallback(callback) } } catch (e: IOException) { Log.e(TAG, "Error initializing GPIO", e) } // Enter キーをサポートするドライバーを用意 inputDriver = InputDriver.Builder(InputDevice.SOURCE_CLASS_BUTTON) .setName("Button") .setVersion(1) .setKeys(intArrayOf(KeyEvent.KEYCODE_ENTER)) .build() // ドライバーを登録 UserDriverManager.getManager().registerInputDriver(inputDriver) } override fun onDestroy() { if (inputDriver != null) { // ドライバーの登録を解除 UserDriverManager.getManager().unregisterInputDriver(inputDriver) inputDriver = null } gpio?.unregisterGpioCallback(callback) try { gpio?.close() } catch (e: IOException) { Log.e(TAG, "Error closing GPIO", e) } super.onDestroy() } override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean { Log.d(TAG, "onKeyDown : $keyCode") if (keyCode == KeyEvent.KEYCODE_ENTER) { return true } return super.onKeyDown(keyCode, event) } override fun onKeyUp(keyCode: Int, event: KeyEvent): Boolean { Log.d(TAG, "onKeyUp : $keyCode") if (keyCode == KeyEvent.KEYCODE_ENTER) { return true } return super.onKeyUp(keyCode, event) } companion object { private const val TAG = "InputDriverActivity" } }

contrib-drivers

https://developer.android.com/things/training/first-device/drivers.html#initialize_the_driver_library

contrib-drivers の button にある ButtonInputDriver を利用すると、GPIO の入力を簡単に KeyEvent に割り当てられます。 implementation 'com.google.android.things.contrib:driver-button:0.3' class ButtonInputDriverActivity : Activity() { private var inputDriver: ButtonInputDriver? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) inputDriver = ButtonInputDriver("GPIO_174", Button.LogicState.PRESSED_WHEN_LOW, KeyEvent.KEYCODE_ENTER) } override fun onDestroy() { inputDriver?.unregister() try { inputDriver?.close() } catch (e: IOException) { Log.e(TAG, "Error closing GPIO", e) } super.onDestroy() } override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean { Log.d(TAG, "onKeyDown : $keyCode") if (keyCode == KeyEvent.KEYCODE_ENTER) { return true } return super.onKeyDown(keyCode, event) } override fun onKeyUp(keyCode: Int, event: KeyEvent): Boolean { Log.d(TAG, "onKeyUp : $keyCode") if (keyCode == KeyEvent.KEYCODE_ENTER) { return true } return super.onKeyUp(keyCode, event) } companion object { private const val TAG = "ButtonInputDriverActivity" } }