#14652: export CallManager services to sflphone-service,
bind service from home activity
separate service thread from UI
Signed-off-by: Emeric Vigier <emeric.vigier@savoirfairelinux.com>
diff --git a/src/com/savoirfairelinux/sflphone/client/Manager.java b/src/com/savoirfairelinux/sflphone/client/Manager.java
index 0a7b6c9..85bb795 100644
--- a/src/com/savoirfairelinux/sflphone/client/Manager.java
+++ b/src/com/savoirfairelinux/sflphone/client/Manager.java
@@ -24,23 +24,29 @@
static SFLPhoneHome uiThread;
static ImageButton buttonCall;
public static ManagerImpl managerImpl;
- public CallManagerJNI callmanagerJNI;
+ public static CallManagerJNI callmanagerJNI;
public CallManagerCallBack callManagerCallBack;
-
+
static {
+ /* return unique instance */
managerImpl = SFLPhoneservice.instance();
- Log.i(TAG, "ManagerImpl::instance() = " + managerImpl);
+ Log.i(TAG, "ManagerImpl::instance() = " + managerImpl);
+ callmanagerJNI = new CallManagerJNI();
}
- public Manager() {}
-
+ public Manager() {
+ Log.i(TAG, "Manager.callManagerJNI = " + callmanagerJNI);
+ callManagerCallBack = new CallManagerCallBack();
+ SFLPhoneservice.setCallbackObject(callManagerCallBack);
+ Log.i(TAG, "callManagerCallBack = " + callManagerCallBack);
+ }
+
public Manager(Handler h) {
-
- Manager.handler = h;
- callmanagerJNI = new CallManagerJNI();
- callManagerCallBack = new CallManagerCallBack();
- SFLPhoneservice.setCallbackObject(callManagerCallBack);
- Log.i(TAG, "callManagerCallBack = " + callManagerCallBack);
+ Manager.handler = h;
+ Log.i(TAG, "Manager.callManagerJNI = " + callmanagerJNI);
+ callManagerCallBack = new CallManagerCallBack();
+ SFLPhoneservice.setCallbackObject(callManagerCallBack);
+ Log.i(TAG, "callManagerCallBack = " + callManagerCallBack);
}
public static void callBack(String s) {
diff --git a/src/com/savoirfairelinux/sflphone/client/SFLPhoneHome.java b/src/com/savoirfairelinux/sflphone/client/SFLPhoneHome.java
index 19bf392..1b2aaac 100644
--- a/src/com/savoirfairelinux/sflphone/client/SFLPhoneHome.java
+++ b/src/com/savoirfairelinux/sflphone/client/SFLPhoneHome.java
@@ -38,13 +38,15 @@
import android.app.Fragment;
import android.app.FragmentManager;
import android.app.FragmentTransaction;
+import android.content.ComponentName;
+import android.content.Context;
import android.content.Intent;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.Handler;
+import android.os.IBinder;
import android.os.Message;
+import android.os.RemoteException;
import android.support.v13.app.FragmentStatePagerAdapter;
import android.support.v4.view.ViewPager;
import android.util.Log;
@@ -64,7 +66,7 @@
import android.widget.TextView;
import com.savoirfairelinux.sflphone.R;
-import com.savoirfairelinux.sflphone.service.ServiceConstants;
+import com.savoirfairelinux.sflphone.service.ISipService;
import com.savoirfairelinux.sflphone.service.SipService;
public class SFLPhoneHome extends Activity implements ActionBar.TabListener, OnClickListener
@@ -73,7 +75,6 @@
static final String TAG = "SFLPhoneHome";
private ButtonSectionFragment buttonFragment;
Handler callbackHandler;
- private Manager manager;
/* default callID */
static String callID = "007";
static boolean callOnGoing = false;
@@ -85,6 +86,8 @@
static Animation animation;
ContactListFragment mContactListFragment;
CallElementList mCallElementList;
+ private boolean mBound = false;
+ private ISipService service;
/**
* The {@link ViewPager} that will host the section contents.
@@ -160,64 +163,80 @@
animation.setRepeatCount(Animation.INFINITE);
// Reverse
animation.setRepeatMode(Animation.REVERSE);
- }
- // FIXME
- static {
- System.loadLibrary("gnustl_shared");
- System.loadLibrary("expat");
- System.loadLibrary("yaml");
- System.loadLibrary("ccgnu2");
- System.loadLibrary("crypto");
- System.loadLibrary("ssl");
- System.loadLibrary("ccrtp1");
- System.loadLibrary("dbus");
- System.loadLibrary("dbus-c++-1");
- System.loadLibrary("samplerate");
- System.loadLibrary("codec_ulaw");
- System.loadLibrary("codec_alaw");
- System.loadLibrary("speexresampler");
- System.loadLibrary("sflphone");
+ /* startService() can be called any number of times without harm */
+ Log.i(TAG, "starting SipService");
+ startSipService();
}
@Override
protected void onStart() {
Log.i(TAG, "onStart");
super.onStart();
+ // Bind to LocalService
+ if (!mBound) {
+ Log.d(TAG, "Binding service...");
+ Intent intent = new Intent(this, SipService.class);
+ bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
+ }
}
+ /* user gets back to the activity, e.g. through task manager */
@Override
protected void onRestart() {
super.onRestart();
}
+ /* activity gets back to the foreground and user input */
@Override
protected void onResume() {
Log.i(TAG, "onResume");
super.onResume();
-
- Log.i(TAG, "starting SipService");
- startSipService();
}
+ /* activity no more in foreground */
@Override
protected void onPause() {
- /* stop the service, no need to check if it is running */
- stopService(new Intent(this, SipService.class));
- serviceIsOn = false;
super.onPause();
}
+ /* activity is no longer visible */
@Override
protected void onStop() {
super.onStop();
+ /* stop the service, if no other bound user, no need to check if it is running */
+ if (mBound) {
+ Log.d(TAG, "Unbinding service...");
+ unbindService(mConnection);
+ mBound = false;
+ }
}
+ /* activity finishes itself or is being killed by the system */
@Override
protected void onDestroy() {
+ serviceIsOn = false;
super.onDestroy();
}
+ /** Defines callbacks for service binding, passed to bindService() */
+ private ServiceConnection mConnection = new ServiceConnection() {
+
+ @Override
+ public void onServiceConnected(ComponentName className,
+ IBinder binder) {
+ service = ISipService.Stub.asInterface(binder);
+ mBound = true;
+ Log.d(TAG, "Service connected");
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName arg0) {
+ mBound = false;
+ Log.d(TAG, "Service disconnected");
+ }
+ };
+
private void startSipService() {
Thread thread = new Thread("StartSFLphoneService") {
public void run() {
@@ -252,13 +271,6 @@
Log.i(TAG, "handleMessage: " + b.getString("callback_string"));
}
};
-
- manager = new Manager(callbackHandler);
- Log.i(TAG, "ManagerImpl::instance() = " + Manager.managerImpl);
- Manager.setActivity(this);
- /* set static AppPath before calling manager.init */
- Manager.managerImpl.setPath(getAppPath());
- Log.i(TAG, "manager created with callbackHandler " + callbackHandler);
}
@Override
@@ -403,26 +415,12 @@
}
}
- public String getAppPath() {
- PackageManager pkgMng = getPackageManager();
- String pkgName = getPackageName();
-
- try {
- PackageInfo pkgInfo = pkgMng.getPackageInfo(pkgName, 0);
- pkgName = pkgInfo.applicationInfo.dataDir;
- } catch (NameNotFoundException e) {
- Log.w(TAG, "Error Package name not found ", e);
- }
-
- Log.d(TAG, "Application path: " + pkgName);
- return pkgName;
- }
-
@Override
public void onClick(View view)
{
buttonService = (Button) findViewById(R.id.buttonService);
-
+
+ try {
switch (view.getId()) {
case R.id.buttonCall:
TextView textView = (TextView) findViewById(R.id.editAccountID);
@@ -432,8 +430,7 @@
if (incomingCallID != "") {
buttonCall.clearAnimation();
-// manager.managerImpl.answerCall(incomingCallID);
- manager.callmanagerJNI.accept(incomingCallID);
+ service.accept(incomingCallID);
callID = incomingCallID;
incomingCallID="";
callOnGoing = true;
@@ -450,9 +447,8 @@
callID = Integer.toString(random.nextInt());
- Log.d(TAG, "manager.managerImpl.placeCall(" + accountID + ", " + callID + ", " + to + ");");
-// manager.managerImpl.outgoingCall(accountID, callID, to);
- manager.callmanagerJNI.placeCall(accountID, callID, to);
+ Log.d(TAG, "service.placeCall(" + accountID + ", " + callID + ", " + to + ");");
+ service.placeCall(accountID, callID, to);
callOnGoing = true;
buttonCall.setEnabled(false);
buttonHangup.setEnabled(true);
@@ -461,17 +457,16 @@
break;
case R.id.buttonHangUp:
if (incomingCallID != "") {
- buttonCall.clearAnimation();
-// manager.managerImpl.refuseCall(incomingCallID);
- manager.callmanagerJNI.refuse(incomingCallID);
- incomingCallID="";
+ buttonCall.clearAnimation();
+ Log.d(TAG, "service.refuse(" + incomingCallID + ");");
+ service.refuse(incomingCallID);
+ incomingCallID="";
buttonCall.setEnabled(true);
buttonHangup.setEnabled(true);
} else {
if (callOnGoing == true) {
- Log.d(TAG, "manager.managerImpl.hangUp(" + callID + ");");
-// manager.managerImpl.hangupCall(callID);
- manager.callmanagerJNI.hangUp(callID);
+ Log.d(TAG, "service.hangUp(" + callID + ");");
+ service.hangUp(callID);
callOnGoing = false;
buttonCall.setEnabled(true);
buttonHangup.setEnabled(false);
@@ -481,8 +476,7 @@
buttonCall.setImageResource(R.drawable.ic_call);
break;
case R.id.buttonInit:
- Manager.managerImpl.setPath("");
- Manager.managerImpl.init("");
+ Log.i(TAG, "R.id.buttonInit");
break;
case R.id.buttonService:
if (!serviceIsOn) {
@@ -515,5 +509,8 @@
Log.w(TAG, "unknown button " + view.getId());
break;
}
+ } catch (RemoteException e) {
+ Log.e(TAG, "Cannot call service method", e);
+ }
}
}
diff --git a/src/com/savoirfairelinux/sflphone/client/SFLphoneApplication.java b/src/com/savoirfairelinux/sflphone/client/SFLphoneApplication.java
index 3c15062..1dc2d99 100644
--- a/src/com/savoirfairelinux/sflphone/client/SFLphoneApplication.java
+++ b/src/com/savoirfairelinux/sflphone/client/SFLphoneApplication.java
@@ -1,30 +1,59 @@
package com.savoirfairelinux.sflphone.client;
import android.app.Application;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
import android.util.Log;
+import com.savoirfairelinux.sflphone.service.SipService;
+
public class SFLphoneApplication extends Application {
-
+
static final String TAG = "SFLphoneApplication";
private boolean serviceRunning;
+ private SipService sipService;
@Override
public void onCreate() {
super.onCreate();
Log.i(TAG, "onCreate");
}
-
+
@Override
public void onTerminate() {
super.onTerminate();
Log.i(TAG, "onTerminate");
}
-
+
public boolean isServiceRunning() {
return serviceRunning;
}
-
+
public void setServiceRunning(boolean r) {
this.serviceRunning = r;
}
+
+ public SipService getSipService() {
+ return sipService;
+ }
+
+ public void setSipService(SipService service) {
+ sipService = service;
+ }
+
+ public String getAppPath() {
+ PackageManager pkgMng = getPackageManager();
+ String pkgName = getPackageName();
+
+ try {
+ PackageInfo pkgInfo = pkgMng.getPackageInfo(pkgName, 0);
+ pkgName = pkgInfo.applicationInfo.dataDir;
+ } catch (NameNotFoundException e) {
+ Log.w(TAG, "Error Package name not found ", e);
+ }
+
+ Log.d(TAG, "Application path: " + pkgName);
+ return pkgName;
+ }
}
diff --git a/src/com/savoirfairelinux/sflphone/service/ISipService.aidl b/src/com/savoirfairelinux/sflphone/service/ISipService.aidl
new file mode 100644
index 0000000..cf6e7e5
--- /dev/null
+++ b/src/com/savoirfairelinux/sflphone/service/ISipService.aidl
@@ -0,0 +1,8 @@
+package com.savoirfairelinux.sflphone.service;
+
+interface ISipService {
+ void placeCall(String accountID, in String callID, in String to);
+ void refuse(in String callID);
+ void accept(in String callID);
+ void hangUp(in String callID);
+}
\ No newline at end of file
diff --git a/src/com/savoirfairelinux/sflphone/service/SipService.java b/src/com/savoirfairelinux/sflphone/service/SipService.java
index c371ada..9ba6223 100644
--- a/src/com/savoirfairelinux/sflphone/service/SipService.java
+++ b/src/com/savoirfairelinux/sflphone/service/SipService.java
@@ -1,13 +1,44 @@
+/**
+ * Copyright (C) 2010-2012 Regis Montoya (aka r3gis - www.r3gis.fr)
+ * Copyright (C) 2004-2012 Savoir-Faire Linux Inc.
+ *
+ * Author: Regis Montoya <r3gis.3R@gmail.com>
+ * Author: Emeric Vigier <emeric.vigier@savoirfairelinux.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * If you own a pjsip commercial license you can also redistribute it
+ * and/or modify it under the terms of the GNU Lesser General Public License
+ * as an android library.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
package com.savoirfairelinux.sflphone.service;
+import java.lang.ref.WeakReference;
+
import android.app.Service;
import android.content.Intent;
import android.os.Binder;
+import android.os.Handler;
+import android.os.HandlerThread;
import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
import android.util.Log;
import android.widget.Toast;
+import com.savoirfairelinux.sflphone.client.Manager;
import com.savoirfairelinux.sflphone.client.SFLphoneApplication;
+import com.savoirfairelinux.sflphone.service.ISipService;
public class SipService extends Service {
@@ -16,16 +47,79 @@
private boolean runFlag = false;
private SipServiceThread sipServiceThread;
private SFLphoneApplication sflphoneApp;
- private final IBinder mBinder = new LocalBinder();
+ private SipServiceExecutor mExecutor;
+ private static HandlerThread executorThread;
+ private CallManagerJNI callManagerJNI;
+ private Manager manager;
+ private boolean isPjSipStackStarted = false;
+
+ /* Implement public interface for the service */
+ private final ISipService.Stub mBinder = new ISipService.Stub() {
+
+ @Override
+ public void placeCall(final String accountID, final String callID, final String to) {
+ getExecutor().execute(new SipRunnable() {
+ @Override
+ protected void doRun() throws SameThreadException {
+ Log.i(TAG, "SipService.placeCall() thread running...");
+ callManagerJNI.placeCall(accountID, callID, to);
+ }
+ });
+ }
+
+ @Override
+ public void refuse(final String callID) {
+ getExecutor().execute(new SipRunnable() {
+ @Override
+ protected void doRun() throws SameThreadException {
+ Log.i(TAG, "SipService.refuse() thread running...");
+ callManagerJNI.refuse(callID);
+ }
+ });
+ }
+
+ @Override
+ public void accept(final String callID) {
+ getExecutor().execute(new SipRunnable() {
+ @Override
+ protected void doRun() throws SameThreadException {
+ Log.i(TAG, "SipService.placeCall() thread running...");
+ callManagerJNI.accept(callID);
+ }
+ });
+ }
+
+ @Override
+ public void hangUp(final String callID) {
+ getExecutor().execute(new SipRunnable() {
+ @Override
+ protected void doRun() throws SameThreadException {
+ Log.i(TAG, "SipService.hangUp() thread running...");
+ callManagerJNI.hangUp(callID);
+ }
+ });
+ }
+ };
+
+ /**
+ * Class used for the client Binder. Because we know this service always
+ * runs in the same process as its clients, we don't need to deal with IPC.
+ */
+ public class LocalBinder extends Binder {
+ public SipService getService() {
+ // Return this instance of LocalService so clients can call public methods
+ return SipService.this;
+ }
+ }
/* called once by startService() */
@Override
public void onCreate() {
Log.i(TAG, "onCreated");
super.onCreate();
- this.sflphoneApp = (SFLphoneApplication) getApplication();
- this.sipServiceThread = new SipServiceThread();
- Log.i(TAG, "onCreated");
+ sflphoneApp = (SFLphoneApplication) getApplication();
+ sipServiceThread = new SipServiceThread();
+ getExecutor().execute(new StartRunnable());
}
/* called for each startService() */
@@ -33,15 +127,12 @@
public int onStartCommand(Intent intent, int flags, int startId) {
Log.i(TAG, "onStarted");
super.onStartCommand(intent, flags, startId);
-// if(intent != null) {
-// Parcelable p = intent.getParcelableExtra(ServiceConstants.EXTRA_OUTGOING_ACTIVITY);
-// Log.i(TAG, "unmarshalled outgoing_activity");
-// }
- this.runFlag = true;
- this.sipServiceThread.start();
- this.sflphoneApp.setServiceRunning(true);
+
+ runFlag = true;
+ sipServiceThread.start();
+ sflphoneApp.setServiceRunning(true);
Toast.makeText(this, "Sflphone Service started", Toast.LENGTH_SHORT).show();
-
+
Log.i(TAG, "onStarted");
return START_STICKY; /* started and stopped explicitly */
}
@@ -50,10 +141,10 @@
public void onDestroy() {
/* called once by stopService() */
super.onDestroy();
- this.runFlag = false;
- this.sipServiceThread.interrupt();
- this.sipServiceThread = null;
- this.sflphoneApp.setServiceRunning(false);
+ runFlag = false;
+ sipServiceThread.interrupt();
+ sipServiceThread = null;
+ sflphoneApp.setServiceRunning(false);
Toast.makeText(this, "Sflphone Service stopped", Toast.LENGTH_SHORT).show();
Log.i(TAG, "onDestroyed");
@@ -65,14 +156,120 @@
return mBinder;
}
- /**
- * Class used for the client Binder. Because we know this service always
- * runs in the same process as its clients, we don't need to deal with IPC.
- */
- public class LocalBinder extends Binder {
- SipService getService() {
- // Return this instance of LocalService so clients can call public methods
- return SipService.this;
+ private static Looper createLooper() {
+ if(executorThread == null) {
+ Log.d(TAG, "Creating new handler thread");
+ // ADT gives a fake warning due to bad parse rule.
+ executorThread = new HandlerThread("SipService.Executor");
+ executorThread.start();
+ }
+ return executorThread.getLooper();
+ }
+
+ public SipServiceExecutor getExecutor() {
+ // create mExecutor lazily
+ if (mExecutor == null) {
+ mExecutor = new SipServiceExecutor(this);
+ }
+ return mExecutor;
+ }
+
+ // Executes immediate tasks in a single executorThread.
+ public static class SipServiceExecutor extends Handler {
+ WeakReference<SipService> handlerService;
+
+ SipServiceExecutor(SipService s) {
+ super(createLooper());
+ handlerService = new WeakReference<SipService>(s);
+ }
+
+ public void execute(Runnable task) {
+ // TODO: add wakelock
+ Message.obtain(this, 0/* don't care */, task).sendToTarget();
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ if (msg.obj instanceof Runnable) {
+ executeInternal((Runnable) msg.obj);
+ } else {
+ Log.w(TAG, "can't handle msg: " + msg);
+ }
+ }
+
+ private void executeInternal(Runnable task) {
+ try {
+ task.run();
+ } catch (Throwable t) {
+ Log.e(TAG, "run task: " + task, t);
+ }
+ }
+ }
+
+ private void startPjSipStack() throws SameThreadException {
+ if (isPjSipStackStarted)
+ return;
+
+ try {
+ System.loadLibrary("gnustl_shared");
+ System.loadLibrary("expat");
+ System.loadLibrary("yaml");
+ System.loadLibrary("ccgnu2");
+ System.loadLibrary("crypto");
+ System.loadLibrary("ssl");
+ System.loadLibrary("ccrtp1");
+ System.loadLibrary("dbus");
+ System.loadLibrary("dbus-c++-1");
+ System.loadLibrary("samplerate");
+ System.loadLibrary("codec_ulaw");
+ System.loadLibrary("codec_alaw");
+ System.loadLibrary("speexresampler");
+ System.loadLibrary("sflphone");
+ isPjSipStackStarted = true;
+ } catch (UnsatisfiedLinkError e) {
+ Log.e(TAG, "Problem with the current Pj stack...", e);
+ isPjSipStackStarted = false;
+ return;
+ } catch (Exception e) {
+ Log.e(TAG, "Problem with the current Pj stack...", e);
+ }
+
+ manager = new Manager();
+ Log.i(TAG, "SipService.ManagerImpl::instance() = " + Manager.managerImpl);
+ /* set static AppPath before calling manager.init */
+ Manager.managerImpl.setPath(sflphoneApp.getAppPath());
+ callManagerJNI = Manager.callmanagerJNI;
+ Log.i(TAG, "startPjSipStack() callManagerJNI = " + callManagerJNI);
+
+ Manager.managerImpl.init("");
+ return;
+ }
+
+ // Enforce same thread contract to ensure we do not call from somewhere else
+ public class SameThreadException extends Exception {
+ private static final long serialVersionUID = -905639124232613768L;
+
+ public SameThreadException() {
+ super("Should be launched from a single worker thread");
+ }
+ }
+
+ public abstract static class SipRunnable implements Runnable {
+ protected abstract void doRun() throws SameThreadException;
+
+ public void run() {
+ try {
+ doRun();
+ }catch(SameThreadException e) {
+ Log.e(TAG, "Not done from same thread");
+ }
+ }
+ }
+
+ class StartRunnable extends SipRunnable {
+ @Override
+ protected void doRun() throws SameThreadException {
+ startPjSipStack();
}
}