Lucas A Lucas A - 2 months ago 64
Android Question

Bad token exception - Unable to add window (Marshmallow - Floating toolbar)

I've been struggling to find out the reason of this exception for a while, I've never been able to reproduce myself but some of my customers are experiencing it. It only happens on Android 6.0.1, and since the crash occurrs within the SDK itself it's quite hard to figure out how it happens.

Other solutions regarding this problems have not helped, such as:

window manager bad token exception

"android.view.WindowManager$BadTokenException: Unable to add window" on buider.show()

I am using DialogFragments and AlertDialogs, I guess it could be the core issue but it just doesn't add up since it only affects Marshmallow users.

Fatal Exception: android.view.WindowManager$BadTokenException: Unable to add window -- token null is not valid; is your activity running?
at android.view.ViewRootImpl.setView(ViewRootImpl.java:849)
at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:337)
at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:91)
at android.widget.PopupWindow.invokePopup(PopupWindow.java:1329)
at android.widget.PopupWindow.showAtLocation(PopupWindow.java:1077)
at android.widget.PopupWindow.showAtLocation(PopupWindow.java:1035)
at com.android.internal.widget.FloatingToolbar$FloatingToolbarPopup.show(FloatingToolbar.java:561)
at com.android.internal.widget.FloatingToolbar.show(FloatingToolbar.java:212)
at com.android.internal.view.FloatingActionMode$FloatingToolbarVisibilityHelper.updateToolbarVisibility(FloatingActionMode.java:411)
at com.android.internal.view.FloatingActionMode$1.run(FloatingActionMode.java:65)
at android.os.Handler.handleCallback(Handler.java:739)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:158)
at android.app.ActivityThread.main(ActivityThread.java:7224)
at java.lang.reflect.Method.invoke(Method.java)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1230)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1120)

Answer

So after aquiring a Samsung device (S6) and being able to reproduce this problem I came up with the solution described below.

The error itself originates from the floating toolbar and action modes that was added in Marshmallow. When selecting text on a marshmallow device, something like this comes up:

floating toolbar

If the user then navigates to another activity (e.g. back key) without closing the floating toolbar, the app crashes on Samsung devices. It is basically trying to add the floating toolbar again to the upcoming activity.

More on floating toolbar: https://developer.android.com/about/versions/marshmallow/android-6.0-changes.html#behavior-text-selection

So the solution is to manage the action mode lifecycle and make sure it finishes before any new activity is started. I've got a base activity class that all my activites inherits from, so it made most sense to add the solution there.

public class BaseActivity extends AppCompatActivity {

    private ActionMode mActionMode;

    /**
     * Store action mode if it is started
     * @param mode
     */
    @Override
    public void onActionModeStarted(ActionMode mode) {
        super.onActionModeStarted(mode);
        mActionMode = mode;
    }

    /**
     * When activity is paused, make sure action mode is ended properly.
     * This check would feel better to have in onDestroy(), but that seems to be
     * too late down the life cycle and the crash keeps on occurring. The drawback 
     * of this solution is that the action mode is finished when app is minimized etc.
     */
    @Override
    protected void onPause() {
        super.onPause();
        endActionMode();
    }

    /**
     * Makes sure action mode is ended
     */
    private void endActionMode() {
        if (mActionMode != null) {
            mActionMode.finish(); /** immediately calls {@link #onActionModeFinished(ActionMode)} */
        }
    }

    /**
     * Clear action mode every time it finishes.
     * @param mode
     */
    @Override
    public void onActionModeFinished(ActionMode mode) {
        super.onActionModeFinished(mode);
        mActionMode = null;
    }
}
Comments