diff options
Diffstat (limited to 'services')
-rw-r--r-- | services/java/com/android/server/SystemServer.java | 1 | ||||
-rw-r--r-- | services/java/com/android/server/sip/SipHelper.java | 464 | ||||
-rw-r--r-- | services/java/com/android/server/sip/SipService.java | 1231 | ||||
-rw-r--r-- | services/java/com/android/server/sip/SipSessionGroup.java | 1393 | ||||
-rw-r--r-- | services/java/com/android/server/sip/SipSessionListenerProxy.java | 217 |
5 files changed, 0 insertions, 3306 deletions
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 5ca386b..38afa3b 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -17,7 +17,6 @@ package com.android.server; import com.android.server.am.ActivityManagerService; -import com.android.server.sip.SipService; import com.android.internal.os.BinderInternal; import com.android.internal.os.SamplingProfilerIntegration; diff --git a/services/java/com/android/server/sip/SipHelper.java b/services/java/com/android/server/sip/SipHelper.java deleted file mode 100644 index 050eddc..0000000 --- a/services/java/com/android/server/sip/SipHelper.java +++ /dev/null @@ -1,464 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.sip; - -import gov.nist.javax.sip.SipStackExt; -import gov.nist.javax.sip.clientauthutils.AccountManager; -import gov.nist.javax.sip.clientauthutils.AuthenticationHelper; - -import android.net.sip.SipProfile; -import android.util.Log; - -import java.text.ParseException; -import java.util.ArrayList; -import java.util.EventObject; -import java.util.List; -import javax.sip.ClientTransaction; -import javax.sip.Dialog; -import javax.sip.DialogTerminatedEvent; -import javax.sip.InvalidArgumentException; -import javax.sip.ListeningPoint; -import javax.sip.PeerUnavailableException; -import javax.sip.RequestEvent; -import javax.sip.ResponseEvent; -import javax.sip.ServerTransaction; -import javax.sip.SipException; -import javax.sip.SipFactory; -import javax.sip.SipProvider; -import javax.sip.SipStack; -import javax.sip.Transaction; -import javax.sip.TransactionAlreadyExistsException; -import javax.sip.TransactionTerminatedEvent; -import javax.sip.TransactionUnavailableException; -import javax.sip.TransactionState; -import javax.sip.address.Address; -import javax.sip.address.AddressFactory; -import javax.sip.address.SipURI; -import javax.sip.header.CSeqHeader; -import javax.sip.header.CallIdHeader; -import javax.sip.header.ContactHeader; -import javax.sip.header.FromHeader; -import javax.sip.header.Header; -import javax.sip.header.HeaderFactory; -import javax.sip.header.MaxForwardsHeader; -import javax.sip.header.ToHeader; -import javax.sip.header.ViaHeader; -import javax.sip.message.Message; -import javax.sip.message.MessageFactory; -import javax.sip.message.Request; -import javax.sip.message.Response; - -/** - * Helper class for holding SIP stack related classes and for various low-level - * SIP tasks like sending messages. - */ -class SipHelper { - private static final String TAG = SipHelper.class.getSimpleName(); - private static final boolean DEBUG = true; - - private SipStack mSipStack; - private SipProvider mSipProvider; - private AddressFactory mAddressFactory; - private HeaderFactory mHeaderFactory; - private MessageFactory mMessageFactory; - - public SipHelper(SipStack sipStack, SipProvider sipProvider) - throws PeerUnavailableException { - mSipStack = sipStack; - mSipProvider = sipProvider; - - SipFactory sipFactory = SipFactory.getInstance(); - mAddressFactory = sipFactory.createAddressFactory(); - mHeaderFactory = sipFactory.createHeaderFactory(); - mMessageFactory = sipFactory.createMessageFactory(); - } - - private FromHeader createFromHeader(SipProfile profile, String tag) - throws ParseException { - return mHeaderFactory.createFromHeader(profile.getSipAddress(), tag); - } - - private ToHeader createToHeader(SipProfile profile) throws ParseException { - return createToHeader(profile, null); - } - - private ToHeader createToHeader(SipProfile profile, String tag) - throws ParseException { - return mHeaderFactory.createToHeader(profile.getSipAddress(), tag); - } - - private CallIdHeader createCallIdHeader() { - return mSipProvider.getNewCallId(); - } - - private CSeqHeader createCSeqHeader(String method) - throws ParseException, InvalidArgumentException { - long sequence = (long) (Math.random() * 10000); - return mHeaderFactory.createCSeqHeader(sequence, method); - } - - private MaxForwardsHeader createMaxForwardsHeader() - throws InvalidArgumentException { - return mHeaderFactory.createMaxForwardsHeader(70); - } - - private MaxForwardsHeader createMaxForwardsHeader(int max) - throws InvalidArgumentException { - return mHeaderFactory.createMaxForwardsHeader(max); - } - - private ListeningPoint getListeningPoint() throws SipException { - ListeningPoint lp = mSipProvider.getListeningPoint(ListeningPoint.UDP); - if (lp == null) lp = mSipProvider.getListeningPoint(ListeningPoint.TCP); - if (lp == null) { - ListeningPoint[] lps = mSipProvider.getListeningPoints(); - if ((lps != null) && (lps.length > 0)) lp = lps[0]; - } - if (lp == null) { - throw new SipException("no listening point is available"); - } - return lp; - } - - private List<ViaHeader> createViaHeaders() - throws ParseException, SipException { - List<ViaHeader> viaHeaders = new ArrayList<ViaHeader>(1); - ListeningPoint lp = getListeningPoint(); - ViaHeader viaHeader = mHeaderFactory.createViaHeader(lp.getIPAddress(), - lp.getPort(), lp.getTransport(), null); - viaHeader.setRPort(); - viaHeaders.add(viaHeader); - return viaHeaders; - } - - private ContactHeader createContactHeader(SipProfile profile) - throws ParseException, SipException { - ListeningPoint lp = getListeningPoint(); - SipURI contactURI = - createSipUri(profile.getUserName(), profile.getProtocol(), lp); - - Address contactAddress = mAddressFactory.createAddress(contactURI); - contactAddress.setDisplayName(profile.getDisplayName()); - - return mHeaderFactory.createContactHeader(contactAddress); - } - - private ContactHeader createWildcardContactHeader() { - ContactHeader contactHeader = mHeaderFactory.createContactHeader(); - contactHeader.setWildCard(); - return contactHeader; - } - - private SipURI createSipUri(String username, String transport, - ListeningPoint lp) throws ParseException { - SipURI uri = mAddressFactory.createSipURI(username, lp.getIPAddress()); - try { - uri.setPort(lp.getPort()); - uri.setTransportParam(transport); - } catch (InvalidArgumentException e) { - throw new RuntimeException(e); - } - return uri; - } - - public ClientTransaction sendKeepAlive(SipProfile userProfile, String tag) - throws SipException { - try { - Request request = createRequest(Request.OPTIONS, userProfile, tag); - - ClientTransaction clientTransaction = - mSipProvider.getNewClientTransaction(request); - clientTransaction.sendRequest(); - return clientTransaction; - } catch (Exception e) { - throw new SipException("sendKeepAlive()", e); - } - } - - public ClientTransaction sendRegister(SipProfile userProfile, String tag, - int expiry) throws SipException { - try { - Request request = createRequest(Request.REGISTER, userProfile, tag); - if (expiry == 0) { - // remove all previous registrations by wildcard - // rfc3261#section-10.2.2 - request.addHeader(createWildcardContactHeader()); - } else { - request.addHeader(createContactHeader(userProfile)); - } - request.addHeader(mHeaderFactory.createExpiresHeader(expiry)); - - ClientTransaction clientTransaction = - mSipProvider.getNewClientTransaction(request); - clientTransaction.sendRequest(); - return clientTransaction; - } catch (ParseException e) { - throw new SipException("sendRegister()", e); - } - } - - private Request createRequest(String requestType, SipProfile userProfile, - String tag) throws ParseException, SipException { - FromHeader fromHeader = createFromHeader(userProfile, tag); - ToHeader toHeader = createToHeader(userProfile); - SipURI requestURI = mAddressFactory.createSipURI("sip:" - + userProfile.getSipDomain()); - List<ViaHeader> viaHeaders = createViaHeaders(); - CallIdHeader callIdHeader = createCallIdHeader(); - CSeqHeader cSeqHeader = createCSeqHeader(requestType); - MaxForwardsHeader maxForwards = createMaxForwardsHeader(); - Request request = mMessageFactory.createRequest(requestURI, - requestType, callIdHeader, cSeqHeader, fromHeader, - toHeader, viaHeaders, maxForwards); - Header userAgentHeader = mHeaderFactory.createHeader("User-Agent", - "SIPAUA/0.1.001"); - request.addHeader(userAgentHeader); - return request; - } - - public ClientTransaction handleChallenge(ResponseEvent responseEvent, - AccountManager accountManager) throws SipException { - AuthenticationHelper authenticationHelper = - ((SipStackExt) mSipStack).getAuthenticationHelper( - accountManager, mHeaderFactory); - ClientTransaction tid = responseEvent.getClientTransaction(); - ClientTransaction ct = authenticationHelper.handleChallenge( - responseEvent.getResponse(), tid, mSipProvider, 5); - ct.sendRequest(); - return ct; - } - - public ClientTransaction sendInvite(SipProfile caller, SipProfile callee, - String sessionDescription, String tag) - throws SipException { - try { - FromHeader fromHeader = createFromHeader(caller, tag); - ToHeader toHeader = createToHeader(callee); - SipURI requestURI = callee.getUri(); - List<ViaHeader> viaHeaders = createViaHeaders(); - CallIdHeader callIdHeader = createCallIdHeader(); - CSeqHeader cSeqHeader = createCSeqHeader(Request.INVITE); - MaxForwardsHeader maxForwards = createMaxForwardsHeader(); - - Request request = mMessageFactory.createRequest(requestURI, - Request.INVITE, callIdHeader, cSeqHeader, fromHeader, - toHeader, viaHeaders, maxForwards); - - request.addHeader(createContactHeader(caller)); - request.setContent(sessionDescription, - mHeaderFactory.createContentTypeHeader( - "application", "sdp")); - - ClientTransaction clientTransaction = - mSipProvider.getNewClientTransaction(request); - if (DEBUG) Log.d(TAG, "send INVITE: " + request); - clientTransaction.sendRequest(); - return clientTransaction; - } catch (ParseException e) { - throw new SipException("sendInvite()", e); - } - } - - public ClientTransaction sendReinvite(Dialog dialog, - String sessionDescription) throws SipException { - try { - Request request = dialog.createRequest(Request.INVITE); - request.setContent(sessionDescription, - mHeaderFactory.createContentTypeHeader( - "application", "sdp")); - - ClientTransaction clientTransaction = - mSipProvider.getNewClientTransaction(request); - if (DEBUG) Log.d(TAG, "send RE-INVITE: " + request); - dialog.sendRequest(clientTransaction); - return clientTransaction; - } catch (ParseException e) { - throw new SipException("sendReinvite()", e); - } - } - - private ServerTransaction getServerTransaction(RequestEvent event) - throws SipException { - ServerTransaction transaction = event.getServerTransaction(); - if (transaction == null) { - Request request = event.getRequest(); - return mSipProvider.getNewServerTransaction(request); - } else { - return transaction; - } - } - - /** - * @param event the INVITE request event - */ - public ServerTransaction sendRinging(RequestEvent event, String tag) - throws SipException { - try { - Request request = event.getRequest(); - ServerTransaction transaction = getServerTransaction(event); - - Response response = mMessageFactory.createResponse(Response.RINGING, - request); - - ToHeader toHeader = (ToHeader) response.getHeader(ToHeader.NAME); - toHeader.setTag(tag); - response.addHeader(toHeader); - if (DEBUG) Log.d(TAG, "send RINGING: " + response); - transaction.sendResponse(response); - return transaction; - } catch (ParseException e) { - throw new SipException("sendRinging()", e); - } - } - - /** - * @param event the INVITE request event - */ - public ServerTransaction sendInviteOk(RequestEvent event, - SipProfile localProfile, String sessionDescription, - ServerTransaction inviteTransaction) - throws SipException { - try { - Request request = event.getRequest(); - Response response = mMessageFactory.createResponse(Response.OK, - request); - response.addHeader(createContactHeader(localProfile)); - response.setContent(sessionDescription, - mHeaderFactory.createContentTypeHeader( - "application", "sdp")); - - if (inviteTransaction == null) { - inviteTransaction = getServerTransaction(event); - } - - if (inviteTransaction.getState() != TransactionState.COMPLETED) { - if (DEBUG) Log.d(TAG, "send OK: " + response); - inviteTransaction.sendResponse(response); - } - - return inviteTransaction; - } catch (ParseException e) { - throw new SipException("sendInviteOk()", e); - } - } - - public void sendInviteBusyHere(RequestEvent event, - ServerTransaction inviteTransaction) throws SipException { - try { - Request request = event.getRequest(); - Response response = mMessageFactory.createResponse( - Response.BUSY_HERE, request); - - if (inviteTransaction.getState() != TransactionState.COMPLETED) { - if (DEBUG) Log.d(TAG, "send BUSY HERE: " + response); - inviteTransaction.sendResponse(response); - } - } catch (ParseException e) { - throw new SipException("sendInviteBusyHere()", e); - } - } - - /** - * @param event the INVITE ACK request event - */ - public void sendInviteAck(ResponseEvent event, Dialog dialog) - throws SipException { - Response response = event.getResponse(); - long cseq = ((CSeqHeader) response.getHeader(CSeqHeader.NAME)) - .getSeqNumber(); - Request ack = dialog.createAck(cseq); - if (DEBUG) Log.d(TAG, "send ACK: " + ack); - dialog.sendAck(ack); - } - - public void sendBye(Dialog dialog) throws SipException { - Request byeRequest = dialog.createRequest(Request.BYE); - if (DEBUG) Log.d(TAG, "send BYE: " + byeRequest); - dialog.sendRequest(mSipProvider.getNewClientTransaction(byeRequest)); - } - - public void sendCancel(ClientTransaction inviteTransaction) - throws SipException { - Request cancelRequest = inviteTransaction.createCancel(); - if (DEBUG) Log.d(TAG, "send CANCEL: " + cancelRequest); - mSipProvider.getNewClientTransaction(cancelRequest).sendRequest(); - } - - public void sendResponse(RequestEvent event, int responseCode) - throws SipException { - try { - Response response = mMessageFactory.createResponse( - responseCode, event.getRequest()); - if (DEBUG) Log.d(TAG, "send response: " + response); - getServerTransaction(event).sendResponse(response); - } catch (ParseException e) { - throw new SipException("sendResponse()", e); - } - } - - public void sendInviteRequestTerminated(Request inviteRequest, - ServerTransaction inviteTransaction) throws SipException { - try { - Response response = mMessageFactory.createResponse( - Response.REQUEST_TERMINATED, inviteRequest); - if (DEBUG) Log.d(TAG, "send response: " + response); - inviteTransaction.sendResponse(response); - } catch (ParseException e) { - throw new SipException("sendInviteRequestTerminated()", e); - } - } - - public static String getCallId(EventObject event) { - if (event == null) return null; - if (event instanceof RequestEvent) { - return getCallId(((RequestEvent) event).getRequest()); - } else if (event instanceof ResponseEvent) { - return getCallId(((ResponseEvent) event).getResponse()); - } else if (event instanceof DialogTerminatedEvent) { - Dialog dialog = ((DialogTerminatedEvent) event).getDialog(); - return getCallId(((DialogTerminatedEvent) event).getDialog()); - } else if (event instanceof TransactionTerminatedEvent) { - TransactionTerminatedEvent e = (TransactionTerminatedEvent) event; - return getCallId(e.isServerTransaction() - ? e.getServerTransaction() - : e.getClientTransaction()); - } else { - Object source = event.getSource(); - if (source instanceof Transaction) { - return getCallId(((Transaction) source)); - } else if (source instanceof Dialog) { - return getCallId((Dialog) source); - } - } - return ""; - } - - public static String getCallId(Transaction transaction) { - return ((transaction != null) ? getCallId(transaction.getRequest()) - : ""); - } - - private static String getCallId(Message message) { - CallIdHeader callIdHeader = - (CallIdHeader) message.getHeader(CallIdHeader.NAME); - return callIdHeader.getCallId(); - } - - private static String getCallId(Dialog dialog) { - return dialog.getCallId().getCallId(); - } -} diff --git a/services/java/com/android/server/sip/SipService.java b/services/java/com/android/server/sip/SipService.java deleted file mode 100644 index 3f43e1c..0000000 --- a/services/java/com/android/server/sip/SipService.java +++ /dev/null @@ -1,1231 +0,0 @@ -/* - * Copyright (C) 2010, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.sip; - -import android.app.AlarmManager; -import android.app.PendingIntent; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.net.ConnectivityManager; -import android.net.NetworkInfo; -import android.net.sip.ISipService; -import android.net.sip.ISipSession; -import android.net.sip.ISipSessionListener; -import android.net.sip.SipErrorCode; -import android.net.sip.SipManager; -import android.net.sip.SipProfile; -import android.net.sip.SipSession; -import android.net.sip.SipSessionAdapter; -import android.net.wifi.WifiManager; -import android.os.Binder; -import android.os.Bundle; -import android.os.Handler; -import android.os.HandlerThread; -import android.os.Looper; -import android.os.Message; -import android.os.RemoteException; -import android.os.SystemClock; -import android.text.TextUtils; -import android.util.Log; - -import java.io.IOException; -import java.net.DatagramSocket; -import java.net.InetAddress; -import java.net.UnknownHostException; -import java.util.Collection; -import java.util.Comparator; -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; -import java.util.Timer; -import java.util.TimerTask; -import java.util.TreeSet; -import javax.sip.SipException; - -/** - * @hide - */ -public final class SipService extends ISipService.Stub { - private static final String TAG = "SipService"; - private static final boolean DEBUG = true; - private static final boolean DEBUG_TIMER = DEBUG && false; - private static final int EXPIRY_TIME = 3600; - private static final int SHORT_EXPIRY_TIME = 10; - private static final int MIN_EXPIRY_TIME = 60; - - private Context mContext; - private String mLocalIp; - private String mNetworkType; - private boolean mConnected; - private WakeupTimer mTimer; - private WifiManager.WifiLock mWifiLock; - private boolean mWifiOnly; - - private MyExecutor mExecutor; - - // SipProfile URI --> group - private Map<String, SipSessionGroupExt> mSipGroups = - new HashMap<String, SipSessionGroupExt>(); - - // session ID --> session - private Map<String, ISipSession> mPendingSessions = - new HashMap<String, ISipSession>(); - - private ConnectivityReceiver mConnectivityReceiver; - - /** - * Creates a {@code SipService} instance. Returns null if SIP API is not - * supported. - */ - public static SipService create(Context context) { - return (SipManager.isApiSupported(context) ? new SipService(context) - : null); - } - - private SipService(Context context) { - if (DEBUG) Log.d(TAG, " service started!"); - mContext = context; - mConnectivityReceiver = new ConnectivityReceiver(); - context.registerReceiver(mConnectivityReceiver, - new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)); - - mTimer = new WakeupTimer(context); - mWifiOnly = SipManager.isSipWifiOnly(context); - } - - private MyExecutor getExecutor() { - // create mExecutor lazily - if (mExecutor == null) mExecutor = new MyExecutor(); - return mExecutor; - } - - public synchronized SipProfile[] getListOfProfiles() { - SipProfile[] profiles = new SipProfile[mSipGroups.size()]; - int i = 0; - for (SipSessionGroupExt group : mSipGroups.values()) { - profiles[i++] = group.getLocalProfile(); - } - return profiles; - } - - public void open(SipProfile localProfile) { - localProfile.setCallingUid(Binder.getCallingUid()); - if (localProfile.getAutoRegistration()) { - openToReceiveCalls(localProfile); - } else { - openToMakeCalls(localProfile); - } - } - - private void openToMakeCalls(SipProfile localProfile) { - try { - createGroup(localProfile); - } catch (SipException e) { - Log.e(TAG, "openToMakeCalls()", e); - // TODO: how to send the exception back - } - } - - private void openToReceiveCalls(SipProfile localProfile) { - open3(localProfile, SipManager.ACTION_SIP_INCOMING_CALL, null); - } - - public synchronized void open3(SipProfile localProfile, - String incomingCallBroadcastAction, ISipSessionListener listener) { - localProfile.setCallingUid(Binder.getCallingUid()); - if (TextUtils.isEmpty(incomingCallBroadcastAction)) { - throw new RuntimeException( - "empty broadcast action for incoming call"); - } - if (DEBUG) Log.d(TAG, "open3: " + localProfile.getUriString() + ": " - + incomingCallBroadcastAction + ": " + listener); - try { - SipSessionGroupExt group = createGroup(localProfile, - incomingCallBroadcastAction, listener); - if (localProfile.getAutoRegistration()) { - group.openToReceiveCalls(); - if (isWifiOn()) grabWifiLock(); - } - } catch (SipException e) { - Log.e(TAG, "openToReceiveCalls()", e); - // TODO: how to send the exception back - } - } - - public synchronized void close(String localProfileUri) { - SipSessionGroupExt group = mSipGroups.remove(localProfileUri); - if (group != null) { - notifyProfileRemoved(group.getLocalProfile()); - group.close(); - if (isWifiOn() && !anyOpened()) releaseWifiLock(); - } - } - - public synchronized boolean isOpened(String localProfileUri) { - SipSessionGroupExt group = mSipGroups.get(localProfileUri); - return ((group != null) ? group.isOpened() : false); - } - - public synchronized boolean isRegistered(String localProfileUri) { - SipSessionGroupExt group = mSipGroups.get(localProfileUri); - return ((group != null) ? group.isRegistered() : false); - } - - public synchronized void setRegistrationListener(String localProfileUri, - ISipSessionListener listener) { - SipSessionGroupExt group = mSipGroups.get(localProfileUri); - if (group != null) group.setListener(listener); - } - - public synchronized ISipSession createSession(SipProfile localProfile, - ISipSessionListener listener) { - localProfile.setCallingUid(Binder.getCallingUid()); - if (!mConnected) return null; - try { - SipSessionGroupExt group = createGroup(localProfile); - return group.createSession(listener); - } catch (SipException e) { - Log.w(TAG, "createSession()", e); - return null; - } - } - - public synchronized ISipSession getPendingSession(String callId) { - if (callId == null) return null; - return mPendingSessions.get(callId); - } - - private String determineLocalIp() { - try { - DatagramSocket s = new DatagramSocket(); - s.connect(InetAddress.getByName("192.168.1.1"), 80); - return s.getLocalAddress().getHostAddress(); - } catch (IOException e) { - Log.w(TAG, "determineLocalIp()", e); - // dont do anything; there should be a connectivity change going - return null; - } - } - - private SipSessionGroupExt createGroup(SipProfile localProfile) - throws SipException { - String key = localProfile.getUriString(); - SipSessionGroupExt group = mSipGroups.get(key); - if (group == null) { - group = new SipSessionGroupExt(localProfile, null, null); - mSipGroups.put(key, group); - notifyProfileAdded(localProfile); - } - return group; - } - - private SipSessionGroupExt createGroup(SipProfile localProfile, - String incomingCallBroadcastAction, ISipSessionListener listener) - throws SipException { - String key = localProfile.getUriString(); - SipSessionGroupExt group = mSipGroups.get(key); - if (group != null) { - group.setIncomingCallBroadcastAction( - incomingCallBroadcastAction); - group.setListener(listener); - } else { - group = new SipSessionGroupExt(localProfile, - incomingCallBroadcastAction, listener); - mSipGroups.put(key, group); - notifyProfileAdded(localProfile); - } - return group; - } - - private void notifyProfileAdded(SipProfile localProfile) { - if (DEBUG) Log.d(TAG, "notify: profile added: " + localProfile); - Intent intent = new Intent(SipManager.ACTION_SIP_ADD_PHONE); - intent.putExtra(SipManager.EXTRA_LOCAL_URI, localProfile.getUriString()); - mContext.sendBroadcast(intent); - } - - private void notifyProfileRemoved(SipProfile localProfile) { - if (DEBUG) Log.d(TAG, "notify: profile removed: " + localProfile); - Intent intent = new Intent(SipManager.ACTION_SIP_REMOVE_PHONE); - intent.putExtra(SipManager.EXTRA_LOCAL_URI, localProfile.getUriString()); - mContext.sendBroadcast(intent); - } - - private boolean anyOpened() { - for (SipSessionGroupExt group : mSipGroups.values()) { - if (group.isOpened()) return true; - } - return false; - } - - private void grabWifiLock() { - if (mWifiLock == null) { - if (DEBUG) Log.d(TAG, "acquire wifi lock"); - mWifiLock = ((WifiManager) - mContext.getSystemService(Context.WIFI_SERVICE)) - .createWifiLock(WifiManager.WIFI_MODE_FULL, TAG); - mWifiLock.acquire(); - } - } - - private void releaseWifiLock() { - if (mWifiLock != null) { - if (DEBUG) Log.d(TAG, "release wifi lock"); - mWifiLock.release(); - mWifiLock = null; - } - } - - private boolean isWifiOn() { - return "WIFI".equalsIgnoreCase(mNetworkType); - //return (mConnected && "WIFI".equalsIgnoreCase(mNetworkType)); - } - - private synchronized void onConnectivityChanged( - String type, boolean connected) { - if (DEBUG) Log.d(TAG, "onConnectivityChanged(): " - + mNetworkType + (mConnected? " CONNECTED" : " DISCONNECTED") - + " --> " + type + (connected? " CONNECTED" : " DISCONNECTED")); - - boolean sameType = type.equals(mNetworkType); - if (!sameType && !connected) return; - - boolean wasWifi = "WIFI".equalsIgnoreCase(mNetworkType); - boolean isWifi = "WIFI".equalsIgnoreCase(type); - boolean wifiOff = (isWifi && !connected) || (wasWifi && !sameType); - boolean wifiOn = isWifi && connected; - if (wifiOff) { - releaseWifiLock(); - } else if (wifiOn) { - if (anyOpened()) grabWifiLock(); - } - - try { - boolean wasConnected = mConnected; - mNetworkType = type; - mConnected = connected; - - if (wasConnected) { - mLocalIp = null; - for (SipSessionGroupExt group : mSipGroups.values()) { - group.onConnectivityChanged(false); - } - } - - if (connected) { - mLocalIp = determineLocalIp(); - for (SipSessionGroupExt group : mSipGroups.values()) { - group.onConnectivityChanged(true); - } - } - - } catch (SipException e) { - Log.e(TAG, "onConnectivityChanged()", e); - } - } - - private synchronized void addPendingSession(ISipSession session) { - try { - mPendingSessions.put(session.getCallId(), session); - } catch (RemoteException e) { - // should not happen with a local call - Log.e(TAG, "addPendingSession()", e); - } - } - - private class SipSessionGroupExt extends SipSessionAdapter { - private SipSessionGroup mSipGroup; - private String mIncomingCallBroadcastAction; - private boolean mOpened; - - private AutoRegistrationProcess mAutoRegistration = - new AutoRegistrationProcess(); - - public SipSessionGroupExt(SipProfile localProfile, - String incomingCallBroadcastAction, - ISipSessionListener listener) throws SipException { - String password = localProfile.getPassword(); - SipProfile p = duplicate(localProfile); - mSipGroup = createSipSessionGroup(mLocalIp, p, password); - mIncomingCallBroadcastAction = incomingCallBroadcastAction; - mAutoRegistration.setListener(listener); - } - - public SipProfile getLocalProfile() { - return mSipGroup.getLocalProfile(); - } - - // network connectivity is tricky because network can be disconnected - // at any instant so need to deal with exceptions carefully even when - // you think you are connected - private SipSessionGroup createSipSessionGroup(String localIp, - SipProfile localProfile, String password) throws SipException { - try { - return new SipSessionGroup(localIp, localProfile, password); - } catch (IOException e) { - // network disconnected - Log.w(TAG, "createSipSessionGroup(): network disconnected?"); - if (localIp != null) { - return createSipSessionGroup(null, localProfile, password); - } else { - // recursive - Log.wtf(TAG, "impossible!"); - throw new RuntimeException("createSipSessionGroup"); - } - } - } - - private SipProfile duplicate(SipProfile p) { - try { - return new SipProfile.Builder(p).setPassword("*").build(); - } catch (Exception e) { - Log.wtf(TAG, "duplicate()", e); - throw new RuntimeException("duplicate profile", e); - } - } - - public void setListener(ISipSessionListener listener) { - mAutoRegistration.setListener(listener); - } - - public void setIncomingCallBroadcastAction(String action) { - mIncomingCallBroadcastAction = action; - } - - public void openToReceiveCalls() throws SipException { - mOpened = true; - if (mConnected) { - mSipGroup.openToReceiveCalls(this); - mAutoRegistration.start(mSipGroup); - } - if (DEBUG) Log.d(TAG, " openToReceiveCalls: " + getUri() + ": " - + mIncomingCallBroadcastAction); - } - - public void onConnectivityChanged(boolean connected) - throws SipException { - mSipGroup.onConnectivityChanged(); - if (connected) { - resetGroup(mLocalIp); - if (mOpened) openToReceiveCalls(); - } else { - // close mSipGroup but remember mOpened - if (DEBUG) Log.d(TAG, " close auto reg temporarily: " - + getUri() + ": " + mIncomingCallBroadcastAction); - mSipGroup.close(); - mAutoRegistration.stop(); - } - } - - private void resetGroup(String localIp) throws SipException { - try { - mSipGroup.reset(localIp); - } catch (IOException e) { - // network disconnected - Log.w(TAG, "resetGroup(): network disconnected?"); - if (localIp != null) { - resetGroup(null); // reset w/o local IP - } else { - // recursive - Log.wtf(TAG, "impossible!"); - throw new RuntimeException("resetGroup"); - } - } - } - - public void close() { - mOpened = false; - mSipGroup.close(); - mAutoRegistration.stop(); - if (DEBUG) Log.d(TAG, " close: " + getUri() + ": " - + mIncomingCallBroadcastAction); - } - - public ISipSession createSession(ISipSessionListener listener) { - return mSipGroup.createSession(listener); - } - - @Override - public void onRinging(ISipSession session, SipProfile caller, - String sessionDescription) { - synchronized (SipService.this) { - try { - if (!isRegistered()) { - session.endCall(); - return; - } - - // send out incoming call broadcast - addPendingSession(session); - Intent intent = SipManager.createIncomingCallBroadcast( - session.getCallId(), sessionDescription) - .setAction(mIncomingCallBroadcastAction); - if (DEBUG) Log.d(TAG, " ringing~~ " + getUri() + ": " - + caller.getUri() + ": " + session.getCallId() - + " " + mIncomingCallBroadcastAction); - mContext.sendBroadcast(intent); - } catch (RemoteException e) { - // should never happen with a local call - Log.e(TAG, "processCall()", e); - } - } - } - - @Override - public void onError(ISipSession session, int errorCode, - String message) { - if (DEBUG) Log.d(TAG, "sip session error: " - + SipErrorCode.toString(errorCode) + ": " + message); - } - - public boolean isOpened() { - return mOpened; - } - - public boolean isRegistered() { - return mAutoRegistration.isRegistered(); - } - - private String getUri() { - return mSipGroup.getLocalProfileUri(); - } - } - - private class KeepAliveProcess implements Runnable { - private static final String TAG = "\\KEEPALIVE/"; - private static final int INTERVAL = 10; - private SipSessionGroup.SipSessionImpl mSession; - - public KeepAliveProcess(SipSessionGroup.SipSessionImpl session) { - mSession = session; - } - - public void start() { - mTimer.set(INTERVAL * 1000, this); - } - - public void run() { - // delegate to mExecutor - getExecutor().addTask(new Runnable() { - public void run() { - realRun(); - } - }); - } - - private void realRun() { - synchronized (SipService.this) { - SipSessionGroup.SipSessionImpl session = mSession.duplicate(); - if (DEBUG) Log.d(TAG, "~~~ keepalive"); - mTimer.cancel(this); - session.sendKeepAlive(); - if (session.isReRegisterRequired()) { - mSession.register(EXPIRY_TIME); - } else { - mTimer.set(INTERVAL * 1000, this); - } - } - } - - public void stop() { - mTimer.cancel(this); - } - } - - private class AutoRegistrationProcess extends SipSessionAdapter - implements Runnable { - private SipSessionGroup.SipSessionImpl mSession; - private SipSessionListenerProxy mProxy = new SipSessionListenerProxy(); - private KeepAliveProcess mKeepAliveProcess; - private int mBackoff = 1; - private boolean mRegistered; - private long mExpiryTime; - private int mErrorCode; - private String mErrorMessage; - - private String getAction() { - return toString(); - } - - public void start(SipSessionGroup group) { - if (mSession == null) { - mBackoff = 1; - mSession = (SipSessionGroup.SipSessionImpl) - group.createSession(this); - // return right away if no active network connection. - if (mSession == null) return; - - // 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 - mSession.unregister(); - if (DEBUG) Log.d(TAG, "start AutoRegistrationProcess for " - + mSession.getLocalProfile().getUriString()); - } - } - - public void stop() { - stop(false); - } - - private void stopButKeepStates() { - stop(true); - } - - private void stop(boolean keepStates) { - if (mSession == null) return; - if (mConnected && mRegistered) mSession.unregister(); - mTimer.cancel(this); - if (mKeepAliveProcess != null) { - mKeepAliveProcess.stop(); - mKeepAliveProcess = null; - } - if (!keepStates) { - mSession = null; - mRegistered = false; - } - } - - private boolean isStopped() { - return (mSession == null); - } - - public void setListener(ISipSessionListener listener) { - synchronized (SipService.this) { - mProxy.setListener(listener); - if (mSession == null) return; - - try { - int state = (mSession == null) - ? SipSession.State.READY_TO_CALL - : mSession.getState(); - if ((state == SipSession.State.REGISTERING) - || (state == SipSession.State.DEREGISTERING)) { - mProxy.onRegistering(mSession); - } else if (mRegistered) { - int duration = (int) - (mExpiryTime - SystemClock.elapsedRealtime()); - mProxy.onRegistrationDone(mSession, duration); - } else if (mErrorCode != SipErrorCode.NO_ERROR) { - if (mErrorCode == SipErrorCode.TIME_OUT) { - mProxy.onRegistrationTimeout(mSession); - } else { - mProxy.onRegistrationFailed(mSession, mErrorCode, - mErrorMessage); - } - } - } catch (Throwable t) { - Log.w(TAG, "setListener(): " + t); - } - } - } - - public boolean isRegistered() { - return mRegistered; - } - - public void run() { - // delegate to mExecutor - getExecutor().addTask(new Runnable() { - public void run() { - realRun(); - } - }); - } - - private void realRun() { - mErrorCode = SipErrorCode.NO_ERROR; - mErrorMessage = null; - if (DEBUG) Log.d(TAG, "~~~ registering"); - synchronized (SipService.this) { - if (mConnected && !isStopped()) mSession.register(EXPIRY_TIME); - } - } - - private boolean isBehindNAT(String address) { - try { - byte[] d = InetAddress.getByName(address).getAddress(); - if ((d[0] == 10) || - (((0x000000FF & ((int)d[0])) == 172) && - ((0x000000F0 & ((int)d[1])) == 16)) || - (((0x000000FF & ((int)d[0])) == 192) && - ((0x000000FF & ((int)d[1])) == 168))) { - return true; - } - } catch (UnknownHostException e) { - Log.e(TAG, "isBehindAT()" + address, e); - } - return false; - } - - private void restart(int duration) { - if (DEBUG) Log.d(TAG, "Refresh registration " + duration + "s later."); - mTimer.cancel(this); - mTimer.set(duration * 1000, this); - } - - private int backoffDuration() { - int duration = SHORT_EXPIRY_TIME * mBackoff; - if (duration > 3600) { - duration = 3600; - } else { - mBackoff *= 2; - } - return duration; - } - - @Override - public void onRegistering(ISipSession session) { - if (DEBUG) Log.d(TAG, "onRegistering(): " + session); - synchronized (SipService.this) { - if (!isStopped() && (session != mSession)) return; - mRegistered = false; - mProxy.onRegistering(session); - } - } - - @Override - public void onRegistrationDone(ISipSession session, int duration) { - if (DEBUG) Log.d(TAG, "onRegistrationDone(): " + session); - synchronized (SipService.this) { - if (!isStopped() && (session != mSession)) return; - - mProxy.onRegistrationDone(session, duration); - - if (isStopped()) return; - - if (duration > 0) { - mSession.clearReRegisterRequired(); - mExpiryTime = SystemClock.elapsedRealtime() - + (duration * 1000); - - if (!mRegistered) { - mRegistered = true; - // allow some overlap to avoid call drop during renew - duration -= MIN_EXPIRY_TIME; - if (duration < MIN_EXPIRY_TIME) { - duration = MIN_EXPIRY_TIME; - } - restart(duration); - - if (isBehindNAT(mLocalIp) || - mSession.getLocalProfile().getSendKeepAlive()) { - if (mKeepAliveProcess == null) { - mKeepAliveProcess = - new KeepAliveProcess(mSession); - } - mKeepAliveProcess.start(); - } - } - } else { - mRegistered = false; - mExpiryTime = -1L; - if (DEBUG) Log.d(TAG, "Refresh registration immediately"); - run(); - } - } - } - - @Override - public void onRegistrationFailed(ISipSession session, int errorCode, - String message) { - if (DEBUG) Log.d(TAG, "onRegistrationFailed(): " + session + ": " - + SipErrorCode.toString(errorCode) + ": " + message); - synchronized (SipService.this) { - if (!isStopped() && (session != mSession)) return; - mErrorCode = errorCode; - mErrorMessage = message; - mProxy.onRegistrationFailed(session, errorCode, message); - - if (errorCode == SipErrorCode.INVALID_CREDENTIALS) { - if (DEBUG) Log.d(TAG, " pause auto-registration"); - stopButKeepStates(); - } else if (!isStopped()) { - onError(); - } - } - } - - @Override - public void onRegistrationTimeout(ISipSession session) { - if (DEBUG) Log.d(TAG, "onRegistrationTimeout(): " + session); - synchronized (SipService.this) { - if (!isStopped() && (session != mSession)) return; - mErrorCode = SipErrorCode.TIME_OUT; - mProxy.onRegistrationTimeout(session); - - if (!isStopped()) { - mRegistered = false; - onError(); - } - } - } - - private void onError() { - mRegistered = false; - restart(backoffDuration()); - if (mKeepAliveProcess != null) { - mKeepAliveProcess.stop(); - mKeepAliveProcess = null; - } - } - } - - private class ConnectivityReceiver extends BroadcastReceiver { - private Timer mTimer = new Timer(); - private MyTimerTask mTask; - - @Override - public void onReceive(Context context, Intent intent) { - String action = intent.getAction(); - if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) { - Bundle b = intent.getExtras(); - if (b != null) { - NetworkInfo netInfo = (NetworkInfo) - b.get(ConnectivityManager.EXTRA_NETWORK_INFO); - String type = netInfo.getTypeName(); - NetworkInfo.State state = netInfo.getState(); - - if (mWifiOnly && (netInfo.getType() != - ConnectivityManager.TYPE_WIFI)) { - if (DEBUG) { - Log.d(TAG, "Wifi only, other connectivity ignored: " - + type); - } - return; - } - - NetworkInfo activeNetInfo = getActiveNetworkInfo(); - if (DEBUG) { - if (activeNetInfo != null) { - Log.d(TAG, "active network: " - + activeNetInfo.getTypeName() - + ((activeNetInfo.getState() == NetworkInfo.State.CONNECTED) - ? " CONNECTED" : " DISCONNECTED")); - } else { - Log.d(TAG, "active network: null"); - } - } - if ((state == NetworkInfo.State.CONNECTED) - && (activeNetInfo != null) - && (activeNetInfo.getType() != netInfo.getType())) { - if (DEBUG) Log.d(TAG, "ignore connect event: " + type - + ", active: " + activeNetInfo.getTypeName()); - return; - } - - if (state == NetworkInfo.State.CONNECTED) { - if (DEBUG) Log.d(TAG, "Connectivity alert: CONNECTED " + type); - onChanged(type, true); - } else if (state == NetworkInfo.State.DISCONNECTED) { - if (DEBUG) Log.d(TAG, "Connectivity alert: DISCONNECTED " + type); - onChanged(type, false); - } else { - if (DEBUG) Log.d(TAG, "Connectivity alert not processed: " - + state + " " + type); - } - } - } - } - - private NetworkInfo getActiveNetworkInfo() { - ConnectivityManager cm = (ConnectivityManager) - mContext.getSystemService(Context.CONNECTIVITY_SERVICE); - return cm.getActiveNetworkInfo(); - } - - private void onChanged(String type, boolean connected) { - synchronized (SipService.this) { - // When turning on WIFI, it needs some time for network - // connectivity to get stabile so we defer good news (because - // we want to skip the interim ones) but deliver bad news - // immediately - if (connected) { - 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 - } else { - if ((mTask != null) && mTask.mNetworkType.equals(type)) { - mTask.cancel(); - } - onConnectivityChanged(type, false); - } - } - } - - private class MyTimerTask extends TimerTask { - private boolean mConnected; - private String mNetworkType; - - public MyTimerTask(String type, boolean connected) { - mNetworkType = type; - mConnected = connected; - } - - @Override - public void run() { - // delegate to mExecutor - getExecutor().addTask(new Runnable() { - public void run() { - realRun(); - } - }); - } - - private void realRun() { - synchronized (SipService.this) { - if (mTask != this) { - Log.w(TAG, " unexpected task: " + mNetworkType - + (mConnected ? " CONNECTED" : "DISCONNECTED")); - return; - } - mTask = null; - if (DEBUG) Log.d(TAG, " deliver change for " + mNetworkType - + (mConnected ? " CONNECTED" : "DISCONNECTED")); - onConnectivityChanged(mNetworkType, mConnected); - } - } - } - } - - // 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. - */ - class WakeupTimer extends BroadcastReceiver { - private static final String TAG = "_SIP.WkTimer_"; - private static final String TRIGGER_TIME = "TriggerTime"; - - private Context mContext; - private AlarmManager mAlarmManager; - - // runnable --> time to execute in SystemClock - private TreeSet<MyEvent> mEventQueue = - new TreeSet<MyEvent>(new MyEventComparator()); - - private PendingIntent mPendingIntent; - - public WakeupTimer(Context context) { - mContext = context; - mAlarmManager = (AlarmManager) - context.getSystemService(Context.ALARM_SERVICE); - - IntentFilter filter = new IntentFilter(getAction()); - context.registerReceiver(this, filter); - } - - /** - * Stops the timer. No event can be scheduled after this method is called. - */ - public synchronized void stop() { - mContext.unregisterReceiver(this); - if (mPendingIntent != null) { - mAlarmManager.cancel(mPendingIntent); - mPendingIntent = null; - } - mEventQueue.clear(); - mEventQueue = null; - } - - private synchronized boolean stopped() { - if (mEventQueue == null) { - Log.w(TAG, "Timer stopped"); - return true; - } else { - return false; - } - } - - private void cancelAlarm() { - mAlarmManager.cancel(mPendingIntent); - mPendingIntent = null; - } - - private void recalculatePeriods() { - if (mEventQueue.isEmpty()) return; - - MyEvent firstEvent = mEventQueue.first(); - int minPeriod = firstEvent.mMaxPeriod; - long minTriggerTime = firstEvent.mTriggerTime; - for (MyEvent e : mEventQueue) { - e.mPeriod = e.mMaxPeriod / minPeriod * minPeriod; - int interval = (int) (e.mLastTriggerTime + e.mMaxPeriod - - minTriggerTime); - interval = interval / minPeriod * minPeriod; - e.mTriggerTime = minTriggerTime + interval; - } - TreeSet<MyEvent> newQueue = new TreeSet<MyEvent>( - mEventQueue.comparator()); - newQueue.addAll((Collection<MyEvent>) mEventQueue); - mEventQueue.clear(); - mEventQueue = newQueue; - if (DEBUG_TIMER) { - Log.d(TAG, "queue re-calculated"); - printQueue(); - } - } - - // Determines the period and the trigger time of the new event and insert it - // to the queue. - private void insertEvent(MyEvent event) { - long now = SystemClock.elapsedRealtime(); - if (mEventQueue.isEmpty()) { - event.mTriggerTime = now + event.mPeriod; - mEventQueue.add(event); - return; - } - MyEvent firstEvent = mEventQueue.first(); - int minPeriod = firstEvent.mPeriod; - if (minPeriod <= event.mMaxPeriod) { - event.mPeriod = event.mMaxPeriod / minPeriod * minPeriod; - int interval = event.mMaxPeriod; - interval -= (int) (firstEvent.mTriggerTime - now); - interval = interval / minPeriod * minPeriod; - event.mTriggerTime = firstEvent.mTriggerTime + interval; - mEventQueue.add(event); - } else { - long triggerTime = now + event.mPeriod; - if (firstEvent.mTriggerTime < triggerTime) { - event.mTriggerTime = firstEvent.mTriggerTime; - event.mLastTriggerTime -= event.mPeriod; - } else { - event.mTriggerTime = triggerTime; - } - mEventQueue.add(event); - recalculatePeriods(); - } - } - - /** - * Sets a periodic timer. - * - * @param period the timer period; in milli-second - * @param callback is called back when the timer goes off; the same callback - * can be specified in multiple timer events - */ - public synchronized void set(int period, Runnable callback) { - if (stopped()) return; - - long now = SystemClock.elapsedRealtime(); - MyEvent event = new MyEvent(period, callback, now); - insertEvent(event); - - if (mEventQueue.first() == event) { - if (mEventQueue.size() > 1) cancelAlarm(); - scheduleNext(); - } - - long triggerTime = event.mTriggerTime; - if (DEBUG_TIMER) { - Log.d(TAG, " add event " + event + " scheduled at " - + showTime(triggerTime) + " at " + showTime(now) - + ", #events=" + mEventQueue.size()); - printQueue(); - } - } - - /** - * Cancels all the timer events with the specified callback. - * - * @param callback the callback - */ - public synchronized void cancel(Runnable callback) { - if (stopped() || mEventQueue.isEmpty()) return; - if (DEBUG_TIMER) Log.d(TAG, "cancel:" + callback); - - MyEvent firstEvent = mEventQueue.first(); - for (Iterator<MyEvent> iter = mEventQueue.iterator(); - iter.hasNext();) { - MyEvent event = iter.next(); - if (event.mCallback == callback) { - iter.remove(); - if (DEBUG_TIMER) Log.d(TAG, " cancel found:" + event); - } - } - if (mEventQueue.isEmpty()) { - cancelAlarm(); - } else if (mEventQueue.first() != firstEvent) { - cancelAlarm(); - firstEvent = mEventQueue.first(); - firstEvent.mPeriod = firstEvent.mMaxPeriod; - firstEvent.mTriggerTime = firstEvent.mLastTriggerTime - + firstEvent.mPeriod; - recalculatePeriods(); - scheduleNext(); - } - if (DEBUG_TIMER) { - Log.d(TAG, "after cancel:"); - printQueue(); - } - } - - private void scheduleNext() { - if (stopped() || mEventQueue.isEmpty()) return; - - if (mPendingIntent != null) { - throw new RuntimeException("pendingIntent is not null!"); - } - - MyEvent event = mEventQueue.first(); - Intent intent = new Intent(getAction()); - intent.putExtra(TRIGGER_TIME, event.mTriggerTime); - PendingIntent pendingIntent = mPendingIntent = - PendingIntent.getBroadcast(mContext, 0, intent, - PendingIntent.FLAG_UPDATE_CURRENT); - mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, - event.mTriggerTime, pendingIntent); - } - - @Override - public synchronized void onReceive(Context context, Intent intent) { - String action = intent.getAction(); - if (getAction().equals(action) - && intent.getExtras().containsKey(TRIGGER_TIME)) { - mPendingIntent = null; - long triggerTime = intent.getLongExtra(TRIGGER_TIME, -1L); - execute(triggerTime); - } else { - Log.d(TAG, "unrecognized intent: " + intent); - } - } - - private void printQueue() { - int count = 0; - for (MyEvent event : mEventQueue) { - Log.d(TAG, " " + event + ": scheduled at " - + showTime(event.mTriggerTime) + ": last at " - + showTime(event.mLastTriggerTime)); - if (++count >= 5) break; - } - if (mEventQueue.size() > count) { - Log.d(TAG, " ....."); - } else if (count == 0) { - Log.d(TAG, " <empty>"); - } - } - - private void execute(long triggerTime) { - if (DEBUG_TIMER) Log.d(TAG, "time's up, triggerTime = " - + showTime(triggerTime) + ": " + mEventQueue.size()); - if (stopped() || mEventQueue.isEmpty()) return; - - for (MyEvent event : mEventQueue) { - if (event.mTriggerTime != triggerTime) break; - if (DEBUG_TIMER) Log.d(TAG, "execute " + event); - - event.mLastTriggerTime = event.mTriggerTime; - event.mTriggerTime += event.mPeriod; - - // run the callback in a new thread to prevent deadlock - new Thread(event.mCallback, "SipServiceTimerCallbackThread") - .start(); - } - if (DEBUG_TIMER) { - Log.d(TAG, "after timeout execution"); - printQueue(); - } - scheduleNext(); - } - - private String getAction() { - return toString(); - } - - private String showTime(long time) { - int ms = (int) (time % 1000); - int s = (int) (time / 1000); - int m = s / 60; - s %= 60; - return String.format("%d.%d.%d", m, s, ms); - } - } - - private static class MyEvent { - int mPeriod; - int mMaxPeriod; - long mTriggerTime; - long mLastTriggerTime; - Runnable mCallback; - - MyEvent(int period, Runnable callback, long now) { - mPeriod = mMaxPeriod = period; - mCallback = callback; - mLastTriggerTime = now; - } - - @Override - public String toString() { - String s = super.toString(); - s = s.substring(s.indexOf("@")); - return s + ":" + (mPeriod / 1000) + ":" + (mMaxPeriod / 1000) + ":" - + toString(mCallback); - } - - private String toString(Object o) { - String s = o.toString(); - int index = s.indexOf("$"); - if (index > 0) s = s.substring(index + 1); - return s; - } - } - - private static class MyEventComparator implements Comparator<MyEvent> { - public int compare(MyEvent e1, MyEvent e2) { - if (e1 == e2) return 0; - int diff = e1.mMaxPeriod - e2.mMaxPeriod; - if (diff == 0) diff = -1; - return diff; - } - - public boolean equals(Object that) { - return (this == that); - } - } - - // Single-threaded executor - private static 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) { - Message.obtain(this, 0/* don't care */, task).sendToTarget(); - } - - @Override - public void handleMessage(Message msg) { - if (msg.obj instanceof Runnable) { - ((Runnable) msg.obj).run(); - } else { - Log.w(TAG, "can't handle msg: " + msg); - } - } - } -} diff --git a/services/java/com/android/server/sip/SipSessionGroup.java b/services/java/com/android/server/sip/SipSessionGroup.java deleted file mode 100644 index 91677a2..0000000 --- a/services/java/com/android/server/sip/SipSessionGroup.java +++ /dev/null @@ -1,1393 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.sip; - -import gov.nist.javax.sip.clientauthutils.AccountManager; -import gov.nist.javax.sip.clientauthutils.UserCredentials; -import gov.nist.javax.sip.header.SIPHeaderNames; -import gov.nist.javax.sip.header.ProxyAuthenticate; -import gov.nist.javax.sip.header.WWWAuthenticate; -import gov.nist.javax.sip.message.SIPMessage; - -import android.net.sip.ISipSession; -import android.net.sip.ISipSessionListener; -import android.net.sip.SipErrorCode; -import android.net.sip.SipProfile; -import android.net.sip.SipSession; -import android.net.sip.SipSessionAdapter; -import android.text.TextUtils; -import android.util.Log; - -import java.io.IOException; -import java.io.UnsupportedEncodingException; -import java.net.DatagramSocket; -import java.net.UnknownHostException; -import java.text.ParseException; -import java.util.Collection; -import java.util.EventObject; -import java.util.HashMap; -import java.util.Map; -import java.util.Properties; -import java.util.TooManyListenersException; - -import javax.sip.ClientTransaction; -import javax.sip.Dialog; -import javax.sip.DialogTerminatedEvent; -import javax.sip.IOExceptionEvent; -import javax.sip.InvalidArgumentException; -import javax.sip.ListeningPoint; -import javax.sip.RequestEvent; -import javax.sip.ResponseEvent; -import javax.sip.ServerTransaction; -import javax.sip.SipException; -import javax.sip.SipFactory; -import javax.sip.SipListener; -import javax.sip.SipProvider; -import javax.sip.SipStack; -import javax.sip.TimeoutEvent; -import javax.sip.Transaction; -import javax.sip.TransactionState; -import javax.sip.TransactionTerminatedEvent; -import javax.sip.TransactionUnavailableException; -import javax.sip.address.Address; -import javax.sip.address.SipURI; -import javax.sip.header.CSeqHeader; -import javax.sip.header.ExpiresHeader; -import javax.sip.header.FromHeader; -import javax.sip.header.MinExpiresHeader; -import javax.sip.header.ViaHeader; -import javax.sip.message.Message; -import javax.sip.message.Request; -import javax.sip.message.Response; - -/** - * Manages {@link ISipSession}'s for a SIP account. - */ -class SipSessionGroup implements SipListener { - private static final String TAG = "SipSession"; - private static final boolean DEBUG = true; - private static final boolean DEBUG_PING = DEBUG && false; - private static final String ANONYMOUS = "anonymous"; - private static final String SERVER_ERROR_PREFIX = "Response: "; - private static final int EXPIRY_TIME = 3600; // in seconds - private static final int CANCEL_CALL_TIMER = 3; // in seconds - - private static final EventObject DEREGISTER = new EventObject("Deregister"); - private static final EventObject END_CALL = new EventObject("End call"); - private static final EventObject HOLD_CALL = new EventObject("Hold call"); - private static final EventObject CONTINUE_CALL - = new EventObject("Continue call"); - - private final SipProfile mLocalProfile; - private final String mPassword; - - private SipStack mSipStack; - private SipHelper mSipHelper; - private String mLastNonce; - private int mRPort; - - // session that processes INVITE requests - private SipSessionImpl mCallReceiverSession; - private String mLocalIp; - - // call-id-to-SipSession map - private Map<String, SipSessionImpl> mSessionMap = - new HashMap<String, SipSessionImpl>(); - - /** - * @param myself the local profile with password crossed out - * @param password the password of the profile - * @throws IOException if cannot assign requested address - */ - public SipSessionGroup(String localIp, SipProfile myself, String password) - throws SipException, IOException { - mLocalProfile = myself; - mPassword = password; - reset(localIp); - } - - synchronized void reset(String localIp) throws SipException, IOException { - mLocalIp = localIp; - if (localIp == null) return; - - SipProfile myself = mLocalProfile; - SipFactory sipFactory = SipFactory.getInstance(); - Properties properties = new Properties(); - properties.setProperty("javax.sip.STACK_NAME", getStackName()); - String outboundProxy = myself.getProxyAddress(); - if (!TextUtils.isEmpty(outboundProxy)) { - Log.v(TAG, "outboundProxy is " + outboundProxy); - properties.setProperty("javax.sip.OUTBOUND_PROXY", outboundProxy - + ":" + myself.getPort() + "/" + myself.getProtocol()); - } - SipStack stack = mSipStack = sipFactory.createSipStack(properties); - - try { - SipProvider provider = stack.createSipProvider( - stack.createListeningPoint(localIp, allocateLocalPort(), - myself.getProtocol())); - provider.addSipListener(this); - mSipHelper = new SipHelper(stack, provider); - } catch (InvalidArgumentException e) { - throw new IOException(e.getMessage()); - } catch (TooManyListenersException e) { - // must never happen - throw new SipException("SipSessionGroup constructor", e); - } - Log.d(TAG, " start stack for " + myself.getUriString()); - stack.start(); - - mLastNonce = null; - mCallReceiverSession = null; - mSessionMap.clear(); - } - - synchronized void onConnectivityChanged() { - for (SipSessionImpl s : mSessionMap.values()) { - s.onError(SipErrorCode.DATA_CONNECTION_LOST, - "data connection lost"); - } - } - - public SipProfile getLocalProfile() { - return mLocalProfile; - } - - public String getLocalProfileUri() { - return mLocalProfile.getUriString(); - } - - private String getStackName() { - return "stack" + System.currentTimeMillis(); - } - - public synchronized void close() { - Log.d(TAG, " close stack for " + mLocalProfile.getUriString()); - mSessionMap.clear(); - closeToNotReceiveCalls(); - if (mSipStack != null) { - mSipStack.stop(); - mSipStack = null; - mSipHelper = null; - } - } - - public synchronized boolean isClosed() { - return (mSipStack == null); - } - - // For internal use, require listener not to block in callbacks. - public synchronized void openToReceiveCalls(ISipSessionListener listener) { - if (mCallReceiverSession == null) { - mCallReceiverSession = new SipSessionCallReceiverImpl(listener); - } else { - mCallReceiverSession.setListener(listener); - } - } - - public synchronized void closeToNotReceiveCalls() { - mCallReceiverSession = null; - } - - public ISipSession createSession(ISipSessionListener listener) { - return (isClosed() ? null : new SipSessionImpl(listener)); - } - - private static int allocateLocalPort() throws SipException { - try { - DatagramSocket s = new DatagramSocket(); - int localPort = s.getLocalPort(); - s.close(); - return localPort; - } catch (IOException e) { - throw new SipException("allocateLocalPort()", e); - } - } - - private synchronized SipSessionImpl getSipSession(EventObject event) { - String key = SipHelper.getCallId(event); - SipSessionImpl session = mSessionMap.get(key); - if ((session != null) && isLoggable(session)) { - Log.d(TAG, "session key from event: " + key); - Log.d(TAG, "active sessions:"); - for (String k : mSessionMap.keySet()) { - Log.d(TAG, " ..." + k + ": " + mSessionMap.get(k)); - } - } - return ((session != null) ? session : mCallReceiverSession); - } - - private synchronized void addSipSession(SipSessionImpl newSession) { - removeSipSession(newSession); - String key = newSession.getCallId(); - mSessionMap.put(key, newSession); - if (isLoggable(newSession)) { - Log.d(TAG, "+++ add a session with key: '" + key + "'"); - for (String k : mSessionMap.keySet()) { - Log.d(TAG, " " + k + ": " + mSessionMap.get(k)); - } - } - } - - private synchronized void removeSipSession(SipSessionImpl session) { - if (session == mCallReceiverSession) return; - String key = session.getCallId(); - SipSessionImpl s = mSessionMap.remove(key); - // sanity check - if ((s != null) && (s != session)) { - Log.w(TAG, "session " + session + " is not associated with key '" - + key + "'"); - mSessionMap.put(key, s); - for (Map.Entry<String, SipSessionImpl> entry - : mSessionMap.entrySet()) { - if (entry.getValue() == s) { - key = entry.getKey(); - mSessionMap.remove(key); - } - } - } - - if ((s != null) && isLoggable(s)) { - Log.d(TAG, "remove session " + session + " @key '" + key + "'"); - for (String k : mSessionMap.keySet()) { - Log.d(TAG, " " + k + ": " + mSessionMap.get(k)); - } - } - } - - public void processRequest(RequestEvent event) { - process(event); - } - - public void processResponse(ResponseEvent event) { - process(event); - } - - public void processIOException(IOExceptionEvent event) { - process(event); - } - - public void processTimeout(TimeoutEvent event) { - process(event); - } - - public void processTransactionTerminated(TransactionTerminatedEvent event) { - process(event); - } - - public void processDialogTerminated(DialogTerminatedEvent event) { - process(event); - } - - private synchronized void process(EventObject event) { - SipSessionImpl session = getSipSession(event); - try { - boolean isLoggable = isLoggable(session, event); - boolean processed = (session != null) && session.process(event); - if (isLoggable && processed) { - Log.d(TAG, "new state after: " - + SipSession.State.toString(session.mState)); - } - } catch (Throwable e) { - Log.w(TAG, "event process error: " + event, e); - session.onError(e); - } - } - - private String extractContent(Message message) { - // Currently we do not support secure MIME bodies. - byte[] bytes = message.getRawContent(); - if (bytes != null) { - try { - if (message instanceof SIPMessage) { - return ((SIPMessage) message).getMessageContent(); - } else { - return new String(bytes, "UTF-8"); - } - } catch (UnsupportedEncodingException e) { - } - } - return null; - } - - private class SipSessionCallReceiverImpl extends SipSessionImpl { - public SipSessionCallReceiverImpl(ISipSessionListener listener) { - super(listener); - } - - public boolean process(EventObject evt) throws SipException { - if (isLoggable(this, evt)) Log.d(TAG, " ~~~~~ " + this + ": " - + SipSession.State.toString(mState) + ": processing " - + log(evt)); - if (isRequestEvent(Request.INVITE, evt)) { - RequestEvent event = (RequestEvent) evt; - SipSessionImpl newSession = new SipSessionImpl(mProxy); - newSession.mServerTransaction = mSipHelper.sendRinging(event, - generateTag()); - newSession.mDialog = newSession.mServerTransaction.getDialog(); - newSession.mInviteReceived = event; - newSession.mPeerProfile = createPeerProfile(event.getRequest()); - newSession.mState = SipSession.State.INCOMING_CALL; - newSession.mPeerSessionDescription = - extractContent(event.getRequest()); - addSipSession(newSession); - mProxy.onRinging(newSession, newSession.mPeerProfile, - newSession.mPeerSessionDescription); - return true; - } else if (isRequestEvent(Request.OPTIONS, evt)) { - mSipHelper.sendResponse((RequestEvent) evt, Response.OK); - return true; - } else { - return false; - } - } - } - - class SipSessionImpl extends ISipSession.Stub { - SipProfile mPeerProfile; - SipSessionListenerProxy mProxy = new SipSessionListenerProxy(); - int mState = SipSession.State.READY_TO_CALL; - RequestEvent mInviteReceived; - Dialog mDialog; - ServerTransaction mServerTransaction; - ClientTransaction mClientTransaction; - String mPeerSessionDescription; - boolean mInCall; - boolean mReRegisterFlag = false; - SessionTimer mTimer; - - // lightweight timer - class SessionTimer { - private boolean mRunning = true; - - void start(final int timeout) { - new Thread(new Runnable() { - public void run() { - sleep(timeout); - if (mRunning) timeout(); - } - }, "SipSessionTimerThread").start(); - } - - synchronized void cancel() { - mRunning = false; - this.notify(); - } - - private void timeout() { - synchronized (SipSessionGroup.this) { - onError(SipErrorCode.TIME_OUT, "Session timed out!"); - } - } - - private synchronized void sleep(int timeout) { - try { - this.wait(timeout * 1000); - } catch (InterruptedException e) { - Log.e(TAG, "session timer interrupted!"); - } - } - } - - public SipSessionImpl(ISipSessionListener listener) { - setListener(listener); - } - - SipSessionImpl duplicate() { - return new SipSessionImpl(mProxy.getListener()); - } - - private void reset() { - mInCall = false; - removeSipSession(this); - mPeerProfile = null; - mState = SipSession.State.READY_TO_CALL; - mInviteReceived = null; - mDialog = null; - mServerTransaction = null; - mClientTransaction = null; - mPeerSessionDescription = null; - - cancelSessionTimer(); - } - - public boolean isInCall() { - return mInCall; - } - - public String getLocalIp() { - return mLocalIp; - } - - public SipProfile getLocalProfile() { - return mLocalProfile; - } - - public SipProfile getPeerProfile() { - return mPeerProfile; - } - - public String getCallId() { - return SipHelper.getCallId(getTransaction()); - } - - private Transaction getTransaction() { - if (mClientTransaction != null) return mClientTransaction; - if (mServerTransaction != null) return mServerTransaction; - return null; - } - - public int getState() { - return mState; - } - - public void setListener(ISipSessionListener listener) { - mProxy.setListener((listener instanceof SipSessionListenerProxy) - ? ((SipSessionListenerProxy) listener).getListener() - : listener); - } - - // process the command in a new thread - private void doCommandAsync(final EventObject command) { - new Thread(new Runnable() { - public void run() { - try { - processCommand(command); - } catch (SipException e) { - Log.w(TAG, "command error: " + command, e); - onError(e); - } - } - }, "SipSessionAsyncCmdThread").start(); - } - - public void makeCall(SipProfile peerProfile, String sessionDescription, - int timeout) { - doCommandAsync(new MakeCallCommand(peerProfile, sessionDescription, - timeout)); - } - - public void answerCall(String sessionDescription, int timeout) { - try { - processCommand(new MakeCallCommand(mPeerProfile, - sessionDescription, timeout)); - } catch (SipException e) { - onError(e); - } - } - - public void endCall() { - doCommandAsync(END_CALL); - } - - public void changeCall(String sessionDescription, int timeout) { - doCommandAsync(new MakeCallCommand(mPeerProfile, sessionDescription, - timeout)); - } - - public void changeCallWithTimeout( - String sessionDescription, int timeout) { - doCommandAsync(new MakeCallCommand(mPeerProfile, sessionDescription, - timeout)); - } - - public void register(int duration) { - doCommandAsync(new RegisterCommand(duration)); - } - - public void unregister() { - doCommandAsync(DEREGISTER); - } - - public boolean isReRegisterRequired() { - return mReRegisterFlag; - } - - public void clearReRegisterRequired() { - mReRegisterFlag = false; - } - - public void sendKeepAlive() { - mState = SipSession.State.PINGING; - try { - processCommand(new OptionsCommand()); - while (SipSession.State.PINGING == mState) { - Thread.sleep(1000); - } - } catch (SipException e) { - Log.e(TAG, "sendKeepAlive failed", e); - } catch (InterruptedException e) { - Log.e(TAG, "sendKeepAlive interrupted", e); - } - } - - private void processCommand(EventObject command) throws SipException { - if (!process(command)) { - onError(SipErrorCode.IN_PROGRESS, - "cannot initiate a new transaction to execute: " - + command); - } - } - - protected String generateTag() { - // 32-bit randomness - return String.valueOf((long) (Math.random() * 0x100000000L)); - } - - public String toString() { - try { - String s = super.toString(); - return s.substring(s.indexOf("@")) + ":" - + SipSession.State.toString(mState); - } catch (Throwable e) { - return super.toString(); - } - } - - public boolean process(EventObject evt) throws SipException { - if (isLoggable(this, evt)) Log.d(TAG, " ~~~~~ " + this + ": " - + SipSession.State.toString(mState) + ": processing " - + log(evt)); - synchronized (SipSessionGroup.this) { - if (isClosed()) return false; - - Dialog dialog = null; - if (evt instanceof RequestEvent) { - dialog = ((RequestEvent) evt).getDialog(); - } else if (evt instanceof ResponseEvent) { - dialog = ((ResponseEvent) evt).getDialog(); - } - if (dialog != null) mDialog = dialog; - - boolean processed; - - switch (mState) { - case SipSession.State.REGISTERING: - case SipSession.State.DEREGISTERING: - processed = registeringToReady(evt); - break; - case SipSession.State.PINGING: - processed = keepAliveProcess(evt); - break; - case SipSession.State.READY_TO_CALL: - processed = readyForCall(evt); - break; - case SipSession.State.INCOMING_CALL: - processed = incomingCall(evt); - break; - case SipSession.State.INCOMING_CALL_ANSWERING: - processed = incomingCallToInCall(evt); - break; - case SipSession.State.OUTGOING_CALL: - case SipSession.State.OUTGOING_CALL_RING_BACK: - processed = outgoingCall(evt); - break; - case SipSession.State.OUTGOING_CALL_CANCELING: - processed = outgoingCallToReady(evt); - break; - case SipSession.State.IN_CALL: - processed = inCall(evt); - break; - default: - processed = false; - } - return (processed || processExceptions(evt)); - } - } - - private boolean processExceptions(EventObject evt) throws SipException { - if (isRequestEvent(Request.BYE, evt)) { - // terminate the call whenever a BYE is received - mSipHelper.sendResponse((RequestEvent) evt, Response.OK); - endCallNormally(); - return true; - } else if (isRequestEvent(Request.CANCEL, evt)) { - mSipHelper.sendResponse((RequestEvent) evt, - Response.CALL_OR_TRANSACTION_DOES_NOT_EXIST); - return true; - } else if (evt instanceof TransactionTerminatedEvent) { - if (isCurrentTransaction((TransactionTerminatedEvent) evt)) { - if (evt instanceof TimeoutEvent) { - processTimeout((TimeoutEvent) evt); - } else { - processTransactionTerminated( - (TransactionTerminatedEvent) evt); - } - return true; - } - } else if (isRequestEvent(Request.OPTIONS, evt)) { - mSipHelper.sendResponse((RequestEvent) evt, Response.OK); - return true; - } else if (evt instanceof DialogTerminatedEvent) { - processDialogTerminated((DialogTerminatedEvent) evt); - return true; - } - return false; - } - - private void processDialogTerminated(DialogTerminatedEvent event) { - if (mDialog == event.getDialog()) { - onError(new SipException("dialog terminated")); - } else { - Log.d(TAG, "not the current dialog; current=" + mDialog - + ", terminated=" + event.getDialog()); - } - } - - private boolean isCurrentTransaction(TransactionTerminatedEvent event) { - Transaction current = event.isServerTransaction() - ? mServerTransaction - : mClientTransaction; - Transaction target = event.isServerTransaction() - ? event.getServerTransaction() - : event.getClientTransaction(); - - if ((current != target) && (mState != SipSession.State.PINGING)) { - Log.d(TAG, "not the current transaction; current=" - + toString(current) + ", target=" + toString(target)); - return false; - } else if (current != null) { - Log.d(TAG, "transaction terminated: " + toString(current)); - return true; - } else { - // no transaction; shouldn't be here; ignored - return true; - } - } - - private String toString(Transaction transaction) { - if (transaction == null) return "null"; - Request request = transaction.getRequest(); - Dialog dialog = transaction.getDialog(); - CSeqHeader cseq = (CSeqHeader) request.getHeader(CSeqHeader.NAME); - return String.format("req=%s,%s,s=%s,ds=%s,", request.getMethod(), - cseq.getSeqNumber(), transaction.getState(), - ((dialog == null) ? "-" : dialog.getState())); - } - - private void processTransactionTerminated( - TransactionTerminatedEvent event) { - switch (mState) { - case SipSession.State.IN_CALL: - case SipSession.State.READY_TO_CALL: - Log.d(TAG, "Transaction terminated; do nothing"); - break; - default: - Log.d(TAG, "Transaction terminated early: " + this); - onError(SipErrorCode.TRANSACTION_TERMINTED, - "transaction terminated"); - } - } - - private void processTimeout(TimeoutEvent event) { - Log.d(TAG, "processing Timeout..."); - switch (mState) { - case SipSession.State.REGISTERING: - case SipSession.State.DEREGISTERING: - reset(); - mProxy.onRegistrationTimeout(this); - break; - case SipSession.State.INCOMING_CALL: - case SipSession.State.INCOMING_CALL_ANSWERING: - case SipSession.State.OUTGOING_CALL: - case SipSession.State.OUTGOING_CALL_CANCELING: - onError(SipErrorCode.TIME_OUT, event.toString()); - break; - case SipSession.State.PINGING: - reset(); - mReRegisterFlag = true; - mState = SipSession.State.READY_TO_CALL; - break; - - default: - Log.d(TAG, " do nothing"); - break; - } - } - - private int getExpiryTime(Response response) { - int expires = EXPIRY_TIME; - ExpiresHeader expiresHeader = (ExpiresHeader) - response.getHeader(ExpiresHeader.NAME); - if (expiresHeader != null) expires = expiresHeader.getExpires(); - expiresHeader = (ExpiresHeader) - response.getHeader(MinExpiresHeader.NAME); - if (expiresHeader != null) { - expires = Math.max(expires, expiresHeader.getExpires()); - } - return expires; - } - - private boolean keepAliveProcess(EventObject evt) throws SipException { - if (evt instanceof OptionsCommand) { - mClientTransaction = mSipHelper.sendKeepAlive(mLocalProfile, - generateTag()); - mDialog = mClientTransaction.getDialog(); - addSipSession(this); - return true; - } else if (evt instanceof ResponseEvent) { - return parseOptionsResult(evt); - } - return false; - } - - private boolean parseOptionsResult(EventObject evt) { - if (expectResponse(Request.OPTIONS, evt)) { - ResponseEvent event = (ResponseEvent) evt; - int rPort = getRPortFromResponse(event.getResponse()); - if (rPort != -1) { - if (mRPort == 0) mRPort = rPort; - if (mRPort != rPort) { - mReRegisterFlag = true; - if (DEBUG) Log.w(TAG, String.format( - "rport is changed: %d <> %d", mRPort, rPort)); - mRPort = rPort; - } else { - if (DEBUG_PING) Log.w(TAG, "rport is the same: " + rPort); - } - } else { - if (DEBUG) Log.w(TAG, "peer did not respond rport"); - } - reset(); - return true; - } - return false; - } - - private int getRPortFromResponse(Response response) { - ViaHeader viaHeader = (ViaHeader)(response.getHeader( - SIPHeaderNames.VIA)); - return (viaHeader == null) ? -1 : viaHeader.getRPort(); - } - - private boolean registeringToReady(EventObject evt) - throws SipException { - if (expectResponse(Request.REGISTER, evt)) { - ResponseEvent event = (ResponseEvent) evt; - Response response = event.getResponse(); - - int statusCode = response.getStatusCode(); - switch (statusCode) { - case Response.OK: - int state = mState; - onRegistrationDone((state == SipSession.State.REGISTERING) - ? getExpiryTime(((ResponseEvent) evt).getResponse()) - : -1); - mLastNonce = null; - mRPort = 0; - return true; - case Response.UNAUTHORIZED: - case Response.PROXY_AUTHENTICATION_REQUIRED: - if (!handleAuthentication(event)) { - if (mLastNonce == null) { - onRegistrationFailed(SipErrorCode.SERVER_ERROR, - "server does not provide challenge"); - } else { - Log.v(TAG, "Incorrect username/password"); - onRegistrationFailed( - SipErrorCode.INVALID_CREDENTIALS, - "incorrect username or password"); - } - } - return true; - default: - if (statusCode >= 500) { - onRegistrationFailed(response); - return true; - } - } - } - return false; - } - - private boolean handleAuthentication(ResponseEvent event) - throws SipException { - Response response = event.getResponse(); - String nonce = getNonceFromResponse(response); - if (((nonce != null) && nonce.equals(mLastNonce)) || - (nonce == null)) { - mLastNonce = nonce; - return false; - } else { - mClientTransaction = mSipHelper.handleChallenge( - event, getAccountManager()); - mDialog = mClientTransaction.getDialog(); - mLastNonce = nonce; - return true; - } - } - - private boolean crossDomainAuthenticationRequired(Response response) { - String realm = getRealmFromResponse(response); - if (realm == null) realm = ""; - return !mLocalProfile.getSipDomain().trim().equals(realm.trim()); - } - - private AccountManager getAccountManager() { - return new AccountManager() { - public UserCredentials getCredentials(ClientTransaction - challengedTransaction, String realm) { - return new UserCredentials() { - public String getUserName() { - return mLocalProfile.getUserName(); - } - - public String getPassword() { - return mPassword; - } - - public String getSipDomain() { - return mLocalProfile.getSipDomain(); - } - }; - } - }; - } - - private String getRealmFromResponse(Response response) { - WWWAuthenticate wwwAuth = (WWWAuthenticate)response.getHeader( - SIPHeaderNames.WWW_AUTHENTICATE); - if (wwwAuth != null) return wwwAuth.getRealm(); - ProxyAuthenticate proxyAuth = (ProxyAuthenticate)response.getHeader( - SIPHeaderNames.PROXY_AUTHENTICATE); - return (proxyAuth == null) ? null : proxyAuth.getRealm(); - } - - private String getNonceFromResponse(Response response) { - WWWAuthenticate wwwAuth = (WWWAuthenticate)response.getHeader( - SIPHeaderNames.WWW_AUTHENTICATE); - if (wwwAuth != null) return wwwAuth.getNonce(); - ProxyAuthenticate proxyAuth = (ProxyAuthenticate)response.getHeader( - SIPHeaderNames.PROXY_AUTHENTICATE); - return (proxyAuth == null) ? null : proxyAuth.getNonce(); - } - - private boolean readyForCall(EventObject evt) throws SipException { - // expect MakeCallCommand, RegisterCommand, DEREGISTER - if (evt instanceof MakeCallCommand) { - MakeCallCommand cmd = (MakeCallCommand) evt; - mPeerProfile = cmd.getPeerProfile(); - mClientTransaction = mSipHelper.sendInvite(mLocalProfile, - mPeerProfile, cmd.getSessionDescription(), - generateTag()); - mDialog = mClientTransaction.getDialog(); - addSipSession(this); - mState = SipSession.State.OUTGOING_CALL; - mProxy.onCalling(this); - startSessionTimer(cmd.getTimeout()); - return true; - } else if (evt instanceof RegisterCommand) { - int duration = ((RegisterCommand) evt).getDuration(); - mClientTransaction = mSipHelper.sendRegister(mLocalProfile, - generateTag(), duration); - mDialog = mClientTransaction.getDialog(); - addSipSession(this); - mState = SipSession.State.REGISTERING; - mProxy.onRegistering(this); - return true; - } else if (DEREGISTER == evt) { - mClientTransaction = mSipHelper.sendRegister(mLocalProfile, - generateTag(), 0); - mDialog = mClientTransaction.getDialog(); - addSipSession(this); - mState = SipSession.State.DEREGISTERING; - mProxy.onRegistering(this); - return true; - } - return false; - } - - private boolean incomingCall(EventObject evt) throws SipException { - // expect MakeCallCommand(answering) , END_CALL cmd , Cancel - if (evt instanceof MakeCallCommand) { - // answer call - mServerTransaction = mSipHelper.sendInviteOk(mInviteReceived, - mLocalProfile, - ((MakeCallCommand) evt).getSessionDescription(), - mServerTransaction); - mState = SipSession.State.INCOMING_CALL_ANSWERING; - startSessionTimer(((MakeCallCommand) evt).getTimeout()); - return true; - } else if (END_CALL == evt) { - mSipHelper.sendInviteBusyHere(mInviteReceived, - mServerTransaction); - endCallNormally(); - return true; - } else if (isRequestEvent(Request.CANCEL, evt)) { - RequestEvent event = (RequestEvent) evt; - mSipHelper.sendResponse(event, Response.OK); - mSipHelper.sendInviteRequestTerminated( - mInviteReceived.getRequest(), mServerTransaction); - endCallNormally(); - return true; - } - return false; - } - - private boolean incomingCallToInCall(EventObject evt) - throws SipException { - // expect ACK, CANCEL request - if (isRequestEvent(Request.ACK, evt)) { - establishCall(); - return true; - } else if (isRequestEvent(Request.CANCEL, evt)) { - // http://tools.ietf.org/html/rfc3261#section-9.2 - // Final response has been sent; do nothing here. - return true; - } - return false; - } - - private boolean outgoingCall(EventObject evt) throws SipException { - if (expectResponse(Request.INVITE, evt)) { - ResponseEvent event = (ResponseEvent) evt; - Response response = event.getResponse(); - - int statusCode = response.getStatusCode(); - switch (statusCode) { - case Response.RINGING: - if (mState == SipSession.State.OUTGOING_CALL) { - mState = SipSession.State.OUTGOING_CALL_RING_BACK; - mProxy.onRingingBack(this); - cancelSessionTimer(); - } - return true; - case Response.OK: - mSipHelper.sendInviteAck(event, mDialog); - mPeerSessionDescription = extractContent(response); - establishCall(); - return true; - case Response.UNAUTHORIZED: - case Response.PROXY_AUTHENTICATION_REQUIRED: - if (crossDomainAuthenticationRequired(response)) { - onError(SipErrorCode.CROSS_DOMAIN_AUTHENTICATION, - getRealmFromResponse(response)); - } else if (handleAuthentication(event)) { - addSipSession(this); - } else if (mLastNonce == null) { - onError(SipErrorCode.SERVER_ERROR, - "server does not provide challenge"); - } else { - onError(SipErrorCode.INVALID_CREDENTIALS, - "incorrect username or password"); - } - return true; - case Response.REQUEST_PENDING: - // TODO: - // rfc3261#section-14.1; re-schedule invite - return true; - default: - if (statusCode >= 400) { - // error: an ack is sent automatically by the stack - onError(response); - return true; - } else if (statusCode >= 300) { - // TODO: handle 3xx (redirect) - } else { - return true; - } - } - return false; - } else if (END_CALL == evt) { - // RFC says that UA should not send out cancel when no - // response comes back yet. We are cheating for not checking - // response. - mSipHelper.sendCancel(mClientTransaction); - mState = SipSession.State.OUTGOING_CALL_CANCELING; - startSessionTimer(CANCEL_CALL_TIMER); - return true; - } - return false; - } - - private boolean outgoingCallToReady(EventObject evt) - throws SipException { - if (evt instanceof ResponseEvent) { - ResponseEvent event = (ResponseEvent) evt; - Response response = event.getResponse(); - int statusCode = response.getStatusCode(); - if (expectResponse(Request.CANCEL, evt)) { - if (statusCode == Response.OK) { - // do nothing; wait for REQUEST_TERMINATED - return true; - } - } else if (expectResponse(Request.INVITE, evt)) { - switch (statusCode) { - case Response.OK: - outgoingCall(evt); // abort Cancel - return true; - case Response.REQUEST_TERMINATED: - endCallNormally(); - return true; - } - } else { - return false; - } - - if (statusCode >= 400) { - onError(response); - return true; - } - } else if (evt instanceof TransactionTerminatedEvent) { - // rfc3261#section-14.1: - // if re-invite gets timed out, terminate the dialog; but - // re-invite is not reliable, just let it go and pretend - // nothing happened. - onError(new SipException("timed out")); - } - return false; - } - - private boolean inCall(EventObject evt) throws SipException { - // expect END_CALL cmd, BYE request, hold call (MakeCallCommand) - // OK retransmission is handled in SipStack - if (END_CALL == evt) { - // rfc3261#section-15.1.1 - mSipHelper.sendBye(mDialog); - endCallNormally(); - return true; - } else if (isRequestEvent(Request.INVITE, evt)) { - // got Re-INVITE - RequestEvent event = mInviteReceived = (RequestEvent) evt; - mState = SipSession.State.INCOMING_CALL; - mPeerSessionDescription = extractContent(event.getRequest()); - mServerTransaction = null; - mProxy.onRinging(this, mPeerProfile, mPeerSessionDescription); - return true; - } else if (isRequestEvent(Request.BYE, evt)) { - mSipHelper.sendResponse((RequestEvent) evt, Response.OK); - endCallNormally(); - return true; - } else if (evt instanceof MakeCallCommand) { - // to change call - mClientTransaction = mSipHelper.sendReinvite(mDialog, - ((MakeCallCommand) evt).getSessionDescription()); - mState = SipSession.State.OUTGOING_CALL; - startSessionTimer(((MakeCallCommand) evt).getTimeout()); - return true; - } - return false; - } - - // timeout in seconds - private void startSessionTimer(int timeout) { - if (timeout > 0) { - mTimer = new SessionTimer(); - mTimer.start(timeout); - } - } - - private void cancelSessionTimer() { - if (mTimer != null) { - mTimer.cancel(); - mTimer = null; - } - } - - private String createErrorMessage(Response response) { - return String.format(SERVER_ERROR_PREFIX + "%s (%d)", - response.getReasonPhrase(), response.getStatusCode()); - } - - private void establishCall() { - mState = SipSession.State.IN_CALL; - mInCall = true; - cancelSessionTimer(); - mProxy.onCallEstablished(this, mPeerSessionDescription); - } - - private void fallbackToPreviousInCall(int errorCode, String message) { - mState = SipSession.State.IN_CALL; - mProxy.onCallChangeFailed(this, errorCode, message); - } - - private void endCallNormally() { - reset(); - mProxy.onCallEnded(this); - } - - private void endCallOnError(int errorCode, String message) { - reset(); - mProxy.onError(this, errorCode, message); - } - - private void endCallOnBusy() { - reset(); - mProxy.onCallBusy(this); - } - - private void onError(int errorCode, String message) { - cancelSessionTimer(); - switch (mState) { - case SipSession.State.REGISTERING: - case SipSession.State.DEREGISTERING: - onRegistrationFailed(errorCode, message); - break; - default: - if ((errorCode != SipErrorCode.DATA_CONNECTION_LOST) - && mInCall) { - fallbackToPreviousInCall(errorCode, message); - } else { - endCallOnError(errorCode, message); - } - } - } - - - private void onError(Throwable exception) { - exception = getRootCause(exception); - onError(getErrorCode(exception), exception.toString()); - } - - private void onError(Response response) { - int statusCode = response.getStatusCode(); - if (!mInCall && (statusCode == Response.BUSY_HERE)) { - endCallOnBusy(); - } else { - onError(getErrorCode(statusCode), createErrorMessage(response)); - } - } - - private int getErrorCode(int responseStatusCode) { - switch (responseStatusCode) { - case Response.TEMPORARILY_UNAVAILABLE: - case Response.FORBIDDEN: - case Response.GONE: - case Response.NOT_FOUND: - case Response.NOT_ACCEPTABLE: - case Response.NOT_ACCEPTABLE_HERE: - return SipErrorCode.PEER_NOT_REACHABLE; - - case Response.REQUEST_URI_TOO_LONG: - case Response.ADDRESS_INCOMPLETE: - case Response.AMBIGUOUS: - return SipErrorCode.INVALID_REMOTE_URI; - - case Response.REQUEST_TIMEOUT: - return SipErrorCode.TIME_OUT; - - default: - if (responseStatusCode < 500) { - return SipErrorCode.CLIENT_ERROR; - } else { - return SipErrorCode.SERVER_ERROR; - } - } - } - - private Throwable getRootCause(Throwable exception) { - Throwable cause = exception.getCause(); - while (cause != null) { - exception = cause; - cause = exception.getCause(); - } - return exception; - } - - private int getErrorCode(Throwable exception) { - String message = exception.getMessage(); - if (exception instanceof UnknownHostException) { - return SipErrorCode.INVALID_REMOTE_URI; - } else if (exception instanceof IOException) { - return SipErrorCode.SOCKET_ERROR; - } else if (message.startsWith(SERVER_ERROR_PREFIX)) { - return SipErrorCode.SERVER_ERROR; - } else { - return SipErrorCode.CLIENT_ERROR; - } - } - - private void onRegistrationDone(int duration) { - reset(); - mProxy.onRegistrationDone(this, duration); - } - - private void onRegistrationFailed(int errorCode, String message) { - reset(); - mProxy.onRegistrationFailed(this, errorCode, message); - } - - private void onRegistrationFailed(Throwable exception) { - reset(); - exception = getRootCause(exception); - onRegistrationFailed(getErrorCode(exception), - exception.toString()); - } - - private void onRegistrationFailed(Response response) { - reset(); - int statusCode = response.getStatusCode(); - onRegistrationFailed(getErrorCode(statusCode), - createErrorMessage(response)); - } - } - - /** - * @return true if the event is a request event matching the specified - * method; false otherwise - */ - private static boolean isRequestEvent(String method, EventObject event) { - try { - if (event instanceof RequestEvent) { - RequestEvent requestEvent = (RequestEvent) event; - return method.equals(requestEvent.getRequest().getMethod()); - } - } catch (Throwable e) { - } - return false; - } - - private static String getCseqMethod(Message message) { - return ((CSeqHeader) message.getHeader(CSeqHeader.NAME)).getMethod(); - } - - /** - * @return true if the event is a response event and the CSeqHeader method - * match the given arguments; false otherwise - */ - private static boolean expectResponse( - String expectedMethod, EventObject evt) { - if (evt instanceof ResponseEvent) { - ResponseEvent event = (ResponseEvent) evt; - Response response = event.getResponse(); - return expectedMethod.equalsIgnoreCase(getCseqMethod(response)); - } - return false; - } - - /** - * @return true if the event is a response event and the response code and - * CSeqHeader method match the given arguments; false otherwise - */ - private static boolean expectResponse( - int responseCode, String expectedMethod, EventObject evt) { - if (evt instanceof ResponseEvent) { - ResponseEvent event = (ResponseEvent) evt; - Response response = event.getResponse(); - if (response.getStatusCode() == responseCode) { - return expectedMethod.equalsIgnoreCase(getCseqMethod(response)); - } - } - return false; - } - - private static SipProfile createPeerProfile(Request request) - throws SipException { - try { - FromHeader fromHeader = - (FromHeader) request.getHeader(FromHeader.NAME); - Address address = fromHeader.getAddress(); - SipURI uri = (SipURI) address.getURI(); - String username = uri.getUser(); - if (username == null) username = ANONYMOUS; - return new SipProfile.Builder(username, uri.getHost()) - .setPort(uri.getPort()) - .setDisplayName(address.getDisplayName()) - .build(); - } catch (IllegalArgumentException e) { - throw new SipException("createPeerProfile()", e); - } catch (ParseException e) { - throw new SipException("createPeerProfile()", e); - } - } - - private static boolean isLoggable(SipSessionImpl s) { - if (s != null) { - switch (s.mState) { - case SipSession.State.PINGING: - return DEBUG_PING; - } - } - return DEBUG; - } - - private static boolean isLoggable(SipSessionImpl s, EventObject evt) { - if (!isLoggable(s)) return false; - if (evt == null) return false; - - if (evt instanceof OptionsCommand) { - return DEBUG_PING; - } else if (evt instanceof ResponseEvent) { - Response response = ((ResponseEvent) evt).getResponse(); - if (Request.OPTIONS.equals(response.getHeader(CSeqHeader.NAME))) { - return DEBUG_PING; - } - return DEBUG; - } else if (evt instanceof RequestEvent) { - return DEBUG; - } - return false; - } - - private static String log(EventObject evt) { - if (evt instanceof RequestEvent) { - return ((RequestEvent) evt).getRequest().toString(); - } else if (evt instanceof ResponseEvent) { - return ((ResponseEvent) evt).getResponse().toString(); - } else { - return evt.toString(); - } - } - - private class OptionsCommand extends EventObject { - public OptionsCommand() { - super(SipSessionGroup.this); - } - } - - private class RegisterCommand extends EventObject { - private int mDuration; - - public RegisterCommand(int duration) { - super(SipSessionGroup.this); - mDuration = duration; - } - - public int getDuration() { - return mDuration; - } - } - - private class MakeCallCommand extends EventObject { - private String mSessionDescription; - private int mTimeout; // in seconds - - public MakeCallCommand(SipProfile peerProfile, - String sessionDescription) { - this(peerProfile, sessionDescription, -1); - } - - public MakeCallCommand(SipProfile peerProfile, - String sessionDescription, int timeout) { - super(peerProfile); - mSessionDescription = sessionDescription; - mTimeout = timeout; - } - - public SipProfile getPeerProfile() { - return (SipProfile) getSource(); - } - - public String getSessionDescription() { - return mSessionDescription; - } - - public int getTimeout() { - return mTimeout; - } - } -} diff --git a/services/java/com/android/server/sip/SipSessionListenerProxy.java b/services/java/com/android/server/sip/SipSessionListenerProxy.java deleted file mode 100644 index f8be0a8..0000000 --- a/services/java/com/android/server/sip/SipSessionListenerProxy.java +++ /dev/null @@ -1,217 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.sip; - -import android.net.sip.ISipSession; -import android.net.sip.ISipSessionListener; -import android.net.sip.SipProfile; -import android.os.DeadObjectException; -import android.util.Log; - -/** Class to help safely run a callback in a different thread. */ -class SipSessionListenerProxy extends ISipSessionListener.Stub { - private static final String TAG = "SipSession"; - - private ISipSessionListener mListener; - - public void setListener(ISipSessionListener listener) { - mListener = listener; - } - - public ISipSessionListener getListener() { - return mListener; - } - - private void proxy(Runnable runnable) { - // One thread for each calling back. - // Note: Guarantee ordering if the issue becomes important. Currently, - // the chance of handling two callback events at a time is none. - new Thread(runnable, "SipSessionCallbackThread").start(); - } - - public void onCalling(final ISipSession session) { - if (mListener == null) return; - proxy(new Runnable() { - public void run() { - try { - mListener.onCalling(session); - } catch (Throwable t) { - handle(t, "onCalling()"); - } - } - }); - } - - public void onRinging(final ISipSession session, final SipProfile caller, - final String sessionDescription) { - if (mListener == null) return; - proxy(new Runnable() { - public void run() { - try { - mListener.onRinging(session, caller, sessionDescription); - } catch (Throwable t) { - handle(t, "onRinging()"); - } - } - }); - } - - public void onRingingBack(final ISipSession session) { - if (mListener == null) return; - proxy(new Runnable() { - public void run() { - try { - mListener.onRingingBack(session); - } catch (Throwable t) { - handle(t, "onRingingBack()"); - } - } - }); - } - - public void onCallEstablished(final ISipSession session, - final String sessionDescription) { - if (mListener == null) return; - proxy(new Runnable() { - public void run() { - try { - mListener.onCallEstablished(session, sessionDescription); - } catch (Throwable t) { - handle(t, "onCallEstablished()"); - } - } - }); - } - - public void onCallEnded(final ISipSession session) { - if (mListener == null) return; - proxy(new Runnable() { - public void run() { - try { - mListener.onCallEnded(session); - } catch (Throwable t) { - handle(t, "onCallEnded()"); - } - } - }); - } - - public void onCallBusy(final ISipSession session) { - if (mListener == null) return; - proxy(new Runnable() { - public void run() { - try { - mListener.onCallBusy(session); - } catch (Throwable t) { - handle(t, "onCallBusy()"); - } - } - }); - } - - public void onCallChangeFailed(final ISipSession session, - final int errorCode, final String message) { - if (mListener == null) return; - proxy(new Runnable() { - public void run() { - try { - mListener.onCallChangeFailed(session, errorCode, message); - } catch (Throwable t) { - handle(t, "onCallChangeFailed()"); - } - } - }); - } - - public void onError(final ISipSession session, final int errorCode, - final String message) { - if (mListener == null) return; - proxy(new Runnable() { - public void run() { - try { - mListener.onError(session, errorCode, message); - } catch (Throwable t) { - handle(t, "onError()"); - } - } - }); - } - - public void onRegistering(final ISipSession session) { - if (mListener == null) return; - proxy(new Runnable() { - public void run() { - try { - mListener.onRegistering(session); - } catch (Throwable t) { - handle(t, "onRegistering()"); - } - } - }); - } - - public void onRegistrationDone(final ISipSession session, - final int duration) { - if (mListener == null) return; - proxy(new Runnable() { - public void run() { - try { - mListener.onRegistrationDone(session, duration); - } catch (Throwable t) { - handle(t, "onRegistrationDone()"); - } - } - }); - } - - public void onRegistrationFailed(final ISipSession session, - final int errorCode, final String message) { - if (mListener == null) return; - proxy(new Runnable() { - public void run() { - try { - mListener.onRegistrationFailed(session, errorCode, message); - } catch (Throwable t) { - handle(t, "onRegistrationFailed()"); - } - } - }); - } - - public void onRegistrationTimeout(final ISipSession session) { - if (mListener == null) return; - proxy(new Runnable() { - public void run() { - try { - mListener.onRegistrationTimeout(session); - } catch (Throwable t) { - handle(t, "onRegistrationTimeout()"); - } - } - }); - } - - private void handle(Throwable t, String message) { - if (t instanceof DeadObjectException) { - mListener = null; - // This creates race but it's harmless. Just don't log the error - // when it happens. - } else if (mListener != null) { - Log.w(TAG, message, t); - } - } -} |