Similar to how you use[custom playback actions](https://developer.android.com/training/cars/media/enable-playback#custom-icons)to support unique capabilities in the playback view, you can use custom browse actions to support unique capabilities in browsing views. For example, you can use custom browse actions so that users can download playlists or add an item to a queue.

When more custom actions exist than displayed by the Original Equipment Manufacturer (OEM), an overflow menu is displayed to the user. Each custom browse action is defined with an:

- **Action ID:**Unique string identifier
- **Action label:**Text displayed to the user
- **Action icon uniform resource identifier (URI):**Vector drawable that can be tintable

![Custom browse action overflow](https://developer.android.com/static/images/training/cars/custom_browse_action_overflow.png)

**Figure 1.**Custom browse action overflow.

You define a list of custom browse actions globally as part of your`BrowseRoot`. Then attach a subset of these actions to individual`MediaItem`.

When a user interacts with a custom browse action, your app receives a callback in`onCustomAction`. You then handle the action and update the list of actions for the`MediaItem`, if necessary. This is useful for stateful actions like Favorite and Download. For actions that need no updating, such as Play Radio, you needn't update the list of actions.

![Custom browse action toolbar](https://developer.android.com/static/images/training/cars/custom_browse_action_toolbar.png)

**Figure 2.**Custom browse action toolbar.

You can also attach custom browse actions to a browse node root. These actions are displayed in a secondary toolbar under the primary toolbar.

To add custom browse actions to your app:

1. Override two methods in your[`MediaBrowserServiceCompat`](https://developer.android.com/reference/androidx/media/MediaBrowserServiceCompat)implementation:

   - [`onLoadItem(String itemId, @NonNull Result<MediaBrowserCompat.MediaItem> result)`](https://developer.android.com/reference/androidx/media/MediaBrowserServiceCompat#onLoadItem(java.lang.String,androidx.media.MediaBrowserServiceCompat.Result%3Candroid.support.v4.media.MediaBrowserCompat.MediaItem%3E))
   - [`onCustomAction(@NonNull String action, Bundle extras, @NonNull Result<Bundle> result)`](https://developer.android.com/reference/androidx/media/MediaBrowserServiceCompat#onCustomAction(java.lang.String,android.os.Bundle,androidx.media.MediaBrowserServiceCompat.Result%3Candroid.os.Bundle%3E))
2. Parse the action limits at runtime:

   In`onGetRoot`, get the maximum number of actions allowed for each`MediaItem`using the key[`BROWSER_ROOT_HINTS_KEY_CUSTOM_BROWSER_ACTION_LIMIT`](https://developer.android.com/reference/androidx/media/utils/MediaConstants#BROWSER_ROOT_HINTS_KEY_CUSTOM_BROWSER_ACTION_LIMIT())in the`rootHints``Bundle`. A limit of 0 indicates that the feature is not supported by the system.
3. Build the global list of custom browse actions. For each action, create a`Bundle`object with these keys:

   - Action ID`EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID`
   - Action label`EXTRAS_KEY_CUSTOM_BROWSER_ACTION_LABEL`
   - Action icon URI`EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ICON_URI`
4. Add all the action`Bundle`objects to a list.

5. Add the global list to your`BrowseRoot`. In the`BrowseRoot`extras`Bundle`, add the list of actions as a`Parcelable``ArrayList`using the key[`BROWSER_SERVICE_EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ROOT_LIST`](https://developer.android.com/reference/androidx/media/utils/MediaConstants#BROWSER_SERVICE_EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ROOT_LIST()).

6. Add actions to your`MediaItem`objects. You can add actions to individual`MediaItem`objects by including the list of action IDs in the`MediaDescriptionCompat`extras using the key[`DESCRIPTION_EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID_LIST`](https://developer.android.com/reference/androidx/media/utils/MediaConstants#DESCRIPTION_EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID_LIST()). This list must be a subset of the global list of actions you defined in the`BrowseRoot`.

7. Handle actions and return progress or results:

   - In`onCustomAction`, handle the action based on the action ID and any other data you need. You can get the ID of the`MediaItem`that triggered the action from the extras using the key[`EXTRAS_KEY_CUSTOM_BROWSER_ACTION_MEDIA_ITEM_ID`](https://developer.android.com/reference/androidx/media/utils/MediaConstants#EXTRAS_KEY_CUSTOM_BROWSER_ACTION_MEDIA_ITEM_ID()).

   - You can update the list of actions for a`MediaItem`by including the key[`EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM`](https://developer.android.com/reference/androidx/media/utils/MediaConstants#EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM())in the progress or result bundle.

### Update the action state

To override these methods in`MediaBrowserServiceCompat`:  

    public void onLoadItem(String itemId, @NonNull Result<MediaBrowserCompat.MediaItem> result)

and  

    public void onCustomAction(@NonNull String action, Bundle extras, @NonNull Result<Bundle> result)

## Parse actions limit

Check how many custom browse actions are supported:  

    public BrowserRoot onGetRoot(@NonNull String clientPackageName, int clientUid, Bundle rootHints) {
        rootHints.getInt(
                MediaConstants.BROWSER_ROOT_HINTS_KEY_CUSTOM_BROWSER_ACTION_LIMIT, 0)
    }

| **Note:** An action limit of`0`or less indicates the feature is not supported.

## Build a custom browse action

Each action needs to be packed into a separate`Bundle`.

- Action ID:

      bundle.putString(MediaConstants.EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID,
                      "<ACTION_ID>")

- Action label:

      bundle.putString(MediaConstants.EXTRAS_KEY_CUSTOM_BROWSER_ACTION_LABEL,
                      "<ACTION_LABEL>")

- Action icon URI:

      bundle.putString(MediaConstants.EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ICON_URI,
                      "<ACTION_ICON_URI>")

## Add custom browse actions to Parcelable ArrayList

Add all custom browse action`Bundle`objects into an`ArrayList`:  

    private ArrayList<Bundle> createCustomActionsList(
                                            CustomBrowseAction browseActions) {
        ArrayList<Bundle> browseActionsBundle = new ArrayList<>();
        for (CustomBrowseAction browseAction : browseActions) {
            Bundle action = new Bundle();
            action.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID,
                    browseAction.mId);
            action.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_LABEL,
                    getString(browseAction.mLabelResId));
            action.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ICON_URI,
                    browseAction.mIcon);
            browseActionsBundle.add(action);
        }
        return browseActionsBundle;
    }

## Add custom browse action list to browse root

    public BrowserRoot onGetRoot(@NonNull String clientPackageName, int clientUid,
                                 Bundle rootHints) {
        Bundle browserRootExtras = new Bundle();
        browserRootExtras.putParcelableArrayList(
                BROWSER_SERVICE_EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ROOT_LIST,
                createCustomActionsList()));
        mRoot = new BrowserRoot(ROOT_ID, browserRootExtras);
        return mRoot;
    }

## Add actions to a MediaItem

Browse Actions IDs in a`MediaItem`need to be a subset of the global list of Browse Actions given on`onGetRoot`. Actions not in the global list are ignored.  

    MediaDescriptionCompat buildDescription (long id, String title, String subtitle,
                    String description, Uri iconUri, Uri mediaUri,
                    ArrayList<String> browseActionIds) {

        MediaDescriptionCompat.Builder bob = new MediaDescriptionCompat.Builder();
        bob.setMediaId(id);
        bob.setTitle(title);
        bob.setSubtitle(subtitle);
        bob.setDescription(description);
        bob.setIconUri(iconUri);
        bob.setMediaUri(mediaUri);

        Bundle extras = new Bundle();
        extras.putStringArrayList(
              DESCRIPTION_EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID_LIST,
              browseActionIds);

        bob.setExtras(extras);
        return bob.build();
    }
    MediaItem mediaItem = new MediaItem(buildDescription(...), flags);

| **Note:** Put only`String`action IDs in the`MediaItem`. The ID is mapped from the global list passed to the`BrowseRoot`.

## Build onCustomAction result

To build the result:

1. Parse`mediaId`from`Bundle extras`

       @Override
       public void onCustomAction(
                   @NonNull String action, Bundle extras, @NonNull Result<Bundle> result){
           String mediaId = extras.getString(MediaConstans.EXTRAS_KEY_CUSTOM_BROWSER_ACTION_MEDIA_ITEM_ID);
                   }

2. For asynchronous results, detach the result,`result.detach`.

3. Build the result bundle:

   1. Display a message to the user:

          mResultBundle.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_MESSAGE,
                        mContext.getString(stringRes))

   2. Update the item (use to update actions in an item):

          mResultBundle.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM, mediaId);

   3. Open the playback view:

          //Shows user the PBV without changing the playback state
          mResultBundle.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_SHOW_PLAYING_ITEM, null);

   4. Update the browse node:

          //Change current browse node to mediaId
          mResultBundle.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_BROWSE_NODE, mediaId);

4. Check the result:

   - **Error:** Call`result.sendError(resultBundle)`
   - **Progress update:** Call`result.sendProgressUpdate(resultBundle)`
   - **Finish:** Call`result.sendResult(resultBundle)`

## Update the action state

By using the`result.sendProgressUpdate(resultBundle)`method with the[`EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM`](https://developer.android.com/reference/androidx/media/utils/MediaConstants#EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM())key, you can update the`MediaItem`to reflect the new state of the action. This lets you provide real-time feedback to the user about the progress and result of their action.

## Sample download action

This example describes how you can use this feature to implement a download action with three states:

- **Download** is the initial state of the action. When the user selects this action, you can swap it with Downloading and call`sendProgressUpdate`to update the user interface (UI).

- **Downloading**state indicates that the download is in progress. You can use this state to show a progress bar or another indicator to the user.

- **Downloaded** state indicates that the download is complete. When the download finishes, you can swap Downloading with Downloaded and call`sendResult`with the[`EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM`](https://developer.android.com/reference/androidx/media/utils/MediaConstants#EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM())key to indicate that the item should be refreshed. Additionally, you can use the[`EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_MESSAGE`](https://developer.android.com/reference/androidx/media/utils/MediaConstants#EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM())key to display a success message to the user.

This approach lets you provide clear feedback to the user about the download process and its current state. You can add more detail with icons to show 25%, 50%, and 75% download states.

## Sample favorite action

Another example is a favorite action with two states:

- **Favorite** is displayed for items not in the user's favorites list. When the user selects this action, swap it with**Favorited** and call`sendResult`with the[`EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM`](https://developer.android.com/reference/androidx/media/utils/MediaConstants#EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM())key to update the UI.

- **Favorited** is displayed for items in the user's favorites list. When the user selects this action, swap it with**Favorite** and call`sendResult`with the[`EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM`](https://developer.android.com/reference/androidx/media/utils/MediaConstants#EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM())key to update the UI.

This approach provides a clear and consistent way for users to manage their favorite items. These examples showcase the flexibility of custom browse actions and how you can use them to implement a variety of functionalities with real-time feedback for an enhanced user experience in the car's Media app.

You can see a comprehensive sample implementation of this feature in the[`TestMediaApp`](https://android.googlesource.com/platform/packages/apps/Car/tests/+/refs/heads/mirror-car-apps-aosp-release/TestMediaApp/)project.