Search code examples
androidgoogle-glassgoogle-gdkandroid-menu

How to manage multiple menus for voice and touch input in Android / Google Glass


I'm developing a Google Glass app that displays n cards to the user, each with its custom menu.

The user should be able to access the menu through Voice ('Ok Glass') and Touch ('Tap'). The following example deals only with two cards (and two menus):

public class JobActivity extends Activity {
    // Manages CardScrollView and CardScrollAdapter
    private CardHolder mCardHolder;                               

    private Menu mCustomMenu;                              
    private GestureDetector mGestureDetector;            

    @Override
    protected void onCreate(Bundle bundle)
    {
        super.onCreate(bundle);
        mCardHolder = new CardHolder(this);

        getWindow().requestFeature(WindowUtils.FEATURE_VOICE_COMMANDS);
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);

        setContentView(mCardHolder.getView());
    }
    mGestureDetector = createGestureDetector(this);
}

private GestureDetector createGestureDetector(Context context) {
    GestureDetector gestureDetector = new GestureDetector(context);

    gestureDetector.setBaseListener(new GestureDetector.BaseListener() {
        @Override
        public boolean onGesture(Gesture gesture) {
            if (gesture == Gesture.TAP) {
                openOptionsMenu();
                return true;
            } else if (gesture == Gesture.SWIPE_RIGHT) {
                if (mCardHolder.getCurrentCardPos() == 0) {
                    // Moves to the next card automatically
                    updateOptionsMenu(getCardTwoMenuItems());
                }
                return true;
            } else if (gesture == Gesture.SWIPE_LEFT) {
                if (mCardHolder.getCurrentCardPos() == 1) {
                    // Moves to the previous card automatically
                    updateOptionsMenu(getCardOneMenuItems());
                }
                return true;
            }
            return false;
        }
    });
    return gestureDetector;
}

@Override
public boolean onGenericMotionEvent(MotionEvent event) {
    if (mGestureDetector != null) return mGestureDetector.onMotionEvent(event);
    return false;
}

@Override
protected void onResume() {
    super.onResume();
    mCardHolder.activate();
}

@Override
protected void onPause() {
    mCardHolder.deactivate(); }
    super.onPause();
}

@Override
public boolean onCreatePanelMenu(int featId, Menu menu) {
    if (featId == WindowUtils.FEATURE_VOICE_COMMANDS || featId == Window.FEATURE_OPTIONS_PANEL) {
        List<String> itemList = getCardOneMenuItems();
        mCustomMenu = menu;

        for (int i = 0; i < itemList.size(); i++)
            mCustomMenu.add(Menu.NONE, i, Menu.NONE, itemList.get(i));

        super.onCreatePanelMenu(featId, mCustomMenu);
        return true;
    }
    return false;
}

private void updateOptionsMenu(List<String> itemList) {
    if (mCustomMenu != null) mCustomMenu.clear();                    

    for (int i = 0; i < itemList.size(); i++)
        mCustomMenu.add(Menu.NONE, i, Menu.NONE, itemList.get(i));

    if (mCustomMenu != null) onPrepareOptionsMenu(mCustomMenu);  
}

@Override
public boolean onMenuItemSelected(int featId, MenuItem menuItem) {
    if (featId == WindowUtils.FEATURE_VOICE_COMMANDS || featId == Window.FEATURE_OPTIONS_PANEL) {

        if (mCardHolder.getCurrentCardPos() == 0) {
            // An item was selected from the menu in the first card

            if (menuItem.getItemId() == 0) {
                // User selected 'Goto the next card'
                updateOptionsMenu(getCardTwoMenuItems());
                mCardHolder.next();     // Moves to the next card
            } else if (menuItem.getItemId() == 1) doSomething();
            else if (menuItem.getItemId() == 2) doSomethingElse();

        } else if (mCardHolder.getCurrentCardPos() == 1) {
            // An item was selected from the menu in the second card

            if (menuItem.getItemId() == 0) {
                updateOptionsMenu(getCardOneMenuItems());
                mCardHolder.previous(); // Moves to the previous card
            } else if (menuItem.getItemId() == 1) doSomethingMore();
        }
        return true;
    }
    return super.onMenuItemSelected(featId, menuItem);
}

private List<String> getCardTwoMenuItems() {
    List<String> cardTwoMenuItems = new ArrayList<>();
    cardTwoMenuItems.add(getResources().getString(R.string.card_previous));
    cardTwoMenuItems.add(getResources().getString(R.string.something_more));
    return cardTwoMenuItems;
}

private List<String> getCardOneMenuItems() {
    List<String> cardOneMenuItems = new ArrayList<>();
    cardOneMenuItems.add(getResources().getString(R.string.card_next));
    cardOneMenuItems.add(getResources().getString(R.string.something));
    cardOneMenuItems.add(getResources().getString(R.string.something_else));
    return cardOneMenuItems;
}

The code above works in several situations:

  • If I say 'Ok Glass' and 'Go to the next card'. Saying 'Ok Glass', now in the second card, brings up the correct menu.

  • If I tap and select 'Go to the next card'. Tapping again, now in the second card, brings up the correct menu.

  • If I swipe right. Saying 'Ok Glass', now in the second card, brings up the correct menu.

However, the code does not work in the following situations:

  • If I say 'Ok Glass' and 'Go to the next card'. If I tap, now in the second card, it brings up the incorrect menu (the one from the first card).

  • If I swipe right. If I tap, now in the second card, it brings up the incorrect menu (the one from the first card).

  • If I tap and select 'Go to the next card'. Saying 'Ok Glass', now in the second card, brings up the incorrect menu (the one from the first card).


Solution

  • The solution is in the onPrepareOptionsMenu

    public boolean onPrepareOptionsMenu (Menu menu){
    
        for (int i = 0; i < mJobMenu.size(); i++)
            menu.add(Menu.NONE, i, Menu.NONE, mJobMenu.getItem(i));
    
    }
    

    The onPrepareOptionsMenu is called by the system and the menu items are added to the menu that is passed to it. This happens every time the menu is called (whether by tap or glass). Hence, if you override this method you should be able to insert values in the menu