6

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: Link dev android.

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;
    }
});
Bulat
  • 720
  • 7
  • 15
Anindya Basu
  • 659
  • 1
  • 6
  • 18

1 Answers1

8

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.

Kyle Venn
  • 4,597
  • 27
  • 41
  • I'm a little confused on how to implement this for my specific case. All I have for my tv app is one fullscreen activity that's just 1 image, and I want to change the image depending on which remote button I press. There is no video or anything else – Anindya Basu Sep 14 '16 at 17:51
  • I edited my answer for the case of just an activity. Let me know if you need any clarification. [This link](http://stackoverflow.com/a/5296551/1759443) might provide more insight. As well as [this link](https://developer.android.com/reference/android/view/KeyEvent.html). – Kyle Venn Sep 15 '16 at 14:42
  • https://codeshare.io/JYPqO I tried implementing it like this however when I'm pressing d-pad buttons in the emulator I'm still not getting any output. Your clarifications helped me understand everything a little more though! Am I not implementing the methods correctly? – Anindya Basu Sep 15 '16 at 17:04
  • 1
    You're so close! So in your example you have two key listeners, one that you've set on a view (mContentView) and one that you set on the activity itself (@Override onKey). You can remove the key listener that you added to the view, and move the actual body of the `onKey` method to the activity's onKey method. Then that should work. I've attached code at the top of my post since you're 90% there. I tested it on my TV and it is working. – Kyle Venn Sep 16 '16 at 14:27
  • 1
    I found another guide that puts things simply for other people's future reference: https://developer.android.com/training/game-controllers/controller-input.html#dpad – Kyle Venn Sep 19 '16 at 16:55
  • also for numlock codes are like this: keyCode=KEYCODE_1, keyCode=KEYCODE_2, keyCode=KEYCODE_3, ... keyCode=KEYCODE_9 – mehmet Aug 25 '18 at 10:53
  • 1
    Wow this comment is truly a life saver, thank you sir. – Mohaimanul Chowdhury Jan 14 '21 at 23:13