Attend XDA's Second Annual Developer Conference, XDA:DevCon 2014!
5,742,296 Members 51,559 Now Online
XDA Developers Android and Mobile Development Forum

[GUIDE] Implement RemoteController in your app

Tip us?
 
Dr.Alexander_Breen
Old
(Last edited by Dr.Alexander_Breen; 7th May 2014 at 08:31 AM.)
#1  
Dr.Alexander_Breen's Avatar
Senior Member - OP
Thanks Meter 901
Posts: 415
Join Date: Jun 2012
Location: Moscow

 
DONATE TO ME
Default [GUIDE] Implement RemoteController in your app

Hello, fellow XDA-ers.
Today I want to tell you about new RemoteController class introduced in Android 4.4.

What does this class do?
Quote:
The RemoteController class is used to control media playback, display and update media metadata and playback status, published by applications using the RemoteControlClient class.
However, the documentation is rather empty. Sure, there are methods etc., but it's not really helpful if you have zero knowledge about this class and you want to implement it right away.

So, here I am to help you.
Sit down and get your IDE and a cup of coffee/tea ready.

WARNING: This guide is oriented at experienced Android developers. So, I'll cover the main points, but don't expect me to go into details of something which is not directly related to RemoteController.

1. Creating a service to control media playback.
To avoid illegal access to metadata and media playback, user have to activate a specific NotificationListenerService in Security settings.
As a developer, you have to implement one.

Requirements for this service:
1. Has to extend [B]NotificationListenerService[/B
2. Has to implement RemoteController.OnClientUpdateListener.


You can look at my implementation on GitHub.

Let's now talk about details.
It's better to leave onNotificationPosted and onNotificationRemoved empty if you don't plan to actually process notifications in your app; otherwise you know what to do.

Now we need to register this service in AndroidManifest.xml.

Add the following to the manifest(replacing <service-package> with actual package where your service lies, and <service-name> with your Service class name):
Code:
Select Code
<service
android:name="<service-package>.<service-name>"
android:label="@string/service_name"
android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE" >
    <intent-filter>
       <action android:name="android.service.notification.NotificationListenerService" />
    </intent-filter>
</service>
Please note: do not override onBind() method, as it will break the functionality of the service.
"service_name" is a name for your service which will be shown in Security->Notification access.

2. Handling client update events.
Now on to implementation of RemoteController.OnClientUpdateListener. .
You may process everything inside this service, or (which I consider a better alternative, as it gives more flexibility, and you can re-use your service for different apps) re-call methods of external callback to process the client update events.

Here, however, we will only talk about the methods and which parameters are passed to them.
The official description is good enough and I recommend reading it before processing further.


Code:
Select Code
onClientChange(boolean clearing)
Pretty self-explanatory. "true" will be passed if metadata has to be cleared as there is no valid RemoteControlClient, "false" otherwise.
Code:
Select Code
onClientMetadataUpdate(RemoteController.MetadataEditor metadataEditor)
metadataEditor is a container which has all the available metadata.

How to access it? Very simple.
For text data, you use RemoteController.MetadataEditor#getString(int key, String defaultValue);

"R.string.unknown" is a reference to String resource with name "unknown", which will be used to replace missing metadata.

To get artist name as a String, use:
metadataEditor.getString(MediaMetadataRetriever.METADATA_KEY_TITLE, getString(R.string.unknown))

To get title of the song as a String, use:
metadataEditor.getString(MediaMetadataRetriever.METADATA_KEY_ALBUM, getString(R.string.unknown))

To get the duration of the song as a long, use:
metadataEditor.getLong(MediaMetadataRetriever.METADATA_KEY_DURATION, 1)
1 is the default duration of the song to be used in case the duration is unknown.

To get the artwork as a Bitmap, use:
metadataEditor.getBitmap(RemoteController.MetadataEditor.BITMAP_KEY_ARTWORK, null)
"null" is the default value for the artwork. You may use some placeholder image, however.

And here is one pitfall.
Naturally, you would expect artist name to be saved with the key MediaMetadataRetriever.METADATA_KEY_ARTIST.
However, some players, like PowerAmp, save it with key MediaMetadataRetriever.METADATA_KEY_ALBUMARTIS.

So, to avoid unnecessary checks, you may use the following(returns String):
mArtistText.setText(editor.getString(MediaMetadataRetriever.METADATA_KEY_ARTIST, editor.getString(MediaMetadataRetriever.METADATA_KEY_ALBUMARTIST, getString(R.string.unknown))));

What does it do - it tries to get the artist name by the key METADATA_KEY_ARTIST, and if there is no such String with this key, it will fall back to default value, which, in turn, will try to get the artist name by the key METADATA_KEY_ALBUMARTIST, and if it fails again, it falls back to "unknown" String resource.
So, you may fetch the metadata using these methods and then process it as you like.

Code:
Select Code
onClientPlaybackStateUpdate(int state, long stateChangeTimeMs, long currentPosMs, float speed)
Called when the state of the player has changed.
Right now this method is not called, probably due to bug.
state - playstate of player. Read the RemoteControllerClient class description to get the list of available playstates.
For example, RemoteControlClient.PLAYSTATE_PLAYING means that music is currently playing.

stateChangeTimeMs - the system time at which the change happened.

currentPosMs - current playback position in milliseconds.

speed - a speed at which playback occurs. 1.0f is normal playback, 2.0f is 2x-speeded playback, 0.5f is 0.5x-speeded playback etc.
Code:
Select Code
onClientPlaybackStateUpdate (int state)
state - playstate of player. Read the RemoteControllerClient class description to get the list of available playstates.
Code:
Select Code
onClientTransportControlUpdate (int transportControlFlags)
transportControlFlags - player capabilities in form of bitmask.
This is one interesting method. It reports the capabilities of current player in form of bitmask.
Let's say, for example, you want to know if current player supports "fast forward" media key.
Here is how to do it:

if(transportControlFlags & RemoteControlClient.FLAG_KEY_MEDIA_FAST_FORWARD != 0) doSomethingIfSupport(); else doSomethingIfDoesNotSupport(); 

All of the flags are listed in RemoteControlClient class description.
3. Creating RemoteController object.

The preparations are finished.
Now we need to construct RemoteController object.

The constructor of RemoteController takes two arguments. First is Context, and second is RemoteController.OnClientUpdateListener.
You should know how to fetch Context already.

Now let's talk about the second parameter. You have to pass YOUR SERVICE implementing RemoteController.OnClientUpdateListener and extending NotificationListenerService. This is a must, otherwise you won't be able to register your RemoteController to the system.

So, in your service, use something like this:
Code:
Select Code
public class RemoteControlService extends NotificationListenerService implements RemoteController.OnClientUpdateListener {
           private RemoteController mRemoteController;
           private Context mContext;
           ...
           @Override
           public void onCreate() {
                mContext = getApplicationContext();
                mRemoteController = new RemoteController(mContext, this);
           }
           ...
Now to activate our RemoteController we have to register it using AudioManager.
Please note that AudioManager#registerRemoteController returns "true" in case the registration was successful, and "false" otherwise.
When can it return "false"? I know only two cases:
1. You have not activated your NotificationListenerService in Security -> Notification Access.
2. Your RemoteController.OnClientUpdateListener implementation is not a class extending NotificationListenerService.
Code:
Select Code
if(!((AudioManager)mContext.getSystemService(Context.AUDIO_SERVICE)).registerRemoteController(mRemoteController)) {
    //handle registration failure
} else {
    mRemoteController.setArtworkConfiguration(BITMAP_WIDTH, BITMAP_HEIGHT);
    setSynchronizationMode(mRemoteController, RemoteController.POSITION_SYNCHRONIZATION_CHECK);
}
Of course, we will have to deactivate RemoteController at some point with this code.
Code:
Select Code
((AudioManager)mContext.getSystemService(Context.AUDIO_SERVICE)).unregisterRemoteController(mRemoteController);
By default you will NOT receive artwork updates.
To receive artwork, call setArtworkConfiguration (int, int). First argument is width of the artwork, and second is the height of the artwork.
Please note that this method can fail, so check if it returns true or false.
To stop receiving artwork, call clearArtworkConfiguration().

4. Controlling media playback.

We can send media key events to RemoteControlClient.
Also, we can change position of playback for players which support it(currently only Google Play Music supports it).

You can send key events using this helper method:
Code:
Select Code
private boolean sendKeyEvent(int keyCode) {
                //send "down" and "up" keyevents.
                KeyEvent keyEvent = new KeyEvent(KeyEvent.ACTION_DOWN, keyCode);
                boolean first = mRemoteController.sendMediaKeyEvent(keyEvent);
                keyEvent = new KeyEvent(KeyEvent.ACTION_UP, keyCode);
                boolean second = mRemoteController.sendMediaKeyEvent(keyEvent);
                
                return first && second; //if both  clicks were delivered successfully
}
"keyCode" is the code of the pressed media button. For example, sending KeyEvent.KEYCODE_MEDIA_NEXT will cause the player to change track to next. Note that we send both "down" event and "up" method - without that it will get stuck after first command.

To seek to some position in current song, use RemoteController#seekTo(long). The parameter is the position in the song in milliseconds.
Please note that it will have no effect if the player does not support remote position control.

5. Getting current position.
RIght now the onClientPlaybackStateUpdate(int state, long stateChangeTimeMs, long currentPosMs, float speed) method is broken, as it's not called when the position is updated. So, you have to manually fetch current position. To do this, use the RemoteController#getEstimatedMediaPosition() method - it returns current position in milliseconds(or other values, like 0, if player does not support position update).

To update it periodically, you may use Handler and Runnable. Look at the implementation on GitHub as the reference.
The Following 11 Users Say Thank You to Dr.Alexander_Breen For This Useful Post: [ Click to Expand ]
 
doomedez
Old
#2  
doomedez's Avatar
Junior Member
Thanks Meter 5
Posts: 17
Join Date: Sep 2013
Location: Minsk
Hi. Good tutorial! How can i use RemoteController without MediaPlayer class? I'm using custom engine for playback.
My app on google play - Stellio
 
Timmmmmm
Old
#3  
Member
Thanks Meter 30
Posts: 55
Join Date: Nov 2009

 
DONATE TO ME
XDA is usually pretty shi tty so I never come on here, and hence I had to reset my password to say thank you! This was really useful.
 
corrytrevor
Old
#4  
Junior Member
Thanks Meter 10
Posts: 23
Join Date: Apr 2012
Location: Ottawa

 
DONATE TO ME
Default Including this code in NotificationListener Service on 4.3

Thank you for taking the time to describe the implementation in such detail. My only problem now is including implements RemoteController.OnClientUpdateListener in my NotificationListener class for an app that supports 4.0+. As soon as this service is started up by a 4.3 device the app crashes (reason is obvious, 4.3 doesn't support remote controller). The only solution I've found is to create 2 seperate notification listener classes, one for 4.3 and one for 4.4 (which has the RemoteController code in it). This also creates 2 entries in Notification Access list in the security settings.

Any ideas on how to make a hybrid service for 4.3/4.4 that implements the necessary RemoteController code?
 
mrmaffen
Old
#5  
mrmaffen's Avatar
Member
Thanks Meter 8
Posts: 73
Join Date: Feb 2010
Quote:
Originally Posted by corrytrevor View Post
Thank you for taking the time to describe the implementation in such detail. My only problem now is including implements RemoteController.OnClientUpdateListener in my NotificationListener class for an app that supports 4.0+. As soon as this service is started up by a 4.3 device the app crashes (reason is obvious, 4.3 doesn't support remote controller). The only solution I've found is to create 2 seperate notification listener classes, one for 4.3 and one for 4.4 (which has the RemoteController code in it). This also creates 2 entries in Notification Access list in the security settings.

Any ideas on how to make a hybrid service for 4.3/4.4 that implements the necessary RemoteController code?
I have the exact same problem. Been searching for hours now, but I just couldn't come up with a solution for that issue.

Does anybody know how to solve this? Any kind of hint would be highly appreciated!
Galaxy Nexus GSM - Android 4.2 JOP40C: Rooted Busybox Odexed
Motorola Xoom - Team EOS 3 JB Nightlies
LG Optimus Speed (P990) - ParanoidAndroid
HTC HD2 - NexusHD2 JB
 
mrmaffen
Old
(Last edited by mrmaffen; 24th May 2014 at 03:02 PM.) Reason: linked to SO
#6  
mrmaffen's Avatar
Member
Thanks Meter 8
Posts: 73
Join Date: Feb 2010
Quote:
Originally Posted by corrytrevor View Post
Thank you for taking the time to describe the implementation in such detail. My only problem now is including implements RemoteController.OnClientUpdateListener in my NotificationListener class for an app that supports 4.0+. As soon as this service is started up by a 4.3 device the app crashes (reason is obvious, 4.3 doesn't support remote controller). The only solution I've found is to create 2 seperate notification listener classes, one for 4.3 and one for 4.4 (which has the RemoteController code in it). This also creates 2 entries in Notification Access list in the security settings.

Any ideas on how to make a hybrid service for 4.3/4.4 that implements the necessary RemoteController code?
I found a solution to this problem (or rather WisdomWolf did). http://stackoverflow.com/questions/2...stener-crashes

Problem solved
Galaxy Nexus GSM - Android 4.2 JOP40C: Rooted Busybox Odexed
Motorola Xoom - Team EOS 3 JB Nightlies
LG Optimus Speed (P990) - ParanoidAndroid
HTC HD2 - NexusHD2 JB
The Following User Says Thank You to mrmaffen For This Useful Post: [ Click to Expand ]
Thread Tools Search this Thread
Search this Thread:

Advanced Search
Display Modes


XDA PORTAL POSTS

Xperia Z1 Stereo Speaker Mod, Cell Phone Unlocking Legal Again! – XDA Developer TV

Cell Phone Unlocking is legal again!! That and much … more

[OTA Captured] Verizon LG G Pad 8.3 Finally Gets Official KitKat Update

It’s been almost nine months since the LG G Pad … more

Enjoy Wallpaper Overload with PhotoPhase

We don’t usually feature live wallpapers on the XDA Portal unless they are somewhat innovative … more

Monitor What Your Phone’s Camera Sees with Android Wear

Having a smart device strapped to your wrist certainly has its merits. A … more