Attached is a platform source patch against android-cts-2.2_r2 that modifies the ringer volume control (side rocker) to include both vibrate & silent options, as was present in Eclair. Also attached is a smali patch against EC05's /system/framework/framework.odex that implements the fix in bytecode.
Finally, attached are testkey-recovery (e.g., ClockworkMod) flashable "update.zip"s, one containing a modified framework.jar for deodexed ROMs, the other containing a modified framework.odex for odexed (stock) ROMs. Both updates include this modification, as well as the TWS bug fix.
I've tested the odex update under stock (w/testkey) recovery, but not ClockworkMod as I currently don't use it, although I imagine it works fine. I've not tested the deodex update as I run an odexed ROM (edit: others report it works too). I have no reason to suspect it wouldn't work, but the usual caveat of keeping a copy of Odin+tar, ClockworkMod backup, etc., to restore should something unexpected happen applies.
Edit: If you're running a custom ROM with other framework.jar changes, it's probably best to wait for that ROM to incorporate the patch.
The Eclair ringer volume control (popup on press of the side volume rocker) contains "ringer vibrate" and "ringer silent" options. In Froyo, this was simplified to a single "ringer vibrate" or "ringer silent" option, depending on the Vibrate setting (Settings -> Sound -> Vibrate). While perhaps conceptually simpler, this has upset some folks who make frequent use of both options, as it requires going into the Settings and fumbling around with a control that really isn't that intuitive to begin with.
This modification doesn't quite restore Eclair behavior, rather it depends on the Vibrate setting. When Vibrate is set to "Always" or "Only in silent mode", both vibrate & silent options are available in the ringer volume control; otherwise, the default "Silent mode" option (Settings -> Sound -> Silent mode, power long-press, or notification panel widget) is still to vibrate. When Vibrate is set to "Never" or "Only when not in silent mode" then the default Froyo behavior is preserved: only silent is available in the ringer volume control, and "Silent mode" always silences (not vibrates) the ringer.
The modification is implemented this way as to be inoffensive to folks who dislike the vibrate option, and never under any circumstance would want their phone to accidentally vibrate. Similarly, it should be mostly inoffensive to folks who always prefer the vibrate option--the only way to activate the silent ringer is by the volume down rocker. So, as long as folks are careful to not accidentally press volume down "too many times", this modification is strictly beneficial (and thus, it's inclusion in ROMs shouldn't be of significant debate ).
With regard to the code: while Froyo simplified the vibrate/silent options in the UI, it's actually gotten a bit more complicated in code. With regard to the ringer, there's three state variables influence what happens. AudioService#mRingerMode is the main variable, with options RINGER_MODE_NORMAL, _VIBRATE, and _SILENT. AudioService::checkForRingerModeChange is the method that implements the volume rocker logic, and setting the ringer mode to one of the above three options is all that's necessary to achieve the desired effect.
The second influential variable is AudioService#mVibrateSetting which indirectly determines when the phone should vibrate. It's not to be queried directly however, rather AudioManager/AudioService::shouldVibrate considers both the Vibrate setting and the ringer mode. Specifically, RINGER_MODE_SILENT should override any instance of a ring or notification vibrate. This is why setting RINGER_MODE_SILENT is sufficient to silence the phone without also having to change the Vibrate setting. Even though the Froyo UI forces one to this, the Eclair behavior is, fortunately for this modification, still preserved in code.
Finally, the third variable, which is new in Froyo, is System.VIBRATE_IN_SILENT (via content provider). It's set by SoundSettings::setPhoneVibrateSettingValue as a function of the Vibrate setting ("Always", "Only in silent mode" are true; "Never", "Only when not in silent mode"" are false) and updated concurrently with it. Normally the ringer volume control queries this variable to determine which "silent" ringer mode to enter. With the modification, this variable is queried to determine if "ringer vibrate" should be presented as an option, and if so, allows for either mode. It's also used by other methods (e.g., Settings -> Sound -> Silent mode) to determine which ringer mode to enter, which isn't altered by the modification.
For the modification, I did consider always presenting "ringer vibrate" and "ringer silent" options, updating VIBRATE_IN_SILENT as necessary so that other activations of "Silent mode" would use the previous setting. But the SoundSettings activity actively updates the Vibrate setting whenever the two variables are in conflict, which would result in a bit of behavioral confusion. It's simpler, both conceputally and in code, to leave it alone.
Anyways, I figured it was worth documenting the behavor here since there's a bit more that goes on behind the scenes than the relatively-simple modification would suggest. I'm not sure which, if any of this changes in Gingerbread. But if the semantics were changed significantly, that might make a port of this modification a little less obvious.
Mirror link (does not require forum login):