Activity or Service? - android

I'm building a Bluetooth application. I want to periodically scan for nearby Bluetooth devices. This program should start when the device starts and continue discovering devices based on a schedule (every 10 mins for example). I've been looking over the Android example of "BlueTooth Chat" in the API documentation, and I don't kow why it never uses the "Service" class. Should I use Service or Activity?
Furthermore, I understand that Services are supposed to be used for "long running tasks," but I also at some point want to provide some kind of GUI notification to the users via this class that discovers Bluetooth devices.
So, can someone please explain which one to use? What is the advantage?

You should use service if you want your scheduling running.Because android will eventually destroy your activity.

Definitely use a Service. In your MainActivity bind to the Service using bindService providing a ServiceConnection object. In this ServiceConnection object send a Message to the service with a reference to a local Messenger object (as part of a replyTo) that the Service can then use to later on send a Message back to your MainActivity. This will enable you to update your MainActivity GUI based on the results of your bluetooth scan.
In short, in your MainActivity, start and bind to your service with:
Intent i = new Intent(this, NetworkService.class);
startService(i);
bindService(i, networkServiceConnection, Context.BIND_AUTO_CREATE);
Define a messenger to respond to messages from the service like:
Messenger messenger = new Messenger(new IncomingHandler());
class IncomingHandler extends Handler {
#Override
public void handleMessage(Message msg) {
switch (msg.what) {
case NetworkService.MSG_SOMETHING:
// do something here
break;
default:
super.handleMessage(msg);
}
}
}
And then write your service connection code like:
private ServiceConnection networkServiceConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
networkService = new Messenger(service);
try {
Message msg = Message.obtain(null, NetworkService.MSG_REGISTER_CLIENT);
msg.replyTo = messenger;
networkService.send(msg);
log.debug("Connected to service");
} catch (RemoteException e) {
// Here, the service has crashed even before we were able to connect
}
}
Note that the replyTo is the messenger we just created.
In your NetworkService, keep track of connected clients with:
ArrayList<Messenger> clients = new ArrayList<Messenger>();
and create your handler like:
class IncomingHandler extends Handler {
#Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_REGISTER_CLIENT:
log.debug("Adding client: " + msg.replyTo);
clients.add(msg.replyTo);
break;
default:
super.handleMessage(msg);
break;
}
}
}
Then, when you want to send a message back to your MainActivity, just do something like the following:
for (int i = 0; i < clients.size(); i++) {
try {
clients.get(i).send(Message.obtain(null, MSG_SOMETHING));
} catch (RemoteException e) {
// If we get here, the client is dead, and we should remove it from the list
log.debug("Removing client: " + clients.get(i));
clients.remove(i);
}
}

Related

Android: bound service can't send first message, all other messages go through. Why does this happen, how to fix it?

I have an android activity and a corresponding service, where the activity is just a UI and the service calculates what to display. The service is bound to the activity.
First I establish a connection:
in the Activity.java file:
final Messenger _messenger = new Messenger(new IncomingHandler(new WeakReference<>(this)));
Messenger _serviceMessenger = null;
private ServiceConnection _connection = new ServiceConnection()
{
#Override
public void onServiceConnected(ComponentName name, IBinder service)
{
_serviceMessenger = new Messenger(service);
try
{
// sending the initial welcome message
Message m = Message.obtain(null, ForegroundService.BIND_SERVICE);
m.replyTo = _messenger;
_serviceMessenger.send(m);
}
catch(RemoteException ex)
{
_serviceMessenger = null;
}
}
#Override
public void onServiceDisconnected(ComponentName name)
{
_serviceMessenger = null;
}
};
private static class IncomingHandler extends Handler
{
private WeakReference<MyActivity> _parent;
IncomingHandler(WeakReference<MyActivity> parent)
{
super();
_parent = parent;
}
#Override
public void handleMessage(Message msg)
{
Log.i(LOG_TAG, "Activity: Received message");
MyActivity ta = _parent.get();
switch(msg.what)
{
case ForegroundService.LOCATION_UPDATED:
if(msg.obj == null)
{
Log.e(LOG_TAG, "Activity: msg null");
ta.setTexts("", null, null);
}
else
{
Log.i(LOG_TAG, "Activity: msg ok");
LocWithName loc = (LocWithName)msg.obj;
ta.setTexts(loc.getName(), Double.toString(loc.getHossz()), Double.toString(loc.getSzel()));
Chronometer chronometer = ta.findViewById(R.id.chronom);
chronometer.setBase(SystemClock.elapsedRealtime());
chronometer.start();
}
break;
case ForegroundService.LOST_GPS:
ta.setTexts("", "unknown", "unknown");
break;
default:
super.handleMessage(msg);
break;
}
}
}
in activity onCreate:
Intent startIntent = new Intent(MyActivity.this, ForegroundService.class);
startIntent.setAction(Constants.ACTION.STARTFOREGROUND_ACTION);
bindService(startIntent, _connection, Context.BIND_AUTO_CREATE);
startService(startIntent);
and in the service java file:
final Messenger _messenger = new Messenger(new IncomingHandler(new WeakReference<>(this)));
Messenger _activityMessenger = null;
private static class IncomingHandler extends Handler
{
WeakReference<ForegroundService> _parent;
IncomingHandler(WeakReference<ForegroundService> parent)
{
super();
_parent = parent;
}
#Override
public void handleMessage(Message msg) {
// received welcome message, now we know who to reply to
if(msg.what == ForegroundService.BIND_SERVICE)
{
_parent.get()._activityMessenger = msg.replyTo;
Log.d(LOG_TAG, "received reply address for messenger"); // after 1st edit
}
else
{
super.handleMessage(msg);
}
}
}
private void sendMessageToUI(int message, LocWithName newLoc)
{
Log.i(LOG_TAG, "Service: sending message to UI");
if(_activityMessenger != null)
{
Log.d(LOG_TAG, "messenger not null"); // after 1st edit
try
{
_activityMessenger.send(Message.obtain(null, message, newLoc));
Log.i(LOG_TAG, "Service: message sent");
}
catch(RemoteException ex)
{
// activity is dead
_activityMessenger = null;
}
}
}
then I start sending messages via the sendMessageToUI() function periodically from the service, namely every 5 seconds. The service's onStartCommand runs the first UI update immediately, which reschedules itself for every other iteration.
What I know:
the first immediate "UI update" in the service does run as logcat shows me the "sending message to UI" text at the correct time
all other updates run
all other updates deliver their messages successfully (which means that the first was stopped by _serviceMessenger being null, not the RemoteException, because the catch block would stop all later messages)
the welcome message from the activity to the service arrives as it is a necessity for further replies from the service
What I have tried:
in the activity, first bind then start the service (example code is in this state, initially it was the other way around), so _activityMessenger isn't null by the time it has to send first message
send a "burner message" so that is the one that doesn't get delivered instead of actually important messages
search google for similar problems to no avail - where there are problems, it doesn't work at all, not just the first time around
search this site for similar problems, same result as with google
Since there are five seconds between the first and the second message, I suspect it is an issue with the speed of initializing something, but I couldn't get further than that. So what exactly happens here and why doesn't it work?
EDIT 1: at the suggestion of #pskink, I added Log.d()-s. It turns out the activity only sends the "welcome message" with reply address after the first run of the UI updater despite being called earlier than startService.
Also, the code sending the messages, after #pskink asking:
in service class:
final Handler handler = new Handler();
Runnable updateUI = new Runnable()
{
// do work to get the information to display
// in this code I set "int message" to one of the constants handled by the activity's IncomingHandler and "LocWithName newLoc" to a useful value or null
sendMessageToUI(message, newLoc);
handler.removeCallbacks(updateUI);
handler.postDelayed(updateUI, 5000);
}
in service onStartCommand:
handler.post(updateUI);
Your mistake is assuming that the bindService() and startService() calls block until the service has been "bound" or "started", respectively. The reality is that onServiceConnected() won't get called until sometime after onCreate() returns. Likewise, the order you call them in is basically meaningless; the OS doesn't guarantee that the service will handle the binding or the onStartCommand() first or second, in this case.
To fix, delete the call to startService() (as #pskink suggested); the service is started by virtue of the fact that you are binding to it. onStartCommand() will no longer be called. Instead, have the Service kick off the updateUI Runnable when it gets the ForegroundService.BIND_SERVICE message. This allows you to establish the appropriate "happens before" relationships -- namely, that the ServiceConnection binding "happens before" you start trying to use _activityMessenger to send messages.

How to make the working Thread communicate with the UI Thread when the working thread is launched from a service

I am using Wifi Direct that detects and connects to a device in a background service.
When a WifiDirect connection is established, i launch a serverThread and a Client Thread respectively for the Server (GO) and for the Client.
My problem now, is that i don't know how to make the working thread communicate with the UI Thread.
What i did is that created a looper and a handler in the ClientThread and Server thread and also a handler for it, to get the jobs.
The socket connection between the client/server is correctly handled.
But the problem is that i don't know how can i send data from the UI Thread to the Working Thread.
What should i do?
here is the code:
In the service when a connection is established i launch the threads:
public void onConnectionInfoAvailable(final WifiP2pInfo info) {
Log.d(MainActivity.TAG, "groupowneradress"+ info.groupOwnerAddress.getHostAddress());
if (info.groupFormed && info.isGroupOwner) {
if (WifiDirectStatus!=1){
isgroupowner=true;
ServerDataThread serverThread= new ServerDataThread(PORT);
serverThread.start();
WifiDirectStatus=1;
}
} else if (info.groupFormed) {
if (WifiDirectStatus!=2){
ClientDataThread clientThread= new ClientDataThread(info.groupOwnerAddress.getHostAddress(),PORT);
clientThread.start();
WifiDirectStatus=2;
}
}
}
The client thread for example:
public class ClientDataThread extends Thread {
public Handler clientHandler;
private static Socket socket=null;
private static final int SOCKET_TIMEOUT = 5000;
private int port;
private String host;
private BufferedReader in=null;
public PrintWriter out=null;
static final int MSG_CLIENT = 2;
ClientDataThread(String host, int port){
this.port=port;
this.host=host;
}
#Override
public void run(){
try{
Log.d(MainActivity.TAG, "ask for connection to the server");
socket=new Socket();
socket.bind(null);
socket.connect((new InetSocketAddress(host, port)), SOCKET_TIMEOUT);
Log.d(MainActivity.TAG, "connection socket has been established");
try{
in=new BufferedReader(new InputStreamReader(socket.getInputStream()));
out= new PrintWriter(socket.getOutputStream());
Log.d(MainActivity.TAG, "The Printwriter is equal to"+out.toString());
Thread t1=new Thread(new Receive_Client(in));
t1.start();
}catch(IOException e){
Log.d(MainActivity.TAG, "the client disconnect");
}
}catch (UnknownHostException e) {
Log.d(MainActivity.TAG, "impossible to connect to the host"+socket.getLocalSocketAddress());
}catch (IOException e) {
Log.d(MainActivity.TAG, "No Server Listening the port"+socket.getLocalPort());
}
Looper.prepare();
clientHandler = new Handler() {
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_CLIENT:
Log.d(MainActivity.TAG,"CDS has received the following message to send to the server"+(String)msg.obj);
try{
out.println((String)msg.obj);
out.flush();
Log.d(MainActivity.TAG, "The message has been sent to the server");
}catch (Exception e){
e.printStackTrace();
}
break;
default:
super.handleMessage(msg);
}
}
};
final Messenger mMessenger = new Messenger(clientHandler);
Looper.loop();
}
}
And in the main activity i want to send a message to the working thread whenever i have a new location update:
public void onLocationChanged(Location location) {
Log.d(MainActivity.TAG, "location changed"+Double.toString(location.getLatitude())+Double.toString(location.getLongitude()));
if (mBound){
Log.d(MainActivity.TAG,"Wifi direct Status"+mDeviceListService.getWifiDirectStatus());
if (mDeviceListService.getWifiDirectStatus()==1){
// This Device acts as a Server
}
if (mDeviceListService.getWifiDirectStatus()==2){
Message msg= Message.obtain(null, ClientDataThread.MSG_CLIENT);
msg.obj= "bonjour";
clientHandler.sendMessage(msg);
}
The main benefit of declaring a BroadcastReveiver is that you can issue the action wherever you want and declare the receiver also whenever you want, so it makes it suitable for complex workflows.
You simply declare the receiver where you want to receive the information:
class ActivityBroadcast extends BroadcastReceiver {
#Override
public void onReceive(final Context context, final Intent intent) {
if (intent.getAction().equals(CustomActions.RECEIVEMYDATA)) {
final String mydata = intent.getStringExtra("mydata");
if ((mydata != null) && (!mydata.isEmpty())) {
// Do your staff
...
}
}
}
}
As you may see, you'll be using Intents, so the data you want to send has to be serializable or parcelable.
Another thing is that you have to register your receiver for some actions, it means that your receiver will only listen for this actions and this way you know that's the data you want to process. Despite Android already has some built-in actions, I strongly recommend defining your own. You may do that simply declaring a public class with public own actions. In my case you'll see I'm using CustomActions.RECEIVEMYDATA, which is just a personalized and unique String.
You now just have to declare your service, register it for your actions and register it. An unregistered receiver won't listen for anything.
final ActivityBroadcast broadcast_signal = new ActivityBroadcast();
final IntentFilter iFilter= new IntentFilter();
iFilter.addAction(CustomActions.RECEIVEMYDATA);
LocalBroadcastManager.getInstance(this).registerReceiver(broadcast_signal, iFilter);
You may see that this is registered as a local broadcast receiver. That means that you'll register actions just within your application. There's another kind of broadcast receiver and it might be used to communicate app-wide, in this case you'd use a global broadcast receiver.
There's just two things left:
The first is to send the signal to that receiver, so whenever and wherever you want, use something like this:
final Intent intentResult = new Intent(CustomActions.RECEIVEMYDATA);
intentResult.putExtra("mydata", "data to send");
LocalBroadcastManager.getInstance(this).sendBroadcast(intentResult);
Don't forget to unregister your broadcast receiver when it's no longer needed. For both this and registering, you should have in mind the lifecycle of the Activity where you're handling it and unregister/register when needed.
LocalBroadcastManager.getInstance(this).unregisterReceiver(broadcast_signal);

Android service not launched by JUnit test

I have this test class to test a remote service:
public class CoreServiceBasicTest extends ServiceTestCase<CoreService> implements ServiceConnection {
/** Tag for logging */
private final static String TAG = CoreServiceBasicTest.class.getName();
/** Receive incoming messages */
private final Messenger inMessenger = new Messenger(new IncomingHandler());
/** Communicate with the service */
private Messenger outMessenger = null;
/** Handler of incoming messages from service */
private static class IncomingHandler extends Handler {
#Override
public void handleMessage(Message msg) {
Log.d(TAG, "Incoming message");
}
}
/** Constructor for service test */
public CoreServiceBasicTest() {
super(CoreService.class);
}
/** Start the service */
#Override
public void setUp() {
// Mandatory
try {
super.setUp();
} catch (Exception e) {
e.printStackTrace();
}
// Start the service
Intent service = new Intent();
service.setClass(this.getContext(), CoreService.class);
startService(service);
Log.d(TAG, "Service started");
}
public void onServiceConnected(ComponentName className, IBinder service) {
outMessenger = new Messenger(service);
Log.d(TAG, "Service attached");
}
public void onServiceDisconnected(ComponentName className) {
// TODO Auto-generated method stub
}
#SmallTest
public void testBindService() {
// Bind to the service
Intent service = new Intent();
service.setClass(getContext(), CoreService.class);
boolean isBound = getContext().bindService(service, this, Context.BIND_AUTO_CREATE);
assertTrue(isBound);
}
}
The problem is that startService(service) in the setUp() method does not launch the service correctly. This is what the AVD shows:
As you can see, the process is launched but the service is not. Then on testBindService(), assertTrue(isBound) fails.
This doesn't happen if I launch the service from an Activity:
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Start the Core service
Intent service = new Intent();
service.setClass(this, CoreService.class);
if (startService(service) == null) {
Toast.makeText(this, "Error starting service!", Toast.LENGTH_LONG).show();
Log.e(TAG, "Error starting service");
} else {
Toast.makeText(this, "Service started sucessfully", Toast.LENGTH_LONG).show();
}
// Die
finish();
}
Here the service is started correctly, as shown below.
How can I start and bind to a remote service that uses Messenger to communicate with activities from an Android Test Project?
The whole point of Android Test Project (test.apk) is to instrument the Application Project (app.apk) and unit-test the Android components (Activity, Service and etc) which are associated with Application Project, in another word, unit-testing Activity and Service that is created and manipulated inside app.apk.
You should not write your MessengerService implementation partially (Messenger, IncomingHandler and etc) second time inside ServiceTestCase implementation under Test project. MessengerService implementation only need to be written once in your Application project's CoreService.java file.
ServiceConnection is used for inter-communication between Activity and Service, as we use ServiceTestCase here (means unit-test service, communication with other components is out-of-scope hence not considered), we don't need a ServiceConnection implementation. The only thing ServiceConnection does is initialize a solid Messenger object so that we could use later, once service is properly created:
public void onServiceConnected(ComponentName className, IBinder service) {
// This is what we want, we will call this manually in our TestCase, after calling
// ServiceTestCase.bindService() and return the IBinder, check out code sample below.
mService = new Messenger(service);
}
Also note that you don't need to call ServiceTestCase.startService() in this case, as ServiceTestCase.bindService() will properly start the service (if it is not started yet) and return a IBinder object we need to use to initialize Messenger object later.
Say if your IncomingHandler.handleMessage() impelementation in CoreService.java look like this:
... ...
switch (msg.what) {
case MSG_SAY_HELLO:
msgReceived = true;
break;
... ...
To test send message functions in ServiceTestCase:
... ...
IBinder messengerBinder = null;
#Override
public void setUp() throws Exception {
super.setUp();
// Bind the service and get a IBinder:
messengerBinder = bindService(new Intent(this.getContext(), CoreService.class));
//log service starting
Log.d(TAG, "Service started and bound");
}
public void testSendMessage() {
// Use IBinder create your Messenger, exactly what we did in ServiceConnection callback method:
Messenger messenger = new Messenger(messengerBinder);
Message msg = Message.obtain(null, MessengerService.MSG_SAY_HELLO, 0, 0);
messenger.send(msg);
// Do some assert stuff here
... ...
}
... ...
If your want to test communication between Activity and Service, then ServiceTestCase is not suitable in this case. Consider using ActivityInstrumentationTestCase2 test the actual Activity (which bound to your CoreService, which gives you ability to indirectly test your Service functions.
Just looking at the documentation for ServiceTestCase it says that the test framework delays starting the service until one of your test methods calls ServiceTestCase.startService() or ServiceTestCase.bindService().
Looking at your code you call ServiceTestCase.startService() in your setUp() method, not in a test method. This doesn't start the service yet. It is waiting for one of your test methods to call ServiceTestCase.startService() or ServiceTestCase.bindService().
Your test method testBindService() isn't calling ServiceTestCase.bindService(), instead it is calling Context.bindService() and failing. The test framework is still waiting, so that's why the service isn't started.
Have another look at the Lifecycle support discussion in the linked developer docs.

Android sending periodically data from service to Activity

I have a application which starts service on the first launch.
After that it pulls data from the server periodically.
I have opened my activity and if there is refresh button, I already have service which is already fetching data in the background that moment I want to disable the button and as soon as new data is loaded I have to show it in activity and enable refresh button.
If activity is not running then it should show notification.
So second point was the easiest and done. I'm stuck on the point 1. How to send periodically data to activity from service? I'm using database to store the data.
Any help on this ?
You can have your service send 'Messages' to your Activity Messenger to make it react as soon as service detects new content (see this android developers help section on Activity/Service Messenging).
Here are samples for Two-Way messaging (from Service to Activity and from Activity to Service). Quoting the doc:
You can see an example of how to provide two-way messaging in the
MessengerService.java (service) and
MessengerServiceActivities.java (client) samples.
Here's the relevant parts.
Incoming Handler in Activity:
/**
* Activity Handler of incoming messages from service.
*/
class IncomingHandler extends Handler {
#Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MessengerService.MSG_SET_VALUE:
mCallbackText.setText("Received from service: " + msg.arg1);
break;
default:
super.handleMessage(msg);
}
}
}
/**
* Activity target published for clients to send messages to IncomingHandler.
*/
final Messenger mMessenger = new Messenger(new IncomingHandler());
In the service, showing only the relevant parts:
/**
* Handler of incoming messages from clients.
*/
class IncomingHandler extends Handler {
#Override
public void handleMessage(Message msg) {
//obtain Activity address from Message
Messenger mClient=msg.replyTo;
try {
// try to send it some mValue
mClient.send(Message.obtain(null,MSG_SET_VALUE, mValue, 0));
} catch (RemoteException e) {
// The client is dead. Remove it
mClient=null;
}
}
}
/**
* Target we publish for clients to send messages to IncomingHandler.
*/
final Messenger mMessenger = new Messenger(new IncomingHandler());
Also you can bind to your service from your activity and periodically call one of your service method to check for new content. For this, if your service is another application, you must use aidl (this is harder). If it's in the same package, I advise you to use the much easier 'local service binding'

android service notify activity completed best way?

I created a service which syncs data from the web on a background thread and want to notify a list activity when the service has completed so it can update it's cursor? What would be the best way to do this? I'm thinking of sending a broadcast when the service is done but not sure if that's the best way to do it. I need to requery the cursor when the service has finished so I'm not sure if that will work well with broadcast receivers? I haven't done alot of android in awhile so thanks in advance.
Use a Handler in your service that registers a client when your ListActivity connects to the service; that is, in your onServiceConnected method in your ListActivity, send a Message to your service that enables you to keep track of connected clients. Then you can simply loop through these clients in your Service and send them a Message when something takes place in your Service that you want to notify your ListActivity about. For more information you can look at code in an on-going project of mine: my ListActivity and my Service stub.
In short, in your MainActivity, start and bind to your service with:
Intent i = new Intent(this, NetworkService.class);
startService(i);
bindService(i, networkServiceConnection, Context.BIND_AUTO_CREATE);
Define a messenger to respond to messages from the service like:
Messenger messenger = new Messenger(new IncomingHandler());
class IncomingHandler extends Handler {
#Override
public void handleMessage(Message msg) {
switch (msg.what) {
case NetworkService.MSG_SOMETHING:
// do something here
break;
default:
super.handleMessage(msg);
}
}
}
And then write your service connection code like:
private ServiceConnection networkServiceConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
networkService = new Messenger(service);
try {
Message msg = Message.obtain(null, NetworkService.MSG_REGISTER_CLIENT);
msg.replyTo = messenger;
networkService.send(msg);
log.debug("Connected to service");
} catch (RemoteException e) {
// Here, the service has crashed even before we were able to connect
}
}
Note that the replyTo is the messenger we just created.
In your NetworkService, keep track of connected clients with:
ArrayList<Messenger> clients = new ArrayList<Messenger>();
and create your handler like:
class IncomingHandler extends Handler {
#Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_REGISTER_CLIENT:
log.debug("Adding client: " + msg.replyTo);
clients.add(msg.replyTo);
break;
default:
super.handleMessage(msg);
break;
}
}
}
Then, when you want to send a message back to your MainActivity, just do something like the following:
for (int i = 0; i < clients.size(); i++) {
try {
clients.get(i).send(Message.obtain(null, MSG_SOMETHING));
} catch (RemoteException e) {
// If we get here, the client is dead, and we should remove it from the list
log.debug("Removing client: " + clients.get(i));
clients.remove(i);
}
}
If you're already using the support library, you could just as easily fire a broadcast from the service using the LocalBroadcastManager back to your activity that would listen for the broadcast being sent.
Using LocalBroadcastManager ensures only your own application would ever receive the broadcast so you don't have to worry about leaking private data or opening up potential security holes.
Also see: how to use LocalBroadcastManager?
EDIT (09/2014):
A better way to do this would be to use an event bus framework like Otto (my favourite) or GreenRobot/EventBus to avoid coupling your components too tightly.
As per The Busy Coder's Guide to Advanced Android Development
An Event Bus is a great way for the service to let other pieces
of the app know that certain work was done. It provides a standard
communications channel (or “bus”) that event producers and event
consumers can hook into. Event producers merely need to hand the event
to the bus; the bus will handle directing those events to relevant
consumers. This reduces the coupling between the producers and
consumers, sometimes even reducing the amount of code needed to source
and sink these events.

Categories

Resources