[Java for Android App Development] Dr.Alexander_Breen:
Hello, my fellow XDAers.

Recentlly I've created a guide about how to implement your all lockscreen-like music controls.

However, the problem was that this way of implementing your remote music controls(let's call it this way, okay?) was extremely counter-intuitive.

Starting from methods which have bunch of setSomething but no getSomething and ending with weird Handler.Callback required.

So, in order to ease this process, I've created small library.

For the sake of simplicity, it's distributed as a JAR file, not as a library project.

Why?

The main reason is:

That doesn't mean that my library is closed-source. It's completely open-sourced, and is available in form of ZIP archive or GitHub repository.

Also, it is licensed under Apache 2.0, so that means you can use it in any of your projects, commercial or not.

Just remember that instead of buying alcohol and junk food you can send me some little donation ;)

Okay, end of introduction.

Now, off to the guide and download links.

The name of this library is:

Remote Metadata Provider​

This library allows you to create your own remote media controls, which will act the same as lockscreen controls.

This is achieved by usage of some Listeners and class called RemoteMetadataProvider.

1. Reference:

Class: RemoteMetadataProvider

[B]static synchronized RemoteMetadataProvider getInstance(Context context)[/B]

This method returns an instance of RemoteMetadataProvider.

Context is required to fetch instance of AudioManager and to send media button commands.
Also, it is used to retrieve launch Intent for current active client(player).

Please note that calling to this method from incorrect API version will result in RuntimeException.
remote-metadata-provider.jar is for API 17 and lower.
remote-metadata-provider-v18.jar is for API 18.

[B]void acquireRemoteControls()[/B]

This method makes current RemoteMetadataProvider active, so you will receive metadata updates.
You MUST call this method when View displaying your metadata becomes visible to the user(the sentence above explains, why).
Please note that only one RemoteMetadataProvider can be active at time.

[B]void dropRemoteControls(boolean destroyRemoteControls)[/B]

This method makes current RemoteMetadataProvider inactive.
You won't be receiving further metadata updates, and most likely you will lose control of current client.
This method MUST be called whenever your view displaying metadata becomes invisible to the user.
If you won't do so, then system can lose it's remote media controls or it can interfere with other apps.

Boolean defines, whenever remote media controls should be destroyed or not. If you have some problems with artwork not displaying in your app after dropping remote controls/acquiring it, when set this parameter to true, otherwise use false(it will save memory and time).

My tests shown that it will not interfere, but it's better to be safe and call this method.

[B]Intent getCurrentClientIntent()[/B]

This will return launch Intent for current player or null if there is no active clients.
It throws NameNotFoundException in case client package wasn't found.
This exception should not happen under normal circumstances - if it was thrown, then probably something bad happened with the system itself.

[B]PendingIntent getCurrentClientPendingIntent()[/B]

Returns PendingIntent of current client or null if there is no current client.
This method isn't used generally, but you can(for example) save PendingIntents of multiple players to re-use them in a future or to prevent fast client loss.

[B]Looper getLooper()[/B]

Returns user-defined Looper which is used if you are processing metadata in some special thread or null if default Looper is used.

[B]OnArtworkChangeListener getOnArtworkChangeListener()[/B]

Returns callback to be invoked when artwork has to be updated or null if there is no such callback..

[B]OnMetadataChangeListener getOnMetadataChangeListener()[/B]

Returns callback to be invoked when metadata has to be updated or null if there is no such callback.

[B]OnPlaybackStateChangeListener getOnPlaybackStateChangeListener()[/B]

Returns callback to be invoked when playback state has changed.

[B]OnRemoteControlFeaturesChangeListener getOnRemoteControlFlagsChangeListener()[/B]

Returns callback to be invoked when remote control features has changed.

[B]boolean isClientActive()[/B]

Returns true if there is active client which can send metadata and receive media commands and false otherwise.

[B]void removeLooper()[/B]

Tells the RemoteMetadataProvider to recreate Handler without Looper(if there was one) on next acquireRemoteControls() call.

[B]void sendBroadcastMediaCommand(int keyCode)[/B]

Sends media button input event with specified keycode(use KeyEvent class to get keycodes from there) in form of Broadcast message.
In most cases it will fail, but some players like Neutron Player and Poweramp are able to receive this broadcast message.
Do not use this method if you have active clients.
Use this method only if you don't have any active clients and you want to try to start client with broadcast message.

[B]void sendBroadcastMediaCommand(MediaCommand command) [/B]

The same as the method above, but you specify command as a MediaCommand enum.

[B]boolean sendMediaCommand(int keyCode)[/B]

This command will send media button input event to current active client.
It will return true if command was successfully delivery or false if it failed.
For example, if there is no active clients, it will return false.

[B]boolean sendMediaCommand(MediaCommand command)[/B]

The same as the method above, but instead it uses MediaCommand enum to specify media button input event.

[B]void setCurrentClientPendingIntent(PendingIntent pintent)[/B]

Sets current client PendingIntent to one specified by you. Do not use it unless you received PendingIntent via getCurrentClientPendingIntent().

[B]void setLooper(Looper looper)[/B]

Sets Looper for processing the metadata receiving in another thread. Call acquireRemoteControls() to start using this Looper.

[B]void setOnArtworkChangeListener(OnArtworkChangeListener l)[/B]

Register a callback to be invoked when artwork should be updated.

[B]void setOnMetadataChangeListener(OnMetadataChangeListener l)[/B]

Register a callback to be invoked when metadata should be updated.

[B]setOnPlaybackStateChangeListener(OnPlaybackStateChangeListener l)[/B]

Register a callback to be invoked when playback state should be updated.

[B]setOnRemoteControlFeaturesChangeListener(OnRemoteControlFeaturesChangeListener l)[/B]

Register a callback to be invoked when remote control features should be updated.

Interface: OnArtworkChangeListener

[B]void onArtworkChanged(Bitmap artwork)[/B]

Called when artwork of current song album was updated.
artwork parameter is a Bitmap containing current artwork. May be null if it wasn't specified by player.
Please note that previous Bitmap is recycled after artwork update! So don't forget to use Bitmap#isRecycled() if you're saving album arts somehow.

Interface: OnMetadataChangeListener

[B]void onMetadataChanged(String artist, String title, String album, String albumArtist, long duration)[/B]

Called when remote metadata was updated.

Parameter artist is the artist of current song. May be null if wasn't specified by player. Some players use albumArtist parameter instead.
Parameter title is the title of current song. May be null if wasn't specified by player.
Parameter album is the current song album title. May be null if wasn't specified by player.
Parameter albumArtist is the current song album artist. May be null if wasn't specified by player. Some players(for example, PowerAmp) use this parameter instead of artist parameter.
Parameter duration is the song duration in milliseconds.

Interface: OnPlaybackStateChangeListener

[B]void onPlaybackStateChanged(PlayState playbackState)[/B]

Called when playback state was changed. For example, this method will be called with parameter PlayState.PAUSED
when playback is paused.
Possible values of playbackState parameters are listed in enum class PlayState.

Interface: OnRemoteControlFeaturesChangeListener

[B]void onFeaturesChanged(List<RemoteControlFeature> usesFeatures)[/B]

Called when information about player was changed.
usesFeatures is a list containing different RemoteControlFeature enums. This list describes, to which
buttons the player will respond correctly.
For example, if this list contains RemoteControlFeature.USES_REWIND, then the player would respond to Rewind command.

Enum: MediaCommand

NEXT - use this constant with RemoteMetadataProvider#sendMediaCommand(MediaCommand) to tell the player to jump to next track.

PREVIOUS - use this constant with RemoteMetadataProvider#sendMediaCommand(MediaCommand) to tell the player to jump to previous track.

PLAY - use this constant with RemoteMetadataProvider#sendMediaCommand(MediaCommand) to tell the player to start playback. Usually it is treated the same as PLAY_PAUSE.

PAUSE - use this constant with RemoteMetadataProvider#sendMediaCommand(MediaCommand) to tell the player to pause playback. Usually it is treated the same as PLAY_PAUSE.

PLAY_PAUSE - use this constant with RemoteMetadataProvider#sendMediaCommand(MediaCommand) to tell the player to pause the track if it was playing and to start it if it was paused.

REWIND - use this constant with RemoteMetadataProvider#sendMediaCommand(MediaCommand) to tell the player to rewind the song.

FAST_FORWARD - use this constant with RemoteMetadataProvider#sendMediaCommand(MediaCommand) to tell the player to fast forward the song.

STOP - use this constant with RemoteMetadataProvider#sendMediaCommand(MediaCommand) to tell the player to stop playback.

Enum: RemoteControlFeature

USES_FAST_FORWARD - indicates that player makes use of Fast Forward button.
USES_NEXT - indicates that player makes use of Next button.
USES_PAUSE - indicates that player makes use of Pause button.
USES_PLAY - indicates that player makes use of Play button.
USES_PLAY_PAUSE - indicates that player makes use of Play button.
USES_PREVIOUS - indicates that player makes use of Previous button.
USES_REWIND - indicates that player makes use of Rewind button.
USES_STOP - indicates that player makes use of Stop button.

Enum: PlayState

BUFFERING - the music is buffering right now and should start playing soon.
ERROR - some error occured. We should not except the music to start playing.
FAST_FORWARDING - the music is being fast forwarded.
PAUSED - the music is paused.
PLAYING - the music is playing.
REWINDING - the music is being rewinded.
SKIPPING_BACKWARDS - the music is being skipped backwards.
SKIPPING_FORWARDS - the music is being skipped forwards.
STOPPED - the music is stopped.

1a. Reference for API 18 version of library:

Only the differences will be listed here. If method/interface/enum is not listed here, then it's the same as in pre-API 18 version of library.

Class: RemoteMetadataProvider

[B]void acquireRemoteControls()[/B]

This method makes current RemoteMetadataProvider active, so you will receive metadata updates.
You MUST call this method when View displaying your metadata becomes visible to the user(the sentence above explains, why).
Multiple RemoteMetadataProviders can be active at the time.
Use this method if you DO NOT NEED artwork updates.

[B]void acquireRemoteControls(int maxWidth, int maxHeight)[/B]

This method makes current RemoteMetadataProvider active, so you will receive metadata updates.
You MUST call this method when View displaying your metadata becomes visible to the user(the sentence above explains, why).
Multiple RemoteMetadataProviders can be active at the time.
Use this method if you NEED artwork updates.
Parameter maxWidth is maximum width of Bitmap artwork which you will receive.
Parameter maxHeight is maximum height of Bitmap artwork which you will receive.

[B]void setPlaybackPositionSyncEnabled(boolean isEnabled)[/B]

Sets state of playback position update. If you pass true to this method, then you will receive position updates by OnPlaybackStateChangeListener callback. If you set false, you will not receive position updates.
Please note that this method have to be called ONLY after calling acquireRemoteControls() method, or it will fail.

Interface: OnPlaybackStateChangeListener

[B]onPlaybackStateChanged(PlayState playbackState, long playbackPosition, float speed)[/B]

Called when playback state, playback speed or playback position was changed.
For example, this method will be called with parameter PlayState.PAUSED
when playback is paused.

Possible values of playbackState parameters are listed in enum class PlayState.

Parameter playbackPosition is current playbackPosition is playback position in ms.
Please note that for this parameter to be updated you have to call RemoteMetadataProvider#setPlaybackPositionSyncEnabled(true) first.

Parameter speed is current playback speed. Normal playback speed is 1.0f, 2x is 2.0f etc.

Enum: RemoteMetadataFeature

Everything is same, except for this three new parameters:
USES_POSITIONING - it indicates that current player will send you position updates.
USES_WRITABLE_POSITIONING - currently not used, added for future. Indicates that player should respond to remote position update.
USES_READABLE_POSITIONING - probably not used too. Indicates that player should tell you it's playback position.

2. How to use:

Usage of my library is extremely simple. First of all, add my library to your project as an external JAR.

Then do the following steps:

1) Get the instance of RemoteMetadataProvider with RemoteMetadataProvider.getInstance(context). Remember, this is a singleton, so all getInstance calls will return the same instance. To save time, save it in field variable, let's call it mProvider.

2) Implement necessary listeners and register them. For example, you want to receive metadata updates and you want to show them in TextViews. Then you can use following code.

mProvider.setOnMetadataChangeListener(new OnMetadataChangeListener() {
[MENTION=439709]override[/MENTION]
public void onMetadataChanged(String artist, String title, String album, String albumArtist, long duration) {
mArtistTextView.setText("ARTIST: "+artist);
mTitleTextView.setText("TITLE: "+title);
mAlbumTextView.setText("ALBUM: "+album);
mAlbumArtistTextView.setText("ALBUM ARTIST: "+albumArtist);
mDurationTextView.setText("DURATION: "+(duration/1000)+"s");
}
});

Other listeners are registered in same manner. Read reference to get the details.

3) When your view with current metadata is displayed, call mProvider.acquireRemoteControls(). For example, if you're displaying metadata in Activity, you should call this method in your onResume() method.

3) When your view is hidden, you should call mProvider.dropRemoteControls(boolean). Generally, the parameter should be false, but there are some cases when artwork isn't being displayed after calling dropRemoteControls() and then acquireRemoteControls(). So, if there are problems with artwork, use true as a parameter.

4) To tell player to do something with playback, use mProvider.sendMediaCommand(MediaCommand).

This method will return true if the command was delivered successfully and false otherwise.

For example, you can use following code to make button with id "next" work as a Next button:

findViewById(R.id.next).setOnClickListener(new OnClickListener() {
[MENTION=439709]override[/MENTION]
public void onClick(View v) {
if(!mProvider.sendMediaCommand(MediaCommand.NEXT)) {
Toast.makeText(getApplicationContext(), "Failed to send NEXT_EVENT", Toast.LENGTH_SHORT).show();
}
}
});

This will send NEXT command to player, or will display toast message with error text if there is no active player.

5) If there is no current player, but you know that there is some player which can receive Broadcast media event, you can use

mProvider.sendBroadcastMediaCommand(MediaCommand).

For example, following code can be used to make button with id "next" send Broadcast command on long click.

findViewById(R.id.next).setOnLongClickListener(new OnLongClickListener() {
[MENTION=439709]override[/MENTION]
public boolean onLongClick(View v) {
mProvider.sendBroadcastMediaCommand(MediaCommand.NEXT);
return true;
}
});

Basically, that's all you need.

3. Licensing

This project is licensed under Apache 2.0 license.

4. Source code

GitHub link: https://github.com/DrBreen/RemoteMetadataProvider

Source codes for library and test application are available at the end of the post.

5. Bugs

-On HTC Sense the system will lose lockscreen controls after calling RemoteMetadataProvider#acquireRemoteControls().

This is due to how the lockscreen is being initialized on HTC Sense, and it can't be fixed unless I'll do deep investigation of decompiled source code and write Xposed module which will somehow re-register original system metadata receiver.

Files with "v18" suffix are for Android 4.3. Without this suffix - for Android 4.2.2 and lower.

Please do not cross-use this libraries, or you will get RuntimeException(in getInstance() method) or AbstractMethodError(if you somehow acquire instance of the RemoteMetadataProvider without calling getInstance()).