diff options
-rw-r--r-- | voip/java/com/android/server/sip/SipService.java | 190 | ||||
-rw-r--r-- | voip/java/com/android/server/sip/SipSessionGroup.java | 10 |
2 files changed, 132 insertions, 68 deletions
diff --git a/voip/java/com/android/server/sip/SipService.java b/voip/java/com/android/server/sip/SipService.java index 1a17d38..f41f156 100644 --- a/voip/java/com/android/server/sip/SipService.java +++ b/voip/java/com/android/server/sip/SipService.java @@ -39,6 +39,7 @@ import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; import android.os.Message; +import android.os.PowerManager; import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; @@ -54,6 +55,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Comparator; import java.util.HashMap; +import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Timer; @@ -93,6 +95,7 @@ public final class SipService extends ISipService.Stub { private ConnectivityReceiver mConnectivityReceiver; private boolean mWifiEnabled; + private MyWakeLock mMyWakeLock; /** * Starts the SIP service. Do nothing if the SIP API is not supported on the @@ -114,6 +117,8 @@ public final class SipService extends ISipService.Stub { new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)); context.registerReceiver(mWifiStateReceiver, new IntentFilter(WifiManager.WIFI_STATE_CHANGED_ACTION)); + mMyWakeLock = new MyWakeLock((PowerManager) + context.getSystemService(Context.POWER_SERVICE)); mTimer = new WakeupTimer(context); mWifiOnly = SipManager.isSipWifiOnly(context); @@ -225,7 +230,11 @@ public final class SipService extends ISipService.Stub { group = mSipGroups.remove(localProfileUri); notifyProfileRemoved(group.getLocalProfile()); group.close(); - if (!anyOpened()) releaseWifiLock(); + + if (!anyOpened()) { + releaseWifiLock(); + mMyWakeLock.reset(); // in case there's leak + } } public synchronized boolean isOpened(String localProfileUri) { @@ -405,6 +414,8 @@ public final class SipService extends ISipService.Stub { for (SipSessionGroupExt group : mSipGroups.values()) { group.onConnectivityChanged(true); } + } else { + mMyWakeLock.reset(); // in case there's a leak } } catch (SipException e) { Log.e(TAG, "onConnectivityChanged()", e); @@ -581,7 +592,7 @@ public final class SipService extends ISipService.Stub { } // KeepAliveProcess is controlled by AutoRegistrationProcess. - // All methods will be invoked in sync with SipService.this except realRun() + // All methods will be invoked in sync with SipService.this. private class KeepAliveProcess implements Runnable { private static final String TAG = "\\KEEPALIVE/"; private static final int INTERVAL = 10; @@ -600,43 +611,33 @@ public final class SipService extends ISipService.Stub { // timeout handler public void run() { - if (!mRunning) return; - final SipSessionGroup.SipSessionImpl session = mSession; - - // delegate to mExecutor - getExecutor().addTask(new Runnable() { - public void run() { - realRun(session); - } - }); - } - - // real timeout handler - private void realRun(SipSessionGroup.SipSessionImpl session) { synchronized (SipService.this) { - if (notCurrentSession(session)) return; + if (!mRunning) return; - session = session.duplicate(); - if (DEBUGV) Log.v(TAG, "~~~ keepalive"); - mTimer.cancel(this); - session.sendKeepAlive(); - if (session.isReRegisterRequired()) { - mSession.register(EXPIRY_TIME); - } else { - mTimer.set(INTERVAL * 1000, this); + if (DEBUGV) Log.v(TAG, "~~~ keepalive: " + + mSession.getLocalProfile().getUriString()); + SipSessionGroup.SipSessionImpl session = mSession.duplicate(); + try { + session.sendKeepAlive(); + if (session.isReRegisterRequired()) { + // Acquire wake lock for the registration process. The + // lock will be released when registration is complete. + mMyWakeLock.acquire(mSession); + mSession.register(EXPIRY_TIME); + } + } catch (Throwable t) { + Log.w(TAG, "keepalive error: " + t); } } } public void stop() { + if (DEBUGV && (mSession != null)) Log.v(TAG, "stop keepalive:" + + mSession.getLocalProfile().getUriString()); mRunning = false; mSession = null; mTimer.cancel(this); } - - private boolean notCurrentSession(ISipSession session) { - return (session != mSession) || !mRunning; - } } private class AutoRegistrationProcess extends SipSessionAdapter @@ -667,6 +668,7 @@ public final class SipService extends ISipService.Stub { // start unregistration to clear up old registration at server // TODO: when rfc5626 is deployed, use reg-id and sip.instance // in registration to avoid adding duplicate entries to server + mMyWakeLock.acquire(mSession); mSession.unregister(); if (DEBUG) Log.d(TAG, "start AutoRegistrationProcess for " + mSession.getLocalProfile().getUriString()); @@ -676,8 +678,11 @@ public final class SipService extends ISipService.Stub { public void stop() { if (!mRunning) return; mRunning = false; - mSession.setListener(null); - if (mConnected && mRegistered) mSession.unregister(); + mMyWakeLock.release(mSession); + if (mSession != null) { + mSession.setListener(null); + if (mConnected && mRegistered) mSession.unregister(); + } mTimer.cancel(this); if (mKeepAliveProcess != null) { @@ -734,29 +739,18 @@ public final class SipService extends ISipService.Stub { return mRegistered; } - // timeout handler + // timeout handler: re-register public void run() { synchronized (SipService.this) { if (!mRunning) return; - final SipSessionGroup.SipSessionImpl session = mSession; - // delegate to mExecutor - getExecutor().addTask(new Runnable() { - public void run() { - realRun(session); - } - }); - } - } - - // real timeout handler - private void realRun(SipSessionGroup.SipSessionImpl session) { - synchronized (SipService.this) { - if (notCurrentSession(session)) return; mErrorCode = SipErrorCode.NO_ERROR; mErrorMessage = null; if (DEBUG) Log.d(TAG, "~~~ registering"); - if (mConnected) session.register(EXPIRY_TIME); + if (mConnected) { + mMyWakeLock.acquire(mSession); + mSession.register(EXPIRY_TIME); + } } } @@ -806,6 +800,7 @@ public final class SipService extends ISipService.Stub { private boolean notCurrentSession(ISipSession session) { if (session != mSession) { ((SipSessionGroup.SipSessionImpl) session).setListener(null); + mMyWakeLock.release(session); return true; } return !mRunning; @@ -842,6 +837,7 @@ public final class SipService extends ISipService.Stub { mKeepAliveProcess.start(); } } + mMyWakeLock.release(session); } else { mRegistered = false; mExpiryTime = -1L; @@ -872,6 +868,7 @@ public final class SipService extends ISipService.Stub { mErrorCode = errorCode; mErrorMessage = message; mProxy.onRegistrationFailed(session, errorCode, message); + mMyWakeLock.release(session); } } @@ -884,6 +881,7 @@ public final class SipService extends ISipService.Stub { mErrorCode = SipErrorCode.TIME_OUT; mProxy.onRegistrationTimeout(session); restartLater(); + mMyWakeLock.release(session); } } @@ -902,7 +900,16 @@ public final class SipService extends ISipService.Stub { private MyTimerTask mTask; @Override - public void onReceive(Context context, Intent intent) { + public void onReceive(final Context context, final Intent intent) { + // Run the handler in MyExecutor to be protected by wake lock + getExecutor().execute(new Runnable() { + public void run() { + onReceiveInternal(context, intent); + } + }); + } + + private void onReceiveInternal(Context context, Intent intent) { String action = intent.getAction(); if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) { Bundle b = intent.getExtras(); @@ -970,11 +977,13 @@ public final class SipService extends ISipService.Stub { if (mTask != null) mTask.cancel(); mTask = new MyTimerTask(type, connected); mTimer.schedule(mTask, 2 * 1000L); - // TODO: hold wakup lock so that we can finish change before - // the device goes to sleep + // hold wakup lock so that we can finish changes before the + // device goes to sleep + mMyWakeLock.acquire(mTask); } else { if ((mTask != null) && mTask.mNetworkType.equals(type)) { mTask.cancel(); + mMyWakeLock.release(mTask); } onConnectivityChanged(type, false); } @@ -994,7 +1003,7 @@ public final class SipService extends ISipService.Stub { @Override public void run() { // delegate to mExecutor - getExecutor().addTask(new Runnable() { + getExecutor().execute(new Runnable() { public void run() { realRun(); } @@ -1012,6 +1021,7 @@ public final class SipService extends ISipService.Stub { if (DEBUG) Log.d(TAG, " deliver change for " + mNetworkType + (mConnected ? " CONNECTED" : "DISCONNECTED")); onConnectivityChanged(mNetworkType, mConnected); + mMyWakeLock.release(this); } } } @@ -1019,7 +1029,6 @@ public final class SipService extends ISipService.Stub { // TODO: clean up pending SipSession(s) periodically - /** * Timer that can schedule events to occur even when the device is in sleep. * Only used internally in this package. @@ -1209,7 +1218,8 @@ public final class SipService extends ISipService.Stub { } @Override - public synchronized void onReceive(Context context, Intent intent) { + public void onReceive(Context context, Intent intent) { + // This callback is already protected by AlarmManager's wake lock. String action = intent.getAction(); if (getAction().equals(action) && intent.getExtras().containsKey(TRIGGER_TIME)) { @@ -1236,7 +1246,7 @@ public final class SipService extends ISipService.Stub { } } - private void execute(long triggerTime) { + private synchronized void execute(long triggerTime) { if (DEBUG_TIMER) Log.d(TAG, "time's up, triggerTime = " + showTime(triggerTime) + ": " + mEventQueue.size()); if (stopped() || mEventQueue.isEmpty()) return; @@ -1248,9 +1258,8 @@ public final class SipService extends ISipService.Stub { event.mLastTriggerTime = event.mTriggerTime; event.mTriggerTime += event.mPeriod; - // run the callback in a new thread to prevent deadlock - new Thread(event.mCallback, "SipServiceTimerCallbackThread") - .start(); + // run the callback in the handler thread to prevent deadlock + getExecutor().execute(event.mCallback); } if (DEBUG_TIMER) { Log.d(TAG, "after timeout execution"); @@ -1314,29 +1323,78 @@ public final class SipService extends ISipService.Stub { } } - // Single-threaded executor - private static class MyExecutor extends Handler { + private static Looper createLooper() { + HandlerThread thread = new HandlerThread("SipService.Executor"); + thread.start(); + return thread.getLooper(); + } + + // Executes immediate tasks in a single thread. + // Hold/release wake lock for running tasks + private class MyExecutor extends Handler { MyExecutor() { super(createLooper()); } - private static Looper createLooper() { - HandlerThread thread = new HandlerThread("SipService"); - thread.start(); - return thread.getLooper(); - } - - void addTask(Runnable task) { + void execute(Runnable task) { + mMyWakeLock.acquire(task); Message.obtain(this, 0/* don't care */, task).sendToTarget(); } @Override public void handleMessage(Message msg) { if (msg.obj instanceof Runnable) { - ((Runnable) msg.obj).run(); + 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); + } finally { + mMyWakeLock.release(task); + } + } + } + + private static class MyWakeLock { + private PowerManager mPowerManager; + private PowerManager.WakeLock mWakeLock; + private HashSet<Object> mHolders = new HashSet<Object>(); + + MyWakeLock(PowerManager powerManager) { + mPowerManager = powerManager; + } + + synchronized void reset() { + mHolders.clear(); + release(null); + if (DEBUGV) Log.v(TAG, "~~~ hard reset wakelock"); + } + + synchronized void acquire(Object holder) { + mHolders.add(holder); + if (mWakeLock == null) { + mWakeLock = mPowerManager.newWakeLock( + PowerManager.PARTIAL_WAKE_LOCK, "SipWakeLock"); + } + if (!mWakeLock.isHeld()) mWakeLock.acquire(); + if (DEBUGV) Log.v(TAG, "acquire wakelock: holder count=" + + mHolders.size()); + } + + synchronized void release(Object holder) { + mHolders.remove(holder); + if ((mWakeLock != null) && mHolders.isEmpty() + && mWakeLock.isHeld()) { + mWakeLock.release(); + } + if (DEBUGV) Log.v(TAG, "release wakelock: holder count=" + + mHolders.size()); + } } } diff --git a/voip/java/com/android/server/sip/SipSessionGroup.java b/voip/java/com/android/server/sip/SipSessionGroup.java index b5f8d39..2b8058f 100644 --- a/voip/java/com/android/server/sip/SipSessionGroup.java +++ b/voip/java/com/android/server/sip/SipSessionGroup.java @@ -547,8 +547,14 @@ class SipSessionGroup implements SipListener { mState = SipSession.State.PINGING; try { processCommand(new OptionsCommand()); - while (SipSession.State.PINGING == mState) { - Thread.sleep(1000); + for (int i = 0; i < 15; i++) { + if (SipSession.State.PINGING != mState) break; + Thread.sleep(200); + } + if (SipSession.State.PINGING == mState) { + // FIXME: what to do if server doesn't respond + reset(); + if (DEBUG) Log.w(TAG, "no response from ping"); } } catch (SipException e) { Log.e(TAG, "sendKeepAlive failed", e); |