Anindya Basu Anindya Basu - 2 months ago 17
Android Question

Android Studio TV remote buttons

I'm trying to make an app for android tv that will use the following buttons from a tv remote: up, down, left, right, center/enter, home, back

What classes/events do I need to do this?
I've been trying to use the Dpad code found here: https://developer.android.com/training/game-controllers/controller-input.html#dpad

But it doesn't work when I try to test it with the android emulator on a TV with the directional pad input. With a lot of Log statements, I found my problem to be the following lines of code:

if (event instanceof MotionEvent) {
// Use the hat axis value to find the D-pad direction
MotionEvent motionEvent = (MotionEvent) event;
float xaxis = motionEvent.getAxisValue(MotionEvent.AXIS_HAT_X);
float yaxis = motionEvent.getAxisValue(MotionEvent.AXIS_HAT_Y);
Log.d("test", "xaxis = " + String.valueOf(xaxis) + " yaxis = " + String.valueOf(yaxis));
}
Log.d("test", "returning directionPressed as - " + String.valueOf(directionPressed));
return directionPressed;


and the output I get is as follows (and prints 2 times, even if I press a button only once):

09-13 14:45:05.643 1489-1489/omgandroid D/test: is motion event = true
09-13 14:45:05.643 1489-1489/omgandroid D/test: is key event = false
09-13 14:45:05.643 1489-1489/omgandroid D/test: xaxis = 0.0 yaxis = 0.0
09-13 14:45:05.643 1489-1489/omgandroid D/test: returning directionPressed as -1


I see that getAxisValue(MotionEvent.AXIS_HAT_X/Y is always returning 0.0 but I don't know why.

Here is the code where I'm calling this function in my MainActivity.java (inside OnCreate):

mContentView.setOnGenericMotionListener(new View.OnGenericMotionListener() {
@Override
public boolean onGenericMotion(View view, MotionEvent event) {
Log.d("test", "this works too");
// Check if this event if from a D-pad and process accordingly.
boolean check = Dpad.isDpadDevice(event);
String str_check = String.valueOf(check);
Log.d("test", "is dpad device? " + str_check);
if (check) {

int press = mDpad.getDirectionPressed(event);
Log.d("test", String.valueOf(press));
switch (press) {
case LEFT:
// Do something for LEFT direction press
Log.d("test", "LEFT");
String uri = source + image;
ImageView img = (ImageView) findViewById(R.id.fullscreen_content);
img.setImageResource(R.drawable.a00_d01_01);
return true;
case RIGHT:
// Do something for RIGHT direction press
Log.d("test", "RIGHT");
return true;
case UP:
// Do something for UP direction press
Log.d("test", "UP");
return true;
case DOWN:
// Do something for DOWN direction press
Log.d("test", "DOWN");
return true;
case CENTER:
// DO something for CENTER direction press
Log.d("test", "CENTER");
return true;
default:
return false;
}
}
return false;
}
});

Answer

If you're not using leanback and you want the functionality in an an Activity, then you can just override the Activity method onKeyDown():

@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
    Log.d("debug", "we are here");
    switch (keyCode) {
        case KeyEvent.KEYCODE_DPAD_UP:
        case KeyEvent.KEYCODE_DPAD_DOWN:
        case KeyEvent.KEYCODE_DPAD_RIGHT:
        case KeyEvent.KEYCODE_DPAD_LEFT:
        case KeyEvent.KEYCODE_BACK:
        case KeyEvent.KEYCODE_ESCAPE:
            Log.d("OnKey", "key pressed!");
            Toast.makeText(MainActivity.this, "key pressed!", Toast.LENGTH_SHORT).show();
            return true;
    }
    return false;
}

And then use a switch statement like below (on the keyCode) to trigger the conditions you want to catch (case KeyEvent.KEYCODE_DPAD_UP, case KeyEvent.KEYCODE_DPAD_DOWN, etc).

As you've put in your code-share, you can also set an OnKeyListener on views, but in this case you only need to override the Activity method.


If you are using leanback (which excels at list management and media playback):

Leanback, the library that Google created to make writing Android TV apps easier, handles this natively as it relates to lists of content as well as media playback. I'd recommend checking out their example project I linked to above.

If you'd like to implement the click handling yourself, you can view their source code and see how they solve it in the PlaybackControlGlue and PlaybackOverlayFragment classes.

Here they handle the events in the onKey(...) method:

@Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
    switch (keyCode) {
        case KeyEvent.KEYCODE_DPAD_UP:
        case KeyEvent.KEYCODE_DPAD_DOWN:
        case KeyEvent.KEYCODE_DPAD_RIGHT:
        case KeyEvent.KEYCODE_DPAD_LEFT:
        case KeyEvent.KEYCODE_BACK:
        case KeyEvent.KEYCODE_ESCAPE:

And some additional events in the dispatchAction(...) method right below it:

boolean canPlay = keyEvent == null ||
                keyEvent.getKeyCode() == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE ||
                keyEvent.getKeyCode() == KeyEvent.KEYCODE_MEDIA_PLAY;

They set the key handler via an input handler:

private final PlaybackOverlayFragment.InputEventHandler mOnInputEventHandler =
        new PlaybackOverlayFragment.InputEventHandler() {
    @Override
    public boolean handleInputEvent(InputEvent event) {
        if (event instanceof KeyEvent) {
            KeyEvent keyEvent = (KeyEvent) event;
            return onKey(null, keyEvent.getKeyCode(), keyEvent);
        }
        return false;
    }
};

which is set via: mFragment.setInputEventHandler(mOnInputEventHandler);


Another good example is in the PlaybackOverlayFragment's onInterceptInputEvent(...) method found here:

private boolean onInterceptInputEvent(InputEvent event) {
    final boolean controlsHidden = areControlsHidden();
    if (DEBUG) Log.v(TAG, "onInterceptInputEvent hidden " + controlsHidden + " " + event);
    boolean consumeEvent = false;
    int keyCode = KeyEvent.KEYCODE_UNKNOWN;

    if (mInputEventHandler != null) {
        consumeEvent = mInputEventHandler.handleInputEvent(event);
    }
    if (event instanceof KeyEvent) {
        keyCode = ((KeyEvent) event).getKeyCode();
    }

    switch (keyCode) {
        case KeyEvent.KEYCODE_DPAD_CENTER:
        case KeyEvent.KEYCODE_DPAD_DOWN:
        case KeyEvent.KEYCODE_DPAD_UP:
        case KeyEvent.KEYCODE_DPAD_LEFT:
        case KeyEvent.KEYCODE_DPAD_RIGHT:

They attach the interceptor via: getVerticalGridView().setOnKeyInterceptListener(mOnKeyInterceptListener);

Let me know if this solves your issue.