[Epic 4G Android Development] mkasick:
3/21/11 Update: Testers have confirmed that this patch works. It has been included in Bonsai 4.0.0 and SyndicateROM Frozen 1.1.0.

Attached is a platform source patch against android-cts-2.2_r2 that, hopefully, should fix the infamous TWS bug. Also attached is a smali patch against EB13's /system/framework/framework.odex that implements the fix in bytecode. Unfortunately, since Samsung does not include all their base framework changes in their source release (e.g., BatteryStatsImpl is missing a bunch of WiMAX stuff), the source can't be patched directly. Finally, also attached is a deodexed EB13 /system/framework/framework.jar that should fix the TWS bug for folks running deodexed ROMs.

However, I need help: Fist, I can't confirm the patch actually fixes it since my phone rarely suffers TWS. Perhaps someone who reliably suffers from the bug can confirm this.

Second, since I still run the odexed stock ROM, and unfortunately can't change that today, I haven't tested the fixed framework.jar at all. Normally I'd at least test to make sure it doesn't cause my phone to blow up. Edit: I learned about the odex signature copy trick and was able to test the patch myself. It appears to have no adverse effects.

So, would someone who suffers TWS and running a deodexed ROM be able to try the fixed framework.jar? Please keep a copy Odin and the EB13 stock (or whatever) tar file on hand since, as far as I know, this will brick your phone. Also, it's a good idea to remove your SD card before testing if you have anything important on it.

Background:

The "Time without signal" (TWS) bug is a reporting problem that inflates the battery usage attributed to "Cell standby", and reports a "Time without signal" of approximately 50%. It's also noted for reporting a "Time on" value for "Cell standby" that's twice as long as the "since unplugged" time, and the phone uptime if the phone has not been charged since boot. A well-known workaround for this bug is to toggle Airplane mode.

Associated with this bug are anecdotal reports of decreased battery life when TWS is present. In researching this problem I've seen no basis for an actual decrease in battery life due to this specific bug. However, that does not preclude a separate "battery drain" issue that is, coincidentally, worked around using the same method (Airplane mode toggle).

Which means, in short, this patch is unlikely to have any affect on battery life. However, if it does resolve TWS, this may help determine if the "battery drain" issue truly exists.

Bug details:

To help determine radio battery consumption, Android matains a history of radio-reported cellular signal stength. The platform source (frameworks/base/core/java/android/os/BatteryStats.java) defines five bins that correspond to a range of signal strengths: NONE_OR_UNKNOWN, POOR, MODERATE, GOOD, and GREAT. For each of these stength bins, Android maintains a "stopwatch timer" (frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java) to count the number of milliseconds for which the radio reports a strength within that particular bin.

The fuelgauge package (packages/apps/Settings/src/com/android/settings/fuelgauge/PowerUsageSummary.java), used by the "Battery use" dialog, computes "Time on" (signalTimeMs) by summing the stopwatch bin times, and computes "Time without signal" (noCoveragePercent) as the "NONE_OR_UNKNOWN bin time" divided by signalTimeMs (total time of all bins). For this calculation to work, it requires that exactly one signal strength bin timer be active at any moment.

Knowing that the TWS bug often results in 50% "Time wihtout signal", and a "Time on" value that's double what it should be, it's likely that the underlying bug is due to having both the NONE_OR_UNKNOWN timer and one of the other (POOR, MODERATE, GOOD, and GREAT) timers active simultaneously. Indeed, this appears to be the case.

Two methods in the BatteryStatsImpl class (frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java) are responsible for starting & stopping the signal strength bin timers (mPhoneSignalStrengthsTimer array). The first is notePhoneStateLocked, which is called when the state (IN_SERVICE, OUT_OF_SERVICE, EMERGENCY_ONLY, and POWER_OFF) of the phone radio changes. The second is notePhoneSignalStrengthLocked, which is called when the radio reports a change in signal strength. Finally, the mPhoneSignalStrengthBin member variable specifies (by array index) which of the bin timers is currently active, and is initialized to -1 to indicate that no timer is active.

Here's the relevant snippets of these two methods:

public void notePhoneStateLocked(int state) {
int bin = mPhoneSignalStrengthBin;
boolean isAirplaneMode = state == ServiceState.STATE_POWER_OFF;
// Stop all timers
if (isAirplaneMode || state == ServiceState.STATE_OUT_OF_SERVICE) {
for (int i = 0; i < NUM_SIGNAL_STRENGTH_BINS; i++) {
while (mPhoneSignalStrengthsTimer[i].isRunningLocked()) {
mPhoneSignalStrengthsTimer[i].stopRunningLocked(this);
}
}
}
...
// If we're back in service or continuing in service, restart the old timer.
if (state == ServiceState.STATE_IN_SERVICE) {
if (bin == -1) bin = SIGNAL_STRENGTH_NONE_OR_UNKNOWN // BUG!;
if (!mPhoneSignalStrengthsTimer[bin].isRunningLocked()) {
mPhoneSignalStrengthsTimer[bin].startRunningLocked(this);
}
} else if (state == ServiceState.STATE_OUT_OF_SERVICE) {
mPhoneSignalStrengthBin = SIGNAL_STRENGTH_NONE_OR_UNKNOWN;
if (!mPhoneSignalStrengthsTimer[mPhoneSignalStrengthBin].isRunningLocked()) {
mPhoneSignalStrengthsTimer[mPhoneSignalStrengthBin].startRunningLocked(this);
}
...
}
mPhoneServiceState = state;
}

public void notePhoneSignalStrengthLocked(SignalStrength signalStrength) {
// Bin the strength.
int bin;
if (mPhoneServiceState == ServiceState.STATE_POWER_OFF
|| mPhoneServiceState == ServiceState.STATE_OUT_OF_SERVICE) {
// Ignore any signal strength changes when radio was turned off or out of service.
return;
}
if (!signalStrength.isGsm()) {
...
} else {
int asu = signalStrength.getGsmSignalStrength();
if (asu < 0 || asu >= 99) bin = SIGNAL_STRENGTH_NONE_OR_UNKNOWN;
else if (asu >= 16) bin = SIGNAL_STRENGTH_GREAT;
else if (asu >= 8) bin = SIGNAL_STRENGTH_GOOD;
else if (asu >= 4) bin = SIGNAL_STRENGTH_MODERATE;
else bin = SIGNAL_STRENGTH_POOR;
}
if (mPhoneSignalStrengthBin != bin) {
if (mPhoneSignalStrengthBin >= 0) {
mPhoneSignalStrengthsTimer[mPhoneSignalStrengthBin].stopRunningLocked(this);
}
mPhoneSignalStrengthBin = bin;
mPhoneSignalStrengthsTimer[bin].startRunningLocked(this);
}
}

For the most part, these two methods appear to manipulate the strengh timers appropraitely. However, if the condition on the line marked with the "BUG!" comment (which I added) is true, then the NONE_OR_UNKNOWN timer is started without updating mPhoneSignalStrengthBin. Thus, in a subsequent signal strength change, a second bin timer will be activated until the radio reports a strength of NONE_OR_UNKNOWN (where only its timer is active), or until the radio state changes to POWER_OFF (Airplane mode) or OUT_OF_SERVICE (where all timers are explicitly disabled).

Since the TWS bug appears to only affect some users, it must be triggered only in certain circumstances. Indeed, the circumstances required for it to trigger and remain active are:

notePhoneStateLocked must never be called with state OUT_OF_SERVICE.

notePhoneStateLocked must be called before notePhoneSignalStrengthLocked when the state is IN_SERVICE.

notePhoneSignalStrengthLocked must never be called with a strength of NONE_OR_UNKNOWN.

The second condition is likely to always be true, and the third condition will be true as long as cellular service is present. Thus, it's likely that phones that suffer from TWS never call notePhoneStateLocked except when switching to IN_STATE.

I suspect what's going on here is a race. Phones with TWS are in cells with relatively little activity, and thus while booting, are able to negotiate cellular service fast enough by the time the BatteryStats notifications are registered, the radios only switch from OUT_OF_SERVICE to IN_SERVICE. Whereas phones without TWS are in cells with enough other activity that, while booting, they take a long enough time to negotiate service that BatteryStats witnesses the both the changes from POWER_OFF to OUT_OF_SERVICE, and from OUT_OF_SERVICE to IN_SERVICE. Hence why the issue happens for some folks fairly regularly, and rarely if ever for others.

In addition to reporting 50% "Time without signal", this bug also results in an inflated "Cell standby" battery usage. This is due to the computing radio's battery usage by the product of the time attributed to each strengh bin, by the average battery power used at that strength. Unfortunately the Epic's power profile (/system/framework/framework-res.apk/res/xml/power_profile.xml) defines the average power used as the same for each bin, resulting in a doubling of "Cell standby" battery usage with the bug present, compared to what "Cell standby" should report. As far as I can tell, this shouldn't have any actual impact on either actual power consumption, or the "% battery left" level.

Mirror link (does not require forum login):

platform_tws_fix.diff

platform_tws_fix_smali.diff

tws_fix_ringer_vib_silent-EC05-deodex.zip

tws_fix_ringer_vib_silent-EC05-odex.zip