Android monkey triggers NullPointerException inside Spinner

1.8k views Asked by At

I'm trying to find some hard-to-reproduce problems in my code. I used the monkey tool. But here, it appears that the problem is inside the Android spinner. Is that an API/framework problem? I tried it again with the same results.

// CRASH: com.panguso.mobile.client (pid 11171)
// Short Msg: java.lang.NullPointerException
// Long Msg: java.lang.NullPointerException
// Build Label: google/soju/crespo:4.1.1/JRO03E/403059:user/release-keys
// Build Changelist: 403059
// Build Time: 1342214487000
// java.lang.NullPointerException
//  at android.widget.Spinner$DialogPopup.dismiss(Spinner.java:828)
//  at android.widget.Spinner$DialogPopup.onClick(Spinner.java:862)
//  at com.android.internal.app.AlertController$AlertParams$3.onItemClick(AlertController.java:924)
//  at android.widget.AdapterView.performItemClick(AdapterView.java:298)
//  at android.widget.AbsListView.performItemClick(AbsListView.java:1086)
//  at android.widget.AbsListView.onKeyUp(AbsListView.java:2996)
//  at android.widget.ListView.commonKey(ListView.java:2196)
//  at android.widget.ListView.onKeyUp(ListView.java:2051)
//  at android.view.KeyEvent.dispatch(KeyEvent.java:2633)
//  at android.view.View.dispatchKeyEvent(View.java:7086)
//  at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1354)
//  at android.widget.ListView.dispatchKeyEvent(ListView.java:2026)
//  at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1358)
//  at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1358)
//  at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1358)
//  at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1358)
//  at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1358)
//  at com.android.internal.policy.impl.PhoneWindow$DecorView.superDispatchKeyEvent(PhoneWindow.java:1892)
//  at com.android.internal.policy.impl.PhoneWindow.superDispatchKeyEvent(PhoneWindow.java:1369)
//  at android.app.Dialog.dispatchKeyEvent(Dialog.java:702)
//  at com.android.internal.policy.impl.PhoneWindow$DecorView.dispatchKeyEvent(PhoneWindow.java:1819)
//  at android.view.ViewRootImpl.deliverKeyEventPostIme(ViewRootImpl.java:3575)
//  at android.view.ViewRootImpl.deliverKeyEvent(ViewRootImpl.java:3531)
//  at android.view.ViewRootImpl.deliverInputEvent(ViewRootImpl.java:3113)
//  at android.view.ViewRootImpl.doProcessInputEvents(ViewRootImpl.java:4153)
//  at android.view.ViewRootImpl.enqueueInputEvent(ViewRootImpl.java:4132)
//  at android.view.ViewRootImpl$WindowInputEventReceiver.onInputEvent(ViewRootImpl.java:4224)
//  at android.view.InputEventReceiver.dispatchInputEvent(InputEventReceiver.java:171)
//  at android.view.InputEventReceiver.nativeConsumeBatchedInputEvents(Native Method)
//  at android.view.InputEventReceiver.consumeBatchedInputEvents(InputEventReceiver.java:163)
//  at android.view.ViewRootImpl.doConsumeBatchedInput(ViewRootImpl.java:4203)
//  at android.view.ViewRootImpl$ConsumeBatchedInputRunnable.run(ViewRootImpl.java:4243)
//  at android.view.Choreographer$CallbackRecord.run(Choreographer.java:725)
//  at android.view.Choreographer.doCallbacks(Choreographer.java:555)
//  at android.view.Choreographer.doFrame(Choreographer.java:523)
//  at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:711)
//  at android.os.Handler.handleCallback(Handler.java:615)
//  at android.os.Handler.dispatchMessage(Handler.java:92)
//  at android.os.Looper.loop(Looper.java:137)
//  at android.app.ActivityThread.main(ActivityThread.java:4745)
//  at java.lang.reflect.Method.invokeNative(Native Method)
//  at java.lang.reflect.Method.invoke(Method.java:511)
//  at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:786)
//  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:553)
//  at dalvik.system.NativeStart.main(Native Method)
// 
** Monkey aborted due to error.
Events injected: 1801
:Sending rotation degree=0, persist=false
:Dropped: keys=5 pointers=0 trackballs=0 flips=0 rotations=0
## Network stats: elapsed time=17738ms (0ms mobile, 17738ms wifi, 0ms not connected)
** System appears to have crashed at event 1801 of 10000 using seed 0
2

There are 2 answers

0
zapl On BEST ANSWER

The stacktrace you posted does not mention any class inside your code, meaning that this problem is at least not directly caused by you.

Little analysis of the stacktrace, from bottom to top since that is the path the code takes (sourcecode for that stacktrace can be found using http://www.grepcode.com/?st=true):

...
at android.view.InputEventReceiver.dispatchInputEvent(InputEventReceiver.java:171)

Monkey issues a key event like you could produce with a soft / hard keyboard.

The key event is then forwarded to the DecorView which contains your app content and the ActionBar. The DecorView forwards the event to a Dialog.

...
at com.android.internal.policy.impl.PhoneWindow$DecorView.dispatchKeyEvent(PhoneWindow.java:1819)
at android.app.Dialog.dispatchKeyEvent(Dialog.java:702)

From here is passed through the view hierarchy until it reaches a ListView

at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1358)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1358)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1358)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1358)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1358)
at android.widget.ListView.dispatchKeyEvent(ListView.java:2026)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1354)
at android.view.View.dispatchKeyEvent(View.java:7086)
at android.view.KeyEvent.dispatch(KeyEvent.java:2633)
at android.widget.ListView.onKeyUp(ListView.java:2051)
at android.widget.ListView.commonKey(ListView.java:2196)

The ListView forwards the key to it's content which seems to be a Spinner and the click seems to trigger a .dismiss() to the Dialog (the dropdown list) this Spinner shows.

at android.widget.AbsListView.onKeyUp(AbsListView.java:2996)
at android.widget.AbsListView.performItemClick(AbsListView.java:1086)
at android.widget.AdapterView.performItemClick(AdapterView.java:298)
at com.android.internal.app.AlertController$AlertParams$3.onItemClick(AlertController.java:924)
at android.widget.Spinner$DialogPopup.onClick(Spinner.java:862)
at android.widget.Spinner$DialogPopup.dismiss(Spinner.java:828)

A Spinner and a stacktrace that does not mention your code could be part of the system UI i.e. the ActionBar.

example
(source: android.com)

As you can see in the image (taken from here) there is a Spinner (showing the date) and a Dialog with a ListView (Day, Week, ...) below. Clicking one of the items or the spinner itself or somewhere outside would close the dialog.

The NullPointerException happens in the following piece of code

public void dismiss() {
    mPopup.dismiss();  // Spinner.java:828
    mPopup = null;
}

mPopup is obviously null. That will only happen if the popup was not shown before since it is set in

public void show() {
    AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
    if (mPrompt != null) {
        builder.setTitle(mPrompt);
    }
    mPopup = builder.setSingleChoiceItems(mListAdapter,
            getSelectedItemPosition(), this).show();
}

Is that an API/framework problem?

I don't know but monkey can click things that are not necessarily reachable for regular users and it can click at times / rates that users will not be able to click. So this problem might be caused by clicking in a way that the API programmers did not foresee / test. Is possible especially since the stacktrace is purely framework and they check for null of mPopup in other places but not here.

But besides that possibility it could also be related to your code. Maybe you added a Spinner somewhere in your layout (or the ActionBar) and do something with it that it was not intended to do. And I guess if you don't override any of the default behavior (esp. key event handling) the framework does not need to jump into your code and would produce exactly the same stacktrace.

0
John On

I am having the same issue with all of my Android devices unfortunately. There is a hypothesis in this answer that I would like to expand on:

But then this is the weird part. The stack trace seem to be when an item of the mPopup was clicked, because DialogPopup is set as its OnClickListener. So at that point mPopup should not be null. But when DialogPopup.dismiss() it is null.

What can explain that? Here's my hypothesis. Well, monkey usually is pretty fast. Faster than actual users. It might be able to click two items before the mPopup was really dismissed by the first DialogPopup.onClick(). So we have two calls to DialogPopup.dismiss(), with the second cauing the NPE.

Considering that this bug is still active I think it is time to come up with solutions to get around this problem. Here are some things that I have noticed as well:

  1. When I am running the monkeytester on the emulator, it never crashes because of the spinner nullpointer bug. I believe this is true because the emulator is A LOT slower than an Android device and the monkey tester cannot issue the touch events fast enough to cause the crash.

  2. When using an Android device with the monkey tester, using the same seed will almost always cause the same crash. This makes sense because the device will receive the same pseudo-random touch events for each seed. I never use the 0 seed anymore because it almost always causes the crash after a couple of touch events for me.


Work Arounds

From all of the searching I have done, there is no actual solution to the problem. But there are a couple of things you can do to avoid the crash.

  1. Use the emulator to do tests that will last longer than a couple of minutes. The emulator performs a lot slower than an actual device so the monkey tester cannot issue the commands fast enough to cause the crash. If you are using the Intel hardware accelerator (HAXM), I would recommend creating an AVD that does not use it. Make sure to enable snapshots and disable GPU acceleration so you don't have to wait forever for your AVD to boot.

  2. If you want to test on an actual device, then make sure you pass the --throttle <millis> parameter to the monkey tester. Here is the definition from the docs:

    Inserts a fixed delay between events. You can use this option to slow down the Monkey. If not specified, there is no delay and the events are generated as rapidly as possible.

Here is an example command of using --throttle:

adb -s shell monkey --throttle 200 -p com.your.package -v 500000

You should try to find the optimal delay for your device. For my Samsung Galaxy Nexus, the delay has to be almost a full second to prevent the crash. This goes without saying, but the longer the delay is, the longer it will take to finish the monkey test. This is why it is important to find a value where your device isn't crashing but it also isn't going super slow.