My stories using a Samsung Intercept (DI06, Virgin Mobile).

And an LG Optimus V.

December 28, 2010

I got a Samsung Intercept for use with Virgin Mobile USA (on their ridiculous $25/mo plan). It is the "DI06" version. It was sold at Best Buy on November 4, 2010 for about $250.

I'm surprised how useful the web browser is. I'm surprised how fun it is to write Android software. I'm disappointed that the phone app keeps getting slower the longer I own it. I'm disappointed that pressing the "call end button" has no immediate effect during a phonecall. I'm disappointed that there are numerous stupid bugs. I'm delighted to learn that Sprint is providing Virgin Mobile customers with second rate data ("EVDO") service, meaning they have no incentive to cancel my outrageously good deal.

I rooted it with rageagainstthecage. I made the root permanent by replacing /system/bin/playlogos1 with a script that makes a tmpfs and puts a setuid "su" in it. For some reason unknown to me the SuperUser.apk file is also needed.

I compiled and installed busybox. I rebuilt ConnectBot to provide pipe (|) and backtick (`) characters. I ported some of my own apps to it. I compiled and installed dropbear sshd, because my apps happen to use it to sync.

I did not overwrite the recovery image or the kernel because I do not want to brick my device. I did not fix any of the numerous bugs because I did not want to do the "deodex dance" to make it possible to change /system/apps or /system/frameworks.

However, the symptoms of the numerous bugs are becoming more painful to live with. Especially because the phone keeps getting slower. Especially the phone app itself. Probably because google makes it accumulate way too much historical information that it is then inefficient at using? Anyways, in the interests of improving performance and making it possible for me to fix bugs and make improvements at will, I am going to replace the Android parts with something that is not all odexed and proprietary, so I can replace individual components on my own timeline.

This means installing a recovery image so that I can have reasonably good odds of unbricking my device. Also because that appears to be the best way to install a new distribution.

So it looks like I can select a few components: a boot image installer (to flash recovery image/kernel), a recovery image, an android "ROM", and perhaps a kernel.

flash_image

Users are advised to use some shitty "SWUpdate" utility, but I don't run Windows or really want to deal with the shittiness of it all so I opt for a Linux flashing tool.

There is a flash_image floating around which came from "eclair moment" that is purported to work on the Intercept. It uses BML, while the publicly-available flash_image source code uses MTD. MTD is the standard Linux way of accessing flash. Samsung says they have some new kind of OneNAND flash memory for which MTD is inappropriate, and for that they provide BML. BML is a low-level interface. On top of BML they provide STL which is slightly less low-level but doesn't seem to give any information about the boot partitions, only the filesystem ones. RFS is the filesystem (an optimized journaling VFAT) that runs on top of STL. Anyways, I read a rumor it doesn't work for the recovery image unless you use it to flash the kernel first, which seems assinine and there is enough mud in the water that I simply do not trust it. Anyways, the real stake in its heart is that it doesn't provide a way to make a backup of the old image.

Recovery image

inxane's recovery - Seems to be the only option. Indicates that it has numerous infelicities but apparently it is sufficient to install a hacked update.zip-like file. Appears to be abandonware.

Android ROM

SlapStick Rom v1.4 - Says not to flash from 2.1 so disqualified. It appears to be just a theme, really (it does not seem to provide de-odexed system files, for example).

UbuntDroid 3.0 - I want to dwell for a moment on how stupid UbuntDroid is. And not because it is the worst "ROM" for the Samsung, but rather because it is relatively good. First, the name is simply stupid, because it has nothing at all to do with Ubuntu (it is not a Linux distribution). Second, it is nearly impossible to tell what it is. It is, in fact, a set of Android 2.1 (Eclair) .apk files that have been de-odexed (or never odexed to begin with), combined with a dalvikvm with substantially ?improved? JIT from Android 2.2 (Frozen Yogurt). The combination is commonly known as "Frozen Eclair." You would not be able to find this information in a clear form on their shitty website, even if you happened to stumble upon my post asking for clarification.

On the bright side, UbuntDroid appears to be relatively popular and well-tested, and in fact a de-odexed 2.1 with a faster dalvikvm is nearly exactly what I might ask for. Though in actuality, a mini dpkg-based distribution running parallel to Android 2.2 was what I actually did ask for. But I might still use UbuntDroid.

It is just unfortunate that the Android hacking community is nearly obscured by the mass of noobs who just want to change all of the colors and "make it fast."

p.s., if you think that UbuntDroid is about "the south american philosophy of humanity towards others," keep in mind that really it is in fact about shameless self-promotion.

Frozen Eclair - This is a distribution that appears to be just the new dalvikvm, so it needs your phone to be already de-odexed.

Main kernel

squealjoy v1.5 - audio works, but old?

squealjoy v2.2 - audio delays? but new... tethering

CherryKernel v1.2 - based on KuroOCedKernel, supposedly fixes audio bugs, adds tethering.

SlapKernel v1.1 - built from the Android 2.2 (froyo) repository, has tethering etc.

What next?

I'm getting the impression that the various "ROMs" (binary distributions) are crap of the crappiest variety and the only advantage to them is that they're slightly less likely to brick your phone than rolling your own.

So I propose this task list:

Here is what I can tell about the partitions on my phone:

The BML partitions can be read using dd, can they be written with dd too???

December 30, 2010

Endless research! The "secondary boot loader" supposedly talks Odin ("download mode"). There are utilities that talk Odin, but I'm not sure if they apply to the Intercept. I don't even know if the Intercept's sbl talks Odin.

I'm not sure if I care about Odin, since it is really only a "last ditch" kind of recovery option. However, if I use Odin today to make a dump of the flash then in theory I should always be able to go back to that state!

So I need to odin-dump the existing data before writing anything.

Okay, looking inside the bml4/bml8 images, I see that there is a mystery 2kB header then a zImage. It seems that inxane's recovery.rfs file doesn't contain the header, so it must be automatically generated by flash_image or something.

I'm getting the impression that if you use the built-in recovery image to install an update.zip then it will ultimately run flash_image to perform the work. Or something. So I should be able to get an authoritative flash_image by extracting it from the recovery image.

Someone wrote a script to extract an initramfs from a zImage. It doesn't work. It searches for a gzip header and unzips it to find the kernel, then within that image it searches for another gzip header and assumes the second gzip header is the initramfs. However, the way it is actually encoded appears to be that there are two gzip headers within the bml, rather than a second header within the kernel. There is an initramfs within the kernel image that the script finds, but it contains just /dev/console and /root, nothing of any interest. *shrug*

Here is the braindead script I wrote that successfully extracts both gzips (the first is the kernel image, the second is the initramfs):

#!/bin/sh

pos=`grep -a -b --only-matching $'\x1F\x8B\x08' $1 | cut -f 1 -d :`
x=1
for i in $pos
do
        dd if=$1 bs=$i skip=1 | gzip -d -c > part$x
        x=$((x+1))
done

Then, to unpack the cpio:

cpio -v -i --no-absolute-filenames < part2

The surprising thing I have just learned is that bml4 and bml8 are identical, and contain a recovery.rc. I wonder if this means that the difference between them is in the form of, i.e., a "init=/recovery.rc" on the kernel command line, provided by the sbl?

Oh, and I found that the phone has a /sbin/redbend_ua, which must be the authoritative BML flasher for this phone. Unfortunately redbend_ua uses raw device names instead of symbolic "recovery" and "boot" names, so in order to replace the recovery image I need to be certain I truly know which bml is the recovery partition.

Also note that this phone doesn't seem to have a "PIT" partition (on other Samsung phones, this partition specifies names for the other partitions), which may throw Heimdall for a loop.

Partition map from disassembled flash_image

I took the flash_image that I have from "eclair moment", and I disassembled it a little. I found the assembly for mtd_find_partition_by_name() which is exactly as it is for the mtd-based flash_image. It uses a table filled in by mtd_scan_partitions(), which is also similar to mtd-based flash_image. However, it also contains this code:

	sscanf(...);
	switch(x) {
		case 0: y="boot1";
		case 1: y="boot2";
		case 2: y="boot3";
		case 3: y="boot";
		case 4: y="system";
		case 5: y="data";
		case 6: y="cache";
		case 7: y="recovery";
		case 8: y="modem";
		case 9: y="efs";
		default: ...?
	}

I think x is the "id" field in /proc/rfs/bmlinfo, and y is probably mtdname. Anyways, it strongly suggests that bml4 is "boot" and bml8 is "recovery", as suspected.

And a promising first start at thorough reverse-engineering, proving I'm oriented.

January 1, 2011

To boot into recovery mode: vol dn, talk, end. To boot into download mode: vol dn, end, camera.

I had to use flash_image after all, because the stock redbend_ua on the intercept doesn't support the "restore" command. I don't see why Samsung developed twenty phones, each with totally different subsystems, instead of just one. *sigh*

inxane's recovery "works well enough." "Go to Console" option doesn't work, but it starts adbd as root so I can "adb shell" into the recovery console and that's enough power for me. It seems to have all of the functionality necessary to let me trash my /system with impunity.

So, now I'm deodexing. It is shocking how extensive the anti-documentarian trend among the kiddies is. Here is a useful script, it will unpack a .odex file and its accompanying .jar/.apk file, and then re-pack them together.

#!/bin/sh
# this will add the .odex file's contents to either a .apk or .jar

# additional args to baksmali are passed in $2, make sure to quote them, as in:
#    $ apk-deodex BluetoothOPP.odex "-c :javax.obex.jar"

ODEX=$1
BASE=`echo $1 | sed -e 's/.odex//'`
APK="$BASE.apk"
JAR="$BASE.jar"
UNCOMP=
# uncomment if you want uncompressed .jar/.apk files:
#UNCOMP=0

if [ ! -e $ODEX ]
then echo 'XXX: no odex'; exit -1; fi

if [ ! -e $APK ]
then
# an .apk is the same thing as a .jar, so just assume we are adding to the
# existing one as if it was an .apk.
        APK=$JAR
fi
if [ ! -e $APK ]
then echo 'XXX: no apk/jar'; exit -1; fi

echo '******************************************************'
echo $APK plus-equals $ODEX

rm -rf out classes.dex x
echo '   baksmali'
if ! baksmali -d ../framework $2 -x $1
then echo 'XXX: baksmali fail'; exit -1; fi
echo '   smali'
if ! smali -o classes.dex out
then echo 'XXX: smali fail'; exit -1; fi
mkdir x
cd x
echo '   unzip apk'
if ! unzip -q ../$APK
then echo 'XXX: unzip fail'; exit -1; fi
mv ../classes.dex ./
echo '   zip apk'
rm -f ../$APK
if ! zip -qr$UNCOMP ../$APK .
then echo 'XXX: zip fail'; exit -1; fi
cd ..
rm -rf out x $ODEX

The UbuntDroid kiddies used "-0" for jar/zip commands to make uncompressed files. It seems like it might load faster, especially since NAND is not really much of a bottleneck (I think). But I'm just not that kind of guy...if I'm using .zip files as my containers, they'll be compressed, right? Uncomment the UNCOMP=0 if you want that though..

The main mass of them converted uneventfully. Here are the ones (in /system/app) where I had to manually specify "bootclasspath" members:

apk-deodex BluetoothOPP.odex "-c :javax.obex.jar"
apk-deodex Contacts.odex "-c :twframework.jar"
apk-deodex TouchWizCalendar.odex "-c :twframework.jar:com.google.android.maps.jar"

I just want to say, this is fantastic. The more I hack on it, the more I love it. With inxane's sketchy recovery installed, I am able to get to a root shell regardless of how badly I break /system. But beyond that, if I break the java part of /system (i.e., in the process of discovering the above de-odexing process), it still starts adbd! So I can connect and inspect (logcat) and molest the system, even though all of the Java failed completely. Which is just fantastic, and makes most failure modes eminently diagnosable.

Anyways, I got it successfully de-odexed. Now onto dalvikvm.

Okay, I got lazy and for dalvikvm I just used the ones from UbuntDroid 3. I just copied over the following files:

bin/dalvikvm
bin/dexopt
bin/logcat
lib/libdl.so
lib/libcutils.so
lib/libz.so
lib/libm.so
lib/libnativehelper.so
lib/libdvm.so
lib/liblog.so
build.prop

On the next boot logcat is full of:

W/dalvikvm( 3413): DexOpt: stale opt version (0x30 33 35 00)
D/installd( 1892): DexInv: --- BEGIN '/system/framework/twframework.jar' ---
D/dalvikvm( 3431): DexOpt: load 101ms, verify 250ms, opt 6ms
D/installd( 1892): DexInv: --- END '/system/framework/twframework.jar' (success)

Which indicates that dalvikvm doesn't mind the fact that I failed to flush the caches, in this instance it is smarter than that.

I'm not sure it was a substantial improvement. The things that seemed slow before still seem slow. But the advantage is that now it's de-odexed, so I can replace individual parts at will.

It seems like high on my priority list is fixing the bug that causes the notifications tray to become inaccessible once the power button has been held down (bringing up the menu that lets you power off). Also there is fixing the dialer, which is super mega unfortunate because really I just want it to be "less slow." Maybe I will just write my own from scratch? I pretty well hate the one it comes with, but mostly because it is just AWOL so often, which sounds like the sort of thing that might be hard to really rectify. But I could just make it so that even if it is slow, it will always respond predictably to input. That's really all it needs. *shrug*.

Really it would simply be sufficient if the "end call" button could be pressed twice in rapid succession to both end the call and lock the phone, rather than being a total clusterfucking mystery.

Also I can implement a less stupid lock screen.

And I can figure out how to make it stop beeping to notify me every time I cross out of cell range.

January 2, 2011

Okay, first stupid nuissance bug traced. I didn't want to be awakened so I turned off the ringer, so when the battery was done charging it vibrated instead. Which is even worse because I haven't found a way to turn that off!

This bug is in the framework/services.jar:com.android.server.status.StatusBarPolicy. There are several bugs here. One is that showFullChargePopup() is called, which is just stupid. Another is that show*Popup() in here calls playTone(), which is just stupid, why would the charging state ever want to interrupt my attention? And the other is that playTone() falls back to vibrate if the tones are disabled. That is stupid, it should only do that if vibrate happens to be enabled.

January 3, 2011

The bug carries on! It is also in framework/android.policy:com.android.internal.policy.impl.LockPatternKeyguardView! What a pain in the moronic ass. Exact same bugs but with different names. Inserted a return-void at the top of batteryRinger() and clipped the call to playSound from onBatteryStatusInform().

I really want to highlight how stupid the Samsungers are. No one ever wants a phone to beep when the battery is empty. But that is popular and Samsung is oriental so of course they have to fucking copy everything that everyone else does. But they don't have to fucking pretend to fucking innovate like stupids. They added a beep which occurs when the battery is full! Because you are so fucking desperate for your phone to finish charging that you are chilling out right beside it waiting for it to charge?!!! What the fuck hell? No, you fucking plug it in when you GO TO BED, and MAGICALLY IT WORKS IN THE MORNING. In the interim you were asleep! That's WHERE THE MAGIC COMES FROM!

And then they took it a step further. If you think ahead and turn off its ringer, it changes it into a vibrate! Nevermind that I didn't turn on vibrate mode! Grr! As if this is so important that I'd want to know about it even when the phone is supposed to be silent! Grr!

This tells me the pattern keyguard lock is also all sorts of crapulence, since it has to override a lot of these statusbar notification kind of things. Which it just shouldn't touch, but it is torn between being locking (security) and keyguard, so it does all sorts of crazy ridiculous hacks all the time and is probably home to a lot of my dissatisfactions. I'll make sure to replace it once I get rid of all of the obnoxious noises (I just wanted a keyguard, and I'm only not using the default one because sliding up is one of the actions that occurs in my pocket).

It also tells me that looking for playSound() calls was appropriate, it was just wrong to assume that the keyguard has nothing to do with monitoring the battery!

Anyways, next nuissance tone: the beep when it gains/loses CDMA. TelephoneRegistry appears to broadcast a SERVICE_STATE action, which is caught by com.android.phone.PhoneApp.PhoneAppBroadcastReceiver() which passes it to PhoneApp.handleServiceStateChanged(), which makes a PhoneApp.PhoneAppSignalInfoTonePlayer thread, which calls into android.media.ToneGenerator.startSound(). I think I will simply remove the creation of PhoneAppSignalInfoTonePlayer.

Yay! Victory!

So I still need to fix the effect of holding the call end button on the statusbar, and I need to make a new locker.

Not so quick

I'm suffering from a lack of testing, and I have discovered that the reason UbuntDroid compressed everything with -0 (no compression) is that some files cannot be loaded from a compressed archive, so merely my de-odexing broke things. I'm going to assume that I just need to have the things in res/raw/ uncompressed, which means I'm going to add them to the existing archive individually, using commands like

zip -0r ../Phone.apk res/raw/

Global Actions

In /system/framework/android.policy.jar there is a com.android.internal.policy.impl.GlobalActions, which is responsible for the menu that shows up when you hold the call end button down for a while. As it is now, once that menu is displayed, the status bar/notifications view is AWOL until the phone is rebooted. Here is the relevant source (from FroYo??):

    public void showDialog(boolean keyguardShowing, boolean isDeviceProvisioned) {
        ....
        mStatusBar.disable(StatusBarManager.DISABLE_EXPAND);
        ....
    }
    public void onDismiss(DialogInterface dialog) {
        mStatusBar.disable(StatusBarManager.DISABLE_NONE);
    }

In other words, it disables it while the dialog is showing. But the smali file from the phone only calls disable() the one time. It does have an onDismiss() but it contains all sorts of complicated jibber-jabber. So I am just going to insert the appropriate call into that jibber-jabber...

Success!

Glory

I just want to remark how awesome this is. For years now I have bought cheap Korean and Taiwanese cellphones and each one has had a small selection of unsolvable bugs, and it has pissed me off a ton. Because they are great little phones but the closed nature means you'll deal with the bugs until you destroy the phone.

To terminate the buggy activity, my repertoire includes dropping the phone from a roller coaster and taking a hammer to it. These approaches have been 100% successful but unsatisfying. Especially because I would then buy a new phone which would have a different set of stupid niggling bugs.

Well now I've learned a new dance. Instead of using a hammer, I fix the phone using basic open hacking tools!! I've now patched the top 3 annoying bugs with this phone. There are two remaining: the keyguard is shit, and the "call end" hard button should work even when the phone is next to my face. And they are small enough it may never happen.

Oh and that holy grail of pipe dreams, vibrate before ring.

January 12, 2011

I think I'm set out to concretely fix the keyguard issues. And it's basically all about when I'm in a call. If I hit the call end button, the phone will do one of two things:

The latter is completely un-fucking-acceptable. I'm not going to hit hard buttons while the phone is next to my face, so it shouldn't disregard them just because its proximity sensor is going off. The former is also completely un-fucking-acceptable. Whatever it does, it must be possible to put the phone in a pocket-ready state with a single button push. But the status quo waits 2 seconds and then puts it in a state where the keylock is off!

New Android from git seems to have some concept of "INCALL_POWER_BUTTON_BEHAVIOR", which allows the user to specify whether it hangs up the call or turns on the screen-lock or ?both?. But even it is explicitly just for when the screen is already on, which is probably not when the phone is proximate to the head?

It looks like the end call button is processed in com.android.internal.policy.impl.PhoneWindowManager.interceptKeyTq(), which receives a RawInputEvent with type=RawInputEvent.EV_KEY, value=(button down), and keycode=KeyEvent.KEYCODE_ENDCALL or KEYCODE_POWER (I'm not sure which). It looks like it winds up in TelephonyService.endCall(), which returns true if it did anything(?).

Here is the contentious part:

// power button should turn off screen in addition to hanging up the phone
if ((handled && code != KeyEvent.KEYCODE_POWER) || !screenIsOn) {
    mShouldTurnOffOnKeyUp = false;
} else {
    // only try to turn off the screen if we didn't already hang up
    mShouldTurnOffOnKeyUp = true;
    mHandler.postDelayed(mPowerLongPress,
            ViewConfiguration.getGlobalActionKeyTimeout());
    result &= ~ACTION_PASS_TO_USER;
}

As I understand it, if the user pressed KEYCODE_ENDCALL instead of KEYCODE_POWER, or if the screen was off at the time, then it won't turn off the screen when the user does a key-up. In practice, though, I also find that it doesn't hang up if the user presses the button with the screen off (which must be implemented in either custom Samsung code or TelephonyService.endCall().

It looks like that's because END_BUTTON_BEHAVIOR must not be ENDCALL_SLEEPS.

Okay, debug output! mEndcallBehavior=2=ENDCALL_SLEEPS. However, mShouldTurnOffOnKeyUp is always false, and that is probably the first problem. Somewhat to my disappointment, interceptKeyTq() never gets called at all when the proximity sensor is on! SUPER LAME!

Also, keycode=6=KEYCODE_ENDCALL. And that's why mShouldTurnOffOnKeyUp is false, because it's not KEYCODE_POWER.

Hmmm, I just hacked it to set mShouldTurnOffOnKeyUp on both sides of its if (just changed a const 0 to a const 1), and it does successfully set it. But it still doesn't sleep. It looks like it properly sets result |= ACTION_GO_TO_SLEEP, but it surely doesn't sleep.

It looks like interceptKeyTq() is called from services.jar:com.android.server.WindowManagerService$KeyQ.preprocessEvent(), which looks a lot like it would call PowerManager.goToSleep(). Which I think winds up in services.jar:com.android.server.PowerManagerService. Might as well just add debug code there??

XXX - mShould is getting stuck on
XXX - goToSleepLocked() has no effect even though time > lastEventTime

January 13, 2011

Oh, the first trouble is that Samsung drastically (?) restructured their interceptKeyTq(). So a lot of what I thought I knew was wrong.

In particular, I missed the fact that it was branching around my Log.d() in the !screenIsOn case! So maybe it does get called even when the proximity sense is true.

One of the reasons this restructuring looks drastic is something unusual the compiler does (either javac or the class->dex converter) for if statements. For "if (x) { y } else { z } ..." most compilers I've worked with much would generate:

 if-eqz x, goto elselab
 y
 goto donelab
 :elselab
 z
 :donelab
 ...

But I am seeing:

 if-eqz x, goto elselab
 y
 :donelab
 ...
 :elselab
 z
 goto donelab

Which is fine, but did you notice the "..." moved?! The entire rest of the function has now been inserted in between y and z. So the smali code occurs in a radically different order than the java code. This is the sort of thing you may not notice if you are just reading the smali code, but if you ever hold it side-by-side with the java code, you start to get seriously spooked after a bit.

This seems sort of like a rational thing to do if you want to optimize for the true condition. The "traditional" approach has 1 branch for true and 1 branch for false, but this novel approach has 0 branches for true and 2 branches for false. But that's a hell of an assumption. Especially because in every other way this code shows no signs of even reasonable peepholing. There is a ton of unnecessary copying that goes on. And it's not just because most of the instructions can only access the first 16 regs. Sometimes they copy from v4 to v0 for no reason, even if the value is never used again (v4 is not live). So why this radical assumption about if() statements?

Almost definitely this is the side effect of some innocent convenience in a tree walk in the compiler, and is not "intentional" at all, just provably correct and thus dismissably boring.

Anyways, it wouldn't sleep in the case where I wanted it to. goToSleep() was being called and it was definitely getting into the meat of goToSleepLocked(), but it wasn't sleeping. I found these in the log that might be relevant:

D/PhoneApp( 1991): setScreenTimeout(MEDIUM)...
D/PhoneApp( 1991): PhoneAPP :: Update Wake Call : true
D/PhoneApp( 1991): requestWakeState(FULL)...
...
D/KeyguardViewMediator( 1928): doKeyguard: not showing because externally disabl
ed
...
D/CallNotifier( 1991): - still showing in-call screen; not releasing wake locks.

But it was sleeping when I didn't want it to -- it was setting mShouldTurnOff... even when I was pressing the button to wake it from sleep. Anyways, some bug fixing (change it to mShouldTurnOff=false if keyguardActive), and PhoneWindowManager now works ideally. It even calls endCall() and goToSleep() if I press the end while the phone is against my cheek!

Of course, endCall() and goToSleep() both don't work in the interesting cases.

The clue to why endCall isn't working is:

01-13 19:39:30.721 D/interceptKeyTq( 1929): calling endCall()
01-13 19:39:30.726 D/PhoneInterfaceManager( 2131): [PhoneIntfMgr] CMD_END_CALL: no call to hang up

Which is emitted whenever keyguardActive and screenIsOn are both false (i.e., in a call with the screen off because of proximity) and I hit the end call button.

PhoneInterfaceManager appears to work like in mainstream Android, endCall() just calls sendRequest(CMD_END_CALL, null), which passes the message to "MainThread", which I guess executes it in PhoneInterfaceManager$MainThreadHandler.handleMessage(CMD_END_CALL). However, that is where they diverge. It appears, in fact, that the only change is they wrapped the meat of case CMD_END_CALL in a:

if (mApp.isScreenOn()) {
  ...
}

Leaving just the log() call that I am seeing that outputs "CMD_END_CALL: no call to hang up" whenever I call endCall() while the thing is close to my cheek.

Seems like an easy fix!

And it seems to work!

Now why does goToSleep() fail? It looks like this code is responsible:

boolean showingDisconnectedConnection =
        PhoneUtils.hasDisconnectedConnections(phone) && isShowingCallScreen;
boolean keepScreenOn = isRinging || isDialing || showingDisconnectedConnection;
...
requestWakeState(keepScreenOn ? WakeState.FULL : WakeState.SLEEP);

Which seems pretty dumb to me. I explicitly hate the disconnected-connection screen, as if my attention is needed to let me know that my call has just ended but giving me the knowledge on that screen isn't worth leaving the screen up for more than a couple seconds (far fewer than 10).

But I'm not sure that tells me why the keyguard doesn't activate? I think that requestWakeState() call is only responsible for cancelling the screen power down that was in process. Maybe if the screen is allowed to reach fully powered down, the keyguard will activate on its own? From looking at PowerManagerService.java, it looks like that may be the case. It sets mStillNeedSleepNotification in goToSleepLocked(), and then tests it in setPowerState() to determine if it should alert the locker.

Of course it looks like they made a few changes in updateWakeState(). But it looks like mostly how it implements keepScreenOn, not how it calculates it. So I will just change it to require more than just showingDisconnectedConnection to keepScreenOn.

Well, it seems to fail to activate the keyguard, symptom being that the next time it turns on, it turns off immediately because mShouldTurnOff is set to true because keyguardActive is false in interceptKeyTq(). You know, this shows up in the log after the hang-up that turns off the screen:

01-14 02:44:37.139 D/KeyguardViewMediator( 1927): doKeyguard: not showing because externally disabled

And it's a race case of some sort -- if I hold down the hang up button for slightly longer (or something) then it "works", except that it takes it a moment to visually get rid of the "showing disconnected connection" screen. But it goes straight from there to keyguard.

PhoneApp.disableKeyguard()/reenableKeyguard() seems to be involved. It looks like reenableKeyguard() is called from InCallScreen.onStop(), which doesn't happen until after some lame stupid delay that is maximum dumb. However, I observe that updateWakeState seems to always be called in this condition, and seems to be called relatively early...at any rate, it is called well before that "doKeyguard: not showing ..." message. So if it is called and showingDisconnectedConnection is true then we can probably just call reenableKeyguard() from there!

Indeed, that seems to work. Yay!

Mission accomplished! I can now hang up my phone and slip it into my pocket with a single button push.

Okay, here is an infelicity. The call end button doesn't turn off the screen when the keyguard is active, but really it should only refrain from turning off the screen if the keyguard is displayed AND the screen is off. If the screen is already on, it should still turn off the screen regardless of keyguard. So instead of checking for keyguardActive to disable mShouldTurnOff, it now checks for keyguardActive && !screenIsOn. That way it will still mShouldTurnOff if the keyguard is inactive and the screen is off (i.e., in a call touching my cheek).

Hmmm, but now it doesn't seem to sleep if I hang up while it is against my cheek. It looks like it is getting into goToSleep() fine and it has no reason it wouldn't go all the way through the meat of goToSleepLocked(). Perhaps that meat winds up being a no-op if the screen is already off? I think so long as I am in the process of pulling the phone away from my head as I hit hangup, it should be fine. *sigh* Something to chew on.

January 15, 2011

I traced the steps that happen when you hang up while it is pressed against your cheek, then (much) later removed from your cheek. I'm making a few assumptions about how the proximity sensor ultimately interacts with everything but:

That is "good enough" in my opinion.

However, there is another scenario. If you press the button as you are pulling it away from your face, then it turns the screen back on before it gets back to the phonebook screen (while it is still at the call disconnected screen), in which case it totally forgets about turning off and remains screen-active at the phonebook screen indefinitely!

You know, really, the trouble is that the mStillNeedSleepNotification check only occurs when the screen turns off (which doesn't happen if it's already off) or the screen turns on by user wake key. If it is also triggered when the screen turns on by proximity event, then that would be sufficient. What would also be sufficient would be probably to explicitly send the sleep notification instantaneously rather than waiting for the screen to turn off (or alternatively, only doing it instantaneously if the screen is already off).

The more I think of it, the more I hate the concept represented by mStill. I don't mind if the phone flickers to the keyguard screen for a moment while it powers down the screen. That is actually reassuring to me. So I'm just going to disable it entirely, and send out the notification immediately where it is set.

Which of course doesn't work, because it doesn't give PhoneApp.updateWakeState() a chance to run, which is what I made responsible for calling reenableKeyguard(). However, that seemed like a pretty arbitrary place to call reenableKeyguard().

Considering better places for the call to reenableKeyguard(), I run into the problem that there is no way to guarantee that the button down processing (which initiates the hang-up) will complete before the button up processing begins! So re-enabling the keyguard must happen from within the same thread as the call to goToSleep(), during button up processing. Which presents some problems because reenableKeyguard() is in PhoneApp, so I'd have to do some sort of egregious hack to somehow achieve the same effect in the system thread.

Another totally different approach is that I could add a flag to the getPhoneInterface().endCall() or similar that tells it to turn off when it's done processing it. Which may really be the thing...

Let's first see how these KeyguardLock things are implemented. It looks like they go through a little dance that ultimately sets boolean mExternallyEnabled in the KeyguardViewMediator class. KeyguardViewMediator.doKeyguard() is called in the system thread, which is ultimately where mExternallyEnabled is checked. So it seems like maybe I could just explicitly set mExternallyEnabled=true in this case?

I think that is sound UI policy. If the user presses the sleep button, it should engage the keyguard no matter what. It's only in the case where inactivity is the trigger where we would want an individual app to be able to disable the keyguard (for example if the app suspects the user is really going to come back to the thing and won't appreciate futzing with the keyguard, because of a certain interaction model).

It took some tail-chasing but PhoneWindowManager (which is the class for interceptKeyTq()) is the instantiator of KeyguardViewMediator, and in fact is itself the main entry point to setKeyguardEnabled(), or something! So should be easy.

Yay! Now to see how well it holds up to usage scenarios.

Bragging rights! List of resolved flaws in Samsung code:

XXX - remove /system/app/Vending.apk, replace /system/app/Maps.apk

January 22, 2011

There, another Android app written (all the time was spent trying to construct the right "tick" for my metronome). I really enjoy developing for it. I have a core of around 100 lines of code that I have used in two projects so far (cut-and-paste style, as you expect for OOP, where real code reuse is a pseudo-myth). It establishes a separate thread running in C with reasonably real-time access to sound (reading or writing) and video (a 32bpp bitmap which is copied to the screen quickly with a single call into Java crap). Also this C thread can trivially read Java variables written ("volatile") in the other thread!

I kind of hate that it is cut-and-paste, but I am comparing it to all of the other cut-and-pasting I have done for this exact problem. I have worked with ALSA, OSS, Enlightenmend Sound Daemon, GStreamer, and CoreAudio. Android is tied with ESD, GStreamer, and ESD for being not-exceedingly-verbose:

bufsz = AudioTrack.getMinBufferSize(
		44100, AudioFormat.CHANNEL_OUT_MONO,
		AudioFormat.ENCODING_PCM_16BIT);
player = new AudioTrack(AudioManager.STREAM_MUSIC,
		44100, AudioFormat.CHANNEL_OUT_MONO,
		AudioFormat.ENCODING_PCM_16BIT,
		bufsz, AudioTrack.MODE_STREAM);
if ((player.getAudioFormat() !=
			AudioFormat.ENCODING_PCM_16BIT) ||
    (player.getChannelCount() != 1)) {
	System.out.println("got funny format/channels back from AudioTrack!");
	sound_stop();
	return false;
}
sampfreq = player.getPlaybackRate();

That's everything, including error checking. And then writing to it is as simple as:

num_write = (*env)->CallIntMethod(env, player, mid_at_write, writebuf, 0, bufsz);

That's as good as it gets, folks. As features go, it beats everything by leaps and bounds except CoreAudio (which is way too complicated) and GStreamer (which inexplicably squiks me out despite being totally awesome). It certainly is well-geared towards easily playing arbitrary-format media files (mp3, etc.) without any effort at all.

It's a little more code to write real-time generated bitmaps to the display, and it was a little bit more of a dance to figure out to make it interact well with being multi-threaded. But once I figured out how to make it go, the dance seemed sensible and perhaps even necessary. They certainly bent over backwards a little to make sure it was possible.

One thing weird about my phone showed up. I noticed that my bitmap appeared to be missing pixels, such as perhaps every fourth column or something (as if it had been downsampled without antialiasing). The bitmap reports as being 320 pixels wide. Wikipedia says the Samsung Intercept is 400x240. Does Android really not even know the actual physical dimensions of the screen, is that entirely hidden from it? It seems especially stupid that Samsung might pull this kind of crap given that Android strongly encourages people to use "dp" specifically to avoid any need for vendors to all pretend to have the same resolution screens. *sigh*

Of course, for all I know, I'm using the API wrong.

Anyways, one other flaw to consider. I think because of WPA passwords (poorly configured wpa_supplicant??), it fails to connect to a lot of wifi automatically. So I have to turn off wifi, and then turn it back on, and then it will connect. It'd be slick if I could fix the underlying networking bug. It'd probably be sufficient if I could just make it easier to restart WiFi. But I think at best I can improve it from three clicks to two, and that would just not be that huge an improvement in practice. So I guess if I want to actually be effective at it, I have to figure out why wpa_supplicant is flaking out. I don't think I even get that stuff to work "right" on my laptop, so probably it's just known-not-to-work-well.

At least, since my home wifi has no password (?), it always connects here automatically.

November 14, 2011

A few weeks ago the Samsung's "return to home screen" virtual button stopped working at all (it thought every push was a long-push), which was infuriating, so I slammed it against the arm of my chair until it flew upon the ground and tossed its battery. My god that phone was awful. It stopped ringing a few weeks later: mission accomplished.

A new phone: LG Optimus V!!!!!!!!!! Oh my god!!!

To me, a phone must be able to place and answer calls without anything insanely infuriating happening. The phone app and the screen lock on the Samsung were both written by idiots at Samsung. They did a very poor job. Answering calls was dodgy at best, hanging up on calls was dodgy, placing calls was a fucking shitstorm. Everything took a long time to respond and also was non-deterministic... I cannot say enough negative things about the Samsung Intercept. Those guys are bozos.

So, to repeat a story you'll read over and over on the virginmobileusa.com reviews of the thing, the LG Optimus V is a thousand times better than the Samsung Intercept. My expectations are so low at this point that I cannot say anything smarter than that, my jaw is dropped.

To answer the phone, the unlock _works_, and then you are talking to the person (!!!) and then when you're done you just double-click the screenlock and it hangs up and turns black and I put it in my pocket. !!! I don't even have to look at the screen except to slide my finger across it once. !!!! !!!!! It doesn't pretend to lock and then unlock itself while it is sitting on the table.

And everything I ask it to do, though it is modest stuff, it does immediately. I never have to guess "have I hit the damn button enough times or do I have to hit it another damn time".

It basically just works. I feel like a rockstar to have a phone this cool.

So let's actually talk about the LG. I bought it, I installed my custom apks on it by just putting them on my website and typing the URL into the browser on the phone. Two of my programs (due to my laziness) uses sshd running on the phone to sync. So I had to root it, install busybox, install dropbear sshd (which I had to patch because it wanted openpty() to be able to use /dev/ptmx, and the kernel on the LG does not have support for /dev/pty), and that was that. I didn't have any incentive to figure out how to install a custom rom just to overcome severe stupidness on the part of Samsung developers. The rooting scene is so well-developed these days that I didn't even have to do any of that manually. Even busybox binaries were in apk form.

I'm not sure if I've ever had better support from a vendor before. Open and easy to hack is good even if they do coat it with a veneer of closedness.

Not that I care to speculate on this device's ramifications for privacy.

But the voice recognition is useless. Sometimes it rocks and you can't believe it, but most of the time it's absolutely useless. But I didn't expect the voice recognition to be useful. I just wanted to see what Google had offered up to compete with Siri.

We'll see about the LG. But basically it's everything the Samsung promised to be but wasn't. Which is great. Oh and only $130. Crazy! Half the price of the Samsung and only a year later.

November 26, 2011

So I've had the LG Optimus V for about two weeks now, and I can say some things about it.

I don't use facebook or any other constant-update app and as a result I get pretty good battery life -- it goes 3 days easily. After 2 days, it can still play mp3s for 3 hours without getting into red battery. In fact, I don't think I've seen the battery indicator below 1/3rd (visually speaking, of course).

Speaking of mp3s, it plays mp3s. A lot of little speakers, like in my older laptops, will play back only half of an mp3 (the high-frequency half). A pretty odd thing for me to say, but true: this phone has great bass.

It did crash once on me. I had just installed a crossword app and then turned the screenlock on. Then when I tried to unlock it later, it didn't respond to any button presses. The Samsung would do this pretty frequently and some variation between waiting and insistently pressing the button over and over would usually wake it up. But I was impatient so I just yanked the battery. Speaking of which, it doesn't seem like it takes forever to boot up like the Samsung did.

After rebooting, it still had root.

Shortyz is a great crossword app, btw.

I like the phone well enough that I'm porting my "soft84" (HP48 evolution) calculator to it.

December 9, 2011

In a span of about fifteen minutes, a guy shot a police officer and then walked away and shot himself. This senseless event happened on the Virginia Tech campus so it was followed by another senseless event: for four hours the university ordered tens of thousands of students, faculty, and staff to cower in basements.

Indiana University has a system like this now, so (in an unrelated incident) we received a robocall at 4am to notify us that there had been shots fired two miles from our off-campus house.

How can we avert such catastrophic wastes of resources? Well, one way would be for everyone to take a chill pill and accept that any university bigger than most towns will have a detectible fatality rate. But I'll advocate for the opposite: more technology, more notification.

What if everyone who saw a shooting reported it to a location-based social network that then informed everyone within a small radius? Such a system would respond much more accurately and promptly to witness reports than the current police & bureaucracy-moderated system. In addition, it wouldn't notify people who are not nearby. And it would provide repeated notifications in the event of repeated incidents, so if it doesn't repeat then you know it's an isolated incident. And the police could receive updates over the same network, as well as having a special police-only network with similar capabilities.

In my mind, the best part is that it doesn't need to be specialized. Such a network would already need heuristics for determining the relevance of each report to each user. It would need to determine, from the user, how important the event is, and how private it is. From there it could determine how widely to distribute the report across the user's social and geographic networks. It could directly ask the user, or it could use a variety of content- and context-based analyses. For example, something is probably not very private if ten people report the same thing at the same time. This is the sort of artform that Facebook and Google are fighting vigorously over.

What I'm trying to say is that every smart phone owner should read Vernor Vinge's book Rainbows End.

p.s., the LG is still the bomb diggity. Once I double-clicked the screenlock button after a phone conversation and it merely hung up the phone without actually engaging the screenlock! So I waited one second to see if it was for-real, and then clicked it one more time and it immediately turned off!!! Once!!! On the Samsung, every single time I hung up a phone call I would have to press the hang-up button between 2 and 8 times, waiting multiple seconds between presses to see what it had decided to respond to! Each fucking time! It took 3-10 seconds to hang it up! Every time!

LG is the best. Samsung can rot in hell.

p.p.s., But read the Vinge book. Seriously. The banal workaday manifestations of the coming technological singularity are so astoundingly subtle from the consumer perspective that we can miss the whole show if we don't have people like Vinge to remind us that all these constant (and ever-more-frequent) baby steps lead somewhere fantastic.

December 15, 2011

I still love my LG and I want to tell you why Android is so awesome.

In 2001 I wrote a personal information managament (PIM) suite consisting of a note manager and a checkbook register. It was actually two suites, one for the HP95LX (a DOS-based palmtop), using HP's custom uh "GUI". The other was a set of scripts for my unix machine to allow it to edit the same database. Plus another set of scripts to sync the two.

In 2003 I wrote a GTK version of the suite for my iPAQ h3765, on which I ran Familiar (a debian-like palmtop distro).

From 2003 to 2008 I didn't see the point in buying another palmtop, because the iPAQ was so useless (it never left the house). But in 2009 I bought a Nokia n810 (which sucks, by the way). Even though my suite worked with GTK (which is the basis for Maemo), it took considerable hacking (almost a rewrite) to get it to play nice with the shitty undocumented quirky window manager on the n810.

So now I've written 3 specialized clients for portable devices I no longer use. Bummer.

Along came Android. In 2010 I got a Samsung (which sucks, by the way), and I wrote yet the fourth set of portable clients for my PIM suite. And I carried it around in my pocket for a year -- I used this fourth edition more in that year than I had used all the previous editions put together.

And now I got an LG. And shazam, with no real work, I have my PIM on there. This is the first time a single client has survived two pieces of hardware.

So thanks to Android, I now use my software because it is on my phone which I actually do carry everywhere. And thanks to Android, I don't have to rewrite it every time I buy a new PDA.

Booya

February 15, 2012

My friend bought a Samsung Galaxy S II and he loves it. It is so fast and so ... well, it does like to have its battery charged every 8 hours, and my friend will admit that the screen is too large, it doesn't fit well in his hand. But it has OLED! And a dual core megaprocessor! And is nearly 1mm thinner than the iPhone 4!

And I was starting to doubt my hatred of all things Samsung. No more!

He admitted that his phone beeps every time its done charging the battery, which happens every single night due to poor battery life.

He's still impressed that it responds to button presses immediately, but my budget LG Optimus V does that.

Rot in hell Samsung.

Oh, and this reminds me of another Samsung phone I used to own. The SPH-i300, a Palm OS device. It was shitty in the way my Intercept was: it was unreliable at making phone calls.