2011/12/21

「お・さ・わ・り えくすチェンジ!」の設計

本エントリは、「AndroiderでAdvent Calendarやろうぜ!」という、Android Advent Calendar 2011企画中の1エントリになっております。
2011年4月に「よちよちAndroidサンデープログラミングの会」なる、初心者プログラマーの会を発足して以降、新しい人との出会いの機会が増えました。そのため、個人名刺をデザインし、自宅のインクジェットプリンタでしこしこと印刷をしていたのですが、インク代も馬鹿にならないので、電子名刺を実現するAndroidアプリを作りました。
名刺デンワ (クリックでAndroid Marketへ)
スマートフォンや携帯電話と連絡先の交換に使える電子名刺アプリ。オリジナルデザインの名刺も作れます。
名刺表示画面


名刺編集画面

このアプリの電子名刺のデータは、名刺記載項目の内容・位置・色などを定義しているXMLファイルと、使用する画像ファイル群で構成されています。

現バージョン(注)では、自分の名刺(自作した名刺データ)を表示して、QRコードを介して他人のアドレス帳に名前や電話番号などを登録させることはできるのですが、他人と名刺データそのものを受け渡しする(そして他人の名刺を管理・表示する)機能がありません。
(注)2011年12月21日時点。2012年1月15日に、本記事の機能を組み込んだバージョンをAndroid Marketに公開しました。
【心の声】なぜなら、名刺データ交換方式を決めあぐねていたのと、実装がめんどくさかったから~
端末間で名刺データを交換する方式については、前述のよちよちAndroidのメンバーとも議論し、以下の案が出ました(書き挙げるとキリがないので代表的なものを)。
Android Beam方式
Android 4.0からサポートされたNFCを使った端末間データ通信で名刺データを交換(ただし方式検討時点ではまだ4.0が発表されておらず、存在を知らなかった)

赤外線通信方式
赤外線通信で名刺データを交換

Bluetooth通信方式
Bluetoothで名刺交換相手を検出し、Bluetooth通信で名刺データを交換

インターネット越しSocket通信方式
一旦、Webサーバに双方のIPアドレスを格納し、それを交換した後、相互に相手端末へSocketを張り、名刺データを交換

QRコード方式
自分の電子名刺データ(ファイル群)を特定のWebサイトに置いておき、そのURLをQRコードとして名刺画面に表示し、相手がそれをQRコードリーダーで読み取ると電子名刺データがダウンロードされる

まねっこBump方式
著名なBumpというアプリで、「レッツ!バーンプ」と叫びながら(まあ、叫ばなくてもいいんだけど)、端末を握った手同士をごっつんこすると、あら不思議、相手と連絡先データを交換できる、というものがあり(方式不明なのですが)、これのまねをする

投げキッス方式
駅のホームで見かけたあの娘に、端末を持った手で投げキッスの振りをすると、あら不思議、あの娘の端末に自分の名刺データが送り込まれるという。類似案多数
正当性とスマートさから言えば、断然Android Beam方式を採るべきですが、いかんせんAndroid 4.0は、まだ世に普及していないので、ボツ。

赤外線は、スマートで妥当ですが、いかんせんガラケー的匂いがする(海外端末では使えない気がする)ので、ボツ。

Bluetoothは用途的には妥当なので、AndroidでのBluetooth通信の仕方を少し調べてみましたが、通信相手の検出に10数秒の時間がかかることもあるようで、その間、名刺交換相手の人と向き合って「見つかりませんね」と苦笑しているのも間が持たない感じがして、ボツ。スマートじゃない。

ならば、相手端末とSocketを張って、と思いましたが、私はスマートフォンをWiMAXルーター越しにインターネットに接続しているもので、NATを超えられない気がして、ボツ。

QRコード方式は、ユーザが自分の名刺データを置くためのWebサーバを持っているわけがなかろう、よしんば私のWebサーバに人様の名刺データを永続的に預かったとして、漏洩事件を起こしてしまって、500円図書カードをユーザの皆様に配布していては身がもたない、ということでボツ。

投げキッス方式は、理想的だし、妄想としては楽しいが、実行不可能なため、ボツ。

ということで、まねっこBump方式を実現するべく、名刺データ交換方式のアウトラインを以下のように定めました。
  1. 名刺交換のトリガとして、双方の端末で「ごっつんこ」を検出(※)
  2. 双方の端末が、サーバプログラム(私のレンタルサーバ上のプログラム)に名刺データを送信
  3. サーバプログラムが、「ほぼ同時に」(※)「ごっつんこ」した端末を名刺交換の相手として、それぞれに相手方の名刺データを返信
  4. 双方の端末で、受信した名刺データを保存
お分かりの通り、(※)の部分が、この方式のキモになります。

そこで、まずは加速度センサーを使って「ごっつんこ」を検出するテストプログラムの作成に着手しました。

が、
【結論】私には、ムリ
最初に、加速度センサーの値をログに出力するプログラムを作成しました。
※加速度センサーの値をログに出力するプログラムのapkは、ここからダウンロードできます。
そのプログラムを動かしながら、実際に端末を持って「ごっつんこ」をしてみたり、「ごっつんこ」ではない振る舞い(例:端末を落とす、手に持って振る、投げキッスをするなどなど)をしてみたりして、様々なサンプルデータを採りました。
そして、それぞれのログをグラフ化して見比べた上で、「ごっつんこ」のみを検出できそうな計算式を立て、その式を使って「ごっつんこ」を検出するプログラムを作成しました。
そしてそのプログラムを実際に動かしてみたのですが、弱い「ごっつんこ」を検出しようとすると、どうパラメータ調整をしても、「ごっつんこ」していないが似たような手の動きをした(端末を振った)ときにも「ごっつんこ」していると判定してしまって、埒があきませんでした。

ならば、「ごっつんこ」に代わる名刺データ交換のトリガを採用しようと考えた挙げ句、「近接センサー」を使うことにしました。すなわち、
こんな感じ

双方の端末のオモテ面同士をくっつけることで、双方、自分の近接センサーで「近づいた」ことを検出し、それを名刺交換のトリガーにすることにしました。これなら、実装が簡単。しかもAndroid Beamばりのスマートさ。
【心の声】きたないオヤジの手と「ごっつんこ」したくないよね!って、苦しい言い訳
ただし、近接センサーが「近づいた」ことを検出する距離は、端末ごとに異なりますので、端末同士をあまりゆっくり近づけると、一方のみが「近づいた」と判定してしまって、後の名刺データ交換が失敗してしまいます。とは言え、素早く「ガツン」と接触させると液晶面のキズが気になりますね。この辺りがBumpに比べて劣る部分。
【心の声】あ、あと女の子と「ごっつんこ」できないところが最高に劣る点
さて次に、双方の端末が「近づいた」ら、それぞれ名刺データをサーバに送信し、その時刻が「ほぼ同時」であったものを名刺交換相手とみなす必要がありますが、ここで想定される問題がいくつかあり、それぞれ以下のような対処を考えました。
双方の端末が「近づいた」時刻をそれぞれの端末のタイマーから取得した場合、端末の時刻設定のズレがあると「ほぼ同時」と判定されない可能性が高くなる
【対処】端末同士が近づいた時刻ではなく、サーバが名刺データを受信したときの時刻をサーバのタイマーから取得し、「ほぼ同時」の判定に使う

上記のようにした場合、名刺データ(xmlファイルと複数の画像ファイル)の大きさや、端末の通信速度により、サーバが名刺データを受信する時刻が大きくズレて、「ほぼ同時」と判定されない可能性が高くなる
【対処】端末からの最初の通信は極力小さいデータ(具体的には端末ユーザの名前情報のみ、以下トリガデータと呼ぶ)にし、サーバが双方の端末からデータを受信する時刻の差を抑える。その後、「ほぼ同時」とみなした名刺交換相手を確定した後に、実際の名刺データを交換する(名刺交換相手のマッチングと実際のデータ交換の二段階に分ける)

サーバが、一方のトリガデータを受信した時点では、もう一方のトリガデータを受信していない
【対処】サーバは、トリガデータを受信したら、来るはずのもう一方のトリガデータを待つために、一定時間(時間t)ウェイトする。その後、実はウェイトを開始する前にすでにもう一方のトリガデータが来ていたかもしれないので「その倍の時間(時間2t)」遡った時点までにトリガデータを送信していた端末を名刺交換相手の候補とみなす(ここキモです。伝わりますか?)。ちなみに「その倍の時間(2t)」が「ほぼ同時」の時間範囲になる

名刺交換している人が複数いた場合、どの人とデータを交換すればよいのか分からない
【対処】「ほぼ同時」の時間を最小化し、名刺交換相手「候補」を極力絞り込んだ上で、最後は人に判断してもらう(端末に交換相手の名前を表示した上で、選んでもらう)

名刺交換相手を「候補」から人手で選ぶ場合、名刺を交換するべき相手を誤って選んでしまったとしても、誤った相手同士で名刺データが交換されないようにしなければならない
【対処】(うーん、説明が難しいので、後で示す処理ロジックを見て、この要件の充足性を確認してください)
まず自分は、サーバ上に「自分に割り当てられたキー(以下「A」)」名のディレクトリを作る。本来名刺交換すべき相手は、そのディレクトリ内に「相手自身に割り当てられたキー(以下「B」)」名のディレクトリを作り、その配下に相手自身の名刺データを保存する。
逆に自分は、「名刺交換相手「候補」から選んだ相手に割り当てられたキー(以下「C」)」名のディレクトリ内に「自分に割り当てられたキー(「A」)」名のディレクトリを作り、そこに自分の名刺データを保存する。
その上で、自分は「自分に割り当てられたキー(「A」)」名のディレクトリ内の「選んだ相手に割り当てられたキー(「C」)」名のディレクトリ内に保存された名刺データを取得し、「自分に割り当てられたキー(「A」)」名のディレクトリを(配下のディレクトリやファイルも含めて)削除するとする。
こうすることで、もし、本来名刺交換するべき相手と、候補から選んだ相手が同一だった場合(正しい場合)、「B=C」であり、すなわち「/A/B」ディレクトリには相手の名刺データが、「/B/A」ディレクトリには自分の名刺データが保存されており、双方、相手の名刺データを取得できる(名刺交換の成功)。
しかし、本来名刺交換するべき相手と、候補から選んだ相手が異なる場合(誤った場合)、「B≠C」であり、ディレクトリ「/A/C」「/C/A」「/B/A」はいずれも存在しないため、自分の名刺データは本来の交換相手にも、誤った相手にも取得されない。また自分は、本来の相手の名刺データも、誤った相手の名刺データも取得できない(名刺交換の失敗)(ここキモです。伝わりますか?)。
また、名刺交換の成功、失敗にかかわらず、サーバにアップされた名刺データは削除される。
以上を本方式の要件として、実際の処理ロジックを定義しました(黒字 : 端末側の処理・操作、赤字 : サーバ側の処理)。
  • 近接センサーをONにする
  • メッセージ表示「相手の端末と近づけてください」
  • 近接センサーのonChange()で端末同士の接近を検出
  • 近接センサーをOFFにする
  • メッセージ表示「交換相手を確認しています」
  • サーバへ自分の姓、名をPOST送信
  • 端末から「姓」「名」を受信したら、キー(自キー)を生成
  • 自キー名のディレクトリを作成
  • DBに、キー(自キー)、姓、名、現在時刻をinsert
  • t秒待つ(sleep)
  • DBから、現在時刻から2t秒以前以降のレコードをselect
  • キー(自キー)、DBからselectした(複数の)相手キー、相手姓、名のリストを端末に返信
  • サーバから受信した相手姓、名のリストを一覧表示(自キー、相手キーも受信している)
  • 名刺交換したい相手を一覧から選択
  • メッセージ表示「自分の名刺を送信しています」
  • サーバへ、相手キー、自キー、自端末ID(をハッシュ化した値)、名刺XMLファイル、画像ファイル(複数)をサーバへマルチパートPOST送信
  • 端末からキーやファイルなどを受信したら、もし相手キー名のディレクトリがあれば、相手キー名ディレクトリ内に自キー名ディレクトリを作成(相手キー名ディレクトリが無ければ、名刺交換相手を間違っている。この場合、失敗レスポンスを返す)
  • 相手キー名ディレクトリ内の自キー名ディレクトリに、名刺XMLファイル、画像ファイル(複数)を保存
  • 同ディレクトリに、自端末IDおよび、名刺XMLファイルや画像ファイルへのファイル名を記載したテキストファイル(以下ファイルリストファイル)を作成
  • アップロード完了レスポンス(ファイルリストが空であるというレスポンス)を端末に返信
  • メッセージ表示「相手の名刺を受信しています」
  • サーバからのレスポンスが「ファイルリストが空である」である限り、サーバへ自キー、相手キーをPOST送信(※)
  • y秒待つ(sleep)
  • 自キー名ディレクトリ内の相手キー名ディレクトリにファイルリストファイルがあれば、ファイルリストの内容(すなわち相手端末IDおよび、相手の名刺XMLファイルや画像ファイルのファイル名)を端末に返信、なければファイルリスト空レスポンスを返信
  • サーバから受信したファイルリストが空でなければ、端末の外部ストレージに、受信した相手端末ID名のディレクトリを作成(空の場合は、前述※のループ)
  • サーバ上の「/(自キー)/(相手キー)/」ディレクトリに格納されているファイルリストのファイル(名刺XMLファイル、画像ファイルへのリンク)全てをダウンロードし、端末の外部ストレージの相手端末ID名ディレクトリに保存(名刺データの取得は完了)
  • サーバへ、自キーをPOST送信
  • 端末から自キーを受信したら、自キー名ディレクトリを削除
  • 端末へ削除完了の旨を返信
  • メッセージ表示「名刺交換が完了しました」
上記のロジックに加えて、端末~サーバ間のリクエスト(HTTP POST、一部マルチパート)とレスポンス(JSON)形式を定義したり、エラーやキャンセル処理などを決めて、いざ、端末側(Android)とサーバ側(PHP)のプロトタイププログラムを作成したところ(3GとWiMAXなど、通信速度が異なる端末間で名刺データを交換する場合の「ほぼ同時」とみなす時間(上述処理ロジックの時間2t)の設定に色々苦労はあったのですが、割愛)、
見事、名刺データ交換を実現できました!(ココ拍手)
※プロトタイププログラムのapkは、ここからダウンロードできます。
※2012年1月15日、おさわりえくすチェンジを組み込んだ「名刺デンワ v2.0.0」をAndroid Marketに公開しましたので、プロトタイプのダウンロードは終了します。
Bumpのスマートさに比べるとずいぶん見劣りがしますし、色々ツッコミどころ満載の処理ロジックですが、素人プログラミングとしては、まあ良しと思っています。

以上、電子名刺のデータ交換方式、名付けて「おさわりエクスチェンジ」、いや、もうちょっと色っぽく「お・さ・わ・り えくすチェンジ!」の設計苦労話でした。名刺デンワ v2.0.0には、この名刺データ交換機能を付けてリリースする予定ですので、名刺交換するときには、「レッツ!お・さ・わ・り」コールをお願いします。
P.S.
で、作ったプロトタイプをよちよちAndroidの定期開発ミーティングに持って行き、あるメンバーの端末にインストールしてもらって、「レッツ!お・さ・わ・り」してみたら、、、その人の端末には近接センサーが無かったというオチ。
そんな端末、あり~~?orz
苦悩は続く。
さて、いよいよクリスマスも目前、Android Advent Calendar 2011も佳境ですね。最後はどんなオチが待っているんでしょうか。
明日は、表@muo_jpさん、裏@kanzmrswさんです。よろしくお願いします。

2011/12/01

Android Advent Calendar 2011準備中

本ブログは、Android Advent Calendar 2011のエントリ向けの準備サイトです。記事掲載まで少々お待ち下さい。