Accessing SQLite from Activity, Async and Service - android

I am reading first 100 SMS from inbox and store it in local database. I am doing this with the help on AsyncTask. Afterwards I am reading those SMS from local database and display it. In onPostExecute of AysncTask after reading & storing first 100 SMS, I am invoking the service to read the remaining SMS and storing it in local database. Till before invoking the service, UI is responsive but after invoking the service, the UI became unresponsive. Is it due to database lock or something else. Please help to solve this problem...
My AsyncTask code
private class InitialSetup extends AsyncTask<String, Integer, Long> {
#Override
protected Long doInBackground(String... urls) {
fetchSMS();
updateDatabase();
return 0L;
}
#Override
protected void onPostExecute(Long result) {
if (this.dialog.isShowing()) {
this.dialog.dismiss();
}
populateUI(getApplicationContext());
Intent intent = new Intent(getApplicationContext(), SMSService.class);
getApplication().startService(intent);
}
}
Service code
public class SMSService extends Service {
Long inboxSMSID, localSMSID;
#Override
public IBinder onBind(Intent arg0) {
return null;
}
#Override
public void onStart(Intent intent, int startid) {
if (intent.hasExtra("InboxSMS") && intent.hasExtra("SMSID")) {
inboxSMSID = intent.getLongExtra("InboxSMS", 0);
localSMSID = intent.getLongExtra("SMSID", 0);
globalToLocalSMSDB(inboxSMSID);
}
}
private void globalToLocalSMSDB(Long id) {
SMSTable smsObj = new SMSTable(getApplicationContext());
smsObj.open();
Uri uriSMSURI = Uri.parse("content://sms/inbox");
Cursor smscur = getContentResolver().query(uriSMSURI, null,
"_id < " + id, null, "_id DESC");
while (smscur.moveToNext()) {
String sender = SMSManagement.getSender(smscur);
String message = SMSManagement.getMessage(smscur);
String timeStamp = SMSManagement.getTime(smscur);
smsObj.insertIntoSMSTable(sender, message, timeStamp);
}
smscur.close();
smsObj.close();
}
}

The service is running in UI thread as well, you have to create worker threads there manually unless it's an IntentService.

Related

How to queue multiple tasks in a foreground service, so that they execute one by one?

public class CopyService extends Service {
private List<CustomFile> taskList;
private AsyncTask fileTask;
#Override
public void onCreate() {
super.onCreate();
taskList = new ArrayList<>();
fileTask = new fileTaskAsync();
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
String filePath = intent.getStringExtra("filePath");
String fileType = intent.getStringExtra("fileType");
String taskType = intent.getStringExtra("taskType");
String fileName = intent.getStringExtra("fileName");
CustomFile customFile = new CustomFile();
customFile.filePath = filePath;
customFile.fileType = fileType;
customFile.taskType = taskType;
customFile.fileName = fileName;
taskList.add(customFile);
Notification notification = getNotification();
startForeground(787, notification);
if (fileTask.getStatus() != AsyncTask.Status.RUNNING) {
CustomFile current = taskList.get(0);
taskList.remove(current);
fileTask = new fileTaskAsync().execute(current);
}
stopSelf();
return START_NOT_STICKY;
}
#Nullable
#Override
public IBinder onBind(Intent intent) {
return null;
}
private class fileTaskAsync extends AsyncTask<CustomFile, Void, String> {
#Override
protected String doInBackground(CustomFile... customFiles) {
CustomFile customFile = customFiles[0];
FileUtils.doFileTask(customFile.filePath, customFile.fileType,
customFile.taskType);
return customFile.fileName;
}
#Override
protected void onPostExecute(String name) {
sendResult(name);
if (!taskList.isEmpty()) {
CustomFile newCurrent = taskList.get(0);
taskList.remove(newCurrent);
fileTask = new fileTaskAsync().execute(newCurrent);
}
}
}
private void sendResult(String name) {
Intent intent = new Intent("taskStatus");
intent.putExtra("taskName", name);
LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
}
}
I need to execute multiple tasks in a service one by one. Task is either copying or moving local files. Suppose, user is copying a big file and he wants to copy or move other files. I need the subsequent tasks to be queued and exected one by one.
Currently, I'm creating a list inside the service and running an async task. In onPostExecute, I check for remaining tasks in the list and start the async task again from there. As shown in the code.
But, I'm concerned about memory leaks. And I'm very new to programming so, I don't know what's the best practice in such situations.
I can't use IntentService, because I want the task to continue even if the user hits home button to open some other app.
AS I said in the comments, I think your solution is reasonable. A Foreground Service is a good candidate for long running work that needs to be executed immediately, and from your description your file copying task matches that criteria.
That said, I don't believe AsyncTask is a good candidate for your problem. AsyncTasks are best deployed when you need to do some quick work off the main thread, in the order of a few hundred milliseconds at most, whereas your copy task could presumably take several seconds.
As you have multiple tasks to complete which aren't directly dependent on one another, I would recommend you make use of a thread pool to conduct this work. For that you can use an ExecutorService:
public class CopyService extends Service {
private final Deque<CustomFile> tasks = new ArrayDeque<>();
private final Deque<Future<?>> futures = new LinkedBlockingDequeue<>();
private final ExecutorService executor = Executors.newCachedThreadPool();
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
//May as well add a factory method to your CustomFile that creates one from an Intent
CustomFile customFile = CustomFile.fromIntent(intent);
tasks.offer(customFile);
//...Add any other tasks to this queue...
Notification notification = getNotification();
startForeground(787, notification);
for(CustomFile file : tasks) {
final Future<?> future = executor.submit(new Runnable() {
#Override
public void run() {
final CustomFile file = tasks.poll();
//Ddo work with the file...
LocalBroadcastManager.getInstance(CopyService.this).sendBroadcast(...);
//Check to see whether we've now executed all tasks. If we have, kill the Service.
if(tasks.isEmpty()) stopSelf();
}
});
futures.offer(future);
}
return START_NOT_STICKY;
}
#Override
public void onDestroy() {
super.onDestroy();
//Clear pending and active work if the Service is being shutdown
//You may want to think about whether you want to reschedule any work here too
for(Future<?> future : futures) {
if(!future.isDone() && !future.isCancelled()) {
future.cancel(true); //May pass "false" here. Terminating work immediately may produce side effects.
}
}
}
#Nullable
#Override
public IBinder onBind(Intent intent) {
return null;
}
This shouldn't cause any memory leaks, as any pending work is destroyed along with the Service.

Android, prevent to kill service/thread

how can i prevent this service with thread to dont be killed from android, i need this notifications always runnig, but when is mobile locked, nothing will happen. I think android kill service or thread or something like that
MainActivity in onCreate
startService(new Intent(this, NotifyService.class));
My service
public class NotifyService extends Service {
private DatabaseOp mDbHelper;
public Vibrator vibrator;
String username;
#Override
public IBinder onBind(Intent intent) {
// TODO Auto-generated method stub
return null;
}
#Override
public void onCreate ()
{
mDbHelper = new DatabaseOp(this);
final boolean cyklus = true;
Thread vlakno = new Thread (new Runnable()
{
#Override
public void run()
{
while (cyklus)
{
try {
Thread.sleep(60000);
} catch (InterruptedException e) {
e.printStackTrace();
}
String sysDate = getSysDate();
String sysDate2 = getSysDate2();
String time = getSysTime();
mDbHelper.open();
Log.v( "sysDate", sysDate );
Cursor cursorU = mDbHelper.fetchUlohaS(0, sysDate);
if (cursorU.getCount() > 0)
{
String idU = cursorU.getString(cursorU.getColumnIndexOrThrow(DatabaseOp.KEY_ID));
String dbDateU = cursorU.getString(cursorU.getColumnIndexOrThrow(DatabaseOp.KEY_DATE));
String menoU = cursorU.getString(cursorU.getColumnIndexOrThrow(DatabaseOp.KEY_NAZOV));
String mHodina = getResources().getString(R.string.cas)+" "+cursorU.getString(cursorU.getColumnIndexOrThrow(DatabaseOp.KEY_HODINA));
Log.v( "task", dbDateU+"/"+sysDate );
if (dbDateU.equals(sysDate))
{
Notify(menoU, mHodina, idU, 0);
}
}
Cursor cursorS = mDbHelper.fetchSviatokS(3, sysDate2);
if (cursorS.getCount() > 0)
{
String idS = cursorS.getString(cursorS.getColumnIndexOrThrow(DatabaseOp.KEY_ID));
String dbDateS = cursorS.getString(cursorS.getColumnIndexOrThrow(DatabaseOp.KEY_DATUM));
String menoS = cursorS.getString(cursorS.getColumnIndexOrThrow(DatabaseOp.KEY_NAZOV));
if (dbDateS.equals(sysDate2) && time.equals("09:00"))
{
Notify(menoS,getResources().getString(R.string.title_section4), idS, 3);
}
}
mDbHelper.close();
}
}
});
vlakno.start();
}
}
Have you tried to use ForgroundService?
Checkout this repo for an example - https://github.com/supercurio/foreground-service-sample-app
I think you should consider AlarmManager. See http://developer.android.com/reference/android/app/AlarmManager.html.
To tip the system to keep your Service alive as long as possible (i.e. before RAM is very short or user kills the service by hand through application info screen), you need to run it as a foreground service -- by using startForeground() method.
If you're looking for a way to run when the device is turned off, read the Keeping The Device Awake training page and consider using AlarmManager instead as suggested by #khris if your task is not very critical in terms of timing precision.

Not able to call runOnUiThread in a thread from inside of a service [duplicate]

This question already has answers here:
Accessing UI thread handler from a service
(7 answers)
Closed 9 years ago.
I wanted to make a service which will check my SMS in every 20 sec if there are any unread SMS then send it to my website then mark as read for posting data to website I used asynctask and it worked fine when I tried manually (by making button click type app)
but in side service I cant define
runOnUiThread(new Runnable() {
#Override
public void run() { new MyAsyncTask().execute(sender,time,message);}
});
it is unable to identify and asks me to define runOnUiThread
Is there any way to call my asynctask from the place where I am calling in below code
public class TestService extends Service {
String sender = null;
String time = null;
String message = null;
#Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
throw new UnsupportedOperationException("Not yet implemented");
}
#Override
public void onCreate() {
Toast.makeText(getApplicationContext(), "Service Created", 1).show();
super.onCreate();
}
#Override
public void onDestroy() {
Toast.makeText(getApplicationContext(), "Service Destroy", 1).show();
super.onDestroy();
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
Toast.makeText(getApplicationContext(), "Service Running ", 1).show();
new Thread(new Runnable() {
public void run() {
ContentValues values = new ContentValues();
Uri mSmsinboxQueryUri = Uri.parse("content://sms/inbox");
String[] columns = new String[] { "_id", "thread_id",
"address", "person", "date", "body", "type" };
Cursor cursor1 = getContentResolver().query(mSmsinboxQueryUri,
null, "read=0", null, null);
DateFormat formatter = new SimpleDateFormat(
"dd/MM/yyyy hh:mm:ss.SSS");
Calendar calendar = Calendar.getInstance();
if (cursor1.getCount() > 0) {
cursor1.moveToFirst();
do {
// Retrieving sender number
sender = (cursor1.getString(cursor1
.getColumnIndex(columns[2])).toString());
// Retriving time of reception
long ms = cursor1.getLong(cursor1
.getColumnIndex(columns[4]));
calendar.setTimeInMillis(ms);
time = formatter.format(calendar.getTime()).toString();
// Retriving the message body
message = (cursor1.getString(cursor1
.getColumnIndex(columns[5])).toString());
runOnUiThread(new Runnable() {
#Override
public void run() {
new MyAsyncTask()
.execute(sender, time, message);
}
});
} while (cursor1.moveToNext());// end of while
}// end of if
// set as read
values.put("read", true);
getContentResolver().update(Uri.parse("content://sms/inbox"),
values, null, null);
}
}).start();
return super.onStartCommand(intent, flags, startId);
}
private class MyAsyncTask extends AsyncTask<String, Integer, Double> {
#Override
protected Double doInBackground(String... params) {
postData(params[0], params[1], params[2]);
return null;
}
protected void onPostExecute(Double result) {
// pb.setVisibility(View.GONE);
Toast.makeText(getApplicationContext(), "command sent",
Toast.LENGTH_LONG).show();
}
protected void onProgressUpdate(Integer... progress) {
// pb.setProgress(progress[0]);
}
public void postData(String sender, String time, String message) {
// Create a new HttpClient and Post Header
HttpClient httpclient = new DefaultHttpClient();
HttpPost httppost = new HttpPost(
"http://www.mysite.co.nf/reciever.php");
try {
// Add your data
List<NameValuePair> nameValuePairs =
new ArrayList<NameValuePair>();
nameValuePairs.add(new BasicNameValuePair("sender", sender));
nameValuePairs.add(new BasicNameValuePair("time", time));
nameValuePairs.add(new BasicNameValuePair("message", message));
httppost.setEntity(new UrlEncodedFormEntity(nameValuePairs));
// Execute HTTP Post Request
HttpResponse response = httpclient.execute(httppost);
} catch (ClientProtocolException e) {} catch (IOException e) {}
}
}
}
Service does not have a method called runOnUiThread(). You're assuming that the method from Activity is also defined for a Service, but it's not.
Solution, just define a method that does exactly that. Here's a simplified example, the rest of your code would remain unchanged.
import android.os.Handler;
public class TestService extends Service {
Handler handler;
#Override
public void onCreate() {
// Handler will get associated with the current thread,
// which is the main thread.
handler = new Handler();
super.onCreate();
}
private void runOnUiThread(Runnable runnable) {
handler.post(runnable);
}
}
For more info, see the docs for Handler. It's used to dump some work onto a specific thread. In this case, the Handler gets associated with the UI thread, since the UI thread always calls Service.onCreate().

Intercepting Outgoing SMS

Is it possible to intercept outgoing SMS before it is actually sent, get its contents then ignore / send it according to some criteria?
eg. block all international text (numbers with leading 00), but allow everything else.
Incoming SMS
You can intercept an incoming sms thru sms listener using Broadcast receiver.You can modify the incoming sms or destroy it so that it does not reaches inbox.
Outgoing SMS
You can listen for outgoing sms by putting content observer over content://sms/out but you can not modify it with the native sms app.You can obviously modify the content of content://sms/out but it has no point.
Based on what I've been able to find, it seems as though the answer is either, "It's impossible" or, that it could be possible, but you'd need to write your own SMS app, so that you received the text before it became an SMS, and then you could perform whatever checks you'd like on it before calling the API to actually queue it to be sent.
Sorry =(
As far as I know, you can spy on outgoing SMS messages but you cannot stop them from being sent out.
Here's how you can detect the outgoing SMS messages:
Listen outgoing SMS or sent box in Android
But since this is done basically by reading from a database, I doubt you can stop the SMS from leaving.
I wish you good luck.
Emmanuel
This is what i have done to make an OutgoingSMSReceiver hope it helps some one some dya!
public final class OutgoingSMSReceiver extends Service {
private static final String CONTENT_SMS = "content://sms/";
private CallerHistoryDataSource database = new CallerHistoryDataSource(UCDGlobalContextProvider.getContext());
static String messageId="";
private class MyContentObserver extends ContentObserver {
public MyContentObserver() {
super(null);
}
#Override
public void onChange(boolean selfChange) {
super.onChange(selfChange);
Uri uriSMSURI = Uri.parse(CONTENT_SMS);
Cursor cur = UCDGlobalContextProvider.getContext().getContentResolver().query(uriSMSURI, null, null, null, null);
// this will make it point to the first record, which is the last SMS sent
cur.moveToNext();
String message_id = cur.getString(cur.getColumnIndex("_id"));
String type = cur.getString(cur.getColumnIndex("type"));
if(type.equals(Constants.SMS_TYPE_OUTGOING)){
/**
* onChange is fired multiple times for a single SMS, this is to prevent multiple entries in db.
*
*/
if(!message_id.equals(messageId))
{
String content = cur.getString(cur.getColumnIndex("body"));
String msisdnWithCountryCodeOrPrefix = cur.getString(cur.getColumnIndex("address"));
String msisdn = MSISDNPreFixHandler.fixMsisdn(msisdnWithCountryCodeOrPrefix);
Sms sms = new Sms();
sms.setType(Constants.SMS_TYPE_OUTGOING);
sms.setMsisdn(msisdn);
sms.setContent(content);
Log.i("MyContentObserver", "Sent SMS saved: "+content);
}
messageId = message_id;
}
}
#Override
public boolean deliverSelfNotifications() {
return false;
}
}
#Override
public IBinder onBind(Intent intent) {
return null;
}
#Override
public void onCreate() {
MyContentObserver contentObserver = new MyContentObserver();
ContentResolver contentResolver = getBaseContext().getContentResolver();
contentResolver.registerContentObserver(Uri.parse(CONTENT_SMS),true, contentObserver);
//Log.v("Caller History: Service Started.", "OutgoingSMSReceiverService");
}
#Override
public void onDestroy() {
//Log.v("Caller History: Service Stopped.", "OutgoingSMSReceiverService");
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
//Log.v("Caller History: Service Started.", "OutgoingSMSReceiverService");
/**
* Constant to return from onStartCommand(Intent, int, int): if this service's process is killed while it is started
* (after returning from onStartCommand(Intent, int, int)), then leave it in the started state but don't retain this delivered intent.
* Later the system will try to re-create the service. Because it is in the started state, it will guarantee to call
* onStartCommand(Intent, int, int) after creating the new service instance; if there are not any pending start commands to be
* delivered to the service, it will be called with a null intent object, so you must take care to check for this.
* This mode makes sense for things that will be explicitly started and stopped to run for arbitrary periods of time, such as a
* service performing background music playback.
*/
return START_STICKY;
}
#Override
public void onStart(Intent intent, int startid) {
Log.v("Caller History: Service Started.", "OutgoingSMSReceiverService");
}
}
Based on "Saad Akbar" response , i make it work but only with rooted device with permission MODIFY_PHONE_STATE
public class OutgoingSMSReceiver extends Service
{
private static final String CONTENT_SMS = "content://sms/";
static String messageId = "";
private class MyContentObserver extends ContentObserver
{
Context context;
private SharedPreferences prefs;
private String phoneNumberBlocked;
public MyContentObserver(Context context) {
super(null);
this.context = context;
}
#Override
public void onChange(boolean selfChange)
{
super.onChange(selfChange);
prefs = context.getSharedPreferences("com.example.testcall", Context.MODE_PRIVATE);
phoneNumberBlocked = prefs.getString("numero", "");
Uri uriSMSURI = Uri.parse(CONTENT_SMS);
Cursor cur = context.getContentResolver().query(uriSMSURI, null, null, null, null);
if (cur.moveToNext())
{
String message_id = cur.getString(cur.getColumnIndex("_id"));
String type = cur.getString(cur.getColumnIndex("type"));
String numeroTelephone=cur.getString(cur.getColumnIndex("address")).trim();
if (numeroTelephone.equals(phoneNumberBlocked))
{
if (cur.getString(cur.getColumnIndex("type")).equals("6"))
{
ContentValues values = new ContentValues();
values.put("type", "5");
context.getContentResolver().update(uriSMSURI,values,"_id= "+message_id,null);
}
else if(cur.getString(cur.getColumnIndex("type")).equals("5"))
{ context.getContentResolver().delete(uriSMSURI,"_id=?",new String[] { message_id});
}
}
}
}
#Override
public boolean deliverSelfNotifications()
{
return false;
}
}
#Override
public void onCreate()
{
MyContentObserver contentObserver = new MyContentObserver(getApplicationContext());
ContentResolver contentResolver = getBaseContext().getContentResolver();
contentResolver.registerContentObserver(Uri.parse(CONTENT_SMS), true, contentObserver);
}
}
<uses-permission android:name="android.permission.MODIFY_PHONE_STATE" />

Text To Speech not working in service

Following are the classes that i'm trying to implement, but i dont know where should i speak the name that i got from brodcast receiver.Can anyoneone help.
SERVICE CLASS
public class SMSTalk extends Service implements OnInitListener, OnUtteranceCompletedListener {
public static TextToSpeech mTts;
private String spokenText;
public String msg=null;
int flag=0;
#Override
public void onCreate() {
mTts = new TextToSpeech(this, this);
// This is a good place to set spokenText
}
public void readName(String temp)
{
msg=temp;
System.out.println("HHHHHHHHHHHHHHHHHHH"+msg);
// mTts.speak(msg, 0, null);
}
#Override
public void onInit(int status) {
SMSReceiver smsReceiver=new SMSReceiver();
if (status == TextToSpeech.SUCCESS) {
int result = mTts.setLanguage(Locale.UK);
if (result != TextToSpeech.LANG_MISSING_DATA && result != TextToSpeech.LANG_NOT_SUPPORTED) {
System.out.println("####"+msg);
Toast.makeText(getApplicationContext(), "SUCCESS",Toast.LENGTH_LONG ).show();
mTts.speak("Hello", 0, null);
flag=1;
}
}
if(flag==1)
{
System.out.println("######"+msg);
mTts.speak(msg, 0, null);
}
}
#Override
public void onUtteranceCompleted(String uttId) {
stopSelf();
System.out.println("onUtteranceCompleted"+msg);
}
#Override
public void onDestroy() {
if (mTts != null) {
mTts.stop();
mTts.shutdown();
}
super.onDestroy();
}
#Override
public IBinder onBind(Intent arg0) {
return null;
}
}
RECEIVER CLASS
public class SMSReceiver extends BroadcastReceiver
{
String name=null;
private Context mContext;
#Override
public void onReceive(Context context, Intent intent)
{
// TODO Auto-generated method stub
int n;
Bundle bundle = intent.getExtras();
Object pdus[] = (Object[]) bundle.get("pdus");
SmsMessage smsMessage[] = new SmsMessage[pdus.length];
for (n = 0; n < pdus.length; n++)
{
smsMessage[n] = SmsMessage.createFromPdu((byte[]) pdus[n]);
}
// show first message
String sms1 = smsMessage[0].getMessageBody();
String from = smsMessage[0].getOriginatingAddress();
//String name = getDisplayNameFromPhoneNo( from);
Uri lookupUri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(from));
Cursor c = context.getContentResolver().query(lookupUri, new String[]{PhoneLookup.DISPLAY_NAME}, null, null, null);
while(c.moveToNext()){
/* If we find a match we put it in a String.*/
name = c.getString(c.getColumnIndexOrThrow(PhoneLookup.DISPLAY_NAME));
}
Toast toast = Toast.makeText(context, "SMS Received from: " + from, Toast.LENGTH_LONG);
toast.show();
System.out.println("!!!!"+name);
Toast.makeText(context, "name: " + name, Toast.LENGTH_LONG).show();
//smsTalk.speakSMS(name);
//SMSTalk.mTts.speak("You have an SMS from "+name, 0, null);
context.startService(new Intent(context,SMSTalk.class));
SMSTalk smsTalk = new SMSTalk();
smsTalk.readName(name);
}
}
Your answer is probably there :
http://developer.android.com/guide/topics/fundamentals/services.html
Citations from the webpage :
Caution:
A service runs in the main thread of its hosting process—the service does not create its own thread and does not run in a separate process (unless you specify otherwise). This means that, if your service is going to do any CPU intensive work or blocking operations (such as MP3 playback or networking), you should create a new thread within the service to do that work. By using a separate thread, you will reduce the risk of Application Not Responding (ANR) errors and the application's main thread can remain dedicated to user interaction with your activities.
Should you use a service or a thread?
A service is simply a component that can run in the background even when the user is not interacting with your application. Thus, you should create a service only if that is what you need.
If you need to perform work outside your main thread, but only while the user is interacting with your application, then you should probably instead create a new thread and not a service. For example, if you want to play some music, but only while your activity is running, you might create a thread in onCreate(), start running it in onStart(), then stop it in onStop(). Also consider using AsyncTask or HandlerThread, instead of the traditional Thread class. See the Processes and Threading document for more information about threads.
Remember that if you do use a service, it still runs in your application's main thread by default, so you should still create a new thread within the service if it performs intensive or blocking operations.
There is a statement in service class "mTts.speak(msg, 0, null);" .It is giving null pointer exception, but here :
public void readName(String temp)
{
msg=temp;
System.out.println("HHHHHHHHHHHHHHHHHHH"+msg);
// mTts.speak(msg, 0, null);
}
it displays the value that i want.So the problem is with placing of "mTts.speak(msg, 0, null);".
PS:I have taken care of threading thing.

Categories

Resources