HTC One VでSBMのMMSを使用してみる


SoftBank MobileのMMSはUserAgentによるアクセス制限がかかっており、HTC One Vに標準搭載のMessageアプリではプッシュ通知は受け取れますが、本文を受信できません。
他の通知手段が充実してきたので、個人的にはキャリアメールはどうでも良くなってきているのですが、諸々の理由でsoftbank.ne.jpドメインのメールを受け取る必要が僅かながらあるので、対応することにしました。

※2012-06-30追記
下記の方法でMMSの受信は可能ですが、受診後もSoftBank側でMMSの削除がされず、サーバサイドでどんどん蓄積されて最終的には新規MMSの受信ができなくなることがわかりました。SoftBankメールアプリをそのままインストールすれば動作するので、とりあえずはそれで凌ぐことに。原因の調査は停滞中。。

MMSのUserAgent変更

HTCの場合は、/system/customize/MNS/default.xmlにてMMSのユーザエージェントを変更することができます。
<module=”MMS”>のあたりを見ると、「HTC_One_V/1.0」が指定されています。実際にメッセージ受信を試みてadb logcat すると、

% adb logcat | grep HttpUtils
... snipped ...
V/HttpUtils( 2165): key=ua_string, value=HTC_One_V/1.0
D/HttpUtils( 2165): [HttpUtils] createHttpClient w/ socket timeout 60000 ms, UA=HTC_One_V/1.0
V/HttpUtils( 2165): key=ua_profile, value=http://www.htcmms.com.tw/Android/Common/PK76/ua-profile.xml
W/System.err( 2165):    at com.android.mms.transport.HttpUtils.httpConnection(HttpUtils.java:502)
D/HttpUtils( 2165): Catched unexcepted http-error exception
D/HttpUtils( 2165): com.htc.messaging.util.HtcHttpTransportError: HTTP error: Forbidden
D/HttpUtils( 2165):     at com.android.mms.transport.HttpUtils.httpConnection(HttpUtils.java:502)
...

確かにUAとして「HTC_One_V/1.0」が使用され、見事に「HTTP error: Forbidden」と言われているのがわかります。

よって、適当な場所にdefault.xmlのバックアップを取った上で、直接sedで置換してしまいます。置換後のユーザエージェントは、SoftBankメールアプリで使用されている「SoftBank/1.0/Smart/MyrMMS」を採用することにしました。

% adb shell

$ su
# mount -o rw,remount /system
# cp /system/customize/MNS/default.xml /sdcard/mns_default.xml.org
# sed -i 's/HTC_One_V\/1\.0/SoftBank\/1\.0\/Smart\/MyrMMS/' /system/customize/MNS/default.xml
# exit
$ exit

念のため、diffをとって変更点を確認しておきます。

2つ目の変更は単にファイル末尾に改行コードがあるかないかの違いですので、スルーでOKです。

# diff /sdcard/mns_default.xml.org /system/customize/MNS/default.xml
--- /sdcard/mns_default.xml.org
+++ /system/customize/MNS/default.xml
@@ -947,7 +947,7 @@
     <module name="MMS">
       <function name="ua_string">
         <set name="single">
-          <item name="value">HTC_One_V/1.0</item>
+          <item name="value">SoftBank/1.0/Smart/MyrMMS</item>
         </set>
       </function>
     </module>
@@ -958,4 +958,4 @@
       </function>
     </module>
   </category>
-</customization_form>
\ No newline at end of file
+</customization_form>

これで、工場出荷状態に初期化(wipe)すれば、変更したユーザエージェントが使用されるようになります。

wipeをせずに反映させる方法

一度端末の初期設定をしてしまうと、/system/customize/MNS/default.xmlをベースとして設定情報がdataパーティション内にDB保存されてしまうので、xmlを書き換えてもDB保存された設定が優先されてしまい、修正を反映させるためには「工場出荷状態に初期化(wipe)」が必要となります。
ただ、いろいろ環境構築してしまって今更wipeなんてありえない、というケースもありますので、xmlに加えてDB修正する方法も示しておきます。

修正するDBの場所は、/data/data/com.htc.provider.CustomizationSettings/databases/customization_settings.dbとなります。
一旦sdcardに複製し、ローカルに持ってきてから編集を行います。

% adb shell

$ su
# cp /data/data/com.htc.provider.CustomizationSettings/databases/customization_settings.db /sdcard/customization_settings.db.org
# exit
$ exit

% adb pull /sdcard/customization_settings.db.org customization_settings.db

SQLite3のDBを扱えるツールもしくはsqlite3コマンドが使用可能な環境を整えておきましょう。以下では、コマンドラインベースで進めます。

sqlite3コマンドは、Android SDK内のtools以下にもあります。

sqlite3コマンドで以下のUPDATE文を実行します。

% sqlite3 customization_settings.db
sqlite> update SettingTable set value=X'80000000424e444c010000000000000009000000750061005f0073007400720069006e00670000000300000054000000424e444c010000000000000005000000760061006c00750065000000000000001900000053006f0066007400420061006e006b002f0031002e0030002f0053006d006100720074002f004d00790072004d004d0053000000' where key='force_change_MMS';
sqlite> .quit

更新したDBファイルを元の場所に書き戻します。

% adb push customization_settings.db /sdcard/
% adb shell

$ su
# cd /data/data/com.htc.provider.CustomizationSettings/databases
# ls -l customization_settings.db
-rw-rw---- app_14   app_14      50176 2012-05-09 17:03 customization_settings.db
# cp /sdcard/customization_settings.db .
# chmod 660 customization_settings.db
# chown app_14.app14 customization_settings.db
# exit
$ exit

これで、MMSが無事受信できるようになるはずです。念のため「adb logcat | grep HttpUtils」してMMSを受信し、UAが変わっていることを確認しておきましょう。

補足:UPDATE文の中身について

このDB内にはSettingTableというテーブルがあり、_id,key,valueというフィールド構成になっています。

% sqlite3 customization_settings.db
SQLite version 3.7.10 2012-01-16 13:28:40 Enter ".help" for instructions Enter SQL statements terminated with a ";"
sqlite> .tables
SettingTable android_metadata
sqlite> .schema SettingTable
CREATE TABLE SettingTable (_id INTEGER primary key autoincrement,key TEXT NOT NULL,value BLOB);

その中のレコードでkeyがforce_change_MMSのものが今回の修正対象になりますので、まずはデータを抜いてきます。

valueがBLOBデータとなっているため、insertモードにして16進データで出力しています。

sqlite> .mode insert
sqlite> select * from SettingTable where key='force_change_MMS';
INSERT INTO table VALUES(30,'force_change_MMS',X'68000000424e444c010000000000000009000000750061005f0073007400720069006e0067000000030000003c000000424e444c010000000000000005000000760061006c00750065000000000000000d0000004800540043005f004f006e0065005f0056002f0031002e0030000000');
sqlite> .quit

この16進データをバイナリエディタに貼り付けて閲覧すると、なにやら見えてきます。
このデータはBundleクラスのデータをParcel化したもので、

  1. データ長(青枠部分、0x68 = 104byte)
  2. “BNDL”(緑枠部分、固定文字列)
  3. データ部(赤枠部分、104byte分のデータ)

となっています。 ※ core/java/android/os/Bundle.javaのwriteToParcelメソッドを参照。

データ部の構造は、key-valueペアの集合になっており、

  1. key-valueペアの個数(青枠部分、0x01 = 1組)
  2. 1組目のkeyデータ(緑枠部分)
  3. 1組目のvalueデータ(赤枠部分)
  4. …(2組以上ある場合はこれの繰り返し、今回は1組のみなので終了)

となっています。 ※ core/java/android/os/Parcel.javaのwriteMapInternalメソッドを参照。

keyやvalueの各データは、先頭4byteがデータ種別を表しており、

  • 0 → VAL_STRING、文字列データ
  • 3 → VAL_BUNDLE、Bundleデータ

となります。 ※ core/java/android/os/Parcel.java#L193あたりを参照。

文字列データは、

  1. 文字数(青枠部分、0x09 = 9文字)
  2. 文字列「ua_string」(緑枠部分、UTF-16LEで9文字分、18byteのデータ)
  3. 文字列終端子(橙枠部分、2byteのデータ)

Bundleデータは、前述した構造の通り、

  1. データ長(青枠部分、0x3C = 60byte)
  2. “BNDL”(緑枠部分、固定文字列)
  3. データ部(赤枠部分、60byteのデータ)

となり、同様に解析していくことができます。最終的に、このデータはJSONで例えるなら、

{
    "ua_string" : {
        "value" : "HTC_One_V/1.0"
    }
}

といった感じになり、「HTC_One_V/1.0」を「SoftBank/1.0/Smart/MyrMMS」と置き換えるので、結果として編集箇所は、

  1. UA文字列データ(赤枠部分) → 「SoftBank/1.0/Smart/MyrMMS」+終端子をUTF-16LEで表記(25文字 – 13文字 = 12文字増加 = 24byte増加)
  2. UA文字列の文字数(緑枠部分) → 25文字 = 0x19
  3. key「ua_string」に対するvalueのBundleデータ長(橙枠部分) → 60byte + 増加分24byte = 84byte = 0x54
  4. 全体のBundleデータ長(青枠部分) → 104byte + 増加分24byte = 128byte = 0x80

の4箇所になります。
最終的には、以下の様なデータになります。


このデータ列を、UPDATE文に指定すればOKです。
BLOBにデータを入れるときは「field_name = X’16進のバイト列’」という表記になるため、上記のようなUPDATE文になっています。

HTC One Vの初期APN設定を書き換える


HTC One VでSoftBankのSIMを利用する場合、初期設定ではAPNがandglobalとなっており、銀SIM等では正しく接続できません。そこでAPN情報を手動設定するのですが、GingerbreadやFroyo時代には使えたはずの/system/etc/apns-conf.xmlがなぜか反応してくれませんでした。※何かミスしているだけの可能性もありますが。

wipeを繰り返していると毎回手動設定するのがいいかげん面倒&設定し忘れでハマるのを避けるために、デフォルトのAPN設定自体を書き換えてしまい、安心・快適環境を目指します。

失敗すると起動しなくなりますので、作業の前にRUUの準備もしくはCWM Recoveryを導入してバックアップしておくことを推奨します。

現時点でHTC One Vは技適証明を取得していないので、以下の内容はあくまで参考情報となります。一応。

デフォルトのAPN設定は、framework-res.apk内のres/xml/apns.xmlに記載されていますので、これを修正します。

作業にはapktool 1.4.3が必要ですので、事前に入手し設定しておきます。

framework-res.apkのデコードとapns.xmlの修正

まずは、framework-res.apkを取得し、apktoolを使ってリソースをデコードします。

% adb pull /system/framework/framework-res.apk .
% apktool d framework-res.apk
I: Loading resource table...
I: Loaded.
I: Decoding file-resources...
I: Decoding values*/* XMLs...
I: Done.
I: Copying assets and libs...

無事apkのデコードが完了すると、framework-resというディレクトリが生成されていますので、この中のres/xml/apns.xmlを修正します。
「SoftBank」を検索すると、中盤ぐらいにandglobalのAPN設定が存在しますので、お好みのAPNに書き換えます。
銀SIMであれば、apn=”open.softbank.ne.jp”、user=”opensoftbank”…が有名ですね。以下のような感じで。

<apn carrier="SoftBank Internet" apn="open.softbank.ne.jp" user="opensoftbank" password="********" mmsc="http://mms/" mmsproxy="mmsopen.softbank.ne.jp" mmsport="8080" mcc="440" mnc="20" type="default,supl,mms" />

X06HTからの移行派はuser=”softbankX06HT”、mmsproxy=”andmms.softbank.ne.jp”もいいかも。passwordは上記と同様の手順でX06HTのapns.xmlから抜いてきましょう。

framework-res.apkのリビルド

apns.xmlの修正が完了したら、framework-res.apkを再生成します。ただし、そのままビルドすると大量のエラーが発生してビルド失敗になるので、いくつかのxmlファイルを書き換える必要があります。

res/values/anims.xml
<anim name=”…”>…</anim> を <item type=”anim” name=”…”>…</item> に修正します。
res/values/layouts.xml
<layout name=”…”>…</layout> を <item type=”layout” name=”…”>…</item> に修正します。
res/values/raws.xml
<raw name=”…”>…</raw> を <item type=”raw” name=”…”>…</item> に修正します。
res/values/plurals.xml
79行目の 「%d of %d」 を 「%1$d of %2$d」 に修正します。各言語用のファイル res/values-**/plurals.xml も同様に修正します。

GNU sedが使える人は、次のように一発置換でもOKです。

% cd framework-res/res
% sed -i 's/<\(anim\|layout\|raw\)/<item type="\1"/;s/<\/\(anim\|layout\|raw\)>/<\/item>/;s/%d\(.*\)%d/%1$d\1%2$d/' values/anims.xml values/layouts.xml values/raws.xml values*/plurals.xml
% cd ../..

その後、apktoolでビルドします。

% apktool b framework-res framework-res.new.apk
W: Could not find sources
I: Checking whether resources has changed...
I: Building resources...
I: Building apk file...

framework-res.new.apkが生成されていれば成功です。

新apk内のapns.xmlを旧apkに移植する

framework-res.apk内部のresources.arscや画像ファイルは無圧縮にする必要があるので、生成したapkはそのままでは問題があります。無圧縮にすべきファイルが多すぎるため、生成したapkからres/xml/apns.xmlを取り出し、既存のframework-res.apkに追記保存することにします。
事前にオリジナルのapkをコピーして保存しておきましょう。
追記後は念のためzipalignも行なっておきます。

zipalignはAndroid SDKのtools以下にありますので必要に応じてパスを通しておきます。

% cp framework-res.apk framework-res.org.apk
% unzip framework-res.new.apk res/xml/apns.xml
% zip framework-res.apk res/xml/apns.xml
% zipalign -f -v 4 framework-res.apk framework-res2.apk

生成したapkを上書きする

最後に、生成したapkを/system/framework以下に書き戻します。
まだadbがrootで動作していないためsdcard経由で転送し、rwモードでsystemパーティションをremount、上書きしてパーミッションを修正します。

% adb push framework-res2.apk /sdcard/
% adb shell

shell@android:/ $ su
shell@android:/ # mount -o rw,remount /system
shell@android:/ # cp /sdcard/framework-res2.apk /system/framework/framework-res.apk
shell@android:/ # chmod 644 /system/framework/framework-res.apk
shell@android:/ # exit
shell@android:/ $ exit

最後にrebootして確認しましょう。

% adb reboot

メニューからAPNを初期設定にリセットして、書き換えたAPNが表示されればOKです。

HTC One Vのroot化と日本語フォント導入


先日、台北にてHTC One V(Asia TW版)を入手してきましたが、例によって日本語フォントがない状態ですので、早速root取得、フォント導入してみます。

bootloaderのunlockからsu導入まで

HTCは、無保証になる代わりにbootloaderのunlock手順をwebで提供しています。HTCdevに行き、Developer Center、Unlock Bootloaderから手順通りに進めれば、簡単にunlockできます。

xda developersのこことかに、bootloaderのunlockからsuperbootを使ったsu導入の手順があるので、ここでは省略します。

まだテストビルドですが、CWM RecoveryのOne V版ができたようなので、こちらの手順でもいいかもしれません。

日本語フォントのインストール

Desire X06HT – Gingerbread(2.3)の導入 その2でも使用していたモトヤLマルベリ3(MTLmr3m.ttf)を使います。モトヤLシーダ3やDroidSansJapaneseを使う場合は適宜読み替えてください。フォントはgithubのplatform_framework_baseから取得しておきます。

busyboxのインストール

cpコマンドが存在しないので、事前にPlayストアからbusyboxをインストールして各種コマンドを使えるようにしておきます。

フォントファイルの設置

フォントファイルは/system/fonts以下に設置しますが、systemパーティションはread onlyになっており、かつro.secure=1なので直接adb pushで置くことができません。SD Card経由で書き込むことにします。

まずは、フォントファイルを/sdcardに転送し、systemパーティションをrwモードでremount。/sdcardから/system/fontsにファイルをコピーします。

% adb push MTLmr3m.ttf /system
% adb shell

shell@android:/ $ su
shell@android:/ # mount -o rw,remount /system
shell@android:/ # cp /sdcard/MTLmr3m.ttf /system/fonts
shell@android:/ # chmod 644 /system/fonts/MTLmr3m.ttf
shell@android:/ # exit
shell@android:/ $ exit

fallbackフォント設定の追記

GBではフォントファイルを置くだけで有効になりましたが、ICSではさらに設定ファイルに追記が必要です。

/system/etc/system_fonts.xmlに記載のフォントで処理できなかった文字は、/system/etc/fallback_fonts.xmlに記載された順序でフォントを検索し、処理されます。
ですので、fallback_fonts.xmlの先頭(<familyset>の次)に以下の内容を記載することで、日本語フォントが使用されるようになります。

    <family>
        <fileset>
            <file>MTLmr3m.ttf</file>
        </fileset>
    </family>

このファイルも直接書き込み不可ですので、取得・編集後、/sdcard経由で書き戻します。

busyboxを入れたことでviが使用可能ですので、使える人はremount後に直接編集してもOKです。

% adb pull /system/etc/fallback_fonts.xml
上記内容を追記
% adb push fallback_fonts.xml /sdcard/
% adb shell

shell@android:/ $ su
shell@android:/ # mount -o rw,remount /system
shell@android:/ # cp /sdcard/fallback_fonts.xml /system/etc
shell@android:/ # chmod 644 /system/etc/fallback_fonts.xml
shell@android:/ # exit
shell@android:/ $ exit

最後に、再起動します。正しく設定されていればフォントが変更されているはずです。

% adb reboot

これで違和感なく使えるようになりました。

フォント導入前
フォント導入後

BansheeのDAAP共有で、埋め込みのカバーアートを有効にする


Banshee 2.2.0の時点で、ローカルにある楽曲に関しては、ID3タグに埋め込んであるカバーアート(アルバムアートワーク)を取得するようになっています。
しかし、DAAP共有でmt-daapd等のDAAPサーバ上の曲を表示する場合、カバーアートはネットワークダウンロードのみとなってしまいます。

ネットワークからダウンロードされるカバーアートは、質が低かったり国内アーティストのものが少なかったりと、あまり満足できるものではありません。
自分自身は、ほとんどの楽曲に対してID3タグにカバーアートを埋め込み済みなので、DAAP経由での再生でも埋め込みカバーアートが表示されるように手を入れてみました。

作成したパッチはこちら。
banshee_daap_embedded_coverart_fix.patch

変更点の概要は、以下の通りです。

  1. カバーアート取得時のファイルURI条件がローカルファイルのみになっていたのを、DAAP配信のファイル(HTTP)も通るように修正。
  2. DAAPプロキシ用のファイルURI生成時に拡張子を付加するよう修正。
  3. DAAPプロキシ側のリクエスト処理時に拡張子を除外するよう修正。

2および3は、ID3タグの処理に用いているtaglib-sharpがファイルのフォーマットを判別する際に拡張子を利用しているためです。

パッチの適用は、Banshee 2.2.0をアルバムアーティスト対応にするのパッチ適用手順と同様の流れで、以下のような形で。

% cd ~/work/banshee/banshee-2.2.0
% quilt new xx_daap_embedded_coverart_fix.patch
% curl -L 'http://media.st/blog/wp-content/uploads/2011/11/banshee_daap_embedded_coverart_fix.patch_.txt' | quilt fold -f -p1
% quilt refresh

後は普通にビルドでOKです。

DAAPでの再生はHTTP経由ですので、初回のカバーアートの表示はローカルファイルに比べると遅いですが、焦らず待ちましょう。
もちろん、一度取得するとBanshee側でカバーアートのキャッシュが生成されますので、2回目以降はスムーズに表示されます。

曲のカバーアートを更新してもキャッシュがある場合は反映されませんが、そこはご愛嬌ということで。
手動で更新する場は、キャッシュは ~/.cache/media-art 以下に保管されていますので、該当の画像を削除すれば再作成されます。

Banshee 2.2.0をアルバムアーティスト対応にする ー DAAP編


Banshee 2.2.0をアルバムアーティスト対応にするに記載した方法で、Bansheeローカルにある楽曲はアルバムアーティストでの絞り込みに対応しました。
しかしDAAPでの楽曲共有の場合、そもそもmt-daapd側がアルバムアーティストに非対応だったため、Firefly(mt-daapd)をアルバムアーティスト対応にするに記載した方法でmt-daapdを改修、DAAP経由でアルバムアーティスト情報が送出されるようにしました。

これで万事OKと思われたのですが、まだBanshee側で表示されません。
DAAP的にはmt-daapd側から正しく送出されているので、問題があるとすればBansheeのほうです。そこでBansheeのソースコードに目を通してみると、案の定拡張機能のDAAP共有が原因でした。

作ったパッチはこちら。
banshee_daap_albumartist_compilation_fix.patch
変更点は、大まかに以下の通りです。

  • DAAP通信時のリクエストクエリに、アルバムアーティスト情報(daap.songalbumartist)を追加。
  • DAAPで取得した曲データにおいて、アルバムアーティスト情報を保持するよう修正。
  • DAAPで取得した曲データにおいて、コンピレーションアルバムかどうかのフラグを保持するよう修正。
  • 曲一覧のアルバムアーティスト取得部分の判定方法を修正。

コンピレーションについては、DAAP通信では取得しているものの、なぜか保持していなかったので追加で入れてあります。
また、曲一覧の表示で、

  • コンピレーションである場合、アルバムアーティストがあればそれを返し、なければVarious Artistsを返す
  • コンピレーションでない場合、アーティスト名を返す

という、よくわからない判定になっていて、このままだとコンピレーションがtrueでない限り、曲一覧でアルバムアーティスト=アーティストになってしまうため、

  • アルバムアーティストがある場合、それを返す
  • アルバムアーティストがない場合、コンピレーションであればVarious Artistsを返し、なければアーティスト名を返す

と判定方法を修正しています。

パッチの適用は、Banshee 2.2.0をアルバムアーティスト対応にするでのパッチ適用手順の最後に行います。

% cd ~/work/banshee/banshee-2.2.0
% quilt new xx_daap_albumartist_compilation_fix.patch
% curl -L 'http://media.st/blog/wp-content/uploads/2011/10/banshee_daap_albumartist_compilation_fix.patch_.txt' | quilt fold -f -p1
% quilt refresh

changelogも適当に追加して、ビルドしましょう。
できたパッケージをインストールし、DAAP経由でアルバムアーティストが表示されればOKです。