tag:blogger.com,1999:blog-144433182024-02-07T23:13:25.570+09:00What happens today?abekatsuhttp://www.blogger.com/profile/12886183524644595053noreply@blogger.comBlogger60125tag:blogger.com,1999:blog-14443318.post-57830444998934770022019-06-22T22:40:00.002+09:002019-06-22T22:50:04.582+09:00Audioファイルを取り扱う<br />
<b id="docs-internal-guid-4d4da0d9-7fff-9d9c-b68a-decc261126ef" style="font-weight: normal;"><br /></b>
<br />
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: "arial"; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;">CDの音楽を取り込むのにApple Lossless形式を用いていたが,現在使用しているカーナビは</span><br />
<span style="font-family: arial; font-size: 11pt; white-space: pre;">Apple Lossless形式を再生することができない。</span><span style="font-family: arial; font-size: 11pt; white-space: pre;">スマートフォンのBluetoothで飛ばせばいい</span><br />
<span style="font-family: arial; font-size: 11pt; white-space: pre;">じゃんというが,普段はドライバーなので運転中にスマートフォンを操作するのは難しい。</span><br />
<span style="font-family: arial; font-size: 11pt; white-space: pre;">できれば,sdカードに保存しておいてジュークボックスみたいに音楽を再生していきたい。</span></div>
<b style="font-weight: normal;"><br /></b>
<br />
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: "arial"; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;">まずは変換対象となるaudioファイルをリストアップすることから始めた。</span><br />
<span style="background-color: transparent; color: black; font-family: "arial"; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;">Python3の</span><a href="https://github.com/kkroening/ffmpeg-python" style="text-decoration: none;"><span style="background-color: transparent; color: #1155cc; font-family: "arial"; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: underline; vertical-align: baseline; white-space: pre;">ffmpeg-python package</span></a><span style="background-color: transparent; color: black; font-family: "arial"; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;"> の </span><a href="https://kkroening.github.io/ffmpeg-python/#ffmpeg.probe" style="text-decoration: none;"><span style="background-color: transparent; color: #1155cc; font-family: "arial"; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: underline; vertical-align: baseline; white-space: pre;">ffmpeg.probe</span></a><span style="background-color: transparent; color: black; font-family: "arial"; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;"> を使ってaudioファイルかどうかを判定する。</span></div>
<b style="font-weight: normal;"><br /></b>
<br />
<div dir="ltr" style="margin-left: 0pt;">
<table style="border-collapse: collapse; border: none;"><colgroup></colgroup><tbody>
<tr style="height: 0pt;"><td style="background-color: #333333; padding: 5pt 5pt 5pt 5pt; vertical-align: top;"><div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: #333333; color: #fcc28c; font-family: "consolas"; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">for</span><span style="background-color: #333333; color: white; font-family: "consolas"; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"> fname </span><span style="background-color: #333333; color: #fcc28c; font-family: "consolas"; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">in</span><span style="background-color: #333333; color: white; font-family: "consolas"; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"> file_list:</span><span style="background-color: #333333; color: white; font-family: "consolas"; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"><br /></span><span style="background-color: #333333; color: white; font-family: "consolas"; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"> full_path = os.path.join(dir_path, fname)</span><span style="background-color: #333333; color: white; font-family: "consolas"; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"><br /></span><span style="background-color: #333333; color: white; font-family: "consolas"; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"> </span><span style="background-color: #333333; color: #fcc28c; font-family: "consolas"; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">try</span><span style="background-color: #333333; color: white; font-family: "consolas"; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">:</span><span style="background-color: #333333; color: white; font-family: "consolas"; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"><br /></span><span style="background-color: #333333; color: white; font-family: "consolas"; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"> js = ffmpeg.probe(full_path)</span><span style="background-color: #333333; color: white; font-family: "consolas"; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"><br /></span><span style="background-color: #333333; color: white; font-family: "consolas"; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"> </span><span style="background-color: #333333; color: #fcc28c; font-family: "consolas"; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">if</span><span style="background-color: #333333; color: white; font-family: "consolas"; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"> js[</span><span style="background-color: #333333; color: #a2fca2; font-family: "consolas"; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">'streams'</span><span style="background-color: #333333; color: white; font-family: "consolas"; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">][</span><span style="background-color: #333333; color: #d36363; font-family: "consolas"; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">0</span><span style="background-color: #333333; color: white; font-family: "consolas"; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">][</span><span style="background-color: #333333; color: #a2fca2; font-family: "consolas"; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">'codec_type'</span><span style="background-color: #333333; color: white; font-family: "consolas"; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">] == </span><span style="background-color: #333333; color: #a2fca2; font-family: "consolas"; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">'audio'</span><span style="background-color: #333333; color: white; font-family: "consolas"; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">:</span><span style="background-color: #333333; color: white; font-family: "consolas"; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"><br /></span><span style="background-color: #333333; color: white; font-family: "consolas"; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"> self._filelists.append({ </span><span style="background-color: #333333; color: #a2fca2; font-family: "consolas"; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">"path"</span><span style="background-color: #333333; color: white; font-family: "consolas"; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"> : full_path,</span><span style="background-color: #333333; color: white; font-family: "consolas"; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"><br /></span><span style="background-color: #333333; color: white; font-family: "consolas"; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"> </span><span style="background-color: #333333; color: #a2fca2; font-family: "consolas"; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">"probe"</span><span style="background-color: #333333; color: white; font-family: "consolas"; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"> : js })</span><span style="background-color: #333333; color: white; font-family: "consolas"; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"><br /></span><span style="background-color: #333333; color: white; font-family: "consolas"; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"> </span><span style="background-color: #333333; color: #fcc28c; font-family: "consolas"; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">except</span><span style="background-color: #333333; color: white; font-family: "consolas"; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"> ffmpeg._run.Error:</span><span style="background-color: #333333; color: white; font-family: "consolas"; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"><br /></span><span style="background-color: #333333; color: white; font-family: "consolas"; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"> </span><span style="background-color: #333333; color: #fcc28c; font-family: "consolas"; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">pass</span></div>
</td></tr>
</tbody></table>
</div>
<b style="font-weight: normal;"><br /></b>
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: "arial"; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;">これで自分が所持しているaudioファイルのリスト,およびcodecを調べることができた。</span><br />
<br /></div>
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: "arial"; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;">次はApple Losslessファイルだけを抽出し,flac形式に変換してみる。</span></div>
<b style="font-weight: normal;"><br /></b>
<br />
<div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: "arial"; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre;">ここまでの実装は <a href="https://github.com/abekatsu/py_convert_alac_to_flac/blob/105708c97b63121f1503213806e448ddffd42f3a/alac2flac.py">github</a> 上にpushした。</span><br />
<br /></div>
<br />
<div id="content3">
<pre class="brush:python"></pre>
</div>
abekatsuhttp://www.blogger.com/profile/12886183524644595053noreply@blogger.com0tag:blogger.com,1999:blog-14443318.post-17777826405875463662013-08-13T09:56:00.002+09:002013-08-13T09:56:55.272+09:00電子コミックの文字問題<p>
既知の問題だけど,電子書籍でコミックを読む場合の辛さはあります。<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjX6D-va_UHilNfsHGN96BaAlm9gaoa8kPgpFzhZrcjdv8NL3L6XmQqKqYkLOBwE5XrwYZRXLox1eX_0X500kWSq5SGh_-O1-kR2RN_vB3hVn3NiHoy6gV58594ansjIiCRdWJ95Q/s1600/kindle_fuon_connect_01_20130813.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjX6D-va_UHilNfsHGN96BaAlm9gaoa8kPgpFzhZrcjdv8NL3L6XmQqKqYkLOBwE5XrwYZRXLox1eX_0X500kWSq5SGh_-O1-kR2RN_vB3hVn3NiHoy6gV58594ansjIiCRdWJ95Q/s320/kindle_fuon_connect_01_20130813.png" /></a></div>
書き文字の
<quote>日本では改名した,●ーダフォン社から。</quote>
の「改名」が潰れていて読むのが大変なことと,吹き出しの文字も結構潰れていますね。<br />
書き文字,吹き出しをどのように見せるか,下から引き出したときにテキストデータが見えるようにするか,じゃどのようにデータを用意するか,大変だけどそこまで求められているか,高解像度でスキャンできれば解決できるのか,皆さんいろいろたどった道ですが,どのように改善できるのかだと思います。
</p>
<p>
これ紙版との比較する必要がありますが,部屋のどこにあるのか探しだせていないです。
</p>abekatsuhttp://www.blogger.com/profile/12886183524644595053noreply@blogger.com0tag:blogger.com,1999:blog-14443318.post-78696771742368552242011-11-03T15:30:00.001+09:002011-11-03T15:31:15.925+09:00android関連のarticleは11月6日までに全削除いたします。abekatsuhttp://www.blogger.com/profile/12886183524644595053noreply@blogger.com0tag:blogger.com,1999:blog-14443318.post-77616311527573547152011-07-26T06:21:00.001+09:002011-07-26T06:21:48.429+09:00Hack for Japan 遠野会場へ行ってきました<p><a href="http://www.hack4.jp/RelatedInfo/Events/PastEvents/110723tono">遠野でのHack For Japanイベント</a>に行ってきました。<br /><br />
<br />
<a href="http://goo.gl/photos/ulcLcIIYBa" imageanchor="1" style="clear:right;margin-bottom:1em;margin-left:1em"><img border="0" src="https://lh5.googleusercontent.com/-qI42zu-iufU/Ti3crqGmFgE/AAAAAAAACCk/j_Me6ZNnXcs/s160-c/HackForJapan.jpg"></a><br />
<br />
<h4>作ったもの</h4><iframe src="https://docs.google.com/present/embed?id=df9phz46_18f94tv4fb" frameborder="0" width="410" height="342"></iframe><br />
<br /><br />
<object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" width="480" height="296" id="utv57959" name="utv_n_294313"><param name="flashvars" value="loc=%2F&autoplay=false&vid=16210978&locale=ja_JP&hasticket=false&v3=1" /><param name="allowfullscreen" value="true" /><param name="allowscriptaccess" value="always" /><param name="src" value="http://www.ustream.tv/flash/viewer.swf" /><embed flashvars="loc=%2F&autoplay=false&vid=16210978&locale=ja_JP&hasticket=false&v3=1" width="480" height="296" allowfullscreen="true" allowscriptaccess="always" id="utv57959" name="utv_n_294313" src="http://www.ustream.tv/flash/viewer.swf" type="application/x-shockwave-flash" /></object><br />
36:30ぐらいから。<br />
<br />
<p>コメントとして,<br />
</p><ul> <li>状況は変化していくので,updateもできるような機能がほしい<br />
<li>PersonFinderと連携してほしい<br />
<li>緊急退避情報として,現在地付近のこれまでの津波の高さ(「波高」,「浸水高」,「遡上高」?)を表示して,近くの高台の場所を緊急避難場所として表示する。過去の教訓をもとにした資料に,1時間は動くなという石碑があったのでそれを参考に。<br />
</ul><p>(不足していたらご指摘いただけませんでしょうか)などがあり,今後の課題とします。
</p>
<h4>改善点</h4><h5>開発環境の準備</h5><p>今回,現地で<a href="http://developer.android.com/sdk/installing.html">Android SDKをインストール</a>したのですが,<a href="http://developer.android.com/sdk/installing.html#AddingComponents">Step 4のAdding Platforms and Other Components</a>で,全てのコンポーネントをインストールしようとして,この作業で1,2時間ぐらいかかった。今回,アイデアソンでしゃべった内容を当日作ることになったので,必ずしも全員が開発環境わけではないので,できれば事前告知しておけばよかった。</p><p><h5>妥協リストの作成</h5><p>まずは動くものを成果物にあげようというコンセプトで,実装する際に妥協したこともあった。たとえば,設定項目(送信先メールアドレス,SMS送信先電話番号)は,保存できていない。このリストを共有できれば,後日協力者を集めるのに役立てられるだろう。
</p>
<p>今回遠野まごころネットさんの方とお話する機会があり,支援物資管理などICTの技術が利用できるところは多々あるが,瞬発力も必要ということで,速やかに動くものを提供する,柔軟に変更点に対応するなど,表に出てくることが必要かと思う。
</p>abekatsuhttp://www.blogger.com/profile/12886183524644595053noreply@blogger.com0tag:blogger.com,1999:blog-14443318.post-29106363150571512072011-05-18T14:57:00.003+09:002011-05-18T15:00:27.502+09:00Google APIs Client Library for JavaのAndroid向けライブラリのソースを公開しました。<a href="http://abekatsu.blogspot.com/2011/05/published-my-android-library-to-use.html">英語版</a>の日本語版です。<br /><br />
<br />
<a href="http://code.google.com/p/google-api-java-client/">Google APIs Client Library for Java</a> <br />
のAndroid向けライブラリのソースを公開しました。<br />
(URL は <a href="http://code.google.com/p/android-library-google-api-java/">http://code.google.com/p/android-library-google-api-java/</a> です。)<br />
<br />
<br />
単に,Google SpreadSheets に対して,OAuth 1.0a で<br />
<br />
<ul>
<li> 新しいSpreadSheetの作成<br />
</li>
<li> worksheet entryの追加<br />
</li>
<li> セルの更新<br />
</li>
</ul>
ができます。他のサービスについても作成していきますが,なんせあまりいいエンジニアじゃないので,スピードは相当遅いです。JavaDocも何も作成していなくて申し訳ございません。
何か,要望,提案や間違いの指摘,バグレポートがありましたら <a href="http://www.google.com/profiles/katsuhisa.abe">こちら</a>よりご連絡ください。
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgCg5WMdqP8tDTRlJ2PTevGcLbzgApnfXJ7O_LVHL5r_fbO6XWTDSfcbKRRU0h7iGyElSq1dIw2spDB6fZL70ar8n7VmQCCLf0gD-iJZl3RU52-kyWz7Yf9Stjw5TgFyufaho8DQQ/s1600/android-library-google-api-java.jpg" imageanchor="1"><img border="0" height="208" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgCg5WMdqP8tDTRlJ2PTevGcLbzgApnfXJ7O_LVHL5r_fbO6XWTDSfcbKRRU0h7iGyElSq1dIw2spDB6fZL70ar8n7VmQCCLf0gD-iJZl3RU52-kyWz7Yf9Stjw5TgFyufaho8DQQ/s320/android-library-google-api-java.jpg" width="320" /></a></div>
abekatsuhttp://www.blogger.com/profile/12886183524644595053noreply@blogger.com0tag:blogger.com,1999:blog-14443318.post-67946691484435177992011-05-18T14:50:00.004+09:002011-05-18T14:58:42.460+09:00Published my android library to use Google APIs Client Library for Java<p>I upload sources of my android library to use <a href="http://code.google.com/p/google-api-java-client/">Google APIs Client Library for Java.</a> <br />
<br />
<br />
This is a just sample for Google Spread Sheet, but it can create new spreadsheet and worksheet, rename the title of worksheet entry, and put cells on your worksheet with OAuth1.0a.<br />
<br />
<br />
URL is <a href="http://code.google.com/p/android-library-google-api-java/">http://code.google.com/p/android-library-google-api-java/</a>. <br />
<br />
<br />
I am preparing JavaDoc and classes for other services. But I am not good engineer, so speed to update migh be very slow. <br />
<br />
<br />
Please contact <a href="http://www.google.com/profiles/katsuhisa.abe">me</a> if you have any requests, suggestions, corrections and bug reports.<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgCg5WMdqP8tDTRlJ2PTevGcLbzgApnfXJ7O_LVHL5r_fbO6XWTDSfcbKRRU0h7iGyElSq1dIw2spDB6fZL70ar8n7VmQCCLf0gD-iJZl3RU52-kyWz7Yf9Stjw5TgFyufaho8DQQ/s1600/android-library-google-api-java.jpg" imageanchor="1" style=""><img border="0" height="208" width="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgCg5WMdqP8tDTRlJ2PTevGcLbzgApnfXJ7O_LVHL5r_fbO6XWTDSfcbKRRU0h7iGyElSq1dIw2spDB6fZL70ar8n7VmQCCLf0gD-iJZl3RU52-kyWz7Yf9Stjw5TgFyufaho8DQQ/s320/android-library-google-api-java.jpg" /></a></div></p>abekatsuhttp://www.blogger.com/profile/12886183524644595053noreply@blogger.com0tag:blogger.com,1999:blog-14443318.post-68527153924029871622011-05-08T11:43:00.001+09:002011-05-08T11:44:22.844+09:00Google Spreadsheets を Androidから扱う (5) DataModelの定義<p><a href="http://code.google.com/p/google-api-java-client/">Google-API-Java-Client</a>を用いて,Google Docsにアクセスするためには,GData-Java-Clientとは異なり,自分でデータモデルを定義する必要があります。<br />
<br />
<blockquote>First, you need to invest in writing a custom data model for the Google API. Please read the JavaDoc for the new XML data model in google-api-java-client. <br /><br />
参照元 (<a href="http://code.google.com/p/gdata-java-client/wiki/MigratingToGoogleApiJavaClient">What if I have a larde code base that uses gdata-java-client</a>)<br />
</blockquote><br />
ここでは,次のfeedを表すデータモデルを具体的に定義してみましょう。feed は<br />
<a href="http://code.google.com/intl/ja/apis/gdata/docs/2.0/reference.html#QueryResponses">QueryResponses</a><br />
に掲載されているデータを利用しました。<br />
<br />
<div id="feed_sample"><pre class="brush:xml"><?xml version="1.0" encoding="UTF-8"?>
<feed xmlns:atom="http://www.w3.org/2005/Atom"
xmlns:openSearch="http://a9.com/-/spec/opensearch/1.1/"
xmlns:gd='http://schemas.google.com/g/2005'
gd:etag='W/"C0QBRXcycSp7ImA9WxRVFUk."'>
<id>http://www.example.com/feed/1234.1/posts/full</id>
<updated>2005-09-16T00:42:06Z</updated>
<title type="text">Books and Romance with Jo and Liz</title>
<link rel="alternate" type="text/html" href="http://www.example.net/"/>
<link rel="http://schemas.google.com/g/2005#feed"
type="application/atom+xml"
href="http://www.example.com/feed/1234.1/posts/full"/>
<link rel="http://schemas.google.com/g/2005#post"
type="application/atom+xml"
href="http://www.example.com/feed/1234.1/posts/full"/>
<link rel="self" type="application/atom+xml"
href="http://www.example.com/feed/1234.1/posts/full"/>
<author>
<name>Elizabeth Bennet</name>
<email>liz@gmail.com</email>
</author>
<generator version="1.0"
uri="http://www.example.com">Example Generator Engine</generator>
<openSearch:totalResults>2</openSearch:totalResults>
<openSearch:startIndex>0</openSearch:startIndex>
<entry gd:etag='W/"C0QBRXcycSp7ImA9WxRVGUo."'>
<id>http://www.example.com/feed/1234.1/posts/full/4521614025009481151</id>
<published>2005-01-09T08:00:00Z</published>
<updated>2005-01-09T08:00:00Z</updated>
<category scheme="http://www.example.com/type" term="blog.post"/>
<title type="text">This is the title of entry 1009</title>
<content type="xhtml">
<div
xmlns="http://www.w3.org/1999/xhtml">This is the entry body of entry 1009</div> </content>
<link rel="alternate" type="text/html"
href="http://www.example.com/posturl"/>
<link rel="edit" type="application/atom+xml"
href="http://www.example.com/feed/1234.1/posts/full/4521614025009481151"/>
<author>
<name>Elizabeth Bennet</name>
<email>liz@gmail.com</email>
</author>
</entry>
<entry gd:etag='W/"C0QBRXrurSp7ImA9WxRVGUo."'>
<id>http://www.example.com/feed/1234.1/posts/full/3067545004648931569</id>
<published>2005-01-07T08:00:00Z</published>
<updated>2005-01-07T08:02:00Z</updated>
<category scheme="http://www.example.com/type" term="blog.post"/>
<title type="text">This is the title of entry 1007</title>
<content type="xhtml">
<div
xmlns="http://www.w3.org/1999/xhtml">This is the entry body of entry 1007</div> </content>
<link rel="alternate" type="text/html"
href="http://www.example.com/posturl"/>
<link rel="edit" type="application/atom+xml"
href="http://www.example.com/feed/1234.1/posts/full/3067545004648931569"/>
<author>
<name>Elizabeth Bennet</name>
<email>liz@gmail.com</email>
</author>
</entry>
</feed>
</pre></div><br />
まず,root要素 feed について定義していきます。<br />
<br />
<div id="feed_element"><pre class="brush:java">public class GoogleDocFeed {
// TBD
}
</pre></div><br />
feed要素の属性にある名前空間に関係した属性,"xmlns:atom","xmlns:openSearch","xmlns:gd" は今回扱いません。<br />
一方,属性 "gd:etag" は今後使うことも考慮して,データモデルを定義します。<br />
<div id="feed_gd:etag"><pre class="brush:java">import com.google.api.client.util.Key;
public class GoogleDocFeed {
@Key("gd:etag")
public string gd_etag;
// TBD
}
</pre></div><br />
つぎに,<id>http://www.example.com/feed/1234.1/posts/full</id> のように,属性を持たず,かつ内部に子要素を含まない要素の<br />
データモデルを定義していきます。<br />
<br />
<div id="feed_gd:id"><pre class="brush:java"> @Key
public string id; // for <id>http://www.example.com/feed/1234.1/posts/full</id>
@Key
public string updated; // for <updated>2005-09-16T00:42:06Z</updated>
</pre></div><br />
つぎに,link要素を定義していきます。feed要素にはlink要素を子要素として複数持つことができますので,<br />
<br />
<div id="feed_gd:links"><pre class="brush:java"> @Key("link")
public List<Link> links;
</pre></div><br />
と定義します。class Link は以下のように定義します。<br />
<br />
<div id="Links"><pre class="brush:java">public class Link {
// for <link rel="alternate" type="text/html" href="http://www.example.net/"/>
@Key("@rel")
public String rel;
@Key("@type")
public String type;
@Key("@href")
public String href;
}
</pre></div><br />
次に,author要素を定義します。<br />
<div id="Author"><pre class="brush:java">public class Author {
/* for
* <author>
* <name>Elizabeth Bennet</name>
* <email>liz@gmail.com</email>
* </author>
*/
@Key
public String name;
@Key
public String email;
}
</pre></div><br />
その他に,属性と要素の内容にテキストコンテンツが与えられているtitle要素を定義します。<br />
<a href="http://javadoc.google-api-java-client.googlecode.com/hg/1.0.9-alpha/com/google/api/client/googleapis/xml/atom/package-summary.html"><br />
JavaDoc Package com.google.api.client.googleapis.xml.atom</a>に,<br />
<blockquote>The optional value parameter of this @Key annotation specifies the XPath name to use to represent the field. For example, an XML attribute a has an XPath name of @a, an XML element <a> has an XPath name of a, and an XML text content has an XPath name of text(). <br />
</blockquote>とありますように,@Key("text()") と指定して,テキストコンテンツの内容を表します。<br />
<br />
<div id="Title"><pre class="brush:java">public class Title {
/* for
* <title type="text">This is the title of entry 1007</title>
*/
@Key("@type")
public String type;
@Key("text()")
public String context;
}
</pre></div>他の要素も同様にして,上記で与えられている feed 要素を定義するデータモデル GoogleDocFeed が定義できます。<br />
<br />
<br />
<div id="fGoogleDocFeed"><pre class="brush:java">import com.google.api.client.util.Key;
public class Category {
/*
* <category scheme="http://www.example.com/type" term="blog.post"/>
*/
@Key("@scheme")
public String scheme;
@Key("@term")
public String term;
}
public class Content {
/*
* <content type="xhtml">
* <div
* xmlns="http://www.w3.org/1999/xhtml">This is the entry body of entry 1007</div> * </content>
*/
@Key("@type")
public String type;
@Key("text()")
public String content;
}
public class Link {
/*
* <link rel="alternate" type="text/html" href="http://www.example.net/"/>
*/
@Key("@rel")
public String rel;
@Key("@type")
public String type;
@Key("@href")
public String href;
}
public class Author {
/* for
* <author>
* <name>Elizabeth Bennet</name>
* <email>liz@gmail.com</email>
* </author>
*/
@Key
public String name;
@Key
public String email;
}
public class Title {
/* for
* <title type="text">This is the title of entry 1007</title>
*/
@Key("@type")
public String type;
@Key("text()")
public String context;
}
public class GoogleDocEntry {
@Key
public string id;
@Key
public string published;
@Key
public string updated;
@Key
public Category category;
@Key
public Title title;
@Key
public Content content;
@Key("link")
public List<Link> links;
@Key
public Author author;
}
public class GoogleDocFeed {
@Key("gd:etag")
public string gd_etag;
@Key
public string id; // for <id>http://www.example.com/feed/1234.1/posts/full</id>
@Key
public string updated; // for <updated>2005-09-16T00:42:06Z</updated>
@Key("link")
public List<Link> links;
@Key
public Author author;
@Key
public Generator generator;
@Key("openSearch:totalResults")
public int totalResults;
@Key("openSearch:startIndex")
public int startIndex;
@Key("entry")
public List<GoogleDocEntry> entries;
}
</pre></div><br />
<br />
今回は,具体的なコンテントをベースにしてデータモデルを定義しましたが,<br />
schema は用意されていますので(例 <a href="http://code.google.com/intl/ja/apis/spreadsheets/data/3.0/reference.html"><br />
Google Spreadsheets API Reference Guide (v3.0)</a>),それを参考にして各自でデータモデルを定義できます。<br />
<br />
<br />
</p>abekatsuhttp://www.blogger.com/profile/12886183524644595053noreply@blogger.com0tag:blogger.com,1999:blog-14443318.post-34031089220223918622011-04-07T11:08:00.001+09:002011-04-07T11:10:24.333+09:00Google Spreadsheets を Androidから扱う (4)<a href="http://abekatsu.blogspot.com/2011/04/google-spreadsheets-android-3.html#links">前回</a>,access Token と access TokenSecretを取得しました。
これを用いて,自分のアカウントのspread sheet一覧を取得してみます。<br />
<a href="http://code.google.com/intl/ja/apis/spreadsheets/data/3.0/developers_guide.html#ListingSpreadsheets">Retrieving a list of spreadsheets</a>を読みますと,
<pre class="brush:bash">
https://spreadsheets.google.com/feeds/spreadsheets/private/full
</pre>
に送信すれば,認証されたユーザのspreadsheetsのfeedを入手することができます。<br />
具体的なコードは以下の通りです。
<h4>HttpTransporへの署名</h4>
<div id="content1">
<pre class="brush:java">
String token = "your access token";
String tokenSecret = "your access tokenSecret";
HttpTransport transport = new ApacheHttpTransport();
OAuthParameters parameters = createOAuthParameters(token, tokenSecret);
parameters.signRequestsUsingAuthorizationHeader(transport);
getSpreadSheets(transport);
</pre>
</div>
各メソッドの内容は以下の通りです。
<h5>createOAuthParameters</h5>
<div id="content2">
<pre class="brush:java">
public OAuthParameters createOAuthParameters(String token, String tokenSecret) {
OAuthParameters authorizer = new OAuthParameters();
authorizer.consumerKey = "anonymous";
authorizer.signer = createOAuthSigner(tokenSecret);
authorizer.token = token;
return authorizer;
}
</pre>
</div>
<h5>createOAuthSigner</h5>
<div id="content3">
<pre class="brush:java">
public OAuthHmacSigner createOAuthSigner(String tokenSecret) {
OAuthHmacSigner signer = new OAuthHmacSigner();
if (tokenSecret != null) {
signer.tokenSharedSecret = tokenSecret;
}
signer.clientSharedSecret = "anonymous";
return signer;
}
</pre>
</div>
<h5>getSpreadSheet</h5>
<div id="content4">
<pre class="brush:java">
public void getSpreadSheets(HttpTransport transport) {
// Set AtomParser
AtomParser parser = new AtomParser();
XmlNamespaceDictionary dictionary = new XmlNamespaceDictionary();
dictionary.set("", Atom.ATOM_NAMESPACE);
dictionary.set("docs", "http://schemas.google.com/docs/2007");
dictionary.set("batch", "http://schemas.google.com/gdata/batch");
dictionary.set("gd", "http://schemas.google.com/g/2005");
dictionary.set("gd:etag", "W/"DEMCQHk7eSt7ImA9WhZSGUw."");
dictionary.set("openSearch", "http://a9.com/-/spec/opensearch/1.1/");
dictionary.set("app", "http://www.w3.org/2007/app");
parser.namespaceDictionary = dictionary;
transport.addParser(parser);
// Set Http Headers
HttpHeaders headers = transport.defaultHeaders;
headers.set("User-Agent", appname);
headers.set("GData-Version", "3.0");
HttpRequest request = transport.buildGetRequest();
request.url = new GoogleUrl("https://spreadsheets.google.com/feeds/spreadsheets/private/full");
try {
HttpResponse response = request.execute();
// null chekc していないので注意
InputStreamReader inr = new InputStreamReader(response.getEntity().getContent());
BufferedReader reader = new BufferedReader(inr, 256);
while ((str = reader.readLine()) != null) {
Log.d(TAG, line);
}
reader.close();
inr.close();
} catch (IOException e) {
Log.e(TAG, e.getMessage());
e.printStackTrace();
}
}
</pre>
</div>
これを実行し,正常にHttpResponseが得られますと,以下のようなXML documentが得られます。
<div id="content5">
<pre class="brush:xml">
<?xml version='1.0' encoding='UTF-8'?>
<feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearch/1.1/' xmlns:gd='http://schemas.google.com/g/2005' gd:etag='W/"DEMCQHk7eSt7ImA9WhZSGUw."'>
<id>https://spreadsheets.google.com/feeds/spreadsheets/private/full</id>
<updated>2011-04-04T11:27:41.701Z</updated>
<category scheme='http://schemas.google.com/spreadsheets/2006' term='http://schemas.google.com/spreadsheets/2006#spreadsheet'/>
<title>Available Spreadsheets - Maskes(user's email address)</title>
<link rel='alternate' type='text/html' href='http://docs.google.com'/>
<link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='https://spreadsheets.google.com/feeds/spreadsheets/private/full'/>
<link rel='self' type='application/atom+xml' href='https://spreadsheets.google.com/feeds/spreadsheets/private/full'/>
<openSearch:totalResults>11</openSearch:totalResults>
<openSearch:startIndex>1</openSearch:startIndex>
<entry gd:etag='"BBQbQxQLQit7ImBr"'>
<id>https://spreadsheets.google.com/feeds/spreadsheets/tRsAk0wSkUi4wCBZxOJITuw</id>
<updated>2011-04-04T04:52:59.849Z</updated>
<category scheme='http://schemas.google.com/spreadsheets/2006' term='http://schemas.google.com/spreadsheets/2006#spreadsheet'/>
<title>Hack For Japan Projects</title>
<content type='application/atom+xml;type=feed' src='https://spreadsheets.google.com/feeds/worksheets/tRsAk0wSkUi4wCBZxOJITuw/private/full'/>
<link rel='http://schemas.google.com/spreadsheets/2006#tablesfeed' type='application/atom+xml' href='https://spreadsheets.google.com/feeds/tRsAk0wSkUi4wCBZxOJITuw/tables'/>
<link rel='alternate' type='text/html' href='https://spreadsheets.google.com/ccc?key=0Amb6cvTCzTQRdFJzQWswd1NrVWk0d0NCWnhPSklUdXc'/>
<link rel='self' type='application/atom+xml' href='https://spreadsheets.google.com/feeds/spreadsheets/private/full/tRsAk0wSkUi4wCBZxOJITuw'/>
<author>
<name>Masked</name>
<email>Masked</email>
</author>
</entry>
</feed>
</pre>
</div>
次回は,得られた XML Document の parse を行います。abekatsuhttp://www.blogger.com/profile/12886183524644595053noreply@blogger.com0tag:blogger.com,1999:blog-14443318.post-1615749398855752162011-04-05T09:29:00.001+09:002011-04-05T09:32:51.157+09:00Google Spreadsheets を Androidから扱う (3)<p>
<a href="http://abekatsu.blogspot.com/2011/04/google-spreadsheets-android-2.html#links">前回</a>,Appを認証した際に得られるoauth_verifierとoauth_tokenの値が返ってくることまで確認しました。
</p>
<p>
今回は,これらの値と<a href="http://abekatsu.blogspot.com/2011/03/google-spreadsheets-android-1.html#links">前々回</a>,Request Toke取得途中に得られた,OAuthCredentialsResponseの値と一緒に,AccesssTokenを取得します。
</p>
<h4>oauth_verifier, oauth_tokenの取得</h4>
<p>
ブラウザからのcallback時に発行されるIntentの中のURIデータを取得し,それをOAuthCallbackUrl に引き渡します。
</p>
<p>
<div id="content1">
<pre class="brush:java">
Uri uri = this.getIntent().getData();
OAuthCallbackUrl callbackUrl;
if (uri != null) {
Log.d(TAG, "uri: " + uri.toString());
callbackUrl = new OAuthCallbackUrl(uri.toString());
}
</pre>
</div>
</p>
<h4>GoogleOAuthGetAccessTokenの作成</h4>
<p>
上で作成した,OAuthCallbackUrl callbackUrl と,Request Token取得時に得られた
OAuthCredentialsResponse credentials (Request Token)を用いて <a href="http://javadoc.google-api-java-client.googlecode.com/hg/1.3.1-alpha/index.html">GoogleOAuthGetAccessToken</a> を作成します。
</p>
<p>
<div id="content2">
<pre class="brush:java">
HttpTransport transport = new ApacheHttpTransport();
GoogleOAuthGetAccessToken accessToken = new GoogleOAuthGetAccessToken();
accessToken.transport = transport;
accessToken.temporaryToken = callbackUrl.token;
accessToken.verifier = callbackUrl.verifier;
accessToken.signer = createOAuthSigner(credentials);
accessToken.consumerKey = "anonymous";
</pre>
</div>
</p>
<h4>Access Tokenの取得</h4>
<p>
GoogleOAuthGetAccessToken accessTokenをexecute()することによって,Access Tokenを取得します。
</p>
<p>
<div id="content3">
<pre class="brush:java">
OAuthCredentialsResponse response = accessToken.execute();
</pre>
</div>
</p>
<p>
この返り値 OAuthCredentialsResponse response に含まれている,
(access)
<a href="http://javadoc.google-api-java-client.googlecode.com/hg/1.3.1-alpha/com/google/api/client/auth/oauth/OAuthCredentialsResponse.html#token">token</a>
と(access)
<a href="http://javadoc.google-api-java-client.googlecode.com/hg/1.3.1-alpha/com/google/api/client/auth/oauth/OAuthCredentialsResponse.html#tokenSecret">tokenSecret</a>
が,今後Spreadsheetsにアクセスするための認可されたHTTP requestを生成する際に必要となります。
</p>
<p>
次回は,自分のアカウントのSpreadSheet一覧を取得します。
</p>abekatsuhttp://www.blogger.com/profile/12886183524644595053noreply@blogger.com0tag:blogger.com,1999:blog-14443318.post-60716424877260153512011-04-04T10:03:00.001+09:002011-04-04T10:07:03.824+09:00Google Spreadsheets を Androidから扱う (2)OAuthの続きです。
<h4>Implict intent</h4>
<a href="http://developer.android.com/guide/topics/intents/intents-filters.html#ifs">androidには,intentでactivity, service, broadcastに応答させる昨日があります。</a>この機能を利用して,Browser上でリンクをクリックして,インストールされているactivityを起動する方法があります。 <br />
とくに,
<blockquote>
Consider, for example, what the browser
application does when the user follows a link on a web page. It first
tries to display the data (as it could if the link was to an HTML
page). If it can't display the data, it puts together an implicit
intent with the scheme and data type and tries to start an activity
that can do the job. If there are no takers, it asks the download
manager to download the data. That puts it under the control of a
content provider, so a potentially larger pool of activities (those
with filters that just name a data type) can respond.
</blockquote>
とありますように,ブラウザでWebページ上のリンクをクリックして,
データが表示されない場合,該当する暗黙のintentを発行するようになっています。
OAuth認証では,この機能を利用して,callback urlに自分のアプリを起動できるようなurlを指定します。<br />
<a href="http://code.google.com/p/io2010-buzzjava/">Google I/O 2010 Buzz Android Sample for Java Client Library for Google API's version 2.2.0-alpha</a>の実装を参考にして,話を進めていきます。<br />
<h4>Request Token (Temporary credentials token) の取得</h4>
<a href="http://abekatsu.blogspot.com/2011/03/google-spreadsheets-android-1.html#links">前回</a>,GoogleOAuthAuthorizeTemporaryTokenUrlを作成いたしましたが,その補足です。<br />
Request Tokenを取得するために,<a href="http://javadoc.google-api-java-client.googlecode.com/hg/1.3.1-alpha/index.html">GoogleOAuthGetTemporaryToken</a>を利用します。<br />
<div id="content1">
<pre class="brush:java">
GoogleOAuthGetTemporaryToken temporaryToken = new GoogleOAuthGetTemporaryToken();
OAuthHmacsinger signer = new OAuthHmacSigner();
signer.clientSharedSecret = "anonymous";
temporaryToken.signer = signer;
temporaryToken.displayName = appname;
temporaryToken.consumerKey = "anonymous";
temporaryToken.scope = "https://spreadsheets.google.com/feeds/";
temporaryToken.transport = transport;
temporaryToken.callback = "x-myspreadsheet://com.example.abekatsu/";
</pre>
</div>
前回との違いは,callbackの指定です。<a href="http://stackoverflow.com/questions/3469908/make-a-link-in-the-android-browser-start-up-my-app/3472228#3472228">custom schemeを使うな</a>という意見がありますが,custom schemeを用いたアプリの起動が広まっている現状を踏まえ,今回は利用します。<br />
競合を避けるために,host部分をjavaのpackage名のように,自分自身のFQDNをTop levelから書いてみました。<br />
<h4>GoogleOAuthAuthorizeTemporaryTokenUrlの作成</h4>
<div id="content2">
<pre class="brush:java">
OAuthCredentialsResponse response = temporaryToken.execute();
GoogleOAuthAuthorizeTemporaryTokenUrl authorizeUrl = new GoogleOAuthAuthorizeTemporaryTokenUrl();
authorizeUrl.template = "mobile";
authorizeUrl.set("scope", scope);
authorizeUrl.set("domain", "anonymous");
authorizeUrl.set("xoauth_displayname", appname);
authorizeUrl.temporaryToken = response.token;
</pre>
</div>
この authorizeUrl で得られるURLを引数にしてブラウザを起動します。
<div id="content3">
<pre class="brush:java">
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse(temporaryTokenUrl.build()));
startActivity(intent);
</pre>
</div>
<p>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhSVyZECfG-JrRpeYYDjjy43DsWDUIiWFlB7zc4seV_QQaTcLDuxjWiVKB_PNMb0bpeixx3NNXAIJdtq9aEC3M44IFr64FIawyVDUY3A2qmmxPEZTJk229ocyKn2sn9PqvK3G_jhA/s1600/device_20110331_02.png" imageanchor="1" style="clear:left; float:left;margin-right:1em; margin-bottom:1em"><img border="0" height="320" width="192" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhSVyZECfG-JrRpeYYDjjy43DsWDUIiWFlB7zc4seV_QQaTcLDuxjWiVKB_PNMb0bpeixx3NNXAIJdtq9aEC3M44IFr64FIawyVDUY3A2qmmxPEZTJk229ocyKn2sn9PqvK3G_jhA/s320/device_20110331_02.png" /></a>
</div><br />
実際に実行しますと,GoogleのAccountページが表示されます。ここで,「Grant access」をクリックすると,上記のcallbackで指定したURLを含む暗黙のIntentが発行されます。
</p>
<br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br />
<h4>暗黙のIntentの取得</h4>
先程のステップで得られた,暗黙のIntentを取得してみましょう。
<h5>AndroidManifest.xml</h5>
まずは,AndroidManifest.xmlで,このIntentを補足するように指定します。
<div id="content4">
<pre class="brush:xml">
<application android:icon="@drawable/icon" android:label="@string/app_name">
<activity android:name=".MyActivity"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<!-- 追加部分 -->
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="x-myspreadsheet"
android:host="com.example.abekatsu" />
</intent-filter>
</activity>
</application>
</pre>
</div>
暗黙のIntentでは,カテゴリーが "android.intent.category.DEFAULT" となります。
<h5>Activityの編集</h5>
ここでは,単純に Intent に含まれているデータを表示させてみます。
<div id="content5">
<pre class="brush:xml">
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
boolean isViewAction = Intent.ACTION_VIEW.equals(getIntent().getAction());
if (isViewAction) {
Uri uri = this.getIntent().getData();
if (uri != null) {
Log.d(TAG, "uri: " + uri.toString());
}
}
}
</pre>
</div>
実際に,アプリケーションを実行して Grant access すると次のデータを含む
暗黙のIntentが発行されます。
<div id="content5">
<pre class="brush:bash">
D/OkodukaiNoteActivity( 235): uri: x-okozukai:///?oauth_verifier=ここにoauth_verifierの文字列&oauth_token=ここにoauth_tokenの文字列
</pre>
</div>
次回はこのauth_tokenをGoogleにauthorizeします。abekatsuhttp://www.blogger.com/profile/12886183524644595053noreply@blogger.com0tag:blogger.com,1999:blog-14443318.post-6236868985536024912011-03-30T15:48:00.001+09:002011-04-05T09:16:34.381+09:00Google Spreadsheets を Androidから扱う (1)<p>
Androidアプリで扱うデータを外部のSpreadsheetに保存することを目的とします。
<h4>library</h4>
今回は<a href="http://code.google.com/p/google-api-java-client/">Google API Client Library for Java</a>の1.3.1-alphaを使ってみます。<br />
とりあえず,プロジェクトに次の3つのjarファイルを参照するように設定いたしました。
<ul>
<li>google-api-client-1.3.1-alpha.jar
<li>google-api-client-googleapis-1.3.1-alpha.jar
<li>dependencies/guava-r08.jar
</ul>
google-api-client-extensions-1.3.1-alpha.jarは Google App Engine用なので,今回は含めません。依存関係は試行錯誤の結果です。dependecies以下のjar(soruceは除く)を予め含めた方が今後楽かもしれません。
<h4>認証</h4>
Spreadsheetなど扱う前に,アプリケーションへ認証を与えることが必要になります。
ここではOAuthを用いた認証を行います。<br />
具体的な手順は,<a href="http://javadoc.google-api-java-client.googlecode.com/hg/1.2.0-alpha/com/google/api/client/googleapis/auth/oauth/package-summary.html">
OAuth 1.0a for Google API's JavaDoc</a>に記載されています。<br />
まずは,temporary credentials token ("request token") を入手します。
installed application では signature method に "HMAC-SHA1",consumerKeyとclientSharedSecretに"anonymos"を使うように求められていますので,それに従います。
<div id="content1">
<pre class="brush:java">
HttpTransport transport = new ApacheHttpTransport();
GoogleOAuthGetTemporaryToken temporaryToken = new GoogleOAuthGetTemporaryToken();
OAuthHmacsinger signer = new OAuthHmacSigner();
signer.clientSharedSecret = "anonymous";
temporaryToken.signer = signer;
temporaryToken.displayName = appname;
temporaryToken.consumerKey = "anonymous";
temporaryToken.scope = "https://spreadsheets.google.com/feeds/";
temporaryToken.transport = transport;
</pre>
</div>
displayNameには,このアプリケーションの名前(String 型)を入れます。scopeに入るべき値は,<a href="http://code.google.com/intl/ja/apis/gdata/faq.html#AuthScopes">Google Data Protocol FAQ</a>を参照してください。 <br />
それでは,TemporaryTokenの値を入手してみます。
<div id="content2">
<pre class="brush:java">
OAuthCredentialsResponse credentials = temporaryToken.execute();
</pre>
</div>
戻り値のtoken値を利用して,<a href="http://javadoc.google-api-java-client.googlecode.com/hg/1.2.0-alpha/index.html?com/google/api/client/googleapis/auth/oauth/GoogleOAuthGetTemporaryToken.html">GoogleOAuthAuthorizeTemoraryTokenUrl</a>を作成します。これで,ユーザにアクセスしていただくurlが得られます。
<div id="content3">
<pre class="brush:java">
if (credentials != null) {
GoogleOAuthAuthorizeTemporaryTokenUrl temporaryTokenUrl = new GoogleOAuthAuthorizeTemporaryTokenUrl();
temporaryTokenUrl.template = "mobile";
temporaryTokenUrl.temporaryToken = credentials.token;
// Uri uri = new Uri(temporaryTokenUrl.build());
Log.d(TAG, "temoraryTokenUrl: " + temporaryTokenUrl.build());
}
</pre>
</div>
実行すると, temoratyTokenUrl の値として,
<div id="content4">
<pre class="brush:bash">
https://www.google.com/accounts/OAuthAuthorizeToken?btmpl=mobile&oauth_token=...some_oauth_token....
</pre>
</div>
が得られました。<br />
この次は,このURLをWebViewに表示させまして,アプリケーションの認証を行います。abekatsuhttp://www.blogger.com/profile/12886183524644595053noreply@blogger.com0tag:blogger.com,1999:blog-14443318.post-90325550278580344572011-03-28T15:55:00.000+09:002011-03-28T15:55:48.747+09:00ContentProviderのテストの仕方<p>
今回,BottomPriceProvider というContentProviderの単体テストを行なう BottomPrivceProviderTest を作成してみましたので,そのメモを記していきます。
</p>
<h4>クラスの作成</h4>
<a href="http://developer.android.com/reference/android/test/ProviderTestCase2.html">ProviderTestCase2>T extends android.content.ContentProvider<</a>を継承して作成します。 <br />
<div id='content1'>
<pre class='brush:java'>
public class BottomPriceProviderTest extends ProviderTestCase2<BottomPriceProvider> {
...
}
</pre>
</div>
<h4>コンストラクタの生成</h4>
super class から コンストラクタを自動生成すると,次のようなコードが生成されます。
<div id='content2'>
<pre class='brush:java'>
public BottomPriceProviderTest(Class<BottomPriceProvider> providerClass,
String providerAuthority) {
super(providerClass, providerAuthority);
// TODO Auto-generated constructor stub
}
</pre>
</div>
面倒なので,引数を指定しない版も作成しておきます。
<div id='content3'>
<pre class='brush:java'>
public BottomPriceProviderTest() {
super(BottomPriceProvider.class, BottomPriceProvider.AUTHORITY);
// TODO Auto-generated constructor stub
}
</pre>
</div>
ここでの,BottomPriceProvider.AUTHORITY は ContentProviderを提供する際に指定するContentのAuthority名です。<br />
<h4>setUp, tearDown</h4>
setUpでは,フィールド mMockContext に<a href="http://developer.android.com/reference/android/test/ProviderTestCase2.html#getMockContext()">getMockContext()</a>で得られるContextを格納しておきます。 <br />
tearDownでは特になにもしません。
<div id='content4'>
<pre class='brush:java'>
private Context mMockContext;
@Override
protected void setUp() throws Exception {
// TODO Auto-generated method stub
super.setUp();
mMockContext = getMockContext();
}
@Override
protected void tearDown() throws Exception {
// TODO Auto-generated method stub
super.tearDown();
}
</pre>
</div>
<h4>テスト</h4>
まずは簡単なところで,insert/query/deleteのテストを書いてみます。
テスト用の<a href="http://developer.android.com/reference/android/content/ContentValues.html">ContentValues</a>を挿入,検索し,その検索結果が挿入したContentValuesの内容と一致することを確認し,最後にテスト用ContentValuesを削除します。
<div id='content4'>
<pre class='brush:java'>
public void testInsertDeleteRecord() {
final ContentResolver resolver = mMockContext.getContentResolver();
final Uri uri = BottomPrice.Prices.CONTENT_URI;
ContentValues cv = new ContentValues();
int price = 480;
String jan_code_str = "4901111725133";
cv.put(BottomPrice.Prices.PRICE, price);
cv.put(BottomPrice.Prices.JAN_CODE, jan_code_str);
Uri insertUri = resolver.insert(uri, cv);
String price_id = insertUri.getPathSegments().get(1);
// Query
Uri queryUri = Uri.withAppendedPath(uri, price_id);
String[] projection = new String[] {
BottomPrice.Prices._ID,
BottomPrice.Prices.PRICE,
BottomPrice.Prices.JAN_CODE,
};
Cursor c = resolver.query(queryUri, projection, null, null, null);
assertEquals("query resuult", 1, c.getCount());
c.moveToFirst();
assertEquals("Jan Code", jan_code_str, c.getString(c.getColumnIndex(BottomPrice.Prices.JAN_CODE)));
assertEquals("Price", price, c.getInt(c.getColumnIndex(BottomPrice.Prices.PRICE)));
c.close();
resolver.delete(queryUri, null, null);
}
</pre>
</div>
<h4></h4>
実際にテストを実行してみましょう。
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiHgqmuviAPQdU3jr4moxca5siDPVKUjgS5LyrxnMyGTA0QvEMmfhIgHwfRcdtSbCvEeFp6_jsKvobQ3j9XSefunQ-W6MWhHM8MmymmIsIPKIDG6fnlmCQo5ef_HQl1DqHAVw8gPw/s1600/screen_2011032801.jpg" imageanchor="1" style="clear:left; float:left;margin-right:1em; margin-bottom:1em"><img border="0" height="182" width="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiHgqmuviAPQdU3jr4moxca5siDPVKUjgS5LyrxnMyGTA0QvEMmfhIgHwfRcdtSbCvEeFp6_jsKvobQ3j9XSefunQ-W6MWhHM8MmymmIsIPKIDG6fnlmCQo5ef_HQl1DqHAVw8gPw/s320/screen_2011032801.jpg" /></a></div>
テスト Errorとなり,Failure Trace をみると
<div id='content5'>
<pre class='brush:java'>
android.database.SQLException: Failed to insert row into content://com.damburisoft.android.app.bottomrpice.provider.bottompriceprovider/prices
at com.damburisoft.android.app.bottomrpice.provider.BottomPriceProvider.insert(BottomPriceProvider.java:107)
at android.content.ContentProvider$Transport.insert(ContentProvider.java:150)
at android.content.ContentResolver.insert(ContentResolver.java:629)
at com.damburisoft.android.provider.BottomPriceProviderTest.testInsertDeleteRecord(BottomPriceProviderTest.java:50)
at java.lang.reflect.Method.invokeNative(Native Method)
at android.test.AndroidTestRunner.runTest(AndroidTestRunner.java:169)
at android.test.AndroidTestRunner.runTest(AndroidTestRunner.java:154)
at android.test.InstrumentationTestRunner.onStart(InstrumentationTestRunner.java:430)
at android.app.Instrumentation$InstrumentationThread.run(Instrumentation.java:1447)
</pre>
</div>
とあるので,これを直していくことによって自分のContentProviderを創り上げていきます。abekatsuhttp://www.blogger.com/profile/12886183524644595053noreply@blogger.com0tag:blogger.com,1999:blog-14443318.post-78075297048649627322011-03-27T22:20:00.000+09:002011-03-27T22:20:33.650+09:00今後の方針 (Mar/27/2011 version)<p>
自分の方針というのを決めていこうと考える。ただ,一度決めた物絶対ということではなく,何度も推敲を重ねていこうと。
<ul>
<li> ◯◯とITを念頭に。ITの中だけで閉じることに何らかの疑問がある。ITを通して現実の世界にある問題を解決する手助けをしよう。
<li> 80%の力で<br /> 重要な時に歯を食いしばるのはいいが,常に歯を食いしばっている状況はおかしいはず。全力で90分フルに走れるフットボール選手はいないように,要素要素で力を抜けるように。
<li> 人は迷惑をかけて生きている。迷惑をかけているかどうか気にしない。自分が無理なく生きていくためにやれることをまず探す。
</ul>
</p>abekatsuhttp://www.blogger.com/profile/12886183524644595053noreply@blogger.com0tag:blogger.com,1999:blog-14443318.post-71170825850718294052011-01-30T19:50:00.003+09:002011-01-30T20:02:08.935+09:00taintdroid code reading 3.<p>
I read a diff of services/java/com/android/server/LocationManagerService.java. <br />
It is almost the same as the diff of telephony.
When location update message is received, the location is stored in Taint object as following:
<pre>
<code>
location.setLatitude(Taint.addTaintDouble(location.getLatitude(), tag));
</code>
</pre>
But I cannot understand the reason why obtained value is set again, that is,
why setLatitude is called. <br />
And the other point I cannot understand is that how to distinguish the values. For example,
the below is a code to store latitude and location.
<pre>
<code>
location.setLatitude(Taint.addTaintDouble(location.getLatitude(), tag));
location.setLongitude(Taint.addTaintDouble(location.getLongitude(), tag));
</code>
</pre>
where tag is given here as following:
<pre>
<code>
int tag = Taint.TAINT_LOCATION;
if (LocationManager.GPS_PROVIDER.equals(provider)) {
tag |= Taint.TAINT_LOCATION_GPS;
}
if (LocationManager.NETWORK_PROVIDER.equals(provider)) {
tag |= Taint.TAINT_LOCATION_NET;
}
</code>
</pre>
Latitude and Longitude are added by addTaingDouble, but I cannot see how to distinguish latitude and longitude. <br />
I paste the whole diff below.
</p>
<pre>
<code>
diff --git a/services/java/com/android/server/LocationManagerService.java b/services/java/com/android/server/LocationManagerService.java
index bbb43d7..4a5846a 100644
--- a/services/java/com/android/server/LocationManagerService.java
+++ b/services/java/com/android/server/LocationManagerService.java
@@ -68,6 +68,10 @@ import com.android.internal.location.LocationProviderProxy;
import com.android.internal.location.MockProvider;
import com.android.internal.location.GpsNetInitiatedHandler;
+// begin WITH_TAINT_TRACKING
+import dalvik.system.Taint;
+// end WITH_TAINT_TRACKING
+
/**
* The service class that manages LocationProviders and issues location
* updates and alerts.
@@ -1526,6 +1530,30 @@ public class LocationManagerService extends ILocationManager.Stub implements Run
Location location = (Location) msg.obj;
String provider = location.getProvider();
+ // begin WITH_TAINT_TRACKING
+ int tag = Taint.TAINT_LOCATION;
+ if (LocationManager.GPS_PROVIDER.equals(provider)) {
+ tag |= Taint.TAINT_LOCATION_GPS;
+ }
+ if (LocationManager.NETWORK_PROVIDER.equals(provider)) {
+ tag |= Taint.TAINT_LOCATION_NET;
+ }
+ location.setLatitude(Taint.addTaintDouble(location.getLatitude(), tag));
+ location.setLongitude(Taint.addTaintDouble(location.getLongitude(), tag));
+ if (location.hasAltitude()) {
+ location.setAltitude(Taint.addTaintDouble(location.getAltitude(), tag));
+ }
+ if (location.hasSpeed()) {
+ location.setSpeed(Taint.addTaintFloat(location.getSpeed(), tag));
+ }
+ if (location.hasBearing()) {
+ location.setBearing(Taint.addTaintFloat(location.getBearing(), tag));
+ }
+ if (location.hasAccuracy()) {
+ location.setAccuracy(Taint.addTaintFloat(location.getAccuracy(), tag));
+ }
+ // end WITH_TAINT_TRACKING
+
// notify other providers of the new location
for (int i = mProviders.size() - 1; i >= 0; i--) {
LocationProviderProxy proxy = mProviders.get(i);
</code>
</pre>
<p>
Also, the diff of location/java/com/android/internal/location/GpsLocationProvider.java is almost the same handling as LocationManagerService.java, that is, when location updated message is received, the values are set in Taint object.
</p>
<p>
Next, I'll read the diff for media.
</p>abekatsuhttp://www.blogger.com/profile/12886183524644595053noreply@blogger.com0tag:blogger.com,1999:blog-14443318.post-15927071635847017772011-01-30T17:00:00.000+09:002011-01-30T17:00:03.858+09:00taintdroid code reading 2.These are diffs for framework/base/telephony.
<pre>
<code>
diff --git a/telephony/java/com/android/internal/telephony/gsm/GSMPhone.java b/telephony/java/com/android/internal/telephony/gsm/GSMPhone.java
index a5188ce..78904c5 100755
--- a/telephony/java/com/android/internal/telephony/gsm/GSMPhone.java
+++ b/telephony/java/com/android/internal/telephony/gsm/GSMPhone.java
@@ -78,6 +78,10 @@ import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
+// begin WITH_TAINT_TRACKING
+import dalvik.system.Taint;
+// end WITH_TAINT_TRACKING
+
/**
* {@hide}
*/
@@ -1276,6 +1280,9 @@ public class GSMPhone extends PhoneBase {
}
mImei = (String)ar.result;
+ // begin WITH_TAINT_TRACKING
+ Taint.addTaintString(mImei, Taint.TAINT_IMEI);
+ // end WITH_TAINT_TRACKING
break;
case EVENT_GET_IMEISV_DONE:
</code>
</pre>
<p>
This diff is to store IMEI number to Taint object.
Taint.addTaintString(mImei, Taint.TAINT_IMEI);</code> is called in public void setMsisdnNumber(String alphaTag, String number, Message onComplete).
</p>
<pre>
<code>
diff --git a/telephony/java/com/android/internal/telephony/gsm/SIMRecords.java b/telephony/java/com/android/internal/telephony/gsm/SIMRecords.java
index d711a80..6beee1b 100644
--- a/telephony/java/com/android/internal/telephony/gsm/SIMRecords.java
+++ b/telephony/java/com/android/internal/telephony/gsm/SIMRecords.java
@@ -38,6 +38,9 @@ import com.android.internal.telephony.MccTable;
import java.util.ArrayList;
+// begin WITH_TAINT_TRACKING
+import dalvik.system.Taint;
+// end WITH_TAINT_TRACKING
/**
* {@hide}
@@ -236,6 +239,9 @@ public final class SIMRecords extends IccRecords {
Message onComplete) {
msisdn = number;
+ // begin WITH_TAINT_TRACKING
+ Taint.addTaintString(msisdn, Taint.TAINT_PHONE_NUMBER);
+ // end WITH_TAINT_TRACKING
msisdnTag = alphaTag;
if(DBG) log("Set MSISDN: " + msisdnTag +" " + msisdn);
@@ -488,6 +494,11 @@ public final class SIMRecords extends IccRecords {
}
imsi = (String) ar.result;
+ // begin WITH_TAINT_TRACKING
+ //if (imsi != null) {
+ //Taint.addTaintString(imsi, Taint.TAINT_IMSI);
+ //}
+ // end WITH_TAINT_TRACKING
// IMSI (MCC+MNC+MSIN) is at least 6 digits, but not more
// than 15 (and usually 15).
@@ -617,6 +628,9 @@ public final class SIMRecords extends IccRecords {
adn = (AdnRecord)ar.result;
msisdn = adn.getNumber();
+ // begin WITH_TAINT_TRACKING
+ Taint.addTaintString(msisdn, Taint.TAINT_PHONE_NUMBER);
+ // end WITH_TAINT_TRACKING
msisdnTag = adn.getAlphaTag();
</code>
</pre>
<h3>Summary</h3>
<p>
These changes stores IMEI, Phone number (ISDN) and ICCID in Taint object
</p>
<h3>Quesion</h3>
<p>
When are these values stored in Taint object?
</p>
<p>
Next, I'll read services/java/com/android/server/LocationManagerService.java.
</p>abekatsuhttp://www.blogger.com/profile/12886183524644595053noreply@blogger.com0tag:blogger.com,1999:blog-14443318.post-17578205989022303742011-01-30T13:35:00.000+09:002011-01-30T13:35:13.008+09:00taintdroid code reading 1.<p>
I am curious how <a href="http://www.appanalysis.org/">taintdroid</a> track apps which use senseitive information.<br />
Firstly, I check the diff between taintdroid and android. <br />
According to <a href="http://appanalysis.org/download.html">TaintDroid Build Instructions</a>, it modifies the source under dalvik/ and frameworks/base.
</p>
<p>
We can obtain the list of modified files by:
<pre>
git diff --name-only 0e9d568ec6b946e77bc0ec1903acac1ef916e6d1
</pre>
for dalvik/, and
<pre>
git diff --name-only 562ac30bddb37b8bebeedfb035111dda41187332.
</pre>
for framework/base.
</p>
<p>
The list for dalvik is too huge, is 456 files, so firstly we check one for framework/base.
<blockquote>
<pre>
<code>
README_TAINTDROID.txt
api/current.xml
cmds/servicemanager/Android.mk
cmds/servicemanager/binder.c
core/java/android/hardware/Camera.java
core/java/android/hardware/SensorManager.java
core/java/android/os/Parcel.java
core/jni/Android.mk
core/jni/android_util_Binder.cpp
include/binder/Parcel.h
libs/binder/Android.mk
libs/binder/Parcel.cpp
location/java/com/android/internal/location/GpsLocationProvider.java
media/java/android/media/AudioRecord.java
media/java/android/media/MediaRecorder.java
media/jni/Android.mk
media/jni/android_media_MediaRecorder.cpp
services/java/com/android/server/LocationManagerService.java
telephony/java/com/android/internal/telephony/gsm/GSMPhone.java
telephony/java/com/android/internal/telephony/gsm/SIMRecords.java
</code>
</pre>
</blockquote>
</p>
<p>
Next, I'll check what changes were made in services and telephont.
</p>abekatsuhttp://www.blogger.com/profile/12886183524644595053noreply@blogger.com0tag:blogger.com,1999:blog-14443318.post-9161232737853851932011-01-30T11:09:00.001+09:002011-01-30T11:22:52.733+09:00repackage? android app<p>
<a href="http://abekatsu.blogspot.com/2011/01/android-app.html#links">先日のblog article</a>で署名を突破できないという指摘を受けました。当然の指摘です。署名を検証するアプリケーション配布サイトですと問題はないのでしょう。<br />
結局は自分が浅はかであることを実感してソースコードがないandroid アプリをいじることができるかどうか,試してみました。ただ,既に知られている情報をただ単に追っただけなので,新規情報量はないです。<br />
<h4>参考</h4>
<ul>
<li><a href="http://kanatoko.wordpress.com/2011/01/21/android%E3%82%A2%E3%83%97%E3%83%AA%E3%82%B1%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E3%81%AE%E3%83%AA%E3%83%90%E3%83%BC%E3%82%B9%E3%82%A8%E3%83%B3%E3%82%B8%E3%83%8B%E3%82%A2%E3%83%AA%E3%83%B3%E3%82%B0/">Androidアプリケーションのリバースエンジニアリング</a>
<li><a href="http://code.google.com/p/smali/">smali - An assembler/disassembler for Android's dex format</a>
</ul>
</p>
<p>
まずは,"Push Me"というボタンを押すと,Toastで"Hello World"というmessageを表示するアプリケーションを用意しました。
<pre>
<code>
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Button b = (Button)findViewById(R.id.Button01);
}
private class ButtonOnClickListener implements View.OnClickListener {
@Override
public void onClick(View v) {
if (v.getId() == R.id.Button01) {
Toast.makeText(getApplicationContext(), "Hello World!", Toast.LENGTH_LONG).show();
}
}
}
</code>
</pre>
一方,"Push Me"というボタンを押すと,Toastで"Hello World"というmessageを表示すると同時に,call_log を読むアプリケーションを用意しました。同様に,キーとなるコード部分を下に記しておきます。
<pre>
<code>
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Button b = (Button)findViewById(R.id.Button01);
b.setOnClickListener(new ButtonOnClickListener());
}
private class ButtonOnClickListener implements View.OnClickListener {
@Override
public void onClick(View v) {
if (v.getId() == R.id.Button01) {
Toast.makeText(getApplicationContext(), "Hello World!", Toast.LENGTH_LONG).show();
queryContentProvidersInfo();
}
}
private void queryContentProvidersInfo() {
String[] projections = new String[] {
CallLog.Calls._ID,
CallLog.Calls.NUMBER,
CallLog.Calls.TYPE,
CallLog.Calls.DURATION,
CallLog.Calls.DATE,
CallLog.Calls.CACHED_NAME,
};
Uri calls = CallLog.Calls.CONTENT_URI;
ContentResolver cr = getContentResolver();
Cursor c = cr.query(calls, projections, null, null, CallLog.Calls.DEFAULT_SORT_ORDER);
if (c != null) {
getColumnData(c);
c.close();
}
}
private void getColumnData(Cursor c) {
if (c.moveToFirst()) {
String number;
int numberColumn = c.getColumnIndex(CallLog.Calls.NUMBER);
String cashed_name;
int cashed_nameColumn = c.getColumnIndex(CallLog.Calls.NUMBER);
do {
number = c.getString(numberColumn);
cashed_name = c.getString(cashed_nameColumn);
Log.d(TAG, "number: " + number);
if (cashed_name != null) {
Log.d(TAG, "cashed_name: " + cashed_name);
}
} while (c.moveToNext());
}
}
}
</code>
</pre>
apkから<a href="http://code.google.com/p/smali/">baksmali</a>を用いて,この部分の処理をdiassembleしたsmali型式コードを以下に貼り付けます。
単にmessageを表示する箇所。
<pre>
<code>
# virtual methods
.method public final onClick(Landroid/view/View;)V
.registers 5
invoke-virtual {p1}, Landroid/view/View;->getId()I
move-result v0
const/high16 v1, 0x7f05
if-ne v0, v1, :cond_9b
iget-object v0, p0, Lcom/damburisoft/app/android/helloworld/a;->a:Lcom/damburisoft/app/android/helloworld/HelloWorldActivity;
invoke-virtual {v0}, Lcom/damburisoft/app/android/helloworld/HelloWorldActivity;->getApplicationContext()Landroid/content/Context;
move-result-object v0
const-string v1, "Hello World!"
const/4 v2, 0x1
invoke-static {v0, v1, v2}, Landroid/widget/Toast;->makeText(Landroid/content/Context;Ljava/lang/CharSequence;I)Landroid/widget/Toast;
move-result-object v0
invoke-virtual {v0}, Landroid/widget/Toast;->show()V
:cond_9b
return-void
.end method
</code>
</pre>
call_logを読む箇所。
<pre>
<code>
# virtual methods
.method public final onClick(Landroid/view/View;)V
.registers 10
const/4 v3, 0x0
const/4 v4, 0x1
const-string v7, "ReadInfo"
const-string v6, "number"
invoke-virtual {p1}, Landroid/view/View;->getId()I
move-result v0
const/high16 v1, 0x7f05
if-ne v0, v1, :cond_9b
iget-object v0, p0, Lcom/damburisoft/android/app/readinfo/a;->a:Lcom/damburisoft/android/app/readinfo/ReadInfo;
invoke-virtual {v0}, Lcom/damburisoft/android/app/readinfo/ReadInfo;->getApplicationContext()Landroid/content/Context;
move-result-object v0
const-string v1, "Hello World!"
invoke-static {v0, v1, v4}, Landroid/widget/Toast;->makeText(Landroid/content/Context;Ljava/lang/CharSequence;I)Landroid/widget/Toast;
move-result-object v0
invoke-virtual {v0}, Landroid/widget/Toast;->show()V
const/4 v0, 0x6
new-array v2, v0, [Ljava/lang/String;
const/4 v0, 0x0
const-string v1, "_id"
aput-object v1, v2, v0
const-string v0, "number"
aput-object v6, v2, v4
const/4 v0, 0x2
const-string v1, "type"
aput-object v1, v2, v0
const/4 v0, 0x3
const-string v1, "duration"
aput-object v1, v2, v0
const/4 v0, 0x4
const-string v1, "date"
aput-object v1, v2, v0
const/4 v0, 0x5
const-string v1, "name"
aput-object v1, v2, v0
sget-object v1, Landroid/provider/CallLog$Calls;->CONTENT_URI:Landroid/net/Uri;
iget-object v0, p0, Lcom/damburisoft/android/app/readinfo/a;->a:Lcom/damburisoft/android/app/readinfo/ReadInfo;
invoke-virtual {v0}, Lcom/damburisoft/android/app/readinfo/ReadInfo;->getContentResolver()Landroid/content/ContentResolver;
move-result-object v0
const-string v5, "date DESC"
move-object v4, v3
invoke-virtual/range {v0 .. v5}, Landroid/content/ContentResolver;->query(Landroid/net/Uri;[Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;Ljava/lang/String;)Landroid/database/Cursor;
move-result-object v0
if-eqz v0, :cond_9b
invoke-interface {v0}, Landroid/database/Cursor;->moveToFirst()Z
move-result v1
if-eqz v1, :cond_98
const-string v1, "number"
invoke-interface {v0, v6}, Landroid/database/Cursor;->getColumnIndex(Ljava/lang/String;)I
move-result v1
const-string v2, "number"
invoke-interface {v0, v6}, Landroid/database/Cursor;->getColumnIndex(Ljava/lang/String;)I
move-result v2
:cond_60
invoke-interface {v0, v1}, Landroid/database/Cursor;->getString(I)Ljava/lang/String;
move-result-object v3
invoke-interface {v0, v2}, Landroid/database/Cursor;->getString(I)Ljava/lang/String;
move-result-object v4
const-string v5, "ReadInfo"
new-instance v5, Ljava/lang/StringBuilder;
const-string v6, "number: "
invoke-direct {v5, v6}, Ljava/lang/StringBuilder;-><init>(Ljava/lang/String;)V
invoke-virtual {v5, v3}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
move-result-object v3
invoke-virtual {v3}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;
move-result-object v3
invoke-static {v7, v3}, Landroid/util/Log;->d(Ljava/lang/String;Ljava/lang/String;)I
if-eqz v4, :cond_92
const-string v3, "ReadInfo"
new-instance v3, Ljava/lang/StringBuilder;
const-string v5, "cashed_name: "
invoke-direct {v3, v5}, Ljava/lang/StringBuilder;-><init>(Ljava/lang/String;)V
invoke-virtual {v3, v4}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
move-result-object v3
invoke-virtual {v3}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;
move-result-object v3
invoke-static {v7, v3}, Landroid/util/Log;->d(Ljava/lang/String;Ljava/lang/String;)I
:cond_92
invoke-interface {v0}, Landroid/database/Cursor;->moveToNext()Z
move-result v3
if-nez v3, :cond_60
:cond_98
invoke-interface {v0}, Landroid/database/Cursor;->close()V
:cond_9b
return-void
.end method
</code>
</pre>
上記のsmali型式のコードを比較して,call_logの内容を読む処理を前者の単にToastでmessageを表示するアプリケーションに追加してみます。追加した結果のsmali型式ファイルは以下の通りです。
<pre>
<code>
# virtual methods
.method public final onClick(Landroid/view/View;)V
.registers 10
const/4 v3, 0x0
const/4 v4, 0x1
const-string v7, "ReadInfo"
const-string v6, "number"
invoke-virtual {p1}, Landroid/view/View;->getId()I
move-result v0
const/high16 v1, 0x7f05
if-ne v0, v1, :cond_1b
iget-object v0, p0, Lcom/damburisoft/app/android/helloworld/a;->a:Lcom/damburisoft/app/android/helloworld/HelloWorldActivity;
invoke-virtual {v0}, Lcom/damburisoft/app/android/helloworld/HelloWorldActivity;->getApplicationContext()Landroid/content/Context;
move-result-object v0
const-string v1, "Hello World!"
const/4 v2, 0x1
invoke-static {v0, v1, v2}, Landroid/widget/Toast;->makeText(Landroid/content/Context;Ljava/lang/CharSequence;I)Landroid/widget/Toast;
move-result-object v0
invoke-virtual {v0}, Landroid/widget/Toast;->show()V
const/4 v0, 0x6
new-array v2, v0, [Ljava/lang/String;
const/4 v0, 0x0
const-string v1, "_id"
aput-object v1, v2, v0
const-string v0, "number"
aput-object v6, v2, v4
const/4 v0, 0x2
const-string v1, "type"
aput-object v1, v2, v0
const/4 v0, 0x3
const-string v1, "duration"
aput-object v1, v2, v0
const/4 v0, 0x4
const-string v1, "date"
aput-object v1, v2, v0
const/4 v0, 0x5
const-string v1, "name"
aput-object v1, v2, v0
sget-object v1, Landroid/provider/CallLog$Calls;->CONTENT_URI:Landroid/net/Uri;
iget-object v0, p0, Lcom/damburisoft/app/android/helloworld/a;->a:Lcom/damburisoft/app/android/helloworld/HelloWorldActivity;
invoke-virtual {v0}, Lcom/damburisoft/app/android/helloworld/HelloWorldActivity;->getContentResolver()Landroid/content/ContentResolver;
move-result-object v0
const-string v5, "date DESC"
move-object v4, v3
invoke-virtual/range {v0 .. v5}, Landroid/content/ContentResolver;->query(Landroid/net/Uri;[Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;Ljava/lang/String;)Landroid/database/Cursor;
move-result-object v0
if-eqz v0, :cond_1b
invoke-interface {v0}, Landroid/database/Cursor;->moveToFirst()Z
move-result v1
if-eqz v1, :cond_18
const-string v1, "number"
invoke-interface {v0, v6}, Landroid/database/Cursor;->getColumnIndex(Ljava/lang/String;)I
move-result v1
const-string v2, "number"
invoke-interface {v0, v6}, Landroid/database/Cursor;->getColumnIndex(Ljava/lang/String;)I
move-result v2
:cond_60
invoke-interface {v0, v1}, Landroid/database/Cursor;->getString(I)Ljava/lang/String;
move-result-object v3
invoke-interface {v0, v2}, Landroid/database/Cursor;->getString(I)Ljava/lang/String;
move-result-object v4
const-string v5, "ReadInfo"
new-instance v5, Ljava/lang/StringBuilder;
const-string v6, "number: "
invoke-direct {v5, v6}, Ljava/lang/StringBuilder;-><init>(Ljava/lang/String;)V
invoke-virtual {v5, v3}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
move-result-object v3
invoke-virtual {v3}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;
move-result-object v3
invoke-static {v7, v3}, Landroid/util/Log;->d(Ljava/lang/String;Ljava/lang/String;)I
if-eqz v4, :cond_92
const-string v3, "ReadInfo"
new-instance v3, Ljava/lang/StringBuilder;
const-string v5, "cashed_name: "
invoke-direct {v3, v5}, Ljava/lang/StringBuilder;-><init>(Ljava/lang/String;)V
invoke-virtual {v3, v4}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
move-result-object v3
invoke-virtual {v3}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;
move-result-object v3
invoke-static {v7, v3}, Landroid/util/Log;->d(Ljava/lang/String;Ljava/lang/String;)I
:cond_92
invoke-interface {v0}, Landroid/database/Cursor;->moveToNext()Z
move-result v3
if-nez v3, :cond_60
:cond_18
invoke-interface {v0}, Landroid/database/Cursor;->close()V
:cond_1b
return-void
.end method
</code>
</pre>
さて,この編集した.smaliファイルでclasses.dexをアセンブルして,HelloWorld.apkのclasses.dexに入れ替えます。
<pre>
% java -Xmx1G -jar ~/lib/smali-1.2.6.jar -o classes.dex out
% zip ~/HelloWorldApp.apk classes.dex
</pre>
classed.dexを入れ替えたHelloWorld.apkをもう一度署名しなおして,emulatorにインストールしてみました。実行した結果,SecurityExceptionが発生しました。
<pre>
<code>
E/AndroidRuntime( 251): java.lang.SecurityException: Permission Denial: reading com.android.providers.contacts.CallLogProvider uri content://call_log/calls from pid=251, uid=10041 requires android.permission.READ_CONTACTS
E/AndroidRuntime( 251): at android.os.Parcel.readException(Parcel.java:1247)
E/AndroidRuntime( 251): at android.database.DatabaseUtils.readExceptionFromParcel(DatabaseUtils.java:160)
E/AndroidRuntime( 251): at android.database.DatabaseUtils.readExceptionFromParcel(DatabaseUtils.java:114)
E/AndroidRuntime( 251): at android.content.ContentProviderProxy.bulkQueryInternal(ContentProviderNative.java:330)
E/AndroidRuntime( 251): at android.content.ContentProviderProxy.query(ContentProviderNative.java:366)
E/AndroidRuntime( 251): at android.content.ContentResolver.query(ContentResolver.java:245)
E/AndroidRuntime( 251): at com.damburisoft.app.android.helloworld.a.onClick(Unknown Source)
E/AndroidRuntime( 251): at android.view.View.performClick(View.java:2408)
E/AndroidRuntime( 251): at android.view.View$PerformClick.run(View.java:8816)
E/AndroidRuntime( 251): at android.os.Handler.handleCallback(Handler.java:587)
E/AndroidRuntime( 251): at android.os.Handler.dispatchMessage(Handler.java:92)
E/AndroidRuntime( 251): at android.os.Looper.loop(Looper.java:123)
E/AndroidRuntime( 251): at android.app.ActivityThread.main(ActivityThread.java:4627)
E/AndroidRuntime( 251): at java.lang.reflect.Method.invokeNative(Native Method)
E/AndroidRuntime( 251): at java.lang.reflect.Method.invoke(Method.java:521)
E/AndroidRuntime( 251): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:868)
E/AndroidRuntime( 251): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:626)
E/AndroidRuntime( 251): at dalvik.system.NativeStart.main(Native Method)
</code>
</pre>
AndroidManifest.xmlを編集していませんので,当然の結果です。ただ,Content Provider CallLogs.Callにアクセスする機能を追加できたのではないかと思われます。<br />
</p>
<p>
今回は,パッケージに入っているクラスをsmali型式のファイルを通して編集することによって,動作を変更いたしましたが,編集ではなく新規にクラスを追加して実行させることも可能ではないのでしょうか。
いろいろと検索してみますと,disassembleしてアプリをハックする方法がいろいろとありますので,そんな難しいことではないのかもしれません。
</p>abekatsuhttp://www.blogger.com/profile/12886183524644595053noreply@blogger.com0tag:blogger.com,1999:blog-14443318.post-70533327812743233472011-01-27T14:39:00.000+09:002011-01-27T14:39:10.052+09:00どのように,偽のandroid appをつくるのか?<p>
GeiminiというAndroid OSを標的とした,Trojanの報告がある。<br />
<a href="http://blog.mylookout.com/2010/12/geinimi_trojan/">Lookout社のGeimini発見のレポート(Blog)</a>
</p>
<p>
このTrojanは,として,第三者が運営するアプリ配布サイトで配布されている正規アプリの海賊版と一緒になって配布されている。<br />
ここでは正規アプリの海賊版を簡単に作成できるかどうか考察してみたい。
</p>
<p>
<a href="http://kanatoko.wordpress.com/2011/01/21/android%E3%82%A2%E3%83%97%E3%83%AA%E3%82%B1%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E3%81%AE%E3%83%AA%E3%83%90%E3%83%BC%E3%82%B9%E3%82%A8%E3%83%B3%E3%82%B8%E3%83%8B%E3%82%A2%E3%83%AA%E3%83%B3%E3%82%B0/">Androidアプリケーションのリバースエンジニアリング</a>より引用すると,<br />
<blockquote>
Androidアプリケーションのリバースエンジニアリングをする場合には、目的として「解析だけしたい」場合と、「解析した上で、さらに動作を自分好みに変更する」、つまりアプリケーションの改造までを行いたい場合があるだろう。前者の場合、JDによるJavaソースコード形式への(ときに不完全な)変換で十分な場合もあるだろう。この場合、読みにくいsmali形式のファイルと格闘する必要がないかもしれない。しかし後者、つまり改造までを行いたい場合、アプリケーション内の目的の箇所を自分の意図を達成するように書き換え、ふたたびAndroidアプリケーションとして動作するよう、正しくアセンブルしなおす必要がある。smaliはこれを可能にしてくれる。非常に精度が高いディスアセンブル・アセンブルが可能なので、classes.dex -> smali -> classes.dexという変換が可能なのだ。
</blockquote>
つまり,既に apk ファイルから,classを解析して(dex2jar),ディスアセンブル(smali)して,再びclasses.dexを作成するツールが存在することを示している。<br />
そこで,今回次のようなデモシナリオを提案する。
<dl>
<li> ボタンを押したら Toast Messageを表示するAndroid Appを作成する。
<li> ボタンをクリックしたとき,その裏で,Identifyを読み取るコードを埋め込む。
<li> 再びアセンブルしてパッケージを作成する。
</dl>
この作業がどれだけ簡単かどうかは,後日考察してみたい。
</p>abekatsuhttp://www.blogger.com/profile/12886183524644595053noreply@blogger.com0tag:blogger.com,1999:blog-14443318.post-5215695616539773312011-01-17T21:54:00.002+09:002011-01-17T22:50:58.746+09:00Update PutYourMind to 1.2<p>
Update <a href="http://abekatsu.blogspot.com/2011/01/putyourmind-android-app.html">PutYourMind</a> to version 1.2
<dl>
<dt>new feature</dt>
<dd>add webpage title with url to your readitlater list.</dd>
</dl>
</p>
<p>
<a href="http://abekatsu.blogspot.com/2011/01/putyourmind-android-app.html">PutYourMind</a>を1.2に更新いたしました。
<dl>
<dt>新規機能</dt>
<dd>WebPageのURLをReadItLaterリストに追加する際に,そのページのtitleも一緒に登録するようにしました。</dd>
</dl>
</p>abekatsuhttp://www.blogger.com/profile/12886183524644595053noreply@blogger.com0tag:blogger.com,1999:blog-14443318.post-14308661325778735112011-01-16T20:17:00.000+09:002011-01-16T20:17:11.575+09:00PackageManager (ApplicationInfo)<p>
Android SDKには,
<a href="http://developer.android.com/reference/android/content/pm/PackageManager.html">PacageManager</a>という,デバイス上にインストールされたアプリケーションパッケージに関する情報を扱うクラスが提供されております。<br />
これで,どんな情報が取得できるか,試してみました。
</p>
<p>
最初は,
<a href="http://developer.android.com/reference/android/content/pm/PackageManager.html#getInstalledApplications(int)">public abstract List<ApplicationInfo> getInstalledApplications (int flags)</a>で得られるApplicationInfoにどんな情報が含まれているかについて調べてみました。
<pre>
<code>
@Override
protected void onResume() {
// TODO Auto-generated method stub
super.onResume();
mPackageManager = getPackageManager();
getApplicationInfoList();
}
private void getApplicationInfoList() {
List<ApplicationInfo> installedAppList = mPackageManager
.getInstalledApplications(PackageManager.GET_META_DATA |
PackageManager.GET_SHARED_LIBRARY_FILES |
PackageManager.GET_UNINSTALLED_PACKAGES);
showApplicationInfos(installedAppList);
}
private void showApplicationInfos(List<ApplicationInfo> infos) {
if (infos != null) {
for (ApplicationInfo info : infos) {
showApplicationInfo(info);
}
}
}
private void showApplicationInfo(ApplicationInfo info) {
if (info != null) {
Log.d(TAG, "showApplicationInfo");
Log.d(TAG, "className: " + info.className);
Log.d(TAG, "packagename: " + info.packageName);
Log.d(TAG, "name: " + info.name);
}
}
</code>
</pre>
</p>
<p>
結果は,こんな感じです。
<pre>
D/PkgInfoSampleAppActivity(20363): showApplicationInfo
D/PkgInfoSampleAppActivity(20363): className: null
D/PkgInfoSampleAppActivity(20363): packagename: com.sonyericsson.android.contentmanager.contentprovider.webmedia
D/PkgInfoSampleAppActivity(20363): name: null
D/PkgInfoSampleAppActivity(20363): showApplicationInfo
D/PkgInfoSampleAppActivity(20363): className: null
D/PkgInfoSampleAppActivity(20363): packagename: com.noshufou.android.su
D/PkgInfoSampleAppActivity(20363): name: null
D/PkgInfoSampleAppActivity(20363): showApplicationInfo
D/PkgInfoSampleAppActivity(20363): className: null
D/PkgInfoSampleAppActivity(20363): packagename: com.damburisoft.android.app.showmyicon
D/PkgInfoSampleAppActivity(20363): name: null
</pre>
packagename は得られましたが,nameやclassNameはなぜかnullです。もっと詳細な情報を得るにはどうしたらいいか,引き続き課題です。
</p>abekatsuhttp://www.blogger.com/profile/12886183524644595053noreply@blogger.com0tag:blogger.com,1999:blog-14443318.post-75604168820493235892011-01-11T22:49:00.000+09:002011-01-11T22:49:12.960+09:00Update PutYourMind to 1.1Update <a href="http://abekatsu.blogspot.com/2011/01/putyourmind-android-app.html">PutYourMind</a> to version 1.1<br />
<p>
<dl>
<dt>new feature</dt><dd>add webpage title with url to your readitlater list.</dd>
</dl>
</p>
<p>
<a href="http://abekatsu.blogspot.com/2011/01/putyourmind-android-app.html">PutYourMind</a>を1.1に更新いたしました。<br>
<dl>
<dt>新規機能</dt><dd>WebPageのURLをReadItLaterリストに追加する際に,そのページのtitleも一緒に登録するようにしました。</dd>
</dl>abekatsuhttp://www.blogger.com/profile/12886183524644595053noreply@blogger.com0tag:blogger.com,1999:blog-14443318.post-39540682904400894072011-01-09T14:26:00.000+09:002011-01-09T14:26:06.885+09:00PutYourMind (android app)<p>
The 2nd. Android application made breaking out.
</p>
<p>
This application catches an implicit intent, which data has a uri data. And put its uri data to your readitlater list. <br />
This application requires your readitlater account.
</p>
<p>
Please search it on Android Market by <a htef="market://search?q=pname:com.damburisoft.android.app.putyourmind">PutYourMind</a>, or use the following QR code.
</p>
<p>
突発的に作成したアンドロイドアプリ第2弾。
</p>
<p>
URIデータを含む暗黙的なインテントをキャッチして,そのデータをReadItLaterに追加します。
</p>
<p>
Android Marketに公開しましたので,
<a htef="market://search?q=pname:com.damburisoft.android.app.putyourmind">PutYourMind</a>で検索するか,QRコードからダウンロードしてみてください。
</p>
<p>
<img src="http://chart.apis.google.com/chart?cht=qr&chs=230x230&chl=market%3A%2F%2Fsearch%3Fq%3Dpname%3Acom.damburisoft.android.app.putyourmind" /><br />
</p>
<p>
アイコンとアプリ名,なんかいいのがないかどうか募集です。
</p>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhLySXXICq9BW_8kwl4Fi3z7tgLLF_kBZxzvGG6D_ecIjj62gzPXK5EM9e2-AUd0gcj5F-udfi8PSYyVw-hDyRA71U2en3sNbSLlHd9Co9_9Urs5KeRSihBxuD-0RteR94pXHiDDA/s1600/device_2011010901.png" imageanchor="1" style=""><img border="0" height="320" width="192" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhLySXXICq9BW_8kwl4Fi3z7tgLLF_kBZxzvGG6D_ecIjj62gzPXK5EM9e2-AUd0gcj5F-udfi8PSYyVw-hDyRA71U2en3sNbSLlHd9Co9_9Urs5KeRSihBxuD-0RteR94pXHiDDA/s320/device_2011010901.png" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh0mfisH5LnjSulsxQy3I9V6M11kYb-xrFP6vCcNYAK2Af_HxpvFyTdpAWpr5w_KzrvN5GJeBbWW61slbKIY8Rh-qsX7-fE-qpE75ClIyox-6K1jmQQiuamWuEvvSoT0_CgUTWKOw/s1600/device_2011010902.png" imageanchor="1" style=""><img border="0" height="320" width="192" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh0mfisH5LnjSulsxQy3I9V6M11kYb-xrFP6vCcNYAK2Af_HxpvFyTdpAWpr5w_KzrvN5GJeBbWW61slbKIY8Rh-qsX7-fE-qpE75ClIyox-6K1jmQQiuamWuEvvSoT0_CgUTWKOw/s320/device_2011010902.png" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjE-CTsA7C7EhoUkUpV4gFoPjHHdFJfryU2r1B0vG2gS3gPbCXFXN-spAa-BAUtAnyqyTRKfv5IOdwQHFmJ746oGU6xqEfcx3KefBlU05M9d0NexdMxB0FLcykslfDf4pzIqz5v-g/s1600/device_2011010903.png" imageanchor="1" style=""><img border="0" height="320" width="192" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjE-CTsA7C7EhoUkUpV4gFoPjHHdFJfryU2r1B0vG2gS3gPbCXFXN-spAa-BAUtAnyqyTRKfv5IOdwQHFmJ746oGU6xqEfcx3KefBlU05M9d0NexdMxB0FLcykslfDf4pzIqz5v-g/s320/device_2011010903.png" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhLAwzDKxKtyimyxPj7ENxJW3WWBWQu3VTV0MeQFMlMhBH8r85obJRbTzYtc0FxuzGsYK447ABrWeYyC2xAqVaIA5_h7QuYrOJ-o8MDByT9DDVZHLZRSpUbpZX9ArFvdS1E4wY16Q/s1600/device_2011010904.png" imageanchor="1" style=""><img border="0" height="320" width="192" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhLAwzDKxKtyimyxPj7ENxJW3WWBWQu3VTV0MeQFMlMhBH8r85obJRbTzYtc0FxuzGsYK447ABrWeYyC2xAqVaIA5_h7QuYrOJ-o8MDByT9DDVZHLZRSpUbpZX9ArFvdS1E4wY16Q/s320/device_2011010904.png" /></a></div>abekatsuhttp://www.blogger.com/profile/12886183524644595053noreply@blogger.com0tag:blogger.com,1999:blog-14443318.post-56939517832136753312011-01-08T04:00:00.001+09:002011-01-08T04:00:02.289+09:00ShowMyIcon (Android Application)<p>
When I went to a twitter off-line party,
who present are a fan of Vegalta Sendai, it was a time to introduce oneself.
Someone used a mobile phone to show her/his twitter's icon image to attendees.
Though I used an Android phone, I didn't prepare my icon image to show them.
So I create a simple application, ShowMyIcon, to show my twitter icon on Android phone.
</p>
<p>
<div class="separator" style="clear: both; ">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhXpKXYO4fBgoL5gLviREntOoOU6wtt9TazWNu9IQKq2v0C3gMVxssV19i2OhQcCFYjByH2C7MZCao_maH-WiDCxbO1y7ORaaivyV_cLclmqr8Ij_d-J5985U1wvD3KiBbnigCGMw/s1600/device_2011010701.png" imageanchor="1" style=""><img border="0" height="320" width="180" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhXpKXYO4fBgoL5gLviREntOoOU6wtt9TazWNu9IQKq2v0C3gMVxssV19i2OhQcCFYjByH2C7MZCao_maH-WiDCxbO1y7ORaaivyV_cLclmqr8Ij_d-J5985U1wvD3KiBbnigCGMw/s320/device_2011010701.png" /></a></div>
<div class="separator" style="clear: both; ">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgZXxkzG_MWeyCyTGwCSj8f2WR__1T_h0pwu_ZwTCZnN0h8WxyxHaNL5Y3elXKR9kcdIWx5iIfic4_ZQc3b7kODd27m0NYkBPXoF8BFOUnJITvD7Ox64_GI7fnYWqqX5uerS_qZiA/s1600/device_2011010702.png" imageanchor="1" style=""><img border="0" height="320" width="180" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgZXxkzG_MWeyCyTGwCSj8f2WR__1T_h0pwu_ZwTCZnN0h8WxyxHaNL5Y3elXKR9kcdIWx5iIfic4_ZQc3b7kODd27m0NYkBPXoF8BFOUnJITvD7Ox64_GI7fnYWqqX5uerS_qZiA/s320/device_2011010702.png" /></a></div>
<p>
<p>
Just put your twitter's name.<br />
Then this application downloads your icon image file and stores it inside.
Once you download, you can show it in without internet connections.
</p>
<p>
This application does not require user authorization.
So it is possible for spoofing.
</p>
<p>
This application, ShowMyIcon is available from an android market. <br />
Please search by "ShowMyIcon" or access the following QR code: <br />
<img src="http://chart.apis.google.com/chart?cht=qr&chs=230x230&chl=market%3A%2F%2Fsearch%3Fq%3Dpname%3Acom.damburisoft.android.app.showmyicon" />
</p>abekatsuhttp://www.blogger.com/profile/12886183524644595053noreply@blogger.com0tag:blogger.com,1999:blog-14443318.post-63765863771953075202010-12-01T17:24:00.000+09:002010-12-01T17:24:07.467+09:00Redmineに関する雑多メモ本格的にバグトラックをしたいのだけど,2点懸念事項がある。その懸念に対する解決策が既にあるようなのでメモです。
ありがとうございます。
Q: 何も考えずにSQLite3でDBを運用した場合,パフォーマンスに問題はないの?
A:
<ul>
<li><a href="http://blog.redmine.jp/articles/change-database/">Redmineで使うデータベースを変更する</a>
<li><a href="http://garin.jp/doc/Ruby/Redmine/sqlite3tomysql">Redmine(Rails) の DB を SQLite3 から MySQL に移行する</a>
</ul>
Q: ユーザー管理は面倒だ。Google Appsのアカウントは使えないの?
A: <a href="http://www.ana-kutsu.com/mt/mt-search.cgi?tag=Google%20Apps&blog_id=1&IncludeBlogs=1">Redmineをセットアップしてみた</a>abekatsuhttp://www.blogger.com/profile/12886183524644595053noreply@blogger.com0tag:blogger.com,1999:blog-14443318.post-28222755864086589182010-08-16T17:53:00.000+09:002010-08-16T17:53:44.493+09:00textsize, sp and dpandroidのdimension type, "sp" (scaled-pixels) や "dp" (density-independent pixel) のことがよくわからなかったので,適当にTextViewを作成してみた。画面サイズは,QVGA,HVGA,WVGA800,WVGA854。画面内のテキストのサイズには最初から10pt,14pt,18pt,10dp,14dp,18dp,10sp,14sp,18spと指定してみた。
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="float: left; margin-right: 1em; text-align: left;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj2bwBCDeZ8wj-ftrUBzxFkyj2nsiZ5IJX1xme1xZAB79pOpjjV1KBERA4Fv07p64FrW9QnydRTgxCLpc3cbfPTv44k8vNCw-VumAsfA2T7Vo_BOeybBlzEYAO6_o5RF3hm24hYcA/s1600/QVGA1_spdp.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" height="200" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj2bwBCDeZ8wj-ftrUBzxFkyj2nsiZ5IJX1xme1xZAB79pOpjjV1KBERA4Fv07p64FrW9QnydRTgxCLpc3cbfPTv44k8vNCw-VumAsfA2T7Vo_BOeybBlzEYAO6_o5RF3hm24hYcA/s200/QVGA1_spdp.png" width="150" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">QVGA</td></tr>
</tbody></table>
<table cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj01imlTM4gCx7bCQ8u33Ydz3InJuyKTCZ5c7dSnaAEDGkiDWnuawmhNutgogHFZt7g5M5fhzuD9wpAw7lzz3iXxz0hawxm3v1vpTMznlIW_NsPJjfjAJquJD2CsqGbR16UOBAhpA/s1600/HVGA_spdp.png" imageanchor="1" style="clear: right; margin-bottom: 1em; margin-left: auto; margin-right: auto;"><img border="0" height="200" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj01imlTM4gCx7bCQ8u33Ydz3InJuyKTCZ5c7dSnaAEDGkiDWnuawmhNutgogHFZt7g5M5fhzuD9wpAw7lzz3iXxz0hawxm3v1vpTMznlIW_NsPJjfjAJquJD2CsqGbR16UOBAhpA/s200/HVGA_spdp.png" width="133" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">HVGA</td></tr>
</tbody></table>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
</div>
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="float: left; margin-right: 1em; text-align: left;"><tbody>
<tr><td style="text-align: center;"><img border="0" height="200" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiLlJDQeS3yKKvMZKPHW8WLBEtiHfcKyVhcHQPOAjD0UsbY5HQIPq2dTJ6-r-R9cfZP62IWxA0Z4VdPkb7nRPfKVtdBQe2-V1SFHMAmQn-8vgRmQwWau5lTAMTmk7sbRg6sBaaBdQ/s200/WVGA800_spdp.png" style="margin-left: auto; margin-right: auto;" width="120" /></td></tr>
<tr><td class="tr-caption" style="text-align: center;">WVGA800</td></tr>
</tbody></table>
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjjFxE9lmfz93rTN2oDPmVqxWeOAuJDowFj-DotLdg5-2oi_ZeucEv5wM7eWNy2BpMenMCM9nngUbeP945gqtJV_xxyzKivjf-GtCF1A4sLna18nz2jb5BgKe9oTFwgYmFOFxjmxg/s1600/WVGA854_spdp.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" height="200" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjjFxE9lmfz93rTN2oDPmVqxWeOAuJDowFj-DotLdg5-2oi_ZeucEv5wM7eWNy2BpMenMCM9nngUbeP945gqtJV_xxyzKivjf-GtCF1A4sLna18nz2jb5BgKe9oTFwgYmFOFxjmxg/s200/WVGA854_spdp.png" width="111" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">WVGA854</td></tr>
</tbody></table>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiLlJDQeS3yKKvMZKPHW8WLBEtiHfcKyVhcHQPOAjD0UsbY5HQIPq2dTJ6-r-R9cfZP62IWxA0Z4VdPkb7nRPfKVtdBQe2-V1SFHMAmQn-8vgRmQwWau5lTAMTmk7sbRg6sBaaBdQ/s1600/WVGA800_spdp.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><span class="Apple-style-span" style="-webkit-text-decorations-in-effect: none; color: black;"></span></a></div>
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
</div>abekatsuhttp://www.blogger.com/profile/12886183524644595053noreply@blogger.com0