設定→端末情報→電池使用量がいかがなものか調べてみた

先日のAndroid関西支部「情報交換会」に参加してきました。
LTにも参加したんだけど、私は下っ端扱いだったので触れません。
きっとseitさんが書いてくれるんじゃないかな?w

さてさて、勉強会の中で、電源管理のお話がでました。
タスク切れば電源が持つってのは違うんだよ?
アプリいっぱい眠ってると電池が減る訳じゃないんだよ?
ってお話があって、

kobashinG「あれ?設定→端末情報→電池使用量ってアテにできないのかな?」

と疑問が湧いたので調べてみました。

設定アプリは

 $ANDROID-HEAD/package/apps/Settings

にありました。

まずはAndroid端末で問題の画面に行って、
画面上の表示を確認。

それをgrep

$ cd ./package/apps/Settings
$ find . -type f -print | xargs grep --color -n "電池使用量" /dev/null

結果からぽいのを探す。

./res/values-ja/strings.xml:864:    "電池使用量"

次はAndroidManifest.xmlからこれを使ってるActivityを探す。
面倒なのでgrepした。

./AndroidManifest.xml:766:                android:label="@string/power_usage_summary_title"

ほしい結果が得られたので、見に行く。

$ vim AndroidManifest.xml
765         <activity android:name=".fuelgauge.PowerUsageSummary"
766                 android:label="@string/power_usage_summary_title"
767                 android:clearTaskOnLaunch="true"
768                 >
769             <intent-filter>
770                 <action android:name="android.intent.action.MAIN" />
771                 <action android:name="android.intent.action.POWER_USAGE_SUMMARY" />
772                 <category android:name="android.intent.category.DEFAULT" />
773                 <category android:name="com.android.settings.SHORTCUT" />
774             </intent-filter>
775         </activity>

どうやら、PowerUsageSummaryってActivityの様子。
早速追っかける。

onCreate()から怪しいとこを読みあさって、
見つけたのがこの処理。

    private void processAppUsage() {
        SensorManager sensorManager = (SensorManager)getSystemService(Context.SENSOR_SERVICE);
        final int which = mStatsType;
        final int speedSteps = mPowerProfile.getNumSpeedSteps();
        final double[] powerCpuNormal = new double[speedSteps];
        final long[] cpuSpeedStepTimes = new long[speedSteps];
        for (int p = 0; p < speedSteps; p++) {
            powerCpuNormal[p] = mPowerProfile.getAveragePower(PowerProfile.POWER_CPU_ACTIVE, p);
        }
        final double averageCostPerByte = getAverageDataCost();
        long uSecTime = mStats.computeBatteryRealtime(SystemClock.elapsedRealtime() * 1000, which);
        mStatsPeriod = uSecTime;
        updateStatsPeriod(uSecTime);
        SparseArray<? extends Uid> uidStats = mStats.getUidStats();
        final int NU = uidStats.size();
        for (int iu = 0; iu < NU; iu++) {
            Uid u = uidStats.valueAt(iu);
            double power = 0;
            double highestDrain = 0;
            String packageWithHighestDrain = null;
            //mUsageList.add(new AppUsage(u.getUid(), new double[] {power}));
            Map<String, ? extends BatteryStats.Uid.Proc> processStats = u.getProcessStats();
            long cpuTime = 0;
            long cpuFgTime = 0;
            long gpsTime = 0;
            if (processStats.size() > 0) {
                // Process CPU time
                for (Map.Entry<String, ? extends BatteryStats.Uid.Proc> ent
                        : processStats.entrySet()) {
                    if (DEBUG) Log.i(TAG, "Process name = " + ent.getKey());
                    Uid.Proc ps = ent.getValue();
                    final long userTime = ps.getUserTime(which);
                    final long systemTime = ps.getSystemTime(which);
                    final long foregroundTime = ps.getForegroundTime(which);
                    cpuFgTime += foregroundTime * 10; // convert to millis
                    final long tmpCpuTime = (userTime + systemTime) * 10; // convert to millis
                    int totalTimeAtSpeeds = 0;
                    // Get the total first
                    for (int step = 0; step < speedSteps; step++) {
                        cpuSpeedStepTimes[step] = ps.getTimeAtCpuSpeedStep(step, which);
                        totalTimeAtSpeeds += cpuSpeedStepTimes[step];
                    }
                    if (totalTimeAtSpeeds == 0) totalTimeAtSpeeds = 1;
                    // Then compute the ratio of time spent at each speed
                    double processPower = 0;
                    for (int step = 0; step < speedSteps; step++) {
                        double ratio = (double) cpuSpeedStepTimes[step] / totalTimeAtSpeeds;
                        processPower += ratio * tmpCpuTime * powerCpuNormal[step];
                    }
                    cpuTime += tmpCpuTime;
                    power += processPower;
                    if (highestDrain < processPower) {
                        highestDrain = processPower;
                        packageWithHighestDrain = ent.getKey();
                    }

                }
                if (DEBUG) Log.i(TAG, "Max drain of " + highestDrain 
                        + " by " + packageWithHighestDrain);
            }
            if (cpuFgTime > cpuTime) {
                if (DEBUG && cpuFgTime > cpuTime + 10000) {
                    Log.i(TAG, "WARNING! Cputime is more than 10 seconds behind Foreground time");
                }
                cpuTime = cpuFgTime; // Statistics may not have been gathered yet.
            }
            power /= 1000;

            // Add cost of data traffic
            power += (u.getTcpBytesReceived(mStatsType) + u.getTcpBytesSent(mStatsType))
                    * averageCostPerByte;

            // Process Sensor usage
            Map<Integer, ? extends BatteryStats.Uid.Sensor> sensorStats = u.getSensorStats();
            for (Map.Entry<Integer, ? extends BatteryStats.Uid.Sensor> sensorEntry
                    : sensorStats.entrySet()) {
                Uid.Sensor sensor = sensorEntry.getValue();
                int sensorType = sensor.getHandle();
                BatteryStats.Timer timer = sensor.getSensorTime();
                long sensorTime = timer.getTotalTimeLocked(uSecTime, which) / 1000;
                double multiplier = 0;
                switch (sensorType) {
                    case Uid.Sensor.GPS:
                        multiplier = mPowerProfile.getAveragePower(PowerProfile.POWER_GPS_ON);
                        gpsTime = sensorTime;
                        break;
                    default:
                        android.hardware.Sensor sensorData =
                                sensorManager.getDefaultSensor(sensorType);
                        if (sensorData != null) {
                            multiplier = sensorData.getPower();
                            if (DEBUG) {
                                Log.i(TAG, "Got sensor " + sensorData.getName() + " with power = "
                                        + multiplier);
                            }
                        }
                }
                power += (multiplier * sensorTime) / 1000;
            }

            // Add the app to the list if it is consuming power
            if (power != 0) {
                BatterySipper app = new BatterySipper(packageWithHighestDrain, DrainType.APP, 0, u,
                        new double[] {power});
                app.cpuTime = cpuTime;
                app.gpsTime = gpsTime;
                app.cpuFgTime = cpuFgTime;
                mUsageList.add(app);
            }
            if (power > mMaxPower) mMaxPower = power;
            mTotalPower += power;
            if (DEBUG) Log.i(TAG, "Added power = " + power);
        }
    }

ちょっと長ったらしいんだけど、上から順に
・CPU使用時間
TCPのrecv/sendの量
・sensor使用時間
を計算してる。
計上されていくのは「power」って子。
そいつを最後にBatterySipperってのに入れてリスト化。

そいつはどこにいくのかっていうと、

    private void refreshStats() {
        ....省略
        processAppUsage();
        processMiscUsage();

        mAppListGroup.setOrderingAsAdded(false);

        Collections.sort(mUsageList);
        for (BatterySipper sipper : mUsageList) {
            if (sipper.getSortValue() < MIN_POWER_THRESHOLD) continue;
            final double percentOfTotal =  ((sipper.getSortValue() / mTotalPower) * 100);
            if (percentOfTotal < 1) continue;
            PowerGaugePreference pref = new PowerGaugePreference(this, 
    ....省略

        if (DEBUG) setTitle("Battery total uAh = " + ((mTotalPower * 1000) / 3600));

sipperっていうのが、BatterySipperのインスタンスで、
sipper.getSortValue()で突っ込んだ「power」が取得できる。

DEBUGで切られてるけど、mTotalPowerが総Ahとすると、

            final double percentOfTotal =  ((sipper.getSortValue() / mTotalPower) * 100);

ここでパーセント計算してるかな。

とすると、
結局「電池使用量」さんは

・CPU使用時間
TCPのrecv/sendの量
・sensor使用時間

をしっかり見てることになる。
ここを目安に、アプリのお行儀を調べるのもありじゃないかなー?