Similar to how you use custom playback actions 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
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.
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:
Override two methods in your
MediaBrowserServiceCompat
implementation:Parse the action limits at runtime:
In
onGetRoot
, get the maximum number of actions allowed for eachMediaItem
using the keyBROWSER_ROOT_HINTS_KEY_CUSTOM_BROWSER_ACTION_LIMIT
in therootHints
Bundle
. A limit of 0 indicates that the feature is not supported by the system.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
- Action ID
Add all the action
Bundle
objects to a list.Add the global list to your
BrowseRoot
. In theBrowseRoot
extrasBundle
, add the list of actions as aParcelable
ArrayList
using the keyBROWSER_SERVICE_EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ROOT_LIST
.Add actions to your
MediaItem
objects. You can add actions to individualMediaItem
objects by including the list of action IDs in theMediaDescriptionCompat
extras using the keyDESCRIPTION_EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID_LIST
. This list must be a subset of the global list of actions you defined in theBrowseRoot
.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 theMediaItem
that triggered the action from the extras using the keyEXTRAS_KEY_CUSTOM_BROWSER_ACTION_MEDIA_ITEM_ID
.You can update the list of actions for a
MediaItem
by including the keyEXTRAS_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)
}
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);
Build onCustomAction result
To build the result:
Parse
mediaId
fromBundle 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); }
For asynchronous results, detach the result,
result.detach
.Build the result bundle:
Display a message to the user:
mResultBundle.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_MESSAGE, mContext.getString(stringRes))
Update the item (use to update actions in an item):
mResultBundle.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM, mediaId);
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);
Update the browse node:
//Change current browse node to mediaId mResultBundle.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_BROWSE_NODE, mediaId);
Check the result:
- Error: Call
result.sendError(resultBundle)
- Progress update: Call
result.sendProgressUpdate(resultBundle)
- Finish: Call
result.sendResult(resultBundle)
- Error: Call
Update the action state
By using the result.sendProgressUpdate(resultBundle)
method with the
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 theEXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM
key to indicate that the item should be refreshed. Additionally, you can use theEXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_MESSAGE
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 theEXTRAS_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 theEXTRAS_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
project.