U300でセルスタンバイでやたらと電力を食う問題について追う(1)

どういう問題かは宮川さんが書かれている通りです。
無線部分の細かい制御を読むのはちょっと無理だと思うのだけど、せっかくAndroidでソースがあるのだからと読める範囲で追いかけてみました。
とっかかりを掴むまで

  • CM7のコードを取ってくる
  • "セルスタンバイ"という文字列を検索
    • リソースファイルが見付かる
    • ./packages/apps/Settings/res/values-ja/strings.xml
    • "power_cell"がリソース名であるようだ
  • "power_cell"でgrep
    • ./packages/apps/Settings/src/com/android/settings/fuelgauge/PowerUsageSummary.javaで使われている
    • というかここだけ
    • ファイル名からすると電力消費に関係する箇所のようだ

ここをとっかりとして読み進める。
呼び出し元

    private void processMiscUsage() {
        final int which = mStatsType;
        long uSecTime = SystemClock.elapsedRealtime() * 1000;
        final long uSecNow = mStats.computeBatteryRealtime(uSecTime, which);
        final long timeSinceUnplugged = uSecNow;
        if (DEBUG) {
            Log.i(TAG, "Uptime since last unplugged = " + (timeSinceUnplugged / 1000));
        }

        addPhoneUsage(uSecNow);
        addScreenUsage(uSecNow);
        addWiFiUsage(uSecNow);
        addBluetoothUsage(uSecNow);
        addIdleUsage(uSecNow); // Not including cellular idle power
        addRadioUsage(uSecNow);
    }

バッテリ駆動に切り替わってからの経過時間のマイクロ秒(といっても1000倍しているだけ)を渡して、電話、画面、WiFiBluetoothなどなどコンポーネント毎の利用電力を出している。
で、以下が実際にpower_cellというリソース名が使われている部分。無線コンポーネントに関係する電力消費を算出しているメソッド

    private void addRadioUsage(long uSecNow) {
        double power = 0;
        final int BINS = BatteryStats.NUM_SIGNAL_STRENGTH_BINS;
        long signalTimeMs = 0;
        for (int i = 0; i < BINS; i++) {
            long strengthTimeMs = mStats.getPhoneSignalStrengthTime(i, uSecNow, mStatsType) / 1000;
            power += strengthTimeMs / 1000
                    * mPowerProfile.getAveragePower(PowerProfile.POWER_RADIO_ON, i);
            signalTimeMs += strengthTimeMs;
        }
        long scanningTimeMs = mStats.getPhoneSignalScanningTime(uSecNow, mStatsType) / 1000;
        power += scanningTimeMs / 1000 * mPowerProfile.getAveragePower(
                PowerProfile.POWER_RADIO_SCANNING);
        BatterySipper bs =
                addEntry(getString(R.string.power_cell), DrainType.CELL, signalTimeMs,
                R.drawable.ic_settings_cell_standby, power);
        if (signalTimeMs != 0) {
            bs.noCoveragePercent = mStats.getPhoneSignalStrengthTime(0, uSecNow, mStatsType)
                    / 1000 * 100.0 / signalTimeMs;
        }
    }

BatterySipperにどの用途でどれぐらいの電力消費があったかを計算して登録する処理を行なっている。
addEntryで登録を行なっているのでそこから逆に読んでいく。
addEntryの引数は

    private BatterySipper addEntry(String label, DrainType drainType, long time, int iconId,
            double power) {

とあるので、最後のpowerが消費電力。powerは+=で足し合わせて2パートからなっており、前半ではシグナル強度の5段階(BatteryStats.NUM_SIGNAL_STRENGTH_BINS)ごとの時間と消費電力の積を合算している。
後半ではスキャンの時間とそのときの電力消費の積を足している。今回の症状から電波を掴んでいる状態での電力消費ではなく、スキャンで浪費していると推測されるので問題は後半のPOWER_RADIO_SCANNINGだろう。
どの状態のときにどれぐらい電力消費があるかという情報はPowerProfileというクラスで定義されており、各ステートと電力消費の対応はxmlから読み込んでHashMapとして保持している。
xml自体ももちろん配布系に含まれており

./frameworks/base/core/res/res/xml/power_profile.xml

に実際の数字が書いてある。が、これは端末ごとに異なるはずなので各機種版もあり各モデル毎のバイナリを作るときに差し替えているはず(読んでない)。例えば

./device/htc/heroc/overlay/frameworks/base/core/res/res/xml/power_profile.xml

とかはディレクトリ名からHTC Hero用。特に各機種版のxmlを読むとradio.scanningとradio.onの消費電力がそれぞれ分かる。同じくHTC Heroから読むと

  <item name="radio.scanning">70</item>
  <array name="radio.on"> <!-- Strength 0 to BINS-1 -->
      <value>3</value>
      <value>3</value>
  </array>

とあり、スキャン中は70mAで掴んでいるときは強度に依らず3mAとある。機種ごとに多少の大小はあるものの、スキャン中はどれも70から88と非常に電力を消費するのに対してradio.onの状態ならば20分の1以下の小さい電力で済むようだ。
フィーチャーフォン時代にも圏外となる状態で電源を入れていると基地局を探すために電池の持ちが悪くなるという話はよく聞いたので、これはそれとも合致する。
以上から、mStats.getPhoneSignalScanningTimeが非常に大きな値を返しているのではないかと推測される。文字通りスキャンに時間を消費してしまって電力を浪費しているのだろう。
(続くかもしれない)