HTC Desire Z Home button dead on CM7: nine suspects, no culprit

Above the Home-key code in Android 2.3's window manager sits a comment promising that the system handles Home itself, "so applications can never break it". I have read that comment many times now, and every time I agree with it a little less. On my HTC Desire Z, a 2010 Android slider I keep around for its keyboard, running CyanogenMod 7 (the community build of Android 2.3), Home did nothing at all. No crash, no wrong app, no delay. A tap on the leftmost button below the screen, a little feedback from the phone, and then the same app still sitting there.

Back worked. Menu worked. My own home-screen app, MeshLauncher, came to the front when asked any other way. What follows is nine theories about Home in the order I had them, the code each one pointed at, and the test that killed it. None of them was right, and I would like that noted before anyone gets attached to one.

Home dead in every app, and logcat had nothing#

The afternoon started with the lock screen, not Home. An earlier factory reset had cleared the old lock settings, so I set a PIN in Settings. The PIN saved, and no lock screen ever appeared, not even after a reboot. dumpsys window listed a status bar, my launcher and the wallpaper, and no keyguard window, the keyguard being Android's name for the lock screen. I parked it. Then Home became the problem, in every app.

The first checks all came back reasonable. MeshLauncher was the default home app. The key layout file mapped scancode 102 to HOME. Sending the Home intent from adb brought the launcher forward at once. Sending the Home key from adb, input keyevent 3 with Settings open, did nothing, same as my thumb. Suspect one was my launcher's manifest, so I gave MeshLauncher the clearTaskOnLaunch flag that ADW, the launcher CM7 ships, declares. After that each tap got feedback from the phone and still no action.

Logcat, the system log, was no help either. Suspect two was an error in the log. I cleared it, opened Calculator, gave myself twelve seconds to press Home, and the log held two lines, neither about Home. A phone that won't go home, and a log with nothing to confess.

Where Android 2.3 handles the Home key#

Every key goes through PhoneWindowManager.interceptKeyBeforeDispatching() before an app sees it, and Home is the one key that never reaches the app at all. In the CM7 source, the path for a quick tap is:

  1. HOME down: mHomePressed = true, and a long-press callback is scheduled after ViewConfiguration.getGlobalActionKeyTimeout(), 500 ms (skipped while the keyguard is on)
  2. HOME up before that: launchHomeFromHotKey()
  3. Held past 500 ms: the callback sets mHomePressed = false and shows the recent-apps dialog, so the key-up does nothing

The feedback on each tap made the long press suspect three: maybe a light tap still counted as a hold. So I tapped quickly, several times. Nothing. The feedback, I found later in the same file, comes from interceptKeyBeforeQueueing(), which buzzes for every virtual key going down. It meant the window manager had heard the tap. It meant nothing about long presses.

The keyguard theory: an empty if#

Suspect four came out of launchHomeFromHotKey() itself:

void launchHomeFromHotKey() {
    if (mKeyguardMediator.isShowingAndNotHidden()) {
        // don't launch home if keyguard showing
    } else if (!mHideLockScreen && mKeyguardMediator.isInputRestricted()) {
        // when in keyguard restricted mode, must first verify unlock
        // before launching home
        // ...
    } else {
        // no keyguard stuff to worry about, just launch home!
        // ...
        startDockOrHome();
    }
}

The first branch is a comment and nothing else. If the keyguard believes it is showing, Home falls into that block, logs nothing and returns. I had a PIN that had never produced a lock screen, which looked a lot like a keyguard confused about whether it was showing. So I cleared every lock setting I could name and rebooted:

adb shell "sqlite3 /data/data/com.android.providers.settings/databases/settings.db \"DELETE FROM secure WHERE name IN ('lockscreen.password_salt','lockscreen.password_type','lock_pattern_autolock','lock_pattern_tactile_feedback_enabled','lockscreen.disabled')\""
adb shell rm /data/system/password.key /data/system/gesture.key
adb reboot

Same. The phone had at this point managed to be locked and unlocked at once, and it was helpful in neither state.

Proving the touch reaches the Home zone#

The Desire Z's four buttons below the screen are not keys. They are zones on the Atmel touchscreen, and a virtual-key map in sysfs tells Android which touches become which keys:

$ adb shell cat /sys/board_properties/virtualkeys.atmel-touchscreen 2>/dev/null || adb shell cat /sys/devices/virtual/input/input1/virtualkeys.atmel-touchscreen 2>/dev/null || find /sys -name "virtualkeys*" 2>/dev/null | head -5
0x01:102:47:830:74:50:0x01:139:155:830:80:50:0x01:158:337:830:90:50:0x01:217:434:830:60:50

Four zones, each scancode:centerX:centerY:width:height: Home (102) at x=47, Menu (139) at 155, Back (158) at 337, Search (217) at 434, all at y=830, just below the 800-line display. Suspect five: the Home zone wasn't registering. I watched the touchscreen with getevent and pressed only Home:

$ adb shell getevent -t /dev/input/event1 2>&1 | grep --line-buffered "0001\|0035.*0000\|0036.*0003"
335-324879: 0003 0030 0000001d
335-324970: 0003 0035 00000054
335-325001: 0003 0036 000003e9

Raw x=0x54 (84) and y=0x3e9 (1001). The touchscreen reports x from 1 to 1023 and y from 2 to 966, so on the 480x800 display that lands near x=39, y=829, on top of the Home zone. The first and last touch reports were about 141 ms apart, far under 500 ms. My thumb touches the glass where Home lives, and I have the hex to prove it. Turning that touch into KEY_HOME happens later, in InputReader inside system_server, which is why getevent never shows a key event for it. Not that it mattered: the buzz had already told me the key existed, and input keyevent 3 had already failed without any touch at all.

Not my launcher: ADW is dead too#

Suspect six was the one I'd had since the morning: my launcher's code. MeshLauncher overrides dispatchKeyEvent, and earlier versions consumed KEYCODE_HOME there. So I re-enabled ADW, opened an app and pressed Home. ADW did not come back. I launched ADW directly, opened Gallery from it and pressed Home. Nothing. Home was broken system-wide, under a launcher I hadn't written. I took the Home handling out of MeshLauncher anyway, and later that made no difference either.

A missing row that reads as a lock, and another black screen#

Suspect seven came from LockPatternUtils, the class that answers "is there a lock?":

public int getKeyguardStoredPasswordQuality() {
    return (int) getLong(PASSWORD_TYPE_KEY, DevicePolicyManager.PASSWORD_QUALITY_SOMETHING);
}

PASSWORD_TYPE_KEY is lockscreen.password_type, one of the rows I had just deleted. With the row gone, the stored quality reads back as PASSWORD_QUALITY_SOMETHING, 0x10000, which is 65536 and means some kind of lock, not none. Deleting a setting does not reset it to "off" here; it resets it to the default, and the default is "something". So I wrote the row back as zero and rebooted:

adb shell "sqlite3 /data/data/com.android.providers.settings/databases/settings.db \"INSERT INTO secure (name,value) VALUES ('lockscreen.password_type','0')\"" && echo "set to 0 (no lock)" && adb reboot

The phone came back to a black screen. I did not panic. I have been through this exact black screen with this exact phone, and these days my composure comes with a SQL query: screen_brightness was 80 of 255. The Desire Z's LCD at 80 in a lit room reads as switched off, a trap I'd already walked into the night of the debloat, less than a day earlier. Brightness up to 200, screen back. Home: nothing. The lock screen I had asked for at the start of the afternoon still hadn't shown up either. Before the day was out I met the same black screen once more, after a later reboot, and went straight for the same setting.

Factory reset, and the crash-dialog theory#

Suspect eight was everything on /data. I sent a factory reset from adb (am broadcast -a android.intent.action.MASTER_CLEAR), the phone wiped /data and rebooted, and Home still did nothing.

That left suspect nine, the one the first version of this post ended on. Before the long-press and keyguard checks, interceptKeyBeforeDispatching() looks at the window that has focus:

private static final int[] WINDOW_TYPES_WHERE_HOME_DOESNT_WORK = {
        WindowManager.LayoutParams.TYPE_SYSTEM_ALERT,
        WindowManager.LayoutParams.TYPE_SYSTEM_ERROR,
    };

If that window is a system alert or a system error, the loop over this array returns true with the comment "don't do anything, but also don't pass it to the app". The "Sorry!" crash dialogs qualify: in this source their base class, BaseErrorDialog, sets TYPE_SYSTEM_ALERT, the first entry, and the debloat had produced plenty of those dialogs. It's a lovely mechanism. I wrote the first draft of this post around it while the phone was still booting from the reset.

Then the phone booted. dumpsys window listed the status bar and its expanded panel, a tracking view, the input method, ADW and the wallpaper. No crash dialog. And every Home test I can account for that afternoon had an ordinary app in front, Settings or Calculator or Gallery, never a dialog. The reflash started with a fresh kernel (boot.img) over fastboot, and on that kernel Home still did nothing. Nine suspects, nine misses.

Nine suspects, one reflash#

#SuspectTestResult
1MeshLauncher's manifestmatched ADW's clearTaskOnLaunchfeedback, no action
2An error in the loglogcat during a Home presstwo lines, neither about Home
3Long press eating the tapquick tapsnothing
4Keyguard stuck "showing"cleared lock rows and key files, rebootedsame
5Touch missing the Home zonegetevent on the touchscreenlands on Home, about 141 ms
6My launcherHome under ADWnothing, system-wide
7Missing row read as a locklockscreen.password_type = 0, rebootedblack screen at 80/255, then same
8Something on /datafactory resetsame
9Crash dialogswindow list after the reset; new boot.imgno dialogs; same

What fixed it was a full reflash of CyanogenMod, which took about another eighty minutes on its own (that part is its own story). On the clean install, before I had changed a single setting or package, Home worked, and so did the lock screen. A factory reset had already wiped /data and a new kernel had replaced the old one, so whatever broke Home lived on /system, or in the cache partition the reflash also cleared. Which file it was, I never found out, because the reflash erased the evidence along with the problem.

The Home key's silent exits#

The search left me a list of the ways Android 2.3 lets a Home tap go nowhere, and three of them log nothing:

  1. Keyguard showing: isShowingAndNotHidden() true, and launchHomeFromHotKey() enters a branch that is only a comment.
  2. System alert or error window in focus: interceptKeyBeforeDispatching() consumes Home and does nothing with it.
  3. Long press: past 500 ms, the recent-apps dialog takes the key-up. This one at least shows you a dialog.

Two more exits, an incoming call ringing and a cancelled key event, do write a line to the log. On this phone none of the five turned out to be the one, which I can now say with the confidence of a man who checked.

The comment still sits above that code, promising that applications can never break Home. It kept its word: every application I own has an alibi, and so does every setting a factory reset can reach. Something else broke it, something on a partition I wiped before I thought to ask it questions. I hold no grudge. A grudge needs a name, and the only name I have for it is /system.

Filed under: android, hardware, debugging

Sources#