# How to Implement a New Two-State Player Action in SmartTube

> Learn how to implement a new two-state player action in SmartTube. Extend TwoStateAction, register it in VideoPlayerGlue, and define XML resources and flags.

- Repository: [Yuriy L/SmartTube](https://github.com/yuliskov/SmartTube)
- Tags: how-to-guide
- Published: 2026-09-14

---

**To implement a new two-state player action in SmartTube, extend the `TwoStateAction` class, register the instance in `VideoPlayerGlue`, and define the corresponding XML resources and configuration flags.**

SmartTube is an open-source Android TV YouTube client that uses the Android Leanback library for its playback interface. Every toggle-style control—from thumbs up/down to screen dimming—is implemented as a specialized **two-state player action**. This guide explains the exact source files and method calls required to add your own toggle control to the player UI.

## Understanding the TwoStateAction Architecture

The foundation for all toggle controls is the **`TwoStateAction`** class located at [`smarttubetv/src/main/java/com/liskovsoft/smartyoutubetv2/tv/ui/playback/actions/TwoStateAction.java`](https://github.com/yuliskov/SmartTube/blob/main/smarttubetv/src/main/java/com/liskovsoft/smartyoutubetv2/tv/ui/playback/actions/TwoStateAction.java). This class extends `PlaybackControlsRow.MultiAction` and manages two distinct visual states through array indexing:

- **`INDEX_OFF` (0)** – Displays the outline icon and "off" label.
- **`INDEX_ON` (1)** – Displays the solid, highlighted icon and "on" label.

When an action transitions to **on**, the class can automatically force a *bound* counterpart action to turn **off**, which is how mutually exclusive toggles like thumbs-up and thumbs-down are implemented. The current state is stored in the action's index and is accessible through the `VideoPlayerGlue` interface.

## Step-by-Step Implementation Guide

### 1. Create a TwoStateAction Subclass

Create a new Java file in the actions package at `smarttubetv/src/main/java/com/liskovsoft/smartyoutubetv2/tv/ui/playback/actions/`. Your class must extend `TwoStateAction` and call the super constructor with a unique action ID and an outline drawable resource.

```java
public class MyNewToggleAction extends TwoStateAction {
    public MyNewToggleAction(Context context) {
        // R.id.action_my_new_toggle defined in ids.xml, R.drawable.lb_ic_my_toggle is the outline icon
        super(context, R.id.action_my_new_toggle, R.drawable.lb_ic_my_toggle);
        
        String[] labels = new String[2];
        labels[INDEX_OFF] = context.getString(R.string.my_toggle_off);
        labels[INDEX_ON] = context.getString(R.string.my_toggle_on);
        setLabels(labels);
    }
}

```

The outline icon you provide is automatically highlighted in the active state using `ActionHelpers.getIconHighlightColor()`.

### 2. (Optional) Bind the Action to a Counterpart

If your toggle must be mutually exclusive with another action—similar to how like/dislike works—instantiate both actions and bind them using `setBoundAction()`:

```java
MyNewToggleAction on = new MyNewToggleAction(context);
AnotherToggleAction off = new AnotherToggleAction(context);

on.setBoundAction(off);
off.setBoundAction(on);

```

The binding logic inside `TwoStateAction.setIndex()` ensures that turning one action **on** forces its bound partner to turn **off**.

### 3. Register the Action in VideoPlayerGlue

Open [`smarttubetv/src/main/java/com/liskovsoft/smartyoutubetv2/tv/ui/playback/other/VideoPlayerGlue.java`](https://github.com/yuliskov/SmartTube/blob/main/smarttubetv/src/main/java/com/liskovsoft/smartyoutubetv2/tv/ui/playback/other/VideoPlayerGlue.java). In the constructor (approximately lines 115-136), register your action by adding it to the internal `mActions` map:

```java
putAction(new MyNewToggleAction(context));

```

This step is required for `VideoPlayerGlue.getButtonState()` and `VideoPlayerGlue.setButtonState()` to recognize your action ID.

### 4. Expose the Action in UI Configuration

SmartTube allows users to enable or disable controls via the player tweaks menu. You must define the following resources:

- **Action ID**: Add a unique ID in [`res/values/ids.xml`](https://github.com/yuliskov/SmartTube/blob/main/res/values/ids.xml):
  ```xml
  <item name="action_my_new_toggle" type="id"/>
  ```

- **Strings**: Add user-facing labels in [`res/values/strings.xml`](https://github.com/yuliskov/SmartTube/blob/main/res/values/strings.xml):
  ```xml
  <string name="my_toggle_off">Toggle Off</string>
  <string name="my_toggle_on">Toggle On</string>
  <string name="action_my_new_toggle">My New Toggle</string>
  ```

- **Icons**: Place the outline PNG assets (e.g., `lb_ic_my_toggle.png`) in `res/drawable`.

- **Configuration Flag**: Add a boolean constant in `PlayerTweaksData` (following the pattern of existing flags like `PLAYER_BUTTON_SOUND_OFF`) to control visibility in settings.

### 5. Toggle the State Programmatically

Other components can read or modify the action state through the glue interface:

```java
// Read current state (returns 0 for OFF, 1 for ON)
int currentState = videoPlayerGlue.getButtonState(R.id.action_my_new_toggle);

// Set state explicitly
videoPlayerGlue.setButtonState(R.id.action_my_new_toggle, TwoStateAction.INDEX_ON);

```

The glue forwards these calls to the underlying `MultiAction` index, updating the UI immediately.

## Key Source Files for Reference

- **[TwoStateAction.java](https://github.com/yuliskov/SmartTube/blob/master/smarttubetv/src/main/java/com/liskovsoft/smartyoutubetv2/tv/ui/playback/actions/TwoStateAction.java)** – Base class defining the two-state mechanism and binding logic.
- **[VideoPlayerGlue.java](https://github.com/yuliskov/SmartTube/blob/master/smarttubetv/src/main/java/com/liskovsoft/smartyoutubetv2/tv/ui/playback/other/VideoPlayerGlue.java)** – Where all actions are instantiated and registered.
- **[ActionHelpers.java](https://github.com/yuliskov/SmartTube/blob/master/smarttubetv/src/main/java/com/liskovsoft/smartyoutubetv2/tv/ui/playback/actions/ActionHelpers.java)** – Utility for generating highlighted icon versions.
- **[ThumbsUpAction.java](https://github.com/yuliskov/SmartTube/blob/master/smarttubetv/src/main/java/com/liskovsoft/smartyoutubetv2/tv/ui/playback/actions/ThumbsUpAction.java)** – Concrete example of a simple toggle implementation.
- **[ids.xml](https://github.com/yuliskov/SmartTube/blob/master/smarttubetv/src/main/res/values/ids.xml)** – Resource definitions for action identifiers.

## Summary

- Extend **`TwoStateAction`** and provide an outline icon and two labels to create a new toggle.
- Use **`setBoundAction()`** to link mutually exclusive controls like thumbs-up and thumbs-down.
- Register the action instance in **`VideoPlayerGlue`** via `putAction()` to make it addressable.
- Define unique IDs in **[`ids.xml`](https://github.com/yuliskov/SmartTube/blob/main/ids.xml)**, strings in **[`strings.xml`](https://github.com/yuliskov/SmartTube/blob/main/strings.xml)**, and add drawables to `res/drawable`.
- Control visibility by adding a flag in **`PlayerTweaksData`** and interact programmatically using `getButtonState()` and `setButtonState()`.

## Frequently Asked Questions

### What is the difference between INDEX_OFF and INDEX_ON in TwoStateAction?

`INDEX_OFF` (value 0) represents the inactive state showing the outline icon, while `INDEX_ON` (value 1) represents the active state showing the solid, highlighted icon. These constants are defined in the `TwoStateAction` class and are used to index into the label and drawable arrays.

### How do I make two actions mutually exclusive in SmartTube?

Instantiate both action classes and call `setBoundAction()` on each instance, passing the counterpart as the argument. According to the source code in [`TwoStateAction.java`](https://github.com/yuliskov/SmartTube/blob/main/TwoStateAction.java), when one bound action receives an `INDEX_ON` state, it automatically forces the other to `INDEX_OFF`.

### Where are the drawable icons for player actions defined?

Place the outline version of your icon (e.g., `lb_ic_my_toggle.png`) in `smarttubetv/src/main/res/drawable/`. The highlighted active version is generated automatically at runtime by `ActionHelpers.getIconHighlightColor()`; you do not need to provide a separate solid icon.

### Can I change the state of a player action programmatically without user interaction?

Yes. Use `VideoPlayerGlue.setButtonState(int actionId, int buttonState)` where `actionId` is your resource ID (e.g., `R.id.action_my_new_toggle`) and `buttonState` is either `TwoStateAction.INDEX_OFF` or `TwoStateAction.INDEX_ON`. The UI will update immediately to reflect the new state.