Related
Hi I didn't know where I could get help on this topic but seeing as some of the guys in here have made such excellent programs I thought you might be familar with c# on windows mobile.
here is my problem.
I have a task I need to do every x seconds I want to be able to start and stop this repeating task immediately.
I have attempted to solve this by creating a class which holds the taks I want to do, in this class I have put a windows.forms.timer with the task as its timer.tick event. this task has a method called start() which sets the timer going.
to start the process in my main thread i create a new thread which creates an instance of my class(mentioned above) and calls its start method.
The problem is the tick event never seems to fire is this because its in a seperate thread to the one which has focus, is it because it doesnt have an asociated form?
is there a better way to do this?
I want an object I can create which I can call methods to start and stop a repeating task.
many thanks,
I hope some one can help.
(sorry if this is completely the wrong place to post this)
Have you set the timer.Enabled = true?
Instead of windows.forms.timer try system.threading.timer!
comparison of timer classes in .net
Why have you put this in a thread, so you can abort it midway through processing its tick event?
Is there a better way you could do this than adding the complexity of threads? Perhaps add a bool to you class and your tick event can check at safe points if it is false (and exit)?
My initial thought to the problem as you have outlined it is that your thread is finished after completing its Start() method, so the tick event never gets called (as the timer no longer exists). You would need to keep the thread alive (without interfereing with your timer).
Windows.Forms.Timer is a bad choice for this as it uses the calling thread - so if you add a System.Threading.Thread.Sleep() (or some other blocking action) to keep your thread alive, your timer wont work. System.Threading.Timer is a better choice as it uses its own thread.
Hi thanks so much for the info I will have a look at threading timers.
I have put it in its own thread so I can abort it early because each "tick" could be between 5-15 mins and the task takes between 1 second and 5 mins so I wanted to be able to make sure the task is started at regular intervals and have the ability to stop the process immediately.
I was just thinking that neither timers or using a bool will allow me to stop the process immediately, is it safe to use thread.abort? i.e. if part of the task is to update a field could it be stopped half way through and corrupt the data?
if it is unsafe is there a safe thread abort i.e. abort as soon as the thread has finished updating any fields or database records(this thread wont update database but its good to know)
also to the point you raised about the thread finishing when my start() method finishes I kept that going using a while loop and a bool i.e.
while(keepGoing)
{
}
this seemed like a really horrid thing to do but do I have any other choice?
I think my design is not good my app basically wants to start off this running process when it loads but needs to be able to interupt it when some data changes.
(as an extra question does any one know if sqlce 3.5 has built in thread safety or will I have to lock functions that access data in my database?)
Threads can be somewhat unpredictable. Thread.abort calls an exception, so as far as i know if you have something like
var1 = "blah";
that would finish - var1 would not be corrupt. But, if you were doing something more complicated such as your database example you cant be sure when it will cut you off. Of course, you can catch the ThreadAbortException, and do some checking if required.
As for your while loop, that almost certainly blocked the thread and is why your timer did not work. Windows forms timers are based around events and will not typically run while one of your methods is still working, unless it yields somehow. Infact, on a single cpu device like your phone that loop would probably block a System.Threading.Timer because while(true); results in 100% cpu usage.
This is a quick example of how I would impliment your timer requirement - mostly stolen from the msdn page on System.Threading.Timer.
Code:
private void ThreadMethod()
{
System.Threading.ManualResetEvent resetEvent = new System.Threading.ManualResetEvent(false);
int interval = 10 * 1000;
System.Threading.Timer timer = new System.Threading.Timer(new System.Threading.TimerCallback(timer_Tick), resetEvent, System.Threading.Timeout.Infinite, System.Threading.Timeout.Infinite);
while (true)
{
resetEvent.Reset();
timer.Change(interval, System.Threading.Timeout.Infinite);
resetEvent.WaitOne();
DoStuffThatTakesOneToFiveMinutes();
}
timer.Dispose();
}
private void timer_Tick(object timerObject)
{
System.Threading.ManualResetEvent resetEvent = (System.Threading.ManualResetEvent)timerObject;
resetEvent.Set();
}
private void DoStuffThatTakesOneToFiveMinutes()
{
MessageBox.Show("hi");
}
ThreadMethod can just be directly called from your thread.
timer.Change starts the timer off. The Infinite means it is not a repeating timer.
resetEvent.WaitOne() simply waits for the event to be triggered. The nice thing about doing it that way is your processing is done by the calling method, and you can be sure at what point in your process. (Well, as much as you can with it all running inside a larger thread.)
Edit:
Infact, that timer.Dispose() is pointless as it will never get called. Probably a good idea to call it if you catch the threadabortexception though.
Also, I think var1 (first example) would simply not change, but im not sure. I dont think it would get corruped either way.
Thanks so much for this information it looks excellent. So if I want to tidy up some stuff on exit I see on msdn it says the finally block will exitcuted before exit or I could catch the abortexception as you suggest, where would I put this code would it be inside the threadMethod? or does it go somewhere else?
also imagine this example I am updating a record in my database, I throw the abort exception and it stops the process midway, is there a way in code to say ignor this exception and continue? i.e. when updating database i could set a bool and then check this bool in the finally or catch block and if its true say ignor this and continue and then outside my thread I could do a check on the thread to see if its still running if it is then i wait for x amount of time and try the abort again?
what would be a good way to notify the calling thread that the thread with the timer is still running as the isAlive property is not included as part of the compact framework, I suppose I could have a bool in the calling thread which the timer thread could change in the finally catch block when the abort exception is passed, but this would mean passing the calling thread to the called thread is there any problems doing this?
sorry this is quite a complicated situation I have made for myself, I really appreciate the help.
Im afraid we have hit the limit of my knowledge on threads and exceptions.
My thought was you could wrap the database parts in a try-catch, at least allowing you to handle the abort gracefully. As far as I know you can not continue the thread at the point it stopped - you can merely ignore it and carry on after the catch. Of course, exceptions are not meant to be used on a trial basis, they are more worst case.
Events may suit you a bit better, as they do not halt the flow. That way your thread could keep track anytime it is doing something important like database access - and the event would only close if safe.
You should be able to have your thread access a bool in the parent object, aslong as you are careful with synchronization. Making sure all parts that read/write to it use lock should be sufficient.
http://www.blackwasp.co.uk/CSharpEvents.aspx
http://msdn.microsoft.com/en-us/library/c5kehkcz.aspx
Thanks again isangelous,
I have thought that events would be a good idea but found them a little confusing, I will have another look and see if I can use them somehow.(thanks for the link)
btw I used the code you provided and tweaked it a bit to get my first problem working, i put the contence of the threadmethod inside a try block catch the abort exception and then call timer.dispose(), seems to work quite nicely so far, although I dont think i have managed to abort half way through a process, it always seems to be before or after but seeing as I dont use any of the variables after its stopped I assume all would be safe anyway.
to make the updating database part safer I was thinking that I would also put that in another thread and if its running when I send the abort exception I would wait until it is free and then stop the timer, what do you think? does sending an exception to a thread halt all its created threads as well? because if so my plan wont work.
btw im alomst 100% confident that actually calling abort using my app while it is updating is impossible but I dont want there to be a bug present I know about.
anyway thanks again you have given me loads of things to try.
I've seen evidence of this particular problem on the web, but can't seem to locate the reasons or a solution.
I continue to get errors and force closes trying two seemingly simple methods to record audio on the Android.
First, using MediaRecord:
MediaRecorder mRecorder = new MediaRecorder();
Log.d("com.mydevice.RecordAudio: ", " try to setAudioSource");
try{
mRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
}
catch(IllegalStateException e){
//Never get here
Log.d("com.mydevice.RecordAudio: ", " parm set error");
}
//Never get here either
Log.d("com.mydevice.RecordAudio: ", " successfully setAudioSource");
The above results in the following logcat output:
D/com.mydevice.RecordAudio: ( 1623): try to setAudioSource
E/audio_input( 1104): unsupported parameter: x-pvmf/media-input-node/cap-config-interface;valtype=key_specific_value
E/audio_input( 1104): VerifyAndSetParameter failed
D/AndroidRuntime( 1623): Shutting down VM
W/dalvikvm( 1623): threadid=3: thread exiting with uncaught exception (group=0x4001b198)
E/AndroidRuntime( 1623): Uncaught handler: thread main exiting due to uncaught exception
Second method, using AudioRecord:
Log.d("com.mydevice.RecordAudio: ", " try to initialize AudioSource");
AudioRecord mRecorder = new AudioRecord(MediaRecorder.AudioSource.MIC, 8000, AudioFormat.CHANNEL_CONFIGURATION_MONO, AudioFormat.ENCODING_PCM_16BIT, 16384);
Log.d(TAG, " recordStart");
try {
mRecorder.startRecording();
} catch (IOException e) {
Log.d(TAG, " recordStart IOException");
}
This results in the following:
D/com.mydevice.RecordAudio: ( 1637): try to initialize AudioSource
D/AudioHardwareMot( 1095): Codec sampling rate already 8000
D/Registration Create( 1637): recordStart
D/Registration Create( 1637): recordStart IOException
From the documentation, I understand that these methods won't necessarily work in the emulator (though I did enable audio and camera in my VMs, just in case), and some people on the 'net indicated that even with emulator errors, a physical phone worked. But, running the two methods above on my phone results in the same errors indicated in the emulator logcat output, with the same force closes.
I am using the SDK revision 5, and tried Android 1.5 (just to try) and 2.1 emulators. I made sure the following was in my manifest:
<uses-permission android:name="android.permission.RECORD_AUDIO" />
I loaded it onto my Motorola Droid 2.1, with Cyanogen 5.0.7.1 and still the errors.
Does anyone have any idea how, or if I can resolve this? I feel like an idiot since it seems so simple...
LOL! Turns out I was being an idiot.
The unsupported parameter message when calling setOutputFormat, was actually not the problem that was causing the force close. Rather, it was the call to setOutputFile. I found this after laboriously adding log messages after every single call.
I tried both local and external storage and using a string and FileDescriptor and nothing was working. Every time I called that method, I got a force close or caught an error in a try/catch. I finally found an example that was sending the correct context. Since I had separated this process from the UI, what I needed to to was instantiate my class with the application context like so:
MyRecordClass(this.getApplicationContext());
and within the MyRecordClass constructor, MyRecordClass(Context context):
this.context = context;
Even though calls to FileOutputStream and getFD() actually still created an empty file without the proper context, the MediaRecorder was obviously crashing and must be due to some sort of context disconnect. Apparently, by adding the above, and now in what I'm guessing is the proper context and even though I still encounter that unsupported parameter error in the logcat, the app continues on without a hitch and I get a valid recording. Joy!
I have now successfully called this process in another thread and even sending .finish() to the UI activity, the recording continues until it's finished (specified by duration). So, there it is...onward.
AudioRecord Problems
Hi!
After a long search on the Internet, I stumbled upon your post about AudioRecord and this gives me hope that my problem might have a solution. I too encountered a problem recording on Milestone (although the code works just fine on Nexus One).
In your reply you mentioned that by setting the correct context you solved it - did it also solve the AudioRecord problem or just the MediaRecorder?
And where did you set a context? From what does your MyRecordClass inherit - Activity? Service?
I am a bit lost here and would welcome any guidance.
Thank you,
Andrey
Hello there, I have a little problem, same like yours, although pass some years.
Meaby u have the solution tothis problem.
Regards!
Hello all i have a questio?
Does anyone know how do i get a service to accuire root and prevent any taskmanager apps from killing it i dev andriod app but dont know about root and all
I dont want to start another service to monitor each other and start the other in case one is killed
sak-venom1997 said:
Hello all i have a questio?
Does anyone know how do i get a service to accuire root and prevent any taskmanager apps from killing it i dev andriod app but dont know about root and all
I dont want to start another service to monitor each other and start the other in case one is killed
Click to expand...
Click to collapse
If I want to use root i simply do something like that
Code:
Runtime.getRuntime().exec(new String []{"su","-c",yourcommand});
As for unkillable service maybe you can set it foreground, but I am not sure it works though:
Code:
Notification notification=new Notification(0,null,System.currentTimeMillis());
notification.flags |= Notification.FLAG_NO_CLEAR;
startForeground(2, notification);
DoR2 said:
If I want to use root i simply do something like that
Code:
Runtime.getRuntime().exec(new String []{"su","-c",yourcommand});
As for unkillable service maybe you can set it foreground, but I am not sure it works though:
Code:
Notification notification=new Notification(0,null,System.currentTimeMillis());
notification.flags |= Notification.FLAG_NO_CLEAR;
startForeground(2, notification);
Click to expand...
Click to collapse
Ya I've tried this android system won't kill but taskmanager can easily kill it and for my application it is essential that this service is not stopped as it monitors information from sensors
The root part
This much I also know what command would I give
Sent from my GT-S5302 using Tapatalk 2
Hit Thanx Button if i helped you!
sak-venom1997 said:
Ya I've tried this android system won't kill but taskmanager can easily kill it and for my application it is essential that this service is not stopped as it monitors information from sensors
The root part
This much I also know what command would I give
Sent from my GT-S5302 using Tapatalk 2
Hit Thanx Button if i helped you!
Click to expand...
Click to collapse
I don't think thats possible. I don't think you can prevent other apps from killing your service.
You could however try to restart service from its own onDestroy() method.
This is just theoretical but this way when something stops service it should re-start itself.
Again I have no idea if this is possible, it's just an idea.
Root task managers that actually kill process(using kill or killall )are completely different matter.
Sent from my Evo 3D GSM using Tapatalk 2
Antivirus services don't get killed unless used a root taskmanager
Sent from my GT-S5302 using Tapatalk 2
Hit Thanx Button if i helped you!
when a service is started with START_STICKY then it will automatically be re-started by the Android system. That's why auto-task killers are a horrible idea, because they kill your service over and over again, and drain your battery easily.
http://developer.android.com/reference/android/app/Service.html#START_STICKY
Unfortunately there's no solution to stop task killers.
You mean there's no way out
Its important for scenarios like mine I'm dumping heavy data to a sql or mysql data base on the cloud that is done only in onDestory() method if done else where would make low memory devices crawl and if a taskmanager kills it I lose all data stored in ram even if it's restarted by the system
Sent from my GT-S5302 using Tapatalk 2
Hit Thanx Button if i helped you!
sak-venom1997 said:
You mean there's no way out
Its important for scenarios like mine I'm dumping heavy data to a sql or mysql data base on the cloud that is done only in onDestory() method if done else where would make low memory devices crawl and if a taskmanager kills it I lose all data stored in ram even if it's restarted by the system
Sent from my GT-S5302 using Tapatalk 2
Hit Thanx Button if i helped you!
Click to expand...
Click to collapse
Maybe you can make something like that:
YourService.java
Code:
@Override
public void onDestroy() {
startService(new Intent(context,Dummy.class));
}
Dummy.java
Code:
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
startService(new Intent(context,YourService.class));
stopSelf();
return super.onStartCommand(intent, flags, startId);
}
I don't know if it is the best way, but I think it will work
DoR2 said:
Maybe you can make something like that:
YourService.java
Code:
@Override
public void onDestroy() {
startService(new Intent(context,Dummy.class));
}
Dummy.java
Code:
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
startService(new Intent(context,YourService.class));
stopSelf();
return super.onStartCommand(intent, flags, startId);
}
I don't know if it is the best way, but I think it will work
Click to expand...
Click to collapse
Hmm when taskmanager kills the service onDestory us not called that's my problem.
Sent from my GT-S5302 using Tapatalk 2
Hit Thanx Button if i helped you!
You should never do important things in OnDestroy as there's no guarantee that the system is calling it, even if no task killers are used. I guess you should rethink your design and push the data earlier, cache it locally, etc.
ramdroid77 said:
You should never do important things in OnDestroy as there's no guarantee that the system is calling it, even if no task killers are used. I guess you should rethink your design and push the data earlier, cache it locally, etc.
Click to expand...
Click to collapse
System never kills a service it only kills activities Google says
activities like dumping data to a sql database should be done in onDestory() method
Well that wasn't the topic I want to prevent taskmanager apps (non root) from killing my service
Sent from my GT-S5302 using Tapatalk 2
Hit Thanx Button if i helped you!
sak-venom1997 said:
System never kills a service it only kills activities Google says
activities like dumping data to a sql database should be done in onDestory() method
Well that wasn't the topic I want to prevent taskmanager apps (non root) from killing my service
Sent from my GT-S5302 using Tapatalk 2
Hit Thanx Button if i helped you!
Click to expand...
Click to collapse
I don't know what your service is exactly doing but permanent foreground services should only be used if you really need them on all the time, e.g. during music playback. According to the docs non-foreground services (like you should normally use) might still get killed by the system after some time...
Otherwise, what happens if the user force stops your application? Or if the phone is shutting down? You shouldn't do any life-depending things in onDestroy, you only want to release allocated resources. Uploading data to the cloud in onDestroy seems plain wrong to me!
Yes, auto-kill task managers are bad, but I think that's not the only problem here
ramdroid77 said:
I don't know what your service is exactly doing but permanent foreground services should only be used if you really need them on all the time, e.g. during music playback. According to the docs non-foreground services (like you should normally use) might still get killed by the system after some time...
Otherwise, what happens if the user force stops your application? Or if the phone is shutting down? You shouldn't do any life-depending things in onDestroy, you only want to release allocated resources. Uploading data to the cloud in onDestroy seems plain wrong to me!
Yes, auto-kill task managers are bad, but I think that's not the only problem here
Click to expand...
Click to collapse
Then where should I write to the database if on in onDestory
And if you force stop the app using Android settings the on destroy is called
Only if it's done with a taskmanager it won't
What my service does it to track the network sites visited by the user in order to bill them accordingly after all we won't sponsor data usage other than official use in our country isp's are not permitted to release info about user other than to the government so if a user kills it with a taskmanager he's off the radar and if database is stored locally the user might tamper with the data
If i write data to sql server periodically the system would crawl as I'd be first compressing it to save data and it's a huge file I will hold data from other in house apps too
Sent from my GT-S5302 using Tapatalk 2
Hit Thanx Button if i helped you!
If you are looking for a way of writing a root app, try this library: http://code.google.com/p/roottools/
At the beginning I tried it without that lib and it worked. However, you will need to keep your shell open if you do not want it to ask for permission again everytime you need to execute a command. And this needs some code. That is why I use roottools now. It is much easier and we can concentrate on our app instead of getting these background things to work.
If your app needs to execute commands not very often, you can ask for the permission every time, but it slows down your application for many commands and there will be annoying Toast messages every time. The advantage would be not having to put some other people into the credits.
That is my experience. I hope that it is helpful.
Well my best idea is to let the user know that your app needs to run the service, so he shouldn't kill it with any task manager. I use root with this code:
Code:
try {
Process proc = Runtime.getRuntime().exec("su");
DataOutputStream DOS = new DataOutputStream(proc.getOutputStream());
//write what you want to send to it here. Example:
DOS.writeBytes("ls /data/data/");
DOS.flush();
DOS.close();
proc.waitFor();
} catch (IOException e1) {
e1.printStackTrace();
} catch (InterruptedException e1) {
e1.printStackTrace();
}
Rotary Heart said:
Well my best idea is to let the user know that your app needs to run the service, so he shouldn't kill it with any task manager. I use root with this code:
Code:
try {
Process proc = Runtime.getRuntime().exec("su");
DataOutputStream DOS = new DataOutputStream(proc.getOutputStream());
//write what you want to send to it here. Example:
DOS.writeBytes("ls /data/data/");
DOS.flush();
DOS.close();
proc.waitFor();
} catch (IOException e1) {
e1.printStackTrace();
} catch (InterruptedException e1) {
e1.printStackTrace();
}
Click to expand...
Click to collapse
At first I did it that way, too. One bad thing with this solution is that everytime you execute a command it will ask for permission again and there will be a new Toast message. If your app has to execute commands multiple times, keeping the shell open is more performant. That is the advantage of roottools.
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
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...