2013年1月30日水曜日

【Xperia AX SO-01E】ステータスバーの時計に秒表示を追加

ステータスバーの時計に秒表示を追加します。
ただ秒を表示するだけならリソースの文字列を変更するだけなのですが、それだと分単位でしか表示が更新されず使い物になりません。今回は時計が自力で秒ごとに表示を更新するようにします。
また、常に時計を更新し続けていると電池がもったいない気がしますので、画面が消灯しているときは更新をストップするようにもします。

ちなみに上の画像はパターンロック画面ですが、なぜかステータスバーに時計が表示されています。
(※スライドロックの場合は元々表示されるようですね。パターンロック等ではステータスバーの時計は非表示になります。)
これはソースに1か所手を加えるだけで実現しますので、おまけとして本記事で触れたいと思います。

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

いじるファイルは、以下になります。
/system/app/SystemUI.apk
/system/app/SystemUI.odex

■SystemUI.apk
SystemUI.apkの作業フォルダ内、「res」フォルダ以下のxmlにリソース追加します。
まずres\values\public.xml。

    <public type="string" name="battery_low_power_settings" id="0x7f08007f" />
    <public type="string" name="power_settings_instructions" id="0x7f080080" />
    <public type="string" name="notifications_off_title" id="0x7f08007c" />
    <public type="string" name="notifications_off_text" id="0x7f08007d" />
    <public type="string" name="toast_rotation_locked" id="0x7f08007b" />
    <public type="string" name="twelve_hour_time_format_sec" id="0x7f080081" />
    <public type="string" name="twenty_four_hour_time_format_sec" id="0x7f080082" />
    <public type="dimen" name="status_bar_edge_ignore" id="0x7f0a0000" />
    <public type="dimen" name="status_bar_recents_app_icon_max_width" id="0x7f0a0001" />
    <public type="dimen" name="status_bar_recents_app_icon_max_height" id="0x7f0a0002" />

458行目あたりにstringの末尾があります。ここに2つstringを追加します。
1つは12時間表示用、もう1つは24時間表示用です。
前の行のIDが「0x7f08007b」なので騙されそうになりますが、ちょっと上に「0x7f080080」まで存在します。ということで追加するstringのIDは「~81」と「~82」になります。

次にres\values\strings.xml。英語バージョンです。

    <string name="accessibility_power">Power</string>
    <string name="accessibility_search">Search</string>
    <string name="twelve_hour_time_format_sec">h:mm:ss a</string>
    <string name="twenty_four_hour_time_format_sec">HH:mm:ss</string>
</resources>

「ss」が「秒」を表します。
元のリソースはframework-res.apkの中にありますが、それに「:ss」を付けただけです。

続きましてres\values-ja\strings.xml。日本語バージョンです。必ずUTF-8Nで保存しましょう。

    <string name="accessibility_power">電源</string>
    <string name="accessibility_search">検索</string>
    <string name="twelve_hour_time_format_sec">h:mm:ssa</string>
    <string name="twenty_four_hour_time_format_sec">H:mm:ss</string>
</resources>

apkのほうの編集はリソース追加だけなので、以上です。

■SystemUI.odex
SystemUI.odexをbaksmaliした展開先の中で、以下のフォルダを探して開きます。
com\android\systemui\statusbar\policy
ここにあるファイルを編集していきます。

【Clock.smali】
「.field private mUpdater:Ljava/lang/Runnable;」(21行目あたり)の後に、新たなフィールドを追加します。

.field private mUpdateHandler:Landroid/os/Handler;

.field private mUpdater:Ljava/lang/Runnable;

.field private mSecondUpdater:Ljava/lang/Runnable;  #追加

これが秒単位で呼ばれるオブジェクトになります。

次に「.method public constructor <init>(Landroid/content/Context;Landroid/util/AttributeSet;I)V」(56行目あたり)を検索。
その26行くらい下、mUpdaterがiput-objectで代入されている部分の下に数行追加。

    .line 69
    new-instance v0, Lcom/android/systemui/statusbar/policy/Clock$1;

    invoke-direct {v0, p0}, Lcom/android/systemui/statusbar/policy/Clock$1;-><init>(Lcom/android/systemui/statusbar/policy/Clock;)V

    iput-object v0, p0, Lcom/android/systemui/statusbar/policy/Clock;->mUpdater:Ljava/lang/Runnable;

    new-instance v0, Lcom/android/systemui/statusbar/policy/Clock$3;  #この行から追加

    invoke-direct {v0, p0}, Lcom/android/systemui/statusbar/policy/Clock$3;-><init>(Lcom/android/systemui/statusbar/policy/Clock;)V

    iput-object v0, p0, Lcom/android/systemui/statusbar/policy/Clock;->mSecondUpdater:Ljava/lang/Runnable;  #この行まで追加

    .line 124
    new-instance v0, Lcom/android/systemui/statusbar/policy/Clock$2;

mSecondUpdaterに「Clock$3」クラスを突っ込む処理です。
Clock$3は後で作ります。

次に「.method static synthetic access$300(Lcom/android/systemui/statusbar/policy/Clock;)Landroid/os/Handler;」(136行目あたり)を検索。
その9行くらい下、「.end method」の後にメソッドを追加します。

    return-object v0
.end method

.method static synthetic access$400(Lcom/android/systemui/statusbar/policy/Clock;)Ljava/lang/Runnable;  #この行から追加
    .registers 2
    .parameter "x0"

    .prologue
    iget-object v0, p0, Lcom/android/systemui/statusbar/policy/Clock;->mSecondUpdater:Ljava/lang/Runnable;

    return-object v0
.end method  #この行まで追加

.method private final getSmallTime()Ljava/lang/CharSequence;
    .registers 22

不思議なメソッドですが、mSecondUpdaterはprivateなので、外部から参照するためにこれが必要になるようです。

次。すぐ下に「.method private final getSmallTime()Ljava/lang/CharSequence;」(157行目あたり)があります。
時計の文字列を生成するメソッドです。
その32行くらい下に「const v16, 0x104009a」がありますが、これは24時間表記のリソースIDになります。
ということで、これをapkの方で追加した24時間表記バージョンのリソースIDに変更します。

    .line 155
    const v16, 0x104009a
↓↓↓
    .line 155
    const v16, 0x7f080082  #変更

同様に、345行目くらいに「const v16, 0x1040099」があり、これは12時間表記のリソースIDですので、今回追加した12時間表記バージョンのリソースIDに変更します。

    :cond_7f
    const v16, 0x1040099
↓↓↓
    :cond_7f
    const v16, 0x7f080081  #変更

今度は「.method protected onAttachedToWindow()V」を検索(604行目あたり)。
その44~46行くらい下の「const-string v1, "android.intent.action.LOCALE_CHANGED"」「invoke-virtual {v0, v1}, Landroid/content/IntentFilter;->addAction(Ljava/lang/String;)V」の後に数行追加。

    .line 100
    const-string v1, "android.intent.action.LOCALE_CHANGED"

    invoke-virtual {v0, v1}, Landroid/content/IntentFilter;->addAction(Ljava/lang/String;)V

    const-string v1, "android.intent.action.SCREEN_ON"  #この行から追加

    invoke-virtual {v0, v1}, Landroid/content/IntentFilter;->addAction(Ljava/lang/String;)V

    const-string v1, "android.intent.action.SCREEN_OFF"

    invoke-virtual {v0, v1}, Landroid/content/IntentFilter;->addAction(Ljava/lang/String;)V  #この行まで追加

    .line 102
    invoke-virtual {p0}, Lcom/android/systemui/statusbar/policy/Clock;->getContext()Landroid/content/Context;

画面が点灯する時にSCREEN_ON、消灯する時にSCREEN_OFFというブロードキャストが飛んでくるのですが、これの受け付けを許可するための処理です。

続きましてメソッドの終わりらへん、689行目くらいに「invoke-virtual {p0}, Lcom/android/systemui/statusbar/policy/Clock;->updateClock()V」がありますが、これを削除して、新たに処理を追加します。

    .line 112
    invoke-virtual {p0}, Lcom/android/systemui/statusbar/policy/Clock;->updateClock()V  #この行を削除
    iget-object v0, p0, Lcom/android/systemui/statusbar/policy/Clock;->mUpdateHandler:Landroid/os/Handler;  #この行から追加

    iget-object v1, p0, Lcom/android/systemui/statusbar/policy/Clock;->mSecondUpdater:Ljava/lang/Runnable;

    invoke-virtual {v0, v1}, Landroid/os/Handler;->post(Ljava/lang/Runnable;)Z  #この行まで追加

    .line 113
    return-void
.end method

updateClock()は時計の表示を更新するものですが、追加した処理の中に含まれますので削除しました。
追加部分は、mSecondUpdaterつまり秒刻み処理を開始するものです。

さて「.method protected onDetachedFromWindow()V」を検索(699行目あたり)。
その19行くらい下の「invoke-virtual {v0, v1}, Landroid/content/Context;->unregisterReceiver(Landroid/content/BroadcastReceiver;)V」の後に処理を追加。

    .line 119
    invoke-virtual {p0}, Lcom/android/systemui/statusbar/policy/Clock;->getContext()Landroid/content/Context;

    move-result-object v0

    iget-object v1, p0, Lcom/android/systemui/statusbar/policy/Clock;->mIntentReceiver:Landroid/content/BroadcastReceiver;

    invoke-virtual {v0, v1}, Landroid/content/Context;->unregisterReceiver(Landroid/content/BroadcastReceiver;)V

    iget-object v0, p0, Lcom/android/systemui/statusbar/policy/Clock;->mUpdateHandler:Landroid/os/Handler;  #この行から追加

    iget-object v1, p0, Lcom/android/systemui/statusbar/policy/Clock;->mSecondUpdater:Ljava/lang/Runnable;

    invoke-virtual {v0, v1}, Landroid/os/Handler;->removeCallbacks(Ljava/lang/Runnable;)V  #この行まで追加

    .line 120
    const/4 v0, 0x0

    iput-boolean v0, p0, Lcom/android/systemui/statusbar/policy/Clock;->mAttached:Z

    .line 122
    :cond_13
    return-void
.end method

先ほどとは逆に、秒刻み処理の予約を取り消す処理です。

さあこのファイル最後の追加です。
「.method final updateClock()V」を検索(736行目あたり)。
まずその直下の「.registers 4」の数値を2ほど増やし、「.registers 6」に書き換えます。

.method final updateClock()V
    .registers 4
↓↓↓
.method final updateClock()V
    .registers 6  #変更

その8行くらい下の「move-result-wide v1」の後に2つほど処理を追加。

    invoke-static {}, Ljava/lang/System;->currentTimeMillis()J

    move-result-wide v1

    const-wide/16 v3, 0x1f4  #この行追加

    add-long v1, v1, v3  #この行追加

    invoke-virtual {v0, v1, v2}, Ljava/util/Calendar;->setTimeInMillis(J)V

「0x1f4」は、10進数で500。現在の時刻に500ミリ秒足して、それを表示させるようにしています。
なぜそんなことをするか?
秒単位の処理は、おおむね秒が変わる瞬間に行われるのですが、たまーに先走って秒が変わる「寸前に」更新処理が呼ばれることがあります。その結果、その回では前の秒が表示され、次の回で秒が飛んでしまいます。これを防ぐために、500ミリ秒ほど遊びを設けることにしました。もちろんミリ秒の位は表示されません(試しに表示させてみたら、○.500前後をうろうろしてました)ので、見た目上は秒が確実にカウントされていることになります。

【Clock$2.smali】
このファイル(クラス)はBroadcastReceiverといって、システムからのブロードキャスト(お知らせ)を受け取る部分になります。
Clock.smaliのほうでも少し触れましたが、今回は画面点灯と消灯のお知らせを受け取る処理を追加します。

「.method public onReceive(Landroid/content/Context;Landroid/content/Intent;)V」(37行目あたり)のちょっと下、「const-string v2, "android.intent.action.TIMEZONE_CHANGED"」(50行目あたり)に処理を追加します。

    .prologue
    .line 127
    invoke-virtual {p2}, Landroid/content/Intent;->getAction()Ljava/lang/String;

    move-result-object v0

    .line 128
    .local v0, action:Ljava/lang/String;
    const-string v2, "android.intent.action.SCREEN_OFF"  #この行から追加

    invoke-virtual {v0, v2}, Ljava/lang/String;->equals(Ljava/lang/Object;)Z

    move-result v2

    if-eqz v2, :cond_1

    iget-object v2, p0, Lcom/android/systemui/statusbar/policy/Clock$2;->this$0:Lcom/android/systemui/statusbar/policy/Clock;

    invoke-static {v2}, Lcom/android/systemui/statusbar/policy/Clock;->access$300(Lcom/android/systemui/statusbar/policy/Clock;)Landroid/os/Handler;

    move-result-object v3

    invoke-static {v2}, Lcom/android/systemui/statusbar/policy/Clock;->access$400(Lcom/android/systemui/statusbar/policy/Clock;)Ljava/lang/Runnable;

    move-result-object v2

    invoke-virtual {v3, v2}, Landroid/os/Handler;->removeCallbacks(Ljava/lang/Runnable;)V

    goto :cond_24

    :cond_1
    const-string v2, "android.intent.action.SCREEN_ON"

    invoke-virtual {v0, v2}, Ljava/lang/String;->equals(Ljava/lang/Object;)Z

    move-result v2

    if-eqz v2, :cond_10

    iget-object v2, p0, Lcom/android/systemui/statusbar/policy/Clock$2;->this$0:Lcom/android/systemui/statusbar/policy/Clock;

    invoke-static {v2}, Lcom/android/systemui/statusbar/policy/Clock;->access$300(Lcom/android/systemui/statusbar/policy/Clock;)Landroid/os/Handler;

    move-result-object v3

    invoke-static {v2}, Lcom/android/systemui/statusbar/policy/Clock;->access$400(Lcom/android/systemui/statusbar/policy/Clock;)Ljava/lang/Runnable;

    move-result-object v2

    invoke-virtual {v3, v2}, Landroid/os/Handler;->post(Ljava/lang/Runnable;)Z

    goto :cond_24

    :cond_10  #この行まで追加
    const-string v2, "android.intent.action.TIMEZONE_CHANGED"

    invoke-virtual {v0, v2}, Ljava/lang/String;->equals(Ljava/lang/Object;)Z

「SCREEN_OFF」がきたら秒更新をやめ、「SCREEN_ON」がきたら秒更新を再開するというだけのことです。

もう1か所、「return-void」(144行目あたり)の前に1行追加します。

    invoke-virtual {v2, v3}, Landroid/os/Handler;->post(Ljava/lang/Runnable;)Z

    .line 139
    :cond_24  #追加
    return-void

    .line 131
    :cond_2f
    const-string v2, "android.intent.action.LOCALE_CHANGED"

SCREEN_ONやSCREEN_OFFの処理が済んだらすぐ抜けられるように、returnのところにラベルを打ちます。

【Clock$3.smali】
このファイルは新規に作成します。
といってもClock$1.smaliが同じような作りですので、Clock$1.smaliを複製し、「Clock$3.smali」にリネームしてください。

ソース内に「Clock$1」が3か所くらい存在しています。(1行目、31行目、45行目あたり)
まず、それらをすべて「Clock$3」 に書き換えます。
(ただし3つ目のものはどっちみちごっそり書き換えますので、そのままでも結構です。)

最後に、40~51行目あたりの「.method public run()V」~「.end method」を、ごっそり以下のように書き換えてください。

.method public run()V
    .registers 7

    .prologue
    iget-object v0, p0, Lcom/android/systemui/statusbar/policy/Clock$3;->this$0:Lcom/android/systemui/statusbar/policy/Clock;

    invoke-virtual {v0}, Lcom/android/systemui/statusbar/policy/Clock;->updateClock()V

    invoke-static {}, Ljava/lang/System;->currentTimeMillis()J

    move-result-wide v0

    const-wide/16 v2, 0x3e8

    rem-long v0, v0, v2

    sub-long v0, v2, v0

    invoke-static {}, Landroid/os/SystemClock;->uptimeMillis()J

    move-result-wide v2

    add-long v0, v2, v0

    iget-object v4, p0, Lcom/android/systemui/statusbar/policy/Clock$3;->this$0:Lcom/android/systemui/statusbar/policy/Clock;

    invoke-static {v4}, Lcom/android/systemui/statusbar/policy/Clock;->access$300(Lcom/android/systemui/statusbar/policy/Clock;)Landroid/os/Handler;

    move-result-object v4

    iget-object v5, p0, Lcom/android/systemui/statusbar/policy/Clock$3;->this$0:Lcom/android/systemui/statusbar/policy/Clock;

    invoke-static {v5}, Lcom/android/systemui/statusbar/policy/Clock;->access$400(Lcom/android/systemui/statusbar/policy/Clock;)Ljava/lang/Runnable;

    move-result-object v5

    invoke-virtual {v4, v5, v0, v1}, Landroid/os/Handler;->postAtTime(Ljava/lang/Runnable;J)Z

    return-void
.end method

簡単に言うと、まずupdateClock()で時計の表示を更新し、次の更新タイミングを計算し、postAtTimeで予約しておくという処理です。
これによって、毎秒ここが呼ばれ、時計が更新されるようになるという具合です。

■おまけ、ステータスバーの時計をロック画面でも表示
SystemUI.odexをbaksmaliした展開先の中で、以下のファイルを編集します。
com\android\systemui\statusbar\phone\PhoneStatusBar.smali

「.method public showClock(Z)V」(6499行目あたり)を検索。
その18行くらい下の「if-eqz p1, :cond_12」の行をコメントアウト、もしくは削除します。

    .line 1110
    if-eqz p1, :cond_12
↓↓↓
    .line 1110
#    if-eqz p1, :cond_12

これだけで、ステータスバーの時計がロック画面でも消えなくなります。

■仕上げ
上記変更後、/system/appのSystemUI.apkとSystemUI.odexを置き換えてください。
ステータスバーの時計がきちんと秒を刻んでいることを確認してください。

0 件のコメント:

コメントを投稿