[LIBRARY / GUIDE] Remote Metadata Provider - Java for Android App Development
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:
The library relies heavily on hidden interface IRemoteControlDisplay and hidden methods of AudioManager.
Accessing IRemoteControlDisplay even with reflection is IMPOSSIBLE, because that will require some tool to modify classes at runtime.
This is way too complex, so instead I use a modified android.jar with hidden classes exposed.
So, in order to use this library, you would have to download modified android.jar(or create it yourself), and create modified android platform for Eclipse...
Doesn't sound like fun, does it?
So, to prevent this, I've packaged my project as a library JAR.
Click to expand...
Click to collapse
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
Code:
[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.
Code:
[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.
Code:
[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.
Code:
[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.
Code:
[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.
Code:
[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.
Code:
[B]OnArtworkChangeListener getOnArtworkChangeListener()[/B]
Returns callback to be invoked when artwork has to be updated or null if there is no such callback..
Code:
[B]OnMetadataChangeListener getOnMetadataChangeListener()[/B]
Returns callback to be invoked when metadata has to be updated or null if there is no such callback.
Code:
[B]OnPlaybackStateChangeListener getOnPlaybackStateChangeListener()[/B]
Returns callback to be invoked when playback state has changed.
Code:
[B]OnRemoteControlFeaturesChangeListener getOnRemoteControlFlagsChangeListener()[/B]
Returns callback to be invoked when remote control features has changed.
Code:
[B]boolean isClientActive()[/B]
Returns true if there is active client which can send metadata and receive media commands and false otherwise.
Code:
[B]void removeLooper()[/B]
Tells the RemoteMetadataProvider to recreate Handler without Looper(if there was one) on next acquireRemoteControls() call.
Code:
[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.
Code:
[B]void sendBroadcastMediaCommand(MediaCommand command) [/B]
The same as the method above, but you specify command as a MediaCommand enum.
Code:
[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.
Code:
[B]boolean sendMediaCommand(MediaCommand command)[/B]
The same as the method above, but instead it uses MediaCommand enum to specify media button input event.
Code:
[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().
Code:
[B]void setLooper(Looper looper)[/B]
Sets Looper for processing the metadata receiving in another thread. Call acquireRemoteControls() to start using this Looper.
Code:
[B]void setOnArtworkChangeListener(OnArtworkChangeListener l)[/B]
Register a callback to be invoked when artwork should be updated.
Code:
[B]void setOnMetadataChangeListener(OnMetadataChangeListener l)[/B]
Register a callback to be invoked when metadata should be updated.
Code:
[B]setOnPlaybackStateChangeListener(OnPlaybackStateChangeListener l)[/B]
Register a callback to be invoked when playback state should be updated.
Code:
[B]setOnRemoteControlFeaturesChangeListener(OnRemoteControlFeaturesChangeListener l)[/B]
Register a callback to be invoked when remote control features should be updated.
Interface: OnArtworkChangeListener
Code:
[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
Code:
[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
Code:
[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
Code:
[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
Code:
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
Code:
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
Code:
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
Code:
[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.
Code:
[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.
Code:
[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
Code:
[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
Code:
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.
Code:
mProvider.setOnMetadataChangeListener(new OnMetadataChangeListener() {
[user=439709]@override[/user]
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:
Code:
findViewById(R.id.next).setOnClickListener(new OnClickListener() {
[user=439709]@override[/user]
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.
Code:
findViewById(R.id.next).setOnLongClickListener(new OnLongClickListener() {
[user=439709]@override[/user]
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()).
Nice job. :good:
Added library and source codes for Android 4.3.
I an getting the following error in the "exported" version of the apk, but not in the debug compiled version.
09-25 07:25:56.112: E/JavaBinder(24862): java.lang.AbstractMethodError: abstract method not implemented
09-25 07:25:56.112: E/JavaBinder(24862): at android.media.IRemoteControlDisplay$Stub.setCurrentClientId(IRemoteControlDisplay.java)
09-25 07:25:56.112: E/JavaBinder(24862): at android.media.IRemoteControlDisplay$Stub.onTransact(IRemoteControlDisplay.java:65)
09-25 07:25:56.112: E/JavaBinder(24862): at android.os.Binder.execTransact(Binder.java:367)
09-25 07:25:56.112: E/JavaBinder(24862): at dalvik.system.NativeStart.run(Native Method)
09-25 07:25:56.112: W/dalvikvm(24862): threadid=8: thread exiting with uncaught exception (group=0x41d3b2a0)
09-25 07:25:56.112: E/android.os.Debug(2243): [email protected] > dumpstate -k -t -z -d -o /data/log/dumpstate_app_error
09-25 07:25:56.112: E/AndroidRuntime(24862): FATAL EXCEPTION: Binder_1
09-25 07:25:56.112: E/AndroidRuntime(24862): java.lang.AbstractMethodError: abstract method not implemented
09-25 07:25:56.112: E/AndroidRuntime(24862): at android.media.IRemoteControlDisplay$Stub.setCurrentClientId(IRemoteControlDisplay.java)
09-25 07:25:56.112: E/AndroidRuntime(24862): at android.media.IRemoteControlDisplay$Stub.onTransact(IRemoteControlDisplay.java:65)
09-25 07:25:56.112: E/AndroidRuntime(24862): at android.os.Binder.execTransact(Binder.java:367)
09-25 07:25:56.112: E/AndroidRuntime(24862): at dalvik.system.NativeStart.run(Native Method)
Click to expand...
Click to collapse
I have added the following lines in proguard to make the warnings go away.
-dontwarn android.media.IRemoteControlDisplay$Stub
-dontwarn android.media.IRemoteControlDisplay
-dontwarn android.media.AudioManager
Click to expand...
Click to collapse
Any idea why this is happening?
spadival said:
I an getting the following error in the "exported" version of the apk, but not in the debug compiled version.
I have added the following lines in proguard to make the warnings go away.
Any idea why this is happening?
Click to expand...
Click to collapse
I think that's because of wrong versioning. It looks like you somehow use the 4.2.2 version for 4.3 or the other way.
Dr.Alexander_Breen said:
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()).
Click to expand...
Click to collapse
So I take it this means that if I wanted to implement this in an application designed to run on API 14+ (including KitKat and beyond) I'll have to use the guide and do it manually. Am I correct?
At first, thank you for this awesome Lib!
It is working very well on 4.2.2 CM.
But on 4.4 (Nexus 7 2013) and 4.1.2 (S3 stock) I am not able to read the playback state. Any idea?
Edit: The next and previous button on 4.4 Nex and 4.1.2 s3 is working well. It is just the playback state..
Edit2: On your floating music widget it is not working, too :/
Edit3: setOnPlaybackStateChangeListener and setOnMetadataChangeListener seem not to work with 4.4
Thanks
WisdomWolf said:
So I take it this means that if I wanted to implement this in an application designed to run on API 14+ (including KitKat and beyond) I'll have to use the guide and do it manually. Am I correct?
Click to expand...
Click to collapse
Well, on KitKat there is a class called RemoteController, which has the same functionality, so you does not need to use this library on API 19+.
v18 library is designed to run only on API 18.
"Normal" version should run fine of API 14, 15, 16, 17.
Okay. Thanks for the reply.
I used now intentfilter with the ending "playstatechanged". With this action the music apps sends the extra "playing" ture/false.
I tested it with s3 music player and google play music on nexus 7. Worked fine. Just the huawei music player is not working.
Edit: Your apps just need an update right now to make them working with 4.3 / 4.4?
On 4.3 your apps should work but on samsung s3 with 4.3 it is not working well with the stock music player. Just as information
Anyway good library
KitKat
Anyone please give tutorial how does RemoteController works on kitkat :/
Dr.Alexander_Breen said:
Well, on KitKat there is a class called RemoteController, which has the same functionality, so you does not need to use this library on API 19+.
v18 library is designed to run only on API 18.
"Normal" version should run fine of API 14, 15, 16, 17.
Click to expand...
Click to collapse
What's wrong with creating a unified library that will work across API 14-18?
WisdomWolf said:
What's wrong with creating a unified library that will work across API 14-18?
Click to expand...
Click to collapse
There's nothing wrong. It just was that I didn't have an idea how to unite them because of conflicting IRemoteControlDisplay.aidl file which has to be in the same package with same name, yet different for different versions. Now I've understood how to combine it, so I'm currently working on RemoteControllerCompat, which will bring unified library usage across all API beginning from ICS(V14).
Oh, OK. I could probably submit a pull request as I've already managed to combine them. Or you can find my fork of your repo on Github.
EDIT: I sent you a pull request. Also, posting my modified version of your RemoteMediaTest application that works with ICS, JB, and KK's RemoteController class.
https://github.com/WisdomWolf/TESTRemoteMedia
Great work!!!
You are bloody awesome mate. You have done a marvelous job with this library. I cannot thank you enough. I am just getting started with Android development, was stuck at those internal and hidden classes. For some reason eclipse would force close after modifying the ADT plugin. That's when i stumbled upon your thread. Thanks a ton once again. You Rock!! :good:
Here is the guide for the RemoteController:
[GUIDE] Implement RemoteController in your app
I am thoroughly stumped. Do you have any suggestions for using RemoteController in an application that is also designed to handle notifications and be backwards compatible with 4.3? The odd requirement to inherit NotificationListener really throws a wrench in things. I know I could actually define two different notification listeners but that seems inefficient and there's got to be a better way.
WisdomWolf said:
I am thoroughly stumped. Do you have any suggestions for using RemoteController in an application that is also designed to handle notifications and be backwards compatible with 4.3? The odd requirement to inherit NotificationListener really throws a wrench in things. I know I could actually define two different notification listeners but that seems inefficient and there's got to be a better way.
Click to expand...
Click to collapse
We probably could use reflection to register the guts of RemoteController directly to AudioService, bypassing the NotificationListener check. I suppose it's possible, but it can have unknown side effects. Do you want me to try it out, or you can do it yourself?
I have very little experience with using reflection, so your help would be greatly appreciated. I've tried all sorts of workarounds, but the registration always fails. The only other solution that I could think of was writing a dummy OnClientUpdateListener interface to be loaded when running API level 18. Not entirely sure how to accomplish that either though, but I believe it's something that could be accomplished by making use of ClassLoader. Any help you can offer would be very welcome. I'd still be trying to wrap my head around metadata if it weren't for your library and articles.
I figured it out. Your tip about reflection was really useful, but after chasing my tail for a bit I discovered that reflection wasn't necessary. I used the source code to find out exactly how the permission access was granted. It looks like it takes the OnClientUpdateListener from the RemoteController that gets passed to it and then checks the class against the enabled notificationlisteners. Once I realized that, I simply rewrote the registerRemoteController method to try getClass().getEnclosingClass() first. The registration bypasses the AudioManager, but all the other method calls are left in tact.
You setup a great example of a support library, so I used that concept to create a support library for KitKat's RemoteController class. It includes the modified registration method along with some passthroughs to otherwise inaccessible methods like getArtworkSize() and getRemoteControlClientPackageName(). I don't know if I'll have time to write a guide, but I've included the library in case anyone else is interested. You can find the source on my github.
Thanks a bunch for the library Dr. Alexander! It works pretty good and has saved me on a project that I'm currently working on.
...but I still have seen an issue or two. Namely, Samsung's default player that comes with its TouchWiz devices seems to not work want to work here. On a Galaxy S3 and S4 the default music player refuses to work with your library. However, other music apps on the phone do work (like Pandora and Rdio).
I'm guessing that this is not an issue that you can truly fix, given the nature of all of this. But I'm just hoping that maybe, just maybe, there's a chance.
Thanks mister, you've done a great job. And without your RemoteController guide, I would have never gotten that working either.
Related
[Q] Android app auto starts again when device rotated
Hello all, I've written a simple audio player app and testing it in Samsung Galaxy S. It is a simple app with can create playlist and some buttons to play the mp3 file. However, there's a funny bug in it. After starting the app, whenever I rotate the phone, the running app will launch another instance of the app. If I rotate again, another instance (3rd) would be started. So u can hear 3 copies of the same audio being played simultaneously. What could be wrong with it? Is there some code which can prevent program re-entry? Thanks.
It's not a bug, that's the way andorid OS works. You need to account for this in OnCreate for your activity. I assume you are spawning off the actual playing of the MP3 in a thread or service. You need to check if the service is already running in OnCreate and attach, else spawn a new one.
Hello Gene, How do I check if an activity is already running? I could not find the answer on the android developer page (the topic on application fundamentals). And no, I'm not converting it to a service yet as this is my first app. Haven't explore service yet. Thanks.
I read on another article, a simple way to prevent this is to add overwrite the onConfigurationChanged function @Override public void onConfigurationChanged(Configuration newConfig) { //ignore orientation change super.onConfigurationChanged(newConfig); } and modify the androidmanifest.xml <activity android:name="selectCategories" android:configChanges="orientation|keyboardHidden"></activity> But it still launch multiple instance of the app. Thanks.
This is a good question and should be in an FAQ somewhere. As already mentioned, changing the display orientation basically restarts your app. Read the Android dev page on Activity lifecycle for more info. A short answer for your question is: the Bundle parameter for onCreate() will be null when your app is first run. When your app is paused and restarted, that Bundle will be non-null. You can store data in that Bundle by overriding onSaveInstanceState(), then check for that data in onCreate(). It's a good idea to learn how to do this (save/read app data on pause/restart). Once you start testing apps by rotating the display at various times, you'll find a lot of them FC at unexpected places.
This is indeed a topic that keeps surprising people who are new to android (ahem, like me 4-5 months ago ). The solutions above are perfect, however in certain situations there's another trick that might make your life much easier. I suspect it won't help you in this case, but it might help others who tackle the problem and see this thread. Certain applications, mainly games, should be fixed in a single orientation. I.E. you won't be playing angry birds on portrait - that game is locked to landscape, as it should be. In order to lock your activity in a certain orientation you add this attribute to the manifest under the Activity tag. So a standard activity might look something like that, combined with the ignore tag from the previous posts: Code: <activity android:screenOrientation="landscape" android:configChanges="orientation|keyboardHidden" android:name="whatever"> The great thing about this combination is that your activity will not restart at all when the phone is rotated. Of course, for a standard activity you'd usually want to support both landscape and portrait, but if you need your app to be in a fixed orientation this is the way to go - no weird FCs or annoying bugs
Hi, How do I check the bundle? And also, what to do if the bundle is not null? Just return? Thanks. regards r_p_ang said: This is a good question and should be in an FAQ somewhere. As already mentioned, changing the display orientation basically restarts your app. Read the Android dev page on Activity lifecycle for more info. A short answer for your question is: the Bundle parameter for onCreate() will be null when your app is first run. When your app is paused and restarted, that Bundle will be non-null. You can store data in that Bundle by overriding onSaveInstanceState(), then check for that data in onCreate(). It's a good idea to learn how to do this (save/read app data on pause/restart). Once you start testing apps by rotating the display at various times, you'll find a lot of them FC at unexpected places. Click to expand... Click to collapse
[GUIDE] Implement your own lockscreen-like music controls
Beginner developers, beware! This guide isn't beginner-friendly at all and it's targeted at developers who have some knowledge about Android development. Or you can use my new library - Remote Metadata Provider, it's MUCH simplier to use. 0. The Introduction You guys probably seen my apps - Floating Music Widget and Android Control Center. They both share one feature - good music player integration. They can show you metadata and Floating Music Widget even shows album art. While some players provide API for external music controls(like PowerAmp), the others just somehow integrate with lockscreen. How? Sit down, get a cup of tea, and listen to me. With the API Level 14 Google introduced class called RemoteControlClient. Citing Google API Reference: RemoteControlClient enables exposing information meant to be consumed by remote controls capable of displaying metadata, artwork and media transport control buttons. Click to expand... Click to collapse I won't explain how this works - you may go and read some tutorials around the web, there are plenty of them. Or check API Reference here. But. Well, we send metadata and album art. Oh, and on 4.3 we can even send playback position. However...how do we receive it? Well, by some reason, I don't know exactly why, Google has hidden this part of API. Maybe they think it's unsere to let you consume other app data, or maybe they just forgot about it. I've asked them multiple times, why did they hid this part of API, but they just ignored me. So, by posting this article, I hope to maybe somehow make them change their minds and publish this API after all. 1. Getting started Please note that this guide won't give you Activity examples, or any other things. It will give you the bare bones of the implementation of your own media controls. It's NOT intended to be used by Android/Java newbies. PLEASE NOTE THAT IT'S A CLOSED API! IT MAY MALFUNCTION OR NOT WORK AT ALL! Of course, you will need Eclipse IDE. Also you will need modified Android build platform with hidden and internal API enabled. There's an excellent guide on how to do this: Using internal (com.android.internal) and hidden (@hide) APIs Read it, do all five steps, then come back here for a read. Please note that you will need to enable hidden APIs for API Level 18(4.3) and one API from 14 to 17. I recommend doing 17. So, you've enabled hidden and internal API, hacked your ADT plugin, and you're craving for knowledge? Good. Now some theory. When the metadata is sent by RemoteControlClient, it is consumed by object called RemoteControlDisplay. But the problem is, there's no explicit RemoteControlDisplay class, but there is only AIDL interface called IRemoteControlDisplay. 2. Understanding IRemoteControlDisplay So, let's check which methods this interface has. void setCurrentClientId(int clientGeneration, in PendingIntent clientMediaIntent, boolean clearing); This method is used to connect music player to your RemoteControlDisplay. First parameter is an internal ID of current player. Second parameter is PendingIntent which will be used for controlling the playback - this is the "address" where you will send commands like "stop playback", "switch to next", etc. About third parameter...my guess is that it's used when the RemoteControlDisplay is disconnected from current music player. You don't really ned this one. For next methods I will explain only useful parameters. void setPlaybackState(int generationId, int state, long stateChangeTimeMs); This method is called when playback state has changed. For example, it's called when you pause your music. "state" is obviously the current state of your music player. It can be one of the following values: Rarely used: RemoteControlClient.PLAYSTATE_ERROR - well, there was some kind of error. Normally, you won't get this one. RemoteControlClient.PLAYSTATE_BUFFERING - the music is buffering and will start playing very-very soon. Normally used: RemoteControlClient.PLAYSTATE_PAUSED - the music is paused RemoteControlClient.PLAYSTATE_PLAYING - the music is playing. You can check other PLAYSTATE_ constant in RemoteControlClient API reference. void setTransportControlFlags(int generationId, int transportControlFlags); In lockscreen it is used for toggling the widget visibility. I couldn't find any appliance for this method in my apps. Well, it sets flags void setMetadata(int generationId, in Bundle metadata); Well, that's obvious. It is called when RemoteControlDisplay have to update current track metadata. The Bundle which we are receiving containing some metadata. The keys for them are all in class MediaMetadataRetriever. So, for example, to extract song title, you have to do it this way: Code: String title=metadata.getString(Integer.toString(MediaMetadataRetriever.METADATA_KEY_TITLE)); From my research I've found that this Bundle can have the following entries: Those are for "String" entries: MediaMetadataRetriever.METADATA_KEY_ALBUMARTIST MediaMetadataRetriever.METADATA_KEY_ARTIST MediaMetadataRetriever.METADATA_KEY_ALBUM MediaMetadataRetriever.METADATA_KEY_TITLE And this one is "long": MediaMetadataRetriever.METADATA_KEY_DURATION void setArtwork(int generationId, in Bitmap artwork); This one is way too obvious. It gives you the Bitmap with artwork of current song. If there is no artwork, the "artwork" parameter will be null. void setAllMetadata(int generationId, in Bundle metadata, in Bitmap artwork); This call just combines previous two. 3. Implementing IRemoteControlDisplay Hey, I now know everything about RemoteControlDisplay, I will implement my own in split second. Code: public class MyRemoteControlDisplay implements IRemoteControlDisplay Please note that IT WON'T WORK THIS WAY! As IRemoteControlDisplay is actually a AIDL interface, we need to somehow handle marshalling and unmarshalling of data. But luckily, we don't need to think about it. There is a class which handles basic IPC operations - IRemoteControlDisplay$Stub. We just need to extend it. So, the right way to implement your own RemoteControlDisplayClass is: Code: public class MyRemoteControlDisplay extends IRemoteControlDisplay.Stub Then you will have to implement methods of IRemoteControlDisplay. However, now listen to me carefully. Please, don't try to write your own super-cool implementation. Just copy and paste the following code Code: public MyRemoteControlDisplay extends IRemoteControlDisplay.Stub { static final int MSG_SET_ARTWORK = 104; static final int MSG_SET_GENERATION_ID = 103; static final int MSG_SET_METADATA = 101; static final int MSG_SET_TRANSPORT_CONTROLS = 102; static final int MSG_UPDATE_STATE = 100; private WeakReference<Handler> mLocalHandler; MyRemoteControlDisplay(Handler handler) { mLocalHandler = new WeakReference<Handler>(handler); } public void setAllMetadata(int generationId, Bundle metadata, Bitmap bitmap) { Handler handler = mLocalHandler.get(); if (handler != null) { handler.obtainMessage(MSG_SET_METADATA, generationId, 0, metadata).sendToTarget(); handler.obtainMessage(MSG_SET_ARTWORK, generationId, 0, bitmap).sendToTarget(); } } public void setArtwork(int generationId, Bitmap bitmap) { Handler handler = mLocalHandler.get(); if (handler != null) { handler.obtainMessage(MSG_SET_ARTWORK, generationId, 0, bitmap).sendToTarget(); } } public void setCurrentClientId(int clientGeneration, PendingIntent mediaIntent, boolean clearing) throws RemoteException { Handler handler = mLocalHandler.get(); if (handler != null) { handler.obtainMessage(MSG_SET_GENERATION_ID, clientGeneration, (clearing ? 1 : 0), mediaIntent).sendToTarget(); } } public void setMetadata(int generationId, Bundle metadata) { Handler handler = mLocalHandler.get(); if (handler != null) { handler.obtainMessage(MSG_SET_METADATA, generationId, 0, metadata).sendToTarget(); } } public void setPlaybackState(int generationId, int state, long stateChangeTimeMs) { Handler handler = mLocalHandler.get(); if (handler != null) { handler.obtainMessage(MSG_UPDATE_STATE, generationId, state).sendToTarget(); } } public void setTransportControlFlags(int generationId, int flags) { Handler handler = mLocalHandler.get(); if (handler != null) { handler.obtainMessage(MSG_SET_TRANSPORT_CONTROLS, generationId, flags).sendToTarget(); } } } Why we have to implement it this way? Well, it's because those methods calls arrive asynchronously, so, to correctly process them all, we need a Handler. Then we send messages to this Handler with necessary parameters, and it processes them. But why use this weird WeakReference? Well, I can't explain it better than Google Developers. Citing the source code comment: /** * This class is required to have weak linkage * because the remote process can hold a strong reference to this binder object and * we can't predict when it will be GC'd in the remote process. Without this code, it * would allow a heavyweight object to be held on this side of the binder when there's * no requirement to run a GC on the other side. */ Click to expand... Click to collapse Tl;dr it's just a clever usage of memory resources. So, my congratulations! We've implemented hidden IRemoteControlDisplay interface. But now it doesn't actually do anything. 4. Using our RCD implementation As you can see, the constructor requires a Handler to be passed to it. But any Handler just won't do, as we have to process messages. So, you can't just write Code: MyRemoteControlDisplay display=new MyRemoteControlDisplay(new Handler()); Then, let's implement our Handler. Again, I recommend you to use the following code, as it's doing it's job fine. This is the code used by default lockscreen with slight modifications. You can try and implement your own Handler, but this is strongly discouraged, as it can get glitchy. Don't worry, we'll soon get to the part where you can use your imagination Code: private int mClientGeneration; private PendingIntent mClientIntent; private Bitmap mArtwork; public static final int MSG_SET_ARTWORK = 104; public static final int MSG_SET_GENERATION_ID = 103; public static final int MSG_SET_METADATA = 101; public static final int MSG_SET_TRANSPORT_CONTROLS = 102; public static final int MSG_UPDATE_STATE = 100; Handler myHandler = new Handler(new Handler.Callback() { [user=439709]@override[/user] public boolean handleMessage(Message msg) { switch (msg.what) { case MSG_UPDATE_STATE: //if client generation is correct(our client is still active), we do some stuff to indicate change of playstate if (mClientGeneration == msg.arg1) updatePlayPauseState(msg.arg2); break; //if client generation is correct(our client is still active), we do some stuff to update our metadata case MSG_SET_METADATA: if (mClientGeneration == msg.arg1) updateMetadata((Bundle) msg.obj); break; case MSG_SET_TRANSPORT_CONTROLS: break; //if our client has changed, we update the PendingIntent to control it and save new generation id case MSG_SET_GENERATION_ID: mClientGeneration = msg.arg1; mClientIntent = (PendingIntent) msg.obj; break; //if client generation is correct(our client is still active), we do some stuff to update our artwork //while recycling the old one to reduce memory usage case MSG_SET_ARTWORK: if (mClientGeneration == msg.arg1) { if (mArtwork != null) { mArtwork.recycle(); } mArtwork = (Bitmap)msg.obj; setArtwork(mArtwork); } break; } return true; } }); I think you can probably guess what do we do here, but I'll explain anyway. updatePlayPauseState(msg.arg2) - it's the method where you actually handle the change of playstate. msg.arg2 is an Integer(or, more correcly, int), which indicates current play state. Please refer to section 2 to see possible play states. For example, you may do check like this: Code: updatePlayState(int state) { if(state==RemoteControlClient.PLAYSTATE_PLAYING) { setButtonImage(R.drawable.play); } else { setButtonImage(R.drawable.pause); } } setArtwork(mArtwork); It's pretty obvious, do whatever you like with this bitmap, just remember that it can be null. updateMetadata((Bundle) msg.obj) That one isn't very easy to handle. There is a bundle containing all of available metadata. So, you can deal with it as you please(you know how to extract data from Bundle, right?), but here's how I do it(modified Google version): Code: private void updateMetadata(Bundle data) { String artist = getMdString(data, MediaMetadataRetriever.METADATA_KEY_ALBUMARTIST); //if we failed to get artist, then we should try another field if(artist==null) { artist=getMdString(data, MediaMetadataRetriever.METADATA_KEY_ARTIST); } if(artist==null) { //if we still failed to get artist, we will return a string containing "Unknown" artist=mContext.getResources().getString(R.string.unknown); } //same idea for title String title = getMdString(data, MediaMetadataRetriever.METADATA_KEY_TITLE); if(title==null) { title=mContext.getResources().getString(R.string.unknown); } //album isn't that necessary, so I just ignore it if it's null String album=getMdString(data, MediaMetadataRetriever.METADATA_KEY_ALBUM); if((artist!=null)&&(title!=null)) { setMetadata(artist, title, album); } } private void updatePlayPauseState(int state) { mPlayButtonState=state; mService.setPlayButtonState(state); } public void setMetadata(String artist, String title, String album) { mMetadataArtistTextView.setText(artist); mMetadataTitleTextView.setText(title); if(album!=null) { mMetadataAlbumTextView.setText(album); } else { mMetadataAlbumTextView.setText(""); } } } private String getMdString(Bundle data, int id) { return data.getString(Integer.toString(id)); } You've got the idea. Okay. So you've implemented your Handler, and created MyRemoteControlDisplayObject. What's next? 5. Registering and unregistering RemoteControlDisplay In order for your RemoteControlDisplay to be able to receive metadata and album arts, it has to be registered via AudioManager. You have to get the instance of AudioManager and then call Audiomanager#registerRemoteControlDisplay(). Code: MyHandler myHandler=new MyHandler(); MyRemoteControlDisplay remoteDisplay=new MyRemoteControlDisplay(myHandler); AudioManager manager=((AudioManager)mContext.getSystemService("audio")); mAudioManager.registerRemoteControlDisplay(remoteDisplay); So, that's it. You've succesfully registered your display. Now it will receive metadata and album art. However, that's not all. When the metadata isn't visible to user, you should unregister your RemoteControlDisplay by using this code: Code: audioManager.unregisterRemoteControlDisplay(remoteDisplay); remoteDisplay=null; mHandler.removeMessages(MSG_SET_GENERATION_ID); mHandler.removeMessages(MSG_SET_METADATA); mHandler.removeMessages(MSG_SET_TRANSPORT_CONTROLS); mHandler.removeMessages(MSG_UPDATE_STATE); This way you will unregister your RemoteControlDisplay, destroy it(or, actually, just give it garbage collector), and remove all unprocessed messages. This is the correct way to unregister your RemoteControlDisplay. Please note! You must register your RemoteControlDisplay every time when the View which displays metadata is shown to the user. This is because 4.2.2 and lower versions support only one RemoteControlDisplay, and if system will decide to register it's own RCD, your RCD will be unregistered automatically. When you're compiling, you have to compile with 4.2.2 modified library for 4.2.2 and lower, and compile with 4.3 modified library for 4.3. That is very important, because if you'll try to launch 4.2.2 implementation on device running 4.3, it will give you AbstractMethodError. If you have any question regarding the implementation, please ask here. Don't ask "How do I start Eclipse", or anything like that. And please, if you use this, at least give me a credit. Finding this out was a really hard job. To understand how it works, I've used source code from Android GitHub repository: KeyguardTransportControlView.java Or donate to me, whatever you like more. However, it would be the best if you give me credit and donate to me
Reserved for Android 4.3 implementation. So, in 4.3 Google change some IRemoteControlDisplay methods. 1. Implementing IRemoteControlDisplay Those are: void setTransportControlFlags(int generationId, int transportControlFlags); is replaced by void setTransportControlInfo(int generationId, int transportControlFlags, int posCapabilities) The new parameter - posCapabilities - is indicating whether the RemoteControlClient provides information about playback position. It is a bit mask for those (public, but hidden) constants: 0 - no info about playback position RemoteControlClient.MEDIA_POSITION_WRITABLE - playback position can be changed RemoteControlClient.MEDIA_POSITION_READABLE - playback position can be read void setPlaybackState(int generationId, int state, long stateChangeTimeMs); is replaced by void setPlaybackState(int generationId, int state, long stateChangeTimeMs, long currentPosMs,float speed); New parameters: currentPosMs - current playback position in milliseconds. If it's positive, it's the current playback position. Negative values means that the position is unknown. RemoteControlClient.PLAYBACK_POSITION_INVALID means that position is unknown, like if you're listening to live radio. RemoteControlClient.PLAYBACK_POSITION_ALWAYS_UNKNOWN - that means the music player doesn't provide current position at all, because it's using legacy API(from 4.2.2). speed - is the playback speed. 1.0 is normal, 0.5 is slower, 2.0 is 2x faster. The rest stays the same. However, the IRemoteControlDisplay implementation in my first post doesn't send this kind of data. So, if you want to send this data to your Handler, you may want to pack it into Bundle and send it as Message.obj. 2. Registering RemoteControlDisplay Now there are two methods: registerRemoteControlDisplay(IRemoteControlDisplay rcd) RemoteControlDisplay registered with this method will NOT receive any artwork bitmaps. Use this method only if you DO NOT NEED ARTWORK. registerRemoteControlDisplay(IRemoteControlDisplay rcd, int w, int h) w and h are maximum width and height of expected artwork bitmap. Use this method if you NEED ARTWORK. 3. Multiple RemoteControlDisplays (FOLLOWING TEXT MAY BE NOT WORKING, AS THIS IS ONLY A THEORY BASED FROM STUDYING OF ANDROID SOURCE CODE) Until API 18 there could be only one active RemoteControlDisplay. So, that means if you register your RCD, and then system decides to do the same, then your RemoteControlDisplay will be unregistered automatically. However, this is not the case in 4.3, as from API 18 it does support multiple RemoteControlDisplay. So, in theory, you can just fire AudioManager#registerRemoteControlDisplay. Didn't tried that, however. And, of course, you have to compile with Android 4.3 library.
Really cool guide. :good: Thank you.
nikwen said: Really cool guide. :good: Thank you. Click to expand... Click to collapse Thanks! I just hope it will have the desired effect and Google will release RemoteControlDisplay API to public, 'cause, you know, now I made it public, there's no reason to hide it now. Now I only need it to be featured on the main page... How about making it sticky or something?
Dr.Alexander_Breen said: Thanks! I just hope it will have the desired effect and Google will release RemoteControlDisplay API to public, 'cause, you know, now I made it public, there's no reason to hide it now. Now I only need it to be featured on the main page... How about making it sticky or something? Click to expand... Click to collapse I voted for it. The sticky: Contact a mod and if it is relevant to the majority of the users, it will be made sticky. Those are the mods for the Java development forum: diestarbucks, poyensa, justmpm, mark manning, crachel, Archer
I was looking for this undocumented api's and customized transparent lock screen that can listen for events. Thank you. I will close my other open thread now. God is great.
@Dr.Alexander_Breen thanks a lot for this, almost spent the day trying to get this to work with reflection. Anyway, I still can't get it to work. I'm trying to do this for my S-View cover mod http://forum.xda-developers.com/showthread.php?t=2368665 But the methods aren't called at all. I've never used handlers before, so I'm guessing there's a problem with those. Can you take a look at the source code? Since it's an Xposed module, and you might not know Xposed, I'll state a few things about it https://github.com/MohammadAG/Xpose...ewpowerampmetadata/SViewPowerampMetadata.java The mod is not a subclass of Activity, so there's no Context, but I do get the Context used by the S-View widget. After getting the Context, and the Handler used by the S-View classes, I register the RemoteControlDisplay (method initializeRemoteControlDisplay(Looper)) The rest is mostly your code. If you're wondering why I construct the Handler in such a weird way, it's because I can't create one on the Xposed module thread (something about Looper.prepare()). Anyway, any help would be appreciated, if the code seems fine, I guess I'll have to use a service and make the module communicate with that instead, though I can't imagine media buttons being any slower.
@MohammadAG: Your code looks fine to me, but there is one thing. You register your RemoteControlDisplay in handleLoadPackage() method. I suppose that this method is called only once, but RemoteControlDisplay needs to be registered every time the metadata view is show to user. Also, added 4.3 implementation.
Dr.Alexander_Breen said: @MohammadAG: Your code looks fine to me, but there is one thing. You register your RemoteControlDisplay in handleLoadPackage() method. I suppose that this method is called only once, but RemoteControlDisplay needs to be registered every time the metadata view is show to user. Also, added 4.3 implementation. Click to expand... Click to collapse But that's only if I unregister it right? I'll see about it, maybe I need to do the Handler bit different in Xposed. Thanks Sent from my GT-I9500 using xda app-developers app
MohammadAG said: But that's only if I unregister it right? I'll see about it, maybe I need to do the Handler bit different in Xposed. Thanks Sent from my GT-I9500 using xda app-developers app Click to expand... Click to collapse Actually, no. As in 4.2 and lower, there can be only one active RCD. So, in case your system decides to register it's own RCD, it automatically unregisters yours.
Dr.Alexander_Breen said: Actually, no. As in 4.2 and lower, there can be only one active RCD. So, in case your system decides to register it's own RCD, it automatically unregisters yours. Click to expand... Click to collapse Oh, so that explains why your app displayed Unknown at some point. I guess that was my RCD being registered. Sent from my GT-I9500 using xda app-developers app
And it's on the portal: Implement Lock Screen-Style Music Controls in Your App
MohammadAG said: Oh, so that explains why your app displayed Unknown at some point. I guess that was my RCD being registered. Sent from my GT-I9500 using xda app-developers app Click to expand... Click to collapse So, have you managed to make your app working?
Dr.Alexander_Breen said: So, have you managed to make your app working? Click to expand... Click to collapse No, actually I haven't :/ I register the display each time the S View screen shows, which is when I want to show metadata, but that doesn't work, the handler's handleMessage method is never called.
MohammadAG said: No, actually I haven't :/ I register the display each time the S View screen shows, which is when I want to show metadata, but that doesn't work, the handler's handleMessage method is never called. Click to expand... Click to collapse Hmm. I'm afraid this is something with Handler in Xposed. You can, however, move IRemoteControlDisplay implementation to service, which will connect with your Xposed part via AIDL or smth like that. Also, check, if methods of IRemoteControlDisplay are being called. Like, fire a log message when method is called.
Dr.Alexander_Breen said: Hmm. I'm afraid this is something with Handler in Xposed. You can, however, move IRemoteControlDisplay implementation to service, which will connect with your Xposed part via AIDL or smth like that. Also, check, if methods of IRemoteControlDisplay are being called. Like, fire a log message when method is called. Click to expand... Click to collapse I thought about that, but I'm pretty sure it'll use up more memory and introduce a bit of lag (if the service was killed for example). I was thinking of hooking registerRemoteControlClient and keeping the registered remote(s). Then I can simply get the data from their methods, do you know if they're kept in memory for as long as the app lives? Sent from my GT-I9500 using xda app-developers app
MohammadAG said: I thought about that, but I'm pretty sure it'll use up more memory and introduce a bit of lag (if the service was killed for example). I was thinking of hooking registerRemoteControlClient and keeping the registered remote(s). Then I can simply get the data from their methods, do you know if they're kept in memory for as long as the app lives? Sent from my GT-I9500 using xda app-developers app Click to expand... Click to collapse Again, please check, if methods of RemoteControlDisplay are being called. Write message to debug log(Log.d) in setMetadata or setAllMetadata methods. As a workaround, I have this idea. If I understand correctly, Xposed can hook the method of the class, not the instance. Then you can hook RemoteControlClient methods to get the metadata directly from the clients. Check the API reference to RCC to get necessary methods.
really cool tutorial Thanks a lot ! Could you post one for implimemtation of IBatteryStats...... etc private battery APIs ?? Sent from my GT-S5302 using Tapatalk 2
sak-venom1997 said: really cool tutorial Thanks a lot ! Could you post one for implimemtation of IBatteryStats...... etc private battery APIs ?? Sent from my GT-S5302 using Tapatalk 2 Click to expand... Click to collapse Well, I think the answer is "no", because I'm no Google Software Engineer. I'll look into this, however, as it will be simplier, I think. Something like connecting remote service with IBatteryStats.Stub.asInterface. I'll look into it, however.
Dr.Alexander_Breen said: Well, I think the answer is "no", because I'm no Google Software Engineer. I'll look into this, however, as it will be simplier, I think. Something like connecting remote service with IBatteryStats.Stub.asInterface. I'll look into it, however. Click to expand... Click to collapse Connecting to serivice and obtaining data i could manage by diging into source but parsing the info is causing trouble thanks Sent from my GT-S5302 using Tapatalk 2
[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? The RemoteController class is used to control media playback, display and update media metadata and playback status, published by applications using the RemoteControlClient class. Click to expand... Click to collapse 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 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: <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: [B]onClientChange(boolean clearing)[/B] Pretty self-explanatory. "true" will be passed if metadata has to be cleared as there is no valid RemoteControlClient, "false" otherwise. Code: [B]onClientMetadataUpdate(RemoteController.MetadataEditor metadataEditor)[/B] [I]metadataEditor[/I] 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: [B]metadataEditor.getString(MediaMetadataRetriever.METADATA_KEY_TITLE, getString(R.string.unknown))[/B] To get title of the song as a String, use: [B]metadataEditor.getString(MediaMetadataRetriever.METADATA_KEY_ALBUM, getString(R.string.unknown))[/B] To get the duration of the song as a long, use: [B]metadataEditor.getLong(MediaMetadataRetriever.METADATA_KEY_DURATION, 1)[/B] 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: [B]metadataEditor.getBitmap(RemoteController.MetadataEditor.BITMAP_KEY_ARTWORK, null)[/B] "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): [B]mArtistText.setText(editor.getString(MediaMetadataRetriever.METADATA_KEY_ARTIST, editor.getString(MediaMetadataRetriever.METADATA_KEY_ALBUMARTIST, getString(R.string.unknown))));[/B] 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: [B]onClientPlaybackStateUpdate(int state, long stateChangeTimeMs, long currentPosMs, float speed)[/B] Called when the state of the player has changed. Right now this method is not called, probably due to bug. [I]state[/I] - playstate of player. Read the [URL="http://developer.android.com/reference/android/media/RemoteControlClient.html"]RemoteControllerClient class description[/URL] to get the list of available playstates. For example, RemoteControlClient.PLAYSTATE_PLAYING means that music is currently playing. [I]stateChangeTimeMs[/I] - the system time at which the change happened. [I]currentPosMs[/I] - current playback position in milliseconds. [I]speed[/I] - 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: [B]onClientPlaybackStateUpdate (int state) [I]state[/I] - playstate of player. Read the [URL="http://developer.android.com/reference/android/media/RemoteControlClient.html"]RemoteControllerClient class description[/URL] to get the list of available playstates.[/B] Code: [B]onClientTransportControlUpdate (int transportControlFlags)[/B] [I]transportControlFlags[/I] - 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: [B]if(transportControlFlags & RemoteControlClient.FLAG_KEY_MEDIA_FAST_FORWARD != 0) doSomethingIfSupport(); else doSomethingIfDoesNotSupport(); [/B] All of the flags are listed in [URL="http://developer.android.com/reference/android/media/RemoteControlClient.html"]RemoteControlClient class description.[/URL] 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: 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: 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: ((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: 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.
Hi. Good tutorial! How can i use RemoteController without MediaPlayer class? I'm using custom engine for playback.
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.
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?
corrytrevor said: 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? Click to expand... Click to collapse 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!
corrytrevor said: 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? Click to expand... Click to collapse I found a solution to this problem (or rather WisdomWolf did). http://stackoverflow.com/questions/...motecontroller-onclientupdatelistener-crashes Problem solved
Finding music to play Hi Thanks for the fantastic tutorial! Is there away to find a receiver if there is no music playing. I have noticed the line Code: I/RemoteController﹕ No-op when sending key click, no receiver right now is logged. Thanks
Thanks for the great tutorial, very helpful for my current project. RemoteController is also used in KitKat built-in Keyguard KeyguardTransportControlView to replace RDC in older version.
ATI-ERP expeditious Business Solutions Great article on building Remotecontroller in an Android application. For More Android Apps visit ati-erp.com ATI-ERP
i can't bind the service, i debug and seemd android start the service well , however trying bind the service onStart seems does not work in 4.4.4 someone has this issue? i tried reboot and other options Nevermind i forgot to put the intent filter on manifest (shame on me)
Sorry to drag up an old thread. Does anyone know how to get the package of the client that is currently connected to / updating the RemoteController? I can't find it anywhere...
How to know what app is foreground and do something when it goes background
So, I'm developing an application for Android which will only be used in my own couple tablets which are going to public use. I need an application which keeps track which (possibly 3rd party)application is foreground, and when that application goes to background the service starts activity which will go foreground and show something to the user(for example like some ad, or some review window which asks for start rating). In Android 5.0 access to logcat is restricted so it needs to be done trough accesibility services I guess? Or is there way to use custom ROM with access to logcat? Or could rooting open it? Thank you.
As for logcat, root allows access to it
You can get running apps using this piece of code Code: ActivityManager am = (ActivityManager) mContext .getSystemService(Activity.ACTIVITY_SERVICE); String packageName = am.getRunningTasks(1).get(0).topActivity .getPackageName(); and use an if conditition and a thread to keep checking, at the moment its not meeting the condition the desired code will be triggered
Writing Global Overlay Help
So the end goal in this project is to view an RTMP stream and interact with the server at the same time. This is a little hard, because obviously VLC or Mx Player or whatever I'm viewing the RTMP stream with doesn't know how to communicate with the custom server. So I'm trying to make an overlay that will sit on top of the player which has buttons, the buttons can then communicate with the server which will alter the stream and you'll see the result streamed to the player. This is sort of like an interactive HUD if you will. Problem is that it's difficult to make an overlay which will take actions if they're clicked, but can also pass those actions to the background app (in this case the player) if it opts not to process them. Right now my app creates a service and the service catches the input and displays whatever. This WORKS, but the problem is no matter what I return from "onTouchEvent(MotionEvent)" in the view of the service (true or false) the home screen is frozen. It catches the press but it won't pass it on, even if the function returns "false" - to say it's not handled. I'm not sure if I'm not passing the touch event on correctly, or is this not even possible? I read somewhere that it's not possible for app A to provide input to app B, but I'm not sure if that is correct (frankly I don't believe it). Basically I want to handle some presses in the overlay and allow some others to go to the active app beneath it. Does anybody have any input for making this work as expected? I'm creating a view to pass to the window manager with the following flags: Code: LayoutParams params = new LayoutParams(LayoutParams.TYPE_SYSTEM_ALERT, LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH | LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSLUCENT); I've tried playing with the flags but to no avail, I cannot get the touches to go through, even if all the onTouchEvent(MotionEvent) function does is return false. Anybody have any ideas? I'd be open to alternative implementation ideas too, the point is I need to basically have two communication channels open, one is a player (ie. VLC) for viewing the RTMP stream, which likely would not change, the other would be the command stream, which will control said server, which I am open to change. I considered trying to use the accelerometer in the service instead of an overlay, but I think that would create a bad proof of concept UI (because there's some latency in the video stream), any alternative ways to communicate with the server would be up for discussion! Maybe I can plug an IO device into the USB port or something, like a keyboard? Extra buttons could be of use. It's only a POC, so some clunkyness IS okay. EDIT: As you can see above, I used an alert type instead of an overlay type, I guess this is because overlays won't accept focus after a certain Android version number. I'm using 4.0.4 ICS.