# Contacts API読んでみた
株式会社グローバルサイバーグループ
マネージャ
藪下 正美
# はじめに
## 自己紹介
• 藪下 正美
• 組み込み系の会社でスマホ作ってる人
• OSとかコンパイラとかの理屈が好きでいつの間にか数学基礎論とか形式言語とか形式手法とか論理学とかに足を踏み入れてしまった人
• 最近アカデミック分が足りてなくてモヤモヤしてるのでTaPLとかSICPとかそこらへんの読書会をやりたがっている
## 会社紹介
• 株式会社グローバルサイバーグループ– http://www.gcg.bz
• 関西出身の組み込み系ソフトハウス• 勉強会支援やってます。会議室借りたい、プロジェクタ借り
たい、登壇してほしいなど気軽にご相談ください。– http://www.gcg.bz/labo_blog/?page_id=943
• 質問受け付けてます。ブログネタください。– http://www.gcg.bz/labo_blog/?page_id=945
• 本日の懇親会は株式会社グローバルサイバーグループの支援を受けています。
## 今日のアジェンダ
• Contacts APIとは• Firefox OSの連絡帳アプリから使い方を見てみる
– 連絡先の保存– 連絡先の検索– 連絡先の全件取得– 連絡先の削除
• Contacts APIの実装のお話– Contacts APIの概要図– Contacts APIの流れ– Contacts APIのDBエントリデータ構造
• 資料書いてみたら前半がすごい長くなっちゃったのでAPIの実装の話は後日やります
## 今日のコードリーディングに使ったソースコード
• 今回のコードリーディングはちょっと古いソースですが以下のURLのを使ってます
– http://reading.fxos.org/source/
# Contacts APIとは
• Contacts APIは連絡先情報を扱うAPIです
• Gaiaでは連絡帳アプリやSMSアプリで使われます
– ざっくりと保存、読み込み、更新、削除のインターフェイスを備えています
# Firefox OSの連絡帳アプリから使い方を見てみる
• もともと自作のアプリで使うための調べものですがせっかくなのでコードリーディングしてみましょう
• 今回のターゲットはContacts APIなので連絡帳アプリから読んでみます
– B2G/apps/communications/contacts
## パーミッション
• まずはパーミッションです
• パーミッションのリストはMDNで見てください
– https://developer.mozilla.org/ja/Apps/App_permissions
### Gaiaの連絡帳アプリのパーミッション
• B2G/gaia/apps/communications/manifest.webapp
"name": "Communications","description": "Gaia Communications","type": "certified","launch_path": "/",
:(snip)
:"permissions": {
:(snip)
:"contacts":{ "access": "readwrite" },
:(snip)
:},
## Contacts APIのパーミッション
• apps/communications配下はいくつかのアプリを含んでいるので沢山パーミッションがありますがキモはここです
• B2G/gaia/apps/communications/manifest.webapp"contacts":{ "access": "readwrite" },
• 見たままContacts APIのパーミッションです• 連絡帳アプリでは読み書きどちらも要求しています
– SMSアプリなどでは読み込みだけ要求しています– 他にも初期設定アプリが読み書き、メールや電話アプリは読み込み
のみで使ってます
"contacts":{ "access": "readwrite" },
### Gaiaの連絡帳アプリの特権
• もうひとつここも勘所です
• B2G/gaia/apps/communications/manifest.webapp
"type": "certified",
• これはアプリ特権の設定です• Gaiaで定義されているプリインアプリなのでcertifiedが設定
されています• ユーザ定義のアプリだとここはprivilegedを使うことになり
ます
### パーミッションまとめ
• Contacts APIを使用する際はpermissions属性にcontacts属性の設定が必要です
– contacts属性にはaccess属性を設定します
– access属性にはreadonlyあるいはreadwriteを設定します
• 特権が必要なのでtype属性にprivilegedかcertifiedの設定が必要です
– privilegedの場合プロンプトが出ます
## 連絡先の保存
• 連絡先の保存を見ていきます
• MDNの解説は以下を見てください
– https://developer.mozilla.org/en-US/docs/Web/API/Contacts_API#Adding_a_contact
### 保存処理
• B2G/gaia/apps/communications/contacts/js/export/sim.js
var _updateContactSimSource = function _updateContactSimSource(theContact,iccContact, cb, errorCb) {
if (!iccContact) {window.console.warn('No Icc Contact provided');cb();return;
}
theContact.url = theContact.url || [];theContact.url.push(_generateIccContactUrl(iccContact.id, _getIccId()));var req = navigator.mozContacts.save(theContact);req.onsuccess = cb;req.onerror = function() {
errorCb(req.error);};
};
### 保存APIの呼び出し
• Contacts APIを使ってるのはここですね
• B2G/gaia/apps/communications/contacts/js/export/sim.js
theContact.url = theContact.url || [];theContact.url.push(_generateIccContactUrl(iccContact.id, _getIccId()));var req = navigator.mozContacts.save(theContact);req.onsuccess = cb;req.onerror = function() {
errorCb(req.error);};
• 連絡先情報を渡してDOMRequestを受け取っているだけのシンプルな呼び出しですね• reqという変数に返り値を受けてonsuccessとonerrorを設定しているこの形はWebAPIの基本形の
一つです– 処理に時間のかかるAPIやchromeプロセスに処理を要求するAPIにはDOMRequestやその派生オブジェクトを返す
ものがたくさんあります– DOMRequestは非同期に動作するAPIの処理終了イベントを表すオブジェクトです
### 保存APIの引数
• 保存APIに渡されているオブジェクトがどんな値を持っているか見ていきます
#### mozContacts.saveの使用個所からさかのぼる1
• まず_updateContactSimSourceのtheContact出所を確認します• 結論から言うとここから追うのははずれだったので細かくソースコードは見せませ
ん
ContactsSIMExport._updateContactSimSourct (theContact)->ContactsSIMExport._doExport (contacts[step]、contactsはContactsSIMExportのプロパティ)->ContactsSIMExport.setContactsToExport (引数のctsがcontactsに設定さ
れる)->ContactsExporter._start (contacts、ContactsExporterのプロパティ)->ContactsExporter._init (theContacts、連絡先IDの配列)
• ここで引数の連絡先IDを持つ連絡先データを配列にしています• ですが連絡先データの出所がmozContacts.find()で取ってきた値なので使ってい
るシーンがなくて残念な感じですね
#### mozContacts.saveの使用個所からさかのぼる2
• 別の使用個所から追います
• B2G/gaia/apps/communications/contacts/js/views/details.js
var request =navigator.mozContacts.save(utils.misc.toMozContact(contact));
• utils.misc.toMozContactで連絡先情報を作っているようなので中身を見てみると
• B2G/gaia/shared/js/contacts/import/utilities/misc.js
utils.misc.toMozContact = function(contact) {var outContact = contact;if (!(contact instanceof mozContact)) {outContact = new mozContact(contact);outContact.id = contact.id || outContact.id;
}return outContact;
};
• new mozContactが連絡先情報みたいですね
#### 連絡先情報オブジェクト
• mozContact何者? と思ったらwebidlを探しに行くのがお作法ですよね!
• B2G/gecko/dom/webidl/Contacts.webidl
[Constructor(optional ContactProperties properties),JSImplementation="@mozilla.org/contact;1"]interface mozContact {
attribute DOMString id;readonly attribute Date? published;readonly attribute Date? updated;
attribute Date? bday;attribute Date? anniversary;
attribute DOMString? sex;attribute DOMString? genderIdentity;
[Cached, Pure] attribute sequence<Blob>? photo;
[Cached, Pure] attribute sequence<ContactAddress>? adr;
[Cached, Pure] attribute sequence<ContactField>? email;[Cached, Pure] attribute sequence<ContactField>? url;[Cached, Pure] attribute sequence<ContactField>? impp;
[Cached, Pure] attribute sequence<ContactTelField>? tel;
[Cached, Pure] attribute sequence<DOMString>? name;[Cached, Pure] attribute sequence<DOMString>? honorificPrefix;[Cached, Pure] attribute sequence<DOMString>? givenName;[Cached, Pure] attribute sequence<DOMString>? phoneticGivenName;[Cached, Pure] attribute sequence<DOMString>? additionalName;[Cached, Pure] attribute sequence<DOMString>? familyName;[Cached, Pure] attribute sequence<DOMString>? phoneticFamilyName;[Cached, Pure] attribute sequence<DOMString>? honorificSuffix;[Cached, Pure] attribute sequence<DOMString>? nickname;[Cached, Pure] attribute sequence<DOMString>? category;[Cached, Pure] attribute sequence<DOMString>? org;[Cached, Pure] attribute sequence<DOMString>? jobTitle;[Cached, Pure] attribute sequence<DOMString>? note;[Cached, Pure] attribute sequence<DOMString>? key;
void init(optional ContactProperties properties);
[ChromeOnly]void setMetadata(DOMString id, Date? published, Date? updated);
jsonifier;};
• メンバが多くておぢさんびっくりなんじゃよ。。。
• プロパティ全部説明するの大変なのでMDNペタリ– https://developer.mozilla.org/en-US/docs/Web/API/mozContact
• ざっくり言うと名前とか読み仮名とか電番とかメアドとかURLとかまあもろもろの配列が持てます
• 性別とは別にセクシャルアイデンティティが設定できて地味に細やかで面白いですね
• idがreadonlyではないのは既存の連絡先を更新する際に使うからです– 既存のIDを設定したmozContactオブジェクトを保存すると置き換えら
れます
### 連絡先の保存まとめ
• 連絡先の保存はnavigator.mozContacts.save()で行います
– mozContacts.save()の引数はmozContactオブジェクトです
– mozContacts.save()の返り値はDOMRequestオブジェクトです
• mozContactオブジェクトプロパティ多い。。。
– webidl見慣れてない人はMDNの以下のページを参照してください
– https://developer.mozilla.org/en-US/docs/Mozilla/WebIDL_bindings
– webidlは変更多いので英語版をこまめに追いかけたほうがいいです(翻訳サボってサーセンw)
## 連絡先の検索
• 次は連絡先の検索を見てみます
• 連絡先を探すのに使えるメソッドは二つあるのでそれぞれ見ていきます
• まずは条件を指定する場合を見ていきます
• B2G/gaia/apps/communications/contacts/js/views/list.jsvar getContactById = function(contactID, successCb, errorCb) {(snip)var options = {
filterBy: ['id'],filterOp: 'equals',filterValue: contactID
};var request = navigator.mozContacts.find(options);request.onsuccess = function findCallback(e) {
var result = e.target.result[0];
if (!fb.isFbContact(result)) {successCb(result);return;
}
var fbContact = new fb.Contact(result);var fbReq = fbContact.getData();fbReq.onsuccess = function() {
successCb(result, fbReq.result);};fbReq.onerror = successCb.bind(null, result);
}; // request.onsuccess
if (typeof errorCb === 'function') {request.onerror = errorCb;
}};
### 検索API
### 検索APIの呼び出し
• 肝心なところだけ見ましょう
• B2G/gaia/apps/communications/contacts/js/views/list.js
var options = {filterBy: ['id'],filterOp: 'equals',filterValue: contactID
};var request = navigator.mozContacts.find(options);
request.onsuccess = function findCallback(e) {:
(snip):
}; // request.onsuccess
if (typeof errorCb === 'function') {request.onerror = errorCb;
}
• mozContacts.saveでも見覚えある形ですね• mozContacts.findもDOMRequestを返します
### 全件取得API
• すべての連絡先を取得するAPIを見てみましょう
• B2G/gaia/apps/communications/contacts/js/views/list.js
initConfiguration(function onInitConfiguration() {var sortBy = (orderByLastName === true ? 'familyName' : 'givenName');var options = {sortBy: sortBy,sortOrder: 'ascending‘
};var cursor = navigator.mozContacts.getAll(options);(snip)var contact = evt.target.result;if (contact) {
(snip)cursor.continue();(snip)
}};cursor.onerror = errorCb;
});
### 全件取得APIの呼び出し
• 全件取得のAPIを使っているのはここです
var cursor = navigator.mozContacts.getAll(options);:
(snip):
cursor.onsuccess = function onsuccess(evt) {:
(snip):
var contact = evt.target.result;if (contact) {
:(snip)
:cursor.continue();
} else {:
(snip):
}};cursor.onerror = errorCb;
### 全件取得APIの呼び出し
• これまでのAPIと少し違ってDOMCursorを返してます• DOMRequestのようにonsuccessとonerrorを設定しているのに
加えてcontinueメソッドの呼び出しをしているこの形は複数の値を返すAPIの基本形です– 複数の値を返すAPIではDOMCursorやその派生オブジェクトを返すもの
がたくさんあります
• DOMCursorは複数の値を返すAPIの反復子を表すオブジェクトです– continueメソッドが呼ばれた上でメソッドを抜けると次のオブジェクト
を引数に再度イベントが発行されます– 引数のオブジェクトがnullの場合そのイテレーションが終端に達したとい
うことです
### 検索APIの引数
• mozContacts.findとmozContacts.getAllは検索オプションを引数に取ることができます• ただしmozContacts.getAllはソート条件のみ設定可能です
• B2G/gecko/dom/webidl/Contacts.webidl
dictionary ContactFindSortOptions {DOMString sortBy; // "givenName" or "familyName“DOMString sortOrder = "ascending"; // e.g. "descending“
};
dictionary ContactFindOptions : ContactFindSortOptions {DOMString filterValue; // e.g. "Tom“DOMString filterOp; // e.g. "startsWith“any filterBy; // e.g. ["givenName", "nickname"]unsigned long filterLimit = 0;
};
### 検索APIのまとめ
• 絞り込んでの検索はnavigator.mozContacts.find()で行います– mozContacts.find()の引数はContactFindOptionsです– mozContacts.find()の返り値はDOMRequestオブジェクトです– DOMRequestオブジェクトのresultはmozContactオブジェクト
の配列です
• 全件取得はnavigator.mozContacts.getAll()で行います– mozContacts.getAll()の引数はContactFindSortOptionsです– mozContacts.getAll()の返り値はDOMCursorオブジェクトです– DOMCursorオブジェクトのresultはmozContactオブジェクトで
す
## 連絡先の削除
• 最後に連絡帳の削除を見ましょう
### 削除API
• B2G/gaia/apps/communications/contacts/js/contacts_remover.js
function deleteContact(currentID) {var request;var outreq = {onSuccess: null, onError: null};var contact = new mozContact();contact.id = currentID;
if (fbData[contact.id]) {var fbContact = new fb.Contact(fbData[contact.id]);request = fbContact.remove();
} else {request = navigator.mozContacts.remove(contact);
}request.onsuccess = function() {outreq.onSuccess();
};request.onerror = function() {outreq.onError();
};return outreq;
}
### 削除APIの呼び出し
• 削除APIの呼び出しだけ見ると
• B2G/gaia/apps/communications/contacts/js/contacts_remover.js
var contact = new mozContact();contact.id = currentID;
if (fbData[contact.id]) {var fbContact = new fb.Contact(fbData[contact.id]);request = fbContact.remove();
} else {request = navigator.mozContacts.remove(contact);
}request.onsuccess = function() {outreq.onSuccess();
};request.onerror = function() {outreq.onError();
};
こんな感じですもう見慣れた形のDOMRequestを返すタイプのAPIでした
### 削除APIの引数
• mozContacts.removeはmozContactオブジェクトを引数に取ります
• B2G/gaia/apps/communications/contacts/js/contacts_remover.js
var contact = new mozContact();contact.id = currentID;
• このやりかたを見るにIDだけあればいいようです
### 削除APIのまとめ
• 削除はnavigator.mozContacts.remove()で行います
– mozContacts.remove()の引数はmozContactです
– mozContacts.remove()の返り値はDOMRequestです
# さいごに
• ちょっと予定と違って中身の説明ができませんでしたがblogかどこかの勉強会でやるのでこうご期待
• といってもContacts APIの実装は素直なのでほかのAPIも一緒にやるかも? Notification APIなんかが近い構造なので一緒にやる可能性高いかも?
• なんかこれ聞いてみたいっていうのがあったら質問箱へどうぞー
– http://www.gcg.bz/labo_blog/?page_id=945