2012年12月29日土曜日

【Xperia AX SO-01E】framework-res.apk をいじらずにカスタムする方法(2)

前回の記事で、framework-res.apkそのものはいじらずにリソースをすげ替える方法を検証してみました。
では、自分で新しく作ったリソースをframework-res.apk内のリソースのように外部から参照できるようにするにはどうしたらよいか、となります。正直こっちの方が需要がありそうです。

いろいろ検証してみたのですが、別apkを作った上で、参照元のコードを書き換える、という方法にしかたどり着けませんでした。

とりあえず今回は、『電源ボタン長押しメニューに「再起動」追加』の記事で追加した再起動のアイコンと文字を変更する手順という形で紹介します。

前提(私の実験環境)は以下です。
・rootとってあること
・作業PCはadbコマンドが使える環境であること
・作業PCにjavaが入っていること
・端末のビルド番号は9.0.G.0.247
・CWMを導入してあること
・母艦はWindows 7
・トラブっても自力で復旧できること(つまり自己責任)

リソース用の「external-res.apk」作成と、「android.policy.odex」編集に手順を分けます。
■A0■external-res.apk作成にあたっての必要物資
・aapt.exe(Android SDKにも含まれていますが、最新版がベター)
・signapk.jar(apkに署名する際に必要)
・testkey.x509.pem(apkに署名する際に必要)
・testkey.pk8(apkに署名する際に必要)

aapt.exeは、「apktool」で検索した先でゲット。
apktoolのダウンロードページに「apktool-install-windows-うんたらかんたら.tar.bz2」というのがありますが、その中に含まれています。
「signapk.jar」「testkey.x509.pem」「testkey.pk8」は、「auto-sign」で検索して「Auto-sign.zip」をゲットし、その中から取り出しましょう。ただ、apkに署名する方法は他にもあると思いますので、ご存知の方はそちらでやった方がいいかもしれません。

■A1■PCにおける作業フォルダの準備
ここでは「c:\apkwork」とします。
この中に、「new」「external-res」フォルダを(無ければ)新規作成します。
必要物資たちは作業フォルダ直下にぶっこみます。

□apkwork ┳ □new(生成されたexternal-res.apkが入る予定)
     ┣ □external-res(ソースファイル群が入る)
     ┃ aapt.exe
     ┃ signapk.jar
     ┃ testkey.x509.pem
     ┗ testkey.pk8

■A2■ソースファイル作成
「external-res.apk」というファイルを作成するため、各種ファイルを捏造します。

【A2-1】「AndroidManifest.xml」作成
テキストエディタで以下の内容を打ち込み、external-resフォルダ内に「AndroidManifest.xml」という名前で保存します。

<?xml version="1.0" encoding="utf-8"?>
<manifest package="com.android.externalres"
  xmlns:android="http://schemas.android.com/apk/res/android">
</manifest>

【A2-2】リソース作成
 external-resフォルダ内に「res」フォルダを新規作成し、さらにその中に「drawable-xhdpi」「values」「values-ja」フォルダを新規作成します。

すげ替えたいアイコンのpng画像ファイル(64×64)を準備し、「ic_lock_reboot.png」という名前にしてdrawable-xhdpiフォルダに投入。

次にテキストエディタで以下の内容を打ち込み、valuesフォルダ内に「public.xml」という名前で保存します。

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <public type="drawable" name="ic_lock_reboot" id="0x7f020000" />
    <public type="string" name="global_action_reboot" id="0x7f030000" />
</resources>

なお、public.xmlを作らなくても自動的にリソースIDは振られますが、自分でIDを把握しやすいので今回は作ってみています。
「attr」以外のリソースIDは0x7f020000から始めないといけないようです。同じ種類のリソースを増やすときはそこから連番で(もちろん16進数で)IDを振っていき、別の種類のリソースは0x7f030000~、0x7f040000~というようにしていきます。このようにきちんとIDを振らないとビルド時に怒られます。

またテキストエディタで以下の内容を打ち込み、同じくvaluesフォルダ内に「strings.xml」という名前で保存します。

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="global_action_reboot">Reboot...</string>
</resources>

さらにテキストエディタで以下の内容を打ち込み、values-jaフォルダ内に「strings.xml」という名前で保存します。これは日本語が含まれていますので、必ず文字コード「UTF-8N」で保存のこと。さもなくば華麗に文字化けします。

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="global_action_reboot">再起動☆</string>
</resources>

2つのフォルダに同じような「strings.xml」を作りましたが、「values-ja」の方は端末の設定が日本語の時に、「values」の方は日本語以外の時に表示される文字列になります。

結果的に、external-resフォルダ内の構成は以下のようになります。

□external-res ┳ □res ┳ □drawable-xhdpi ━ ic_lock_reboot.png
        ┃    ┣ □values ┳ public.xml
        ┃    ┃     ┗ strings.xml
        ┃    ┗ □values-ja ━ strings.xml
        ┗ AndroidManifest.xml

■A3■apk生成
「apkwork」フォルダでコマンドプロンプトを開き、以下のコマンドを実行します。

C:\apkwork>aapt package -S external-res\res -M external-res\AndroidManifest.xml -f -v -F new\u_external-res.apk

newフォルダにu_external-res.apkが生成されます。
なぜ頭に「u_」を付けたかというと、この後apkに署名して新しいapkを作るからです。

■A4■apkに署名
apkに署名しておかないと、別のapkからリソースを参照できないようですので、署名しておきます。

C:\apkwork>java -jar signapk.jar testkey.x509.pem testkey.pk8 new\u_external-res.apk new\external-res.apk

newフォルダにexternal-res.apkが生成されます。
端末への送信は後でいっぺんにやります。

■B1■ソースファイルの書き換え等
さてここからは、以前の記事ですでにメニューに「再起動」を追加しているという前提で話を進めます。
作業フォルダは「c:\odexwork」とします。

以下のフォルダ内のファイルを編集します。

C:\odexwork\android.policy\com\android\internal\policy\impl

【B1-1】
「GlobalActions.smali」を書き換えます。

「.method private createDialog()Landroid/app/AlertDialog;」を検索。(394行目あたり)

そのちょっと下(400行目あたり)の「const v13」の値を、今回新たに作ったアイコン画像のリソースIDに変更します。

    .prologue
    const v12, 0x1080030

    const v13, 0x108004d
↓↓↓
    .prologue
    const v12, 0x1080030

    const v13, 0x7f020000  #変更

そしてその54行くらい下(454行目あたり)の「const v2」の値も、今回新たに作った文字列のリソースIDに変更します。

    new-instance v1, Lcom/android/internal/policy/impl/GlobalActions$9;

    const v2, 0x10403a9
↓↓↓
    new-instance v1, Lcom/android/internal/policy/impl/GlobalActions$9;

    const v2, 0x7f030000  #変更

【B1-2】
「GlobalActions$SinglePressAction.smali」を書き換えます。

「.method public create(~」を検索。(48行目あたり)

その直下の「.registers」の値を3つほど増やします。

.method public create(Landroid/content/Context;Landroid/view/View;Landroid/view/ViewGroup;Landroid/view/LayoutInflater;)Landroid/view/View;
    .registers 10
↓↓↓
.method public create(Landroid/content/Context;Landroid/view/View;Landroid/view/ViewGroup;Landroid/view/LayoutInflater;)Landroid/view/View;
    .registers 13  #変更

その51行くらい下(100行目あたり)の「move-result-object v3」からさらに13行くらい下(113行目あたり)の「invoke-virtualうんたらかんたら->setText(I)V」までをごっそり削除、別の処理に書き換えます。

    .line 423
    invoke-virtual {p1}, Landroid/content/Context;->getResources()Landroid/content/res/Resources;

    move-result-object v3  #この行から削除

    iget v4, p0, Lcom/android/internal/policy/impl/GlobalActions$SinglePressAction;->mIconResId:I

    invoke-virtual {v3, v4}, Landroid/content/res/Resources;->getDrawable(I)Landroid/graphics/drawable/Drawable;

    move-result-object v3

    invoke-virtual {v0, v3}, Landroid/widget/ImageView;->setImageDrawable(Landroid/graphics/drawable/Drawable;)V

    .line 424
    iget v3, p0, Lcom/android/internal/policy/impl/GlobalActions$SinglePressAction;->mMessageResId:I

    invoke-virtual {v1, v3}, Landroid/widget/TextView;->setText(I)V  #この行まで削除
    move-result-object v5  #この行から追加

    const-string v6, "com.android.externalres"

    const/4 v7, 0x4

    invoke-virtual {p1, v6, v7}, Landroid/content/Context;->createPackageContext(Ljava/lang/String;I)Landroid/content/Context;

    move-result-object v6

    invoke-virtual {v6}, Landroid/content/Context;->getResources()Landroid/content/res/Resources;

    move-result-object v6

    iget v4, p0, Lcom/android/internal/policy/impl/GlobalActions$SinglePressAction;->mIconResId:I

    const v3, 0x7f000000

    if-ge v4, v3, :cond_10

    invoke-virtual {v5, v4}, Landroid/content/res/Resources;->getDrawable(I)Landroid/graphics/drawable/Drawable;

    move-result-object v3

    goto :cond_11

    :cond_10
    invoke-virtual {v6, v4}, Landroid/content/res/Resources;->getDrawable(I)Landroid/graphics/drawable/Drawable;

    move-result-object v3

    :cond_11
    invoke-virtual {v0, v3}, Landroid/widget/ImageView;->setImageDrawable(Landroid/graphics/drawable/Drawable;)V

    .line 424
    iget v4, p0, Lcom/android/internal/policy/impl/GlobalActions$SinglePressAction;->mMessageResId:I

    const v3, 0x7f000000

    if-ge v4, v3, :cond_20

    invoke-virtual {v5, v4}, Landroid/content/res/Resources;->getText(I)Ljava/lang/CharSequence;

    move-result-object v3

    goto :cond_21

    :cond_20
    invoke-virtual {v6, v4}, Landroid/content/res/Resources;->getText(I)Ljava/lang/CharSequence;

    move-result-object v3

    :cond_21
    invoke-virtual {v1, v3}, Landroid/widget/TextView;->setText(Ljava/lang/CharSequence;)V  #この行まで追加

    .line 426
    return-object v2
.end method

結構がっつり書き換えちゃってますが、何をしているかというと、まずcreatePackageContext→getResourcesでパッケージ名が「com.android.externalres」つまり「external-res.apk」のリソースを取得。与えられたリソースIDが0x7f000000より小さければ従来通りの(framework-res.apkの)リソースを使用、0x7f000000以上であればexternal-res.apkのリソースを使用する、という処理にしています。
あと、setTextは元々直接IDがぶっこまれていましたが、一旦リソースからテキストを取得、それを改めてsetTextに渡すように変更しました。

■B2■smaliしたりdeodex_android.policy.jar作成
C:\odexwork>java -Xmx512m -jar smali-1.4.1.jar --api-level 15 -o classes.dex android.policy
C:\odexwork>copy /y old\android.policy.jar new\deodex_android.policy.jar
C:\odexwork>7za a -tzip -mx0 new\deodex_android.policy.jar classes.dex
C:\odexwork>del classes.dex

以前と同様です。

■C1■端末をCWMモードにして待機
以降は端末をCWMモードにして作業をすすめます。
CWMが起動したら、「mounts and storage」に入り、「mount /system」と「mount /data」をしておきます。
もちろんPCと端末はUSBでつないでおいてください。

■C2■作ったファイルを端末にぶっこむ
C:\odexwork>adb push ..\apkwork\new\external-res.apk /data/local/tmp
C:\odexwork>adb push new\deodex_android.policy.jar /data/local/tmp

まあとにかく手順Aで作ったexternal-res.apkと手順Bで作ったdeodex_android.policy.jarを端末の作業フォルダに送り込みます。

■C3■android.policy.odex作成
以降は端末のシェルに入っての作業となります。
以前のままなら、すでに/data/local/tmpにandroid.policy.odexがあるはずですので、dexopt-wrapperの前にあらかじめ「rm android.policy.odex」しておきます。

C:\odexwork>adb shell
~ # cd /data/local/tmp
/data/local/tmp # rm android.policy.odex
/data/local/tmp # ./dexopt-wrapper deodex_android.policy.jar android.policy.odex /system/framework/core.jar:/system/framework/core-junit.jar:/system/framework/bouncycastle.jar:/system/framework/ext.jar:/system/framework/framework.jar:/system/framework/framework_ext.jar:/system/framework/services.jar:/system/framework/apache-xml.jar:/system/framework/filterfw.jar
/data/local/tmp # busybox dd if=/system/app/SystemUI.odex of=SystemUI.odex bs=1 count=20 skip=52 seek=52 conv=notrunc

■C4■パーミッション変更
/data/local/tmp # chmod 0644 external-res.apk
/data/local/tmp # chmod 0644 android.policy.odex

生成物のパーミッションを0644に変更。
odexのほうはすでに0644になっているかもしれませんが、念の為。

■C5■仕上げ
/data/local/tmp # cp external-res.apk /system/framework
/data/local/tmp # cp android.policy.odex /system/framework
/data/local/tmp # reboot

external-res.apkは/system/frameworkに入れてますが、/system/appとかに入れても大丈夫なようです。お好みで。
reboot後、再起動のアイコンと文字が変化していれば成功。

3 件のコメント:

  1. はじめまして。

    こちらの記事は大変勉強になるのでいつも参考にさせてもらってます。

    私はLT25iで遊んでますが、どうもandroid.policy.odexがうまくできませんでした。(ブートループします。)

    やはりどっかの値が違うんでしょうかね~?

    返信削除
    返信
    1. すみません。自己解決しました。コピペに頼るとこうなるという...。

      削除
    2. 変数(vなんちゃら)が変わると難儀しますよね。
      端末アップデート時に大変なのは大体それです。

      削除