Calling openOptionsMenu() in XE16 causes WindowManager.BadTokenException
Asked Answered
G

2

6

I have a GDK app that worked fine in XE12 but now crashes in XE16 after transitioning to GDK:19. In particular, calling openOptionsMenu() in an Activity (in this case, to open an options menu on a Live Card) causes BadTokenExceptions.

Logcat output:

04-16 03:36:43.197: E/AndroidRuntime(2465): FATAL EXCEPTION: main
04-16 03:36:43.197: E/AndroidRuntime(2465): Process: com.voidstar.glass.sample.pinDrop, PID: 2465
04-16 03:36:43.197: E/AndroidRuntime(2465): java.lang.RuntimeException: Unable to resume activity {com.voidstar.glass.sample.pinDrop/com.voidstar.glass.sample.pinDrop.MenuActivity}: android.view.WindowManager$BadTokenException: Unable to add window -- token null is not valid; is your activity running?
04-16 03:36:43.197: E/AndroidRuntime(2465):     at android.app.ActivityThread.performResumeActivity(ActivityThread.java:2828)
04-16 03:36:43.197: E/AndroidRuntime(2465):     at android.app.ActivityThread.handleResumeActivity(ActivityThread.java:2857)
04-16 03:36:43.197: E/AndroidRuntime(2465):     at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2290)
04-16 03:36:43.197: E/AndroidRuntime(2465):     at android.app.ActivityThread.access$800(ActivityThread.java:138)
04-16 03:36:43.197: E/AndroidRuntime(2465):     at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1236)
04-16 03:36:43.197: E/AndroidRuntime(2465):     at android.os.Handler.dispatchMessage(Handler.java:102)
04-16 03:36:43.197: E/AndroidRuntime(2465):     at android.os.Looper.loop(Looper.java:149)
04-16 03:36:43.197: E/AndroidRuntime(2465):     at android.app.ActivityThread.main(ActivityThread.java:5061)
04-16 03:36:43.197: E/AndroidRuntime(2465):     at java.lang.reflect.Method.invokeNative(Native Method)
04-16 03:36:43.197: E/AndroidRuntime(2465):     at java.lang.reflect.Method.invoke(Method.java:515)
04-16 03:36:43.197: E/AndroidRuntime(2465):     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:794)
04-16 03:36:43.197: E/AndroidRuntime(2465):     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:610)
04-16 03:36:43.197: E/AndroidRuntime(2465):     at dalvik.system.NativeStart.main(Native Method)
04-16 03:36:43.197: E/AndroidRuntime(2465): Caused by: android.view.WindowManager$BadTokenException: Unable to add window -- token null is not valid; is your activity running?
04-16 03:36:43.197: E/AndroidRuntime(2465):     at android.view.ViewRootImpl.setView(ViewRootImpl.java:561)
04-16 03:36:43.197: E/AndroidRuntime(2465):     at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:259)
04-16 03:36:43.197: E/AndroidRuntime(2465):     at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:69)
04-16 03:36:43.197: E/AndroidRuntime(2465):     at com.android.internal.policy.impl.PhoneWindow.openPanel(PhoneWindow.java:693)
04-16 03:36:43.197: E/AndroidRuntime(2465):     at com.android.internal.policy.impl.PhoneWindow.openPanel(PhoneWindow.java:555)
04-16 03:36:43.197: E/AndroidRuntime(2465):     at android.app.Activity.openOptionsMenu(Activity.java:2878)
04-16 03:36:43.197: E/AndroidRuntime(2465):     at com.voidstar.glass.sample.pinDrop.MenuActivity.onResume(MenuActivity.java:71)
04-16 03:36:43.197: E/AndroidRuntime(2465):     at android.app.Instrumentation.callActivityOnResume(Instrumentation.java:1194)
04-16 03:36:43.197: E/AndroidRuntime(2465):     at android.app.Activity.performResume(Activity.java:5316)
04-16 03:36:43.197: E/AndroidRuntime(2465):     at android.app.ActivityThread.performResumeActivity(ActivityThread.java:2818)
04-16 03:36:43.197: E/AndroidRuntime(2465):     ... 12 more

Method in Service that binds the Activity to the Live Card:

@Override
    public int onStartCommand(Intent intent, int flags, int startId) {      
        // This method is called whenever the Glassware is invoked via voice commands or the OK Glass menu.
        if (mLiveCard == null) {    
            Log.d(TAG, "Connecting mLocationManager");

            Criteria criteria = new Criteria();
            criteria.setAccuracy(Criteria.ACCURACY_COARSE);

            PinDropLocationListener listener = new PinDropLocationListener();
            locationListeners.add(listener);
            mLocationManager.requestSingleUpdate(criteria, listener, null);

            mLiveCard = new LiveCard(getBaseContext(), LIVE_CARD_TAG);
            mLiveCard.setViews(new RemoteViews(getPackageName(), R.layout.activity_waiting));
            mLiveCard.attach(this); // Prevent this Service from being killed to free up memory

            Intent menuIntent = new Intent(this, MenuActivity.class); // Since menus can only be attached to Activities, we create an activity to own and launch the menu.
            menuIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
            mLiveCard.setAction(PendingIntent.getActivity(this, 0, menuIntent, 0)); // This Intent will be fired whenever the LiveCard is tapped.

            Log.d(TAG, "Publishing LiveCard");
            mLiveCard.publish(PublishMode.REVEAL); // Add the LiveCard to the Timeline and switch to it
            Log.d(TAG, "Done publishing LiveCard");
        } else {
            mLiveCard.navigate(); // Switch to the app if it's already running
        }

        return START_STICKY; // No idea what this does. Your guess is as good as mine.
    }

And the problematic Activity:

/**
 * Activity showing the options menu.
 */
public class MenuActivity extends Activity {
    // This is technically an Immersion!
    // Because Services have no UI, we need to open this Activity, which in turn opens its menu!

    PinDropService.MenuBinder mBinder;

    private static String TAG = "PinDropMenu";

    boolean hasLocation;

    /*
     * Links this Activity to the Service that spawned it, so the Menu can send and receive information
     */
    private ServiceConnection mConnection = new ServiceConnection() {

        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            if (service instanceof PinDropService.MenuBinder) {
                mBinder = (PinDropService.MenuBinder)service;
                hasLocation = mBinder.hasLocation();
                Log.d(TAG, hasLocation ? "Received has location" : "Received no location");
                //openOptionsMenu();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {}
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        bindService(new Intent(this, PinDropService.class), mConnection, 0);
    }

    @Override
    public void onResume() {
        super.onResume();
        openOptionsMenu();
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        MenuInflater inflater = getMenuInflater();
        inflater.inflate(R.menu.pindropmenu, menu);
        return true;
    }

    @Override
    public boolean onPrepareOptionsMenu(Menu menu) {
        if (!hasLocation) {
            menu.findItem(R.id.directions).setVisible(false);
            menu.findItem(R.id.remember).setVisible(false);
        }
        else {
            menu.findItem(R.id.directions).setVisible(true);
            menu.findItem(R.id.remember).setVisible(true);
        }
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle item selection.
        switch (item.getItemId()) {
            case R.id.directions:
                mBinder.startNavigation();
                return true;
            case R.id.remember:
                mBinder.addToTimeline(); // TODO: Add Mirror functionality!
                return true;
            case R.id.stop: // IT IS CRITICALLY IMPORTANT TO ADD THIS OR THE GLASSWARE CAN'T BE KILLED IN USERSPACE!
                stopService(new Intent(this, PinDropService.class));
                return true;
            default:
                return super.onOptionsItemSelected(item);
        }
    }

    @Override
    public void onOptionsMenuClosed(Menu menu) {
        // Nothing else to do, closing the Activity.
        finish();
    }

    @Override
    public void onStop() {
        super.onStop();
        unbindService(mConnection); // Don't leak Services!
    }
}

Tapping the Live Card causes an immediate crash, and the Logcat output above is dumped. What's odd is that if the commented-out openOptionsMenu() is uncommented and the existing openOptionsMenu() is commented out, the first tap will actually open the menu. The second attempt to open the menu will fail, with similar Logcat output (the BadTokenException is the main exception instead of an inner exception on RuntimeException).

Goad answered 16/4, 2014 at 8:18 Comment(0)
R
4

As petey said, but you need to override onAttachedToWindow() in addition to onResume(). My code now looks something like:

private boolean isAttached = false;

@Override
public void onAttachedToWindow() {
  super.onAttachedToWindow();
  this.isAttached = true;
  openOptionsMenu();
}

@Override
public void onResume() {
  super.onResume();
  if (this.isAttached)
    openOptionsMenu();
}
Ressler answered 17/4, 2014 at 14:36 Comment(0)
M
5

Answer from dario in skylight1,

"samples have been updated as well - from Compass:"

@Override
public void onAttachedToWindow() {
    super.onAttachedToWindow();
    mAttachedToWindow = true;
    openOptionsMenu();
}

TY dario!

Mauramauralia answered 16/4, 2014 at 14:22 Comment(0)
R
4

As petey said, but you need to override onAttachedToWindow() in addition to onResume(). My code now looks something like:

private boolean isAttached = false;

@Override
public void onAttachedToWindow() {
  super.onAttachedToWindow();
  this.isAttached = true;
  openOptionsMenu();
}

@Override
public void onResume() {
  super.onResume();
  if (this.isAttached)
    openOptionsMenu();
}
Ressler answered 17/4, 2014 at 14:36 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.