1 /* 2 * Copyright (C) 2010, The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.sip; 18 19 import android.app.PendingIntent; 20 import android.content.BroadcastReceiver; 21 import android.content.Context; 22 import android.content.Intent; 23 import android.content.IntentFilter; 24 import android.net.ConnectivityManager; 25 import android.net.NetworkInfo; 26 import android.net.sip.ISipService; 27 import android.net.sip.ISipSession; 28 import android.net.sip.ISipSessionListener; 29 import android.net.sip.SipErrorCode; 30 import android.net.sip.SipManager; 31 import android.net.sip.SipProfile; 32 import android.net.sip.SipSession; 33 import android.net.sip.SipSessionAdapter; 34 import android.net.wifi.WifiManager; 35 import android.os.Binder; 36 import android.os.Bundle; 37 import android.os.Handler; 38 import android.os.HandlerThread; 39 import android.os.Looper; 40 import android.os.Message; 41 import android.os.PowerManager; 42 import android.os.Process; 43 import android.os.RemoteException; 44 import android.os.ServiceManager; 45 import android.os.SystemClock; 46 import android.telephony.Rlog; 47 48 import java.io.IOException; 49 import java.net.DatagramSocket; 50 import java.net.InetAddress; 51 import java.net.UnknownHostException; 52 import java.util.ArrayList; 53 import java.util.HashMap; 54 import java.util.Map; 55 import java.util.concurrent.Executor; 56 import javax.sip.SipException; 57 58 /** 59 * @hide 60 */ 61 public final class SipService extends ISipService.Stub { 62 static final String TAG = "SipService"; 63 static final boolean DBG = true; 64 private static final int EXPIRY_TIME = 3600; 65 private static final int SHORT_EXPIRY_TIME = 10; 66 private static final int MIN_EXPIRY_TIME = 60; 67 private static final int DEFAULT_KEEPALIVE_INTERVAL = 10; // in seconds 68 private static final int DEFAULT_MAX_KEEPALIVE_INTERVAL = 120; // in seconds 69 70 private Context mContext; 71 private String mLocalIp; 72 private int mNetworkType = -1; 73 private SipWakeupTimer mTimer; 74 private WifiManager.WifiLock mWifiLock; 75 private boolean mSipOnWifiOnly; 76 77 private SipKeepAliveProcessCallback mSipKeepAliveProcessCallback; 78 79 private MyExecutor mExecutor = new MyExecutor(); 80 81 // SipProfile URI --> group 82 private Map<String, SipSessionGroupExt> mSipGroups = 83 new HashMap<String, SipSessionGroupExt>(); 84 85 // session ID --> session 86 private Map<String, ISipSession> mPendingSessions = 87 new HashMap<String, ISipSession>(); 88 89 private ConnectivityReceiver mConnectivityReceiver; 90 private SipWakeLock mMyWakeLock; 91 private int mKeepAliveInterval; 92 private int mLastGoodKeepAliveInterval = DEFAULT_KEEPALIVE_INTERVAL; 93 94 /** 95 * Starts the SIP service. Do nothing if the SIP API is not supported on the 96 * device. 97 */ 98 public static void start(Context context) { 99 if (SipManager.isApiSupported(context)) { 100 ServiceManager.addService("sip", new SipService(context)); 101 context.sendBroadcast(new Intent(SipManager.ACTION_SIP_SERVICE_UP)); 102 if (DBG) slog("start:"); 103 } 104 } 105 106 private SipService(Context context) { 107 if (DBG) log("SipService: started!"); 108 mContext = context; 109 mConnectivityReceiver = new ConnectivityReceiver(); 110 111 mWifiLock = ((WifiManager) 112 context.getSystemService(Context.WIFI_SERVICE)) 113 .createWifiLock(WifiManager.WIFI_MODE_FULL, TAG); 114 mWifiLock.setReferenceCounted(false); 115 mSipOnWifiOnly = SipManager.isSipWifiOnly(context); 116 117 mMyWakeLock = new SipWakeLock((PowerManager) 118 context.getSystemService(Context.POWER_SERVICE)); 119 120 mTimer = new SipWakeupTimer(context, mExecutor); 121 } 122 123 @Override 124 public synchronized SipProfile[] getListOfProfiles() { 125 mContext.enforceCallingOrSelfPermission( 126 android.Manifest.permission.USE_SIP, null); 127 boolean isCallerRadio = isCallerRadio(); 128 ArrayList<SipProfile> profiles = new ArrayList<SipProfile>(); 129 for (SipSessionGroupExt group : mSipGroups.values()) { 130 if (isCallerRadio || isCallerCreator(group)) { 131 profiles.add(group.getLocalProfile()); 132 } 133 } 134 return profiles.toArray(new SipProfile[profiles.size()]); 135 } 136 137 @Override 138 public synchronized void open(SipProfile localProfile) { 139 mContext.enforceCallingOrSelfPermission( 140 android.Manifest.permission.USE_SIP, null); 141 localProfile.setCallingUid(Binder.getCallingUid()); 142 try { 143 createGroup(localProfile); 144 } catch (SipException e) { 145 loge("openToMakeCalls()", e); 146 // TODO: how to send the exception back 147 } 148 } 149 150 @Override 151 public synchronized void open3(SipProfile localProfile, 152 PendingIntent incomingCallPendingIntent, 153 ISipSessionListener listener) { 154 mContext.enforceCallingOrSelfPermission( 155 android.Manifest.permission.USE_SIP, null); 156 localProfile.setCallingUid(Binder.getCallingUid()); 157 if (incomingCallPendingIntent == null) { 158 if (DBG) log("open3: incomingCallPendingIntent cannot be null; " 159 + "the profile is not opened"); 160 return; 161 } 162 if (DBG) log("open3: " + localProfile.getUriString() + ": " 163 + incomingCallPendingIntent + ": " + listener); 164 try { 165 SipSessionGroupExt group = createGroup(localProfile, 166 incomingCallPendingIntent, listener); 167 if (localProfile.getAutoRegistration()) { 168 group.openToReceiveCalls(); 169 updateWakeLocks(); 170 } 171 } catch (SipException e) { 172 loge("open3:", e); 173 // TODO: how to send the exception back 174 } 175 } 176 177 private boolean isCallerCreator(SipSessionGroupExt group) { 178 SipProfile profile = group.getLocalProfile(); 179 return (profile.getCallingUid() == Binder.getCallingUid()); 180 } 181 182 private boolean isCallerCreatorOrRadio(SipSessionGroupExt group) { 183 return (isCallerRadio() || isCallerCreator(group)); 184 } 185 186 private boolean isCallerRadio() { 187 return (Binder.getCallingUid() == Process.PHONE_UID); 188 } 189 190 @Override 191 public synchronized void close(String localProfileUri) { 192 mContext.enforceCallingOrSelfPermission( 193 android.Manifest.permission.USE_SIP, null); 194 SipSessionGroupExt group = mSipGroups.get(localProfileUri); 195 if (group == null) return; 196 if (!isCallerCreatorOrRadio(group)) { 197 if (DBG) log("only creator or radio can close this profile"); 198 return; 199 } 200 201 group = mSipGroups.remove(localProfileUri); 202 notifyProfileRemoved(group.getLocalProfile()); 203 group.close(); 204 205 updateWakeLocks(); 206 } 207 208 @Override 209 public synchronized boolean isOpened(String localProfileUri) { 210 mContext.enforceCallingOrSelfPermission( 211 android.Manifest.permission.USE_SIP, null); 212 SipSessionGroupExt group = mSipGroups.get(localProfileUri); 213 if (group == null) return false; 214 if (isCallerCreatorOrRadio(group)) { 215 return true; 216 } else { 217 if (DBG) log("only creator or radio can query on the profile"); 218 return false; 219 } 220 } 221 222 @Override 223 public synchronized boolean isRegistered(String localProfileUri) { 224 mContext.enforceCallingOrSelfPermission( 225 android.Manifest.permission.USE_SIP, null); 226 SipSessionGroupExt group = mSipGroups.get(localProfileUri); 227 if (group == null) return false; 228 if (isCallerCreatorOrRadio(group)) { 229 return group.isRegistered(); 230 } else { 231 if (DBG) log("only creator or radio can query on the profile"); 232 return false; 233 } 234 } 235 236 @Override 237 public synchronized void setRegistrationListener(String localProfileUri, 238 ISipSessionListener listener) { 239 mContext.enforceCallingOrSelfPermission( 240 android.Manifest.permission.USE_SIP, null); 241 SipSessionGroupExt group = mSipGroups.get(localProfileUri); 242 if (group == null) return; 243 if (isCallerCreator(group)) { 244 group.setListener(listener); 245 } else { 246 if (DBG) log("only creator can set listener on the profile"); 247 } 248 } 249 250 @Override 251 public synchronized ISipSession createSession(SipProfile localProfile, 252 ISipSessionListener listener) { 253 if (DBG) log("createSession: profile" + localProfile); 254 mContext.enforceCallingOrSelfPermission( 255 android.Manifest.permission.USE_SIP, null); 256 localProfile.setCallingUid(Binder.getCallingUid()); 257 if (mNetworkType == -1) { 258 if (DBG) log("createSession: mNetworkType==-1 ret=null"); 259 return null; 260 } 261 try { 262 SipSessionGroupExt group = createGroup(localProfile); 263 return group.createSession(listener); 264 } catch (SipException e) { 265 if (DBG) loge("createSession;", e); 266 return null; 267 } 268 } 269 270 @Override 271 public synchronized ISipSession getPendingSession(String callId) { 272 mContext.enforceCallingOrSelfPermission( 273 android.Manifest.permission.USE_SIP, null); 274 if (callId == null) return null; 275 return mPendingSessions.get(callId); 276 } 277 278 private String determineLocalIp() { 279 try { 280 DatagramSocket s = new DatagramSocket(); 281 s.connect(InetAddress.getByName("192.168.1.1"), 80); 282 return s.getLocalAddress().getHostAddress(); 283 } catch (IOException e) { 284 if (DBG) loge("determineLocalIp()", e); 285 // dont do anything; there should be a connectivity change going 286 return null; 287 } 288 } 289 290 private SipSessionGroupExt createGroup(SipProfile localProfile) 291 throws SipException { 292 String key = localProfile.getUriString(); 293 SipSessionGroupExt group = mSipGroups.get(key); 294 if (group == null) { 295 group = new SipSessionGroupExt(localProfile, null, null); 296 mSipGroups.put(key, group); 297 notifyProfileAdded(localProfile); 298 } else if (!isCallerCreator(group)) { 299 throw new SipException("only creator can access the profile"); 300 } 301 return group; 302 } 303 304 private SipSessionGroupExt createGroup(SipProfile localProfile, 305 PendingIntent incomingCallPendingIntent, 306 ISipSessionListener listener) throws SipException { 307 String key = localProfile.getUriString(); 308 SipSessionGroupExt group = mSipGroups.get(key); 309 if (group != null) { 310 if (!isCallerCreator(group)) { 311 throw new SipException("only creator can access the profile"); 312 } 313 group.setIncomingCallPendingIntent(incomingCallPendingIntent); 314 group.setListener(listener); 315 } else { 316 group = new SipSessionGroupExt(localProfile, 317 incomingCallPendingIntent, listener); 318 mSipGroups.put(key, group); 319 notifyProfileAdded(localProfile); 320 } 321 return group; 322 } 323 324 private void notifyProfileAdded(SipProfile localProfile) { 325 if (DBG) log("notify: profile added: " + localProfile); 326 Intent intent = new Intent(SipManager.ACTION_SIP_ADD_PHONE); 327 intent.putExtra(SipManager.EXTRA_LOCAL_URI, localProfile.getUriString()); 328 mContext.sendBroadcast(intent); 329 if (mSipGroups.size() == 1) { 330 registerReceivers(); 331 } 332 } 333 334 private void notifyProfileRemoved(SipProfile localProfile) { 335 if (DBG) log("notify: profile removed: " + localProfile); 336 Intent intent = new Intent(SipManager.ACTION_SIP_REMOVE_PHONE); 337 intent.putExtra(SipManager.EXTRA_LOCAL_URI, localProfile.getUriString()); 338 mContext.sendBroadcast(intent); 339 if (mSipGroups.size() == 0) { 340 unregisterReceivers(); 341 } 342 } 343 344 private void stopPortMappingMeasurement() { 345 if (mSipKeepAliveProcessCallback != null) { 346 mSipKeepAliveProcessCallback.stop(); 347 mSipKeepAliveProcessCallback = null; 348 } 349 } 350 351 private void startPortMappingLifetimeMeasurement( 352 SipProfile localProfile) { 353 startPortMappingLifetimeMeasurement(localProfile, 354 DEFAULT_MAX_KEEPALIVE_INTERVAL); 355 } 356 357 private void startPortMappingLifetimeMeasurement( 358 SipProfile localProfile, int maxInterval) { 359 if ((mSipKeepAliveProcessCallback == null) 360 && (mKeepAliveInterval == -1) 361 && isBehindNAT(mLocalIp)) { 362 if (DBG) log("startPortMappingLifetimeMeasurement: profile=" 363 + localProfile.getUriString()); 364 365 int minInterval = mLastGoodKeepAliveInterval; 366 if (minInterval >= maxInterval) { 367 // If mLastGoodKeepAliveInterval also does not work, reset it 368 // to the default min 369 minInterval = mLastGoodKeepAliveInterval 370 = DEFAULT_KEEPALIVE_INTERVAL; 371 log(" reset min interval to " + minInterval); 372 } 373 mSipKeepAliveProcessCallback = new SipKeepAliveProcessCallback( 374 localProfile, minInterval, maxInterval); 375 mSipKeepAliveProcessCallback.start(); 376 } 377 } 378 379 private void restartPortMappingLifetimeMeasurement( 380 SipProfile localProfile, int maxInterval) { 381 stopPortMappingMeasurement(); 382 mKeepAliveInterval = -1; 383 startPortMappingLifetimeMeasurement(localProfile, maxInterval); 384 } 385 386 private synchronized void addPendingSession(ISipSession session) { 387 try { 388 cleanUpPendingSessions(); 389 mPendingSessions.put(session.getCallId(), session); 390 if (DBG) log("#pending sess=" + mPendingSessions.size()); 391 } catch (RemoteException e) { 392 // should not happen with a local call 393 loge("addPendingSession()", e); 394 } 395 } 396 397 private void cleanUpPendingSessions() throws RemoteException { 398 Map.Entry<String, ISipSession>[] entries = 399 mPendingSessions.entrySet().toArray( 400 new Map.Entry[mPendingSessions.size()]); 401 for (Map.Entry<String, ISipSession> entry : entries) { 402 if (entry.getValue().getState() != SipSession.State.INCOMING_CALL) { 403 mPendingSessions.remove(entry.getKey()); 404 } 405 } 406 } 407 408 private synchronized boolean callingSelf(SipSessionGroupExt ringingGroup, 409 SipSessionGroup.SipSessionImpl ringingSession) { 410 String callId = ringingSession.getCallId(); 411 for (SipSessionGroupExt group : mSipGroups.values()) { 412 if ((group != ringingGroup) && group.containsSession(callId)) { 413 if (DBG) log("call self: " 414 + ringingSession.getLocalProfile().getUriString() 415 + " -> " + group.getLocalProfile().getUriString()); 416 return true; 417 } 418 } 419 return false; 420 } 421 422 private synchronized void onKeepAliveIntervalChanged() { 423 for (SipSessionGroupExt group : mSipGroups.values()) { 424 group.onKeepAliveIntervalChanged(); 425 } 426 } 427 428 private int getKeepAliveInterval() { 429 return (mKeepAliveInterval < 0) 430 ? mLastGoodKeepAliveInterval 431 : mKeepAliveInterval; 432 } 433 434 private boolean isBehindNAT(String address) { 435 try { 436 // TODO: How is isBehindNAT used and why these constanst address: 437 // 10.x.x.x | 192.168.x.x | 172.16.x.x .. 172.19.x.x 438 byte[] d = InetAddress.getByName(address).getAddress(); 439 if ((d[0] == 10) || 440 (((0x000000FF & d[0]) == 172) && 441 ((0x000000F0 & d[1]) == 16)) || 442 (((0x000000FF & d[0]) == 192) && 443 ((0x000000FF & d[1]) == 168))) { 444 return true; 445 } 446 } catch (UnknownHostException e) { 447 loge("isBehindAT()" + address, e); 448 } 449 return false; 450 } 451 452 private class SipSessionGroupExt extends SipSessionAdapter { 453 private static final String SSGE_TAG = "SipSessionGroupExt"; 454 private static final boolean SSGE_DBG = true; 455 private SipSessionGroup mSipGroup; 456 private PendingIntent mIncomingCallPendingIntent; 457 private boolean mOpenedToReceiveCalls; 458 459 private SipAutoReg mAutoRegistration = 460 new SipAutoReg(); 461 462 public SipSessionGroupExt(SipProfile localProfile, 463 PendingIntent incomingCallPendingIntent, 464 ISipSessionListener listener) throws SipException { 465 if (SSGE_DBG) log("SipSessionGroupExt: profile=" + localProfile); 466 mSipGroup = new SipSessionGroup(duplicate(localProfile), 467 localProfile.getPassword(), mTimer, mMyWakeLock); 468 mIncomingCallPendingIntent = incomingCallPendingIntent; 469 mAutoRegistration.setListener(listener); 470 } 471 472 public SipProfile getLocalProfile() { 473 return mSipGroup.getLocalProfile(); 474 } 475 476 public boolean containsSession(String callId) { 477 return mSipGroup.containsSession(callId); 478 } 479 480 public void onKeepAliveIntervalChanged() { 481 mAutoRegistration.onKeepAliveIntervalChanged(); 482 } 483 484 // TODO: remove this method once SipWakeupTimer can better handle variety 485 // of timeout values 486 void setWakeupTimer(SipWakeupTimer timer) { 487 mSipGroup.setWakeupTimer(timer); 488 } 489 490 private SipProfile duplicate(SipProfile p) { 491 try { 492 return new SipProfile.Builder(p).setPassword("*").build(); 493 } catch (Exception e) { 494 loge("duplicate()", e); 495 throw new RuntimeException("duplicate profile", e); 496 } 497 } 498 499 public void setListener(ISipSessionListener listener) { 500 mAutoRegistration.setListener(listener); 501 } 502 503 public void setIncomingCallPendingIntent(PendingIntent pIntent) { 504 mIncomingCallPendingIntent = pIntent; 505 } 506 507 public void openToReceiveCalls() throws SipException { 508 mOpenedToReceiveCalls = true; 509 if (mNetworkType != -1) { 510 mSipGroup.openToReceiveCalls(this); 511 mAutoRegistration.start(mSipGroup); 512 } 513 if (SSGE_DBG) log("openToReceiveCalls: " + getUri() + ": " 514 + mIncomingCallPendingIntent); 515 } 516 517 public void onConnectivityChanged(boolean connected) 518 throws SipException { 519 if (SSGE_DBG) { 520 log("onConnectivityChanged: connected=" + connected + " uri=" 521 + getUri() + ": " + mIncomingCallPendingIntent); 522 } 523 mSipGroup.onConnectivityChanged(); 524 if (connected) { 525 mSipGroup.reset(); 526 if (mOpenedToReceiveCalls) openToReceiveCalls(); 527 } else { 528 mSipGroup.close(); 529 mAutoRegistration.stop(); 530 } 531 } 532 533 public void close() { 534 mOpenedToReceiveCalls = false; 535 mSipGroup.close(); 536 mAutoRegistration.stop(); 537 if (SSGE_DBG) log("close: " + getUri() + ": " + mIncomingCallPendingIntent); 538 } 539 540 public ISipSession createSession(ISipSessionListener listener) { 541 if (SSGE_DBG) log("createSession"); 542 return mSipGroup.createSession(listener); 543 } 544 545 @Override 546 public void onRinging(ISipSession s, SipProfile caller, 547 String sessionDescription) { 548 SipSessionGroup.SipSessionImpl session = 549 (SipSessionGroup.SipSessionImpl) s; 550 synchronized (SipService.this) { 551 try { 552 if (!isRegistered() || callingSelf(this, session)) { 553 if (SSGE_DBG) log("onRinging: end notReg or self"); 554 session.endCall(); 555 return; 556 } 557 558 // send out incoming call broadcast 559 addPendingSession(session); 560 Intent intent = SipManager.createIncomingCallBroadcast( 561 session.getCallId(), sessionDescription); 562 if (SSGE_DBG) log("onRinging: uri=" + getUri() + ": " 563 + caller.getUri() + ": " + session.getCallId() 564 + " " + mIncomingCallPendingIntent); 565 mIncomingCallPendingIntent.send(mContext, 566 SipManager.INCOMING_CALL_RESULT_CODE, intent); 567 } catch (PendingIntent.CanceledException e) { 568 loge("onRinging: pendingIntent is canceled, drop incoming call", e); 569 session.endCall(); 570 } 571 } 572 } 573 574 @Override 575 public void onError(ISipSession session, int errorCode, 576 String message) { 577 if (SSGE_DBG) log("onError: errorCode=" + errorCode + " desc=" 578 + SipErrorCode.toString(errorCode) + ": " + message); 579 } 580 581 public boolean isOpenedToReceiveCalls() { 582 return mOpenedToReceiveCalls; 583 } 584 585 public boolean isRegistered() { 586 return mAutoRegistration.isRegistered(); 587 } 588 589 private String getUri() { 590 return mSipGroup.getLocalProfileUri(); 591 } 592 593 private void log(String s) { 594 Rlog.d(SSGE_TAG, s); 595 } 596 597 private void loge(String s, Throwable t) { 598 Rlog.e(SSGE_TAG, s, t); 599 } 600 601 } 602 603 private class SipKeepAliveProcessCallback implements Runnable, 604 SipSessionGroup.KeepAliveProcessCallback { 605 private static final String SKAI_TAG = "SipKeepAliveProcessCallback"; 606 private static final boolean SKAI_DBG = true; 607 private static final int MIN_INTERVAL = 5; // in seconds 608 private static final int PASS_THRESHOLD = 10; 609 private static final int NAT_MEASUREMENT_RETRY_INTERVAL = 120; // in seconds 610 private SipProfile mLocalProfile; 611 private SipSessionGroupExt mGroup; 612 private SipSessionGroup.SipSessionImpl mSession; 613 private int mMinInterval; 614 private int mMaxInterval; 615 private int mInterval; 616 private int mPassCount; 617 618 public SipKeepAliveProcessCallback(SipProfile localProfile, 619 int minInterval, int maxInterval) { 620 mMaxInterval = maxInterval; 621 mMinInterval = minInterval; 622 mLocalProfile = localProfile; 623 } 624 625 public void start() { 626 synchronized (SipService.this) { 627 if (mSession != null) { 628 return; 629 } 630 631 mInterval = (mMaxInterval + mMinInterval) / 2; 632 mPassCount = 0; 633 634 // Don't start measurement if the interval is too small 635 if (mInterval < DEFAULT_KEEPALIVE_INTERVAL || checkTermination()) { 636 if (SKAI_DBG) log("start: measurement aborted; interval=[" + 637 mMinInterval + "," + mMaxInterval + "]"); 638 return; 639 } 640 641 try { 642 if (SKAI_DBG) log("start: interval=" + mInterval); 643 644 mGroup = new SipSessionGroupExt(mLocalProfile, null, null); 645 // TODO: remove this line once SipWakeupTimer can better handle 646 // variety of timeout values 647 mGroup.setWakeupTimer(new SipWakeupTimer(mContext, mExecutor)); 648 649 mSession = (SipSessionGroup.SipSessionImpl) 650 mGroup.createSession(null); 651 mSession.startKeepAliveProcess(mInterval, this); 652 } catch (Throwable t) { 653 onError(SipErrorCode.CLIENT_ERROR, t.toString()); 654 } 655 } 656 } 657 658 public void stop() { 659 synchronized (SipService.this) { 660 if (mSession != null) { 661 mSession.stopKeepAliveProcess(); 662 mSession = null; 663 } 664 if (mGroup != null) { 665 mGroup.close(); 666 mGroup = null; 667 } 668 mTimer.cancel(this); 669 if (SKAI_DBG) log("stop"); 670 } 671 } 672 673 private void restart() { 674 synchronized (SipService.this) { 675 // Return immediately if the measurement process is stopped 676 if (mSession == null) return; 677 678 if (SKAI_DBG) log("restart: interval=" + mInterval); 679 try { 680 mSession.stopKeepAliveProcess(); 681 mPassCount = 0; 682 mSession.startKeepAliveProcess(mInterval, this); 683 } catch (SipException e) { 684 loge("restart", e); 685 } 686 } 687 } 688 689 private boolean checkTermination() { 690 return ((mMaxInterval - mMinInterval) < MIN_INTERVAL); 691 } 692 693 // SipSessionGroup.KeepAliveProcessCallback 694 @Override 695 public void onResponse(boolean portChanged) { 696 synchronized (SipService.this) { 697 if (!portChanged) { 698 if (++mPassCount != PASS_THRESHOLD) return; 699 // update the interval, since the current interval is good to 700 // keep the port mapping. 701 if (mKeepAliveInterval > 0) { 702 mLastGoodKeepAliveInterval = mKeepAliveInterval; 703 } 704 mKeepAliveInterval = mMinInterval = mInterval; 705 if (SKAI_DBG) { 706 log("onResponse: portChanged=" + portChanged + " mKeepAliveInterval=" 707 + mKeepAliveInterval); 708 } 709 onKeepAliveIntervalChanged(); 710 } else { 711 // Since the rport is changed, shorten the interval. 712 mMaxInterval = mInterval; 713 } 714 if (checkTermination()) { 715 // update mKeepAliveInterval and stop measurement. 716 stop(); 717 // If all the measurements failed, we still set it to 718 // mMinInterval; If mMinInterval still doesn't work, a new 719 // measurement with min interval=DEFAULT_KEEPALIVE_INTERVAL 720 // will be conducted. 721 mKeepAliveInterval = mMinInterval; 722 if (SKAI_DBG) { 723 log("onResponse: checkTermination mKeepAliveInterval=" 724 + mKeepAliveInterval); 725 } 726 } else { 727 // calculate the new interval and continue. 728 mInterval = (mMaxInterval + mMinInterval) / 2; 729 if (SKAI_DBG) { 730 log("onResponse: mKeepAliveInterval=" + mKeepAliveInterval 731 + ", new mInterval=" + mInterval); 732 } 733 restart(); 734 } 735 } 736 } 737 738 // SipSessionGroup.KeepAliveProcessCallback 739 @Override 740 public void onError(int errorCode, String description) { 741 if (SKAI_DBG) loge("onError: errorCode=" + errorCode + " desc=" + description); 742 restartLater(); 743 } 744 745 // timeout handler 746 @Override 747 public void run() { 748 mTimer.cancel(this); 749 restart(); 750 } 751 752 private void restartLater() { 753 synchronized (SipService.this) { 754 int interval = NAT_MEASUREMENT_RETRY_INTERVAL; 755 mTimer.cancel(this); 756 mTimer.set(interval * 1000, this); 757 } 758 } 759 760 private void log(String s) { 761 Rlog.d(SKAI_TAG, s); 762 } 763 764 private void loge(String s) { 765 Rlog.d(SKAI_TAG, s); 766 } 767 768 private void loge(String s, Throwable t) { 769 Rlog.d(SKAI_TAG, s, t); 770 } 771 } 772 773 private class SipAutoReg extends SipSessionAdapter 774 implements Runnable, SipSessionGroup.KeepAliveProcessCallback { 775 private String SAR_TAG; 776 private static final boolean SAR_DBG = true; 777 private static final int MIN_KEEPALIVE_SUCCESS_COUNT = 10; 778 779 private SipSessionGroup.SipSessionImpl mSession; 780 private SipSessionGroup.SipSessionImpl mKeepAliveSession; 781 private SipSessionListenerProxy mProxy = new SipSessionListenerProxy(); 782 private int mBackoff = 1; 783 private boolean mRegistered; 784 private long mExpiryTime; 785 private int mErrorCode; 786 private String mErrorMessage; 787 private boolean mRunning = false; 788 789 private int mKeepAliveSuccessCount = 0; 790 791 public void start(SipSessionGroup group) { 792 if (!mRunning) { 793 mRunning = true; 794 mBackoff = 1; 795 mSession = (SipSessionGroup.SipSessionImpl) 796 group.createSession(this); 797 // return right away if no active network connection. 798 if (mSession == null) return; 799 800 // start unregistration to clear up old registration at server 801 // TODO: when rfc5626 is deployed, use reg-id and sip.instance 802 // in registration to avoid adding duplicate entries to server 803 mMyWakeLock.acquire(mSession); 804 mSession.unregister(); 805 SAR_TAG = "SipAutoReg:" + mSession.getLocalProfile().getUriString(); 806 if (SAR_DBG) log("start: group=" + group); 807 } 808 } 809 810 private void startKeepAliveProcess(int interval) { 811 if (SAR_DBG) log("startKeepAliveProcess: interval=" + interval); 812 if (mKeepAliveSession == null) { 813 mKeepAliveSession = mSession.duplicate(); 814 } else { 815 mKeepAliveSession.stopKeepAliveProcess(); 816 } 817 try { 818 mKeepAliveSession.startKeepAliveProcess(interval, this); 819 } catch (SipException e) { 820 loge("startKeepAliveProcess: interval=" + interval, e); 821 } 822 } 823 824 private void stopKeepAliveProcess() { 825 if (mKeepAliveSession != null) { 826 mKeepAliveSession.stopKeepAliveProcess(); 827 mKeepAliveSession = null; 828 } 829 mKeepAliveSuccessCount = 0; 830 } 831 832 // SipSessionGroup.KeepAliveProcessCallback 833 @Override 834 public void onResponse(boolean portChanged) { 835 synchronized (SipService.this) { 836 if (portChanged) { 837 int interval = getKeepAliveInterval(); 838 if (mKeepAliveSuccessCount < MIN_KEEPALIVE_SUCCESS_COUNT) { 839 if (SAR_DBG) { 840 log("onResponse: keepalive doesn't work with interval " 841 + interval + ", past success count=" 842 + mKeepAliveSuccessCount); 843 } 844 if (interval > DEFAULT_KEEPALIVE_INTERVAL) { 845 restartPortMappingLifetimeMeasurement( 846 mSession.getLocalProfile(), interval); 847 mKeepAliveSuccessCount = 0; 848 } 849 } else { 850 if (SAR_DBG) { 851 log("keep keepalive going with interval " 852 + interval + ", past success count=" 853 + mKeepAliveSuccessCount); 854 } 855 mKeepAliveSuccessCount /= 2; 856 } 857 } else { 858 // Start keep-alive interval measurement on the first 859 // successfully kept-alive SipSessionGroup 860 startPortMappingLifetimeMeasurement( 861 mSession.getLocalProfile()); 862 mKeepAliveSuccessCount++; 863 } 864 865 if (!mRunning || !portChanged) return; 866 867 // The keep alive process is stopped when port is changed; 868 // Nullify the session so that the process can be restarted 869 // again when the re-registration is done 870 mKeepAliveSession = null; 871 872 // Acquire wake lock for the registration process. The 873 // lock will be released when registration is complete. 874 mMyWakeLock.acquire(mSession); 875 mSession.register(EXPIRY_TIME); 876 } 877 } 878 879 // SipSessionGroup.KeepAliveProcessCallback 880 @Override 881 public void onError(int errorCode, String description) { 882 if (SAR_DBG) { 883 loge("onError: errorCode=" + errorCode + " desc=" + description); 884 } 885 onResponse(true); // re-register immediately 886 } 887 888 public void stop() { 889 if (!mRunning) return; 890 mRunning = false; 891 mMyWakeLock.release(mSession); 892 if (mSession != null) { 893 mSession.setListener(null); 894 if (mNetworkType != -1 && mRegistered) mSession.unregister(); 895 } 896 897 mTimer.cancel(this); 898 stopKeepAliveProcess(); 899 900 mRegistered = false; 901 setListener(mProxy.getListener()); 902 } 903 904 public void onKeepAliveIntervalChanged() { 905 if (mKeepAliveSession != null) { 906 int newInterval = getKeepAliveInterval(); 907 if (SAR_DBG) { 908 log("onKeepAliveIntervalChanged: interval=" + newInterval); 909 } 910 mKeepAliveSuccessCount = 0; 911 startKeepAliveProcess(newInterval); 912 } 913 } 914 915 public void setListener(ISipSessionListener listener) { 916 synchronized (SipService.this) { 917 mProxy.setListener(listener); 918 919 try { 920 int state = (mSession == null) 921 ? SipSession.State.READY_TO_CALL 922 : mSession.getState(); 923 if ((state == SipSession.State.REGISTERING) 924 || (state == SipSession.State.DEREGISTERING)) { 925 mProxy.onRegistering(mSession); 926 } else if (mRegistered) { 927 int duration = (int) 928 (mExpiryTime - SystemClock.elapsedRealtime()); 929 mProxy.onRegistrationDone(mSession, duration); 930 } else if (mErrorCode != SipErrorCode.NO_ERROR) { 931 if (mErrorCode == SipErrorCode.TIME_OUT) { 932 mProxy.onRegistrationTimeout(mSession); 933 } else { 934 mProxy.onRegistrationFailed(mSession, mErrorCode, 935 mErrorMessage); 936 } 937 } else if (mNetworkType == -1) { 938 mProxy.onRegistrationFailed(mSession, 939 SipErrorCode.DATA_CONNECTION_LOST, 940 "no data connection"); 941 } else if (!mRunning) { 942 mProxy.onRegistrationFailed(mSession, 943 SipErrorCode.CLIENT_ERROR, 944 "registration not running"); 945 } else { 946 mProxy.onRegistrationFailed(mSession, 947 SipErrorCode.IN_PROGRESS, 948 String.valueOf(state)); 949 } 950 } catch (Throwable t) { 951 loge("setListener: ", t); 952 } 953 } 954 } 955 956 public boolean isRegistered() { 957 return mRegistered; 958 } 959 960 // timeout handler: re-register 961 @Override 962 public void run() { 963 synchronized (SipService.this) { 964 if (!mRunning) return; 965 966 mErrorCode = SipErrorCode.NO_ERROR; 967 mErrorMessage = null; 968 if (SAR_DBG) log("run: registering"); 969 if (mNetworkType != -1) { 970 mMyWakeLock.acquire(mSession); 971 mSession.register(EXPIRY_TIME); 972 } 973 } 974 } 975 976 private void restart(int duration) { 977 if (SAR_DBG) log("restart: duration=" + duration + "s later."); 978 mTimer.cancel(this); 979 mTimer.set(duration * 1000, this); 980 } 981 982 private int backoffDuration() { 983 int duration = SHORT_EXPIRY_TIME * mBackoff; 984 if (duration > 3600) { 985 duration = 3600; 986 } else { 987 mBackoff *= 2; 988 } 989 return duration; 990 } 991 992 @Override 993 public void onRegistering(ISipSession session) { 994 if (SAR_DBG) log("onRegistering: " + session); 995 synchronized (SipService.this) { 996 if (notCurrentSession(session)) return; 997 998 mRegistered = false; 999 mProxy.onRegistering(session); 1000 } 1001 } 1002 1003 private boolean notCurrentSession(ISipSession session) { 1004 if (session != mSession) { 1005 ((SipSessionGroup.SipSessionImpl) session).setListener(null); 1006 mMyWakeLock.release(session); 1007 return true; 1008 } 1009 return !mRunning; 1010 } 1011 1012 @Override 1013 public void onRegistrationDone(ISipSession session, int duration) { 1014 if (SAR_DBG) log("onRegistrationDone: " + session); 1015 synchronized (SipService.this) { 1016 if (notCurrentSession(session)) return; 1017 1018 mProxy.onRegistrationDone(session, duration); 1019 1020 if (duration > 0) { 1021 mExpiryTime = SystemClock.elapsedRealtime() 1022 + (duration * 1000); 1023 1024 if (!mRegistered) { 1025 mRegistered = true; 1026 // allow some overlap to avoid call drop during renew 1027 duration -= MIN_EXPIRY_TIME; 1028 if (duration < MIN_EXPIRY_TIME) { 1029 duration = MIN_EXPIRY_TIME; 1030 } 1031 restart(duration); 1032 1033 SipProfile localProfile = mSession.getLocalProfile(); 1034 if ((mKeepAliveSession == null) && (isBehindNAT(mLocalIp) 1035 || localProfile.getSendKeepAlive())) { 1036 startKeepAliveProcess(getKeepAliveInterval()); 1037 } 1038 } 1039 mMyWakeLock.release(session); 1040 } else { 1041 mRegistered = false; 1042 mExpiryTime = -1L; 1043 if (SAR_DBG) log("Refresh registration immediately"); 1044 run(); 1045 } 1046 } 1047 } 1048 1049 @Override 1050 public void onRegistrationFailed(ISipSession session, int errorCode, 1051 String message) { 1052 if (SAR_DBG) log("onRegistrationFailed: " + session + ": " 1053 + SipErrorCode.toString(errorCode) + ": " + message); 1054 synchronized (SipService.this) { 1055 if (notCurrentSession(session)) return; 1056 1057 switch (errorCode) { 1058 case SipErrorCode.INVALID_CREDENTIALS: 1059 case SipErrorCode.SERVER_UNREACHABLE: 1060 if (SAR_DBG) log(" pause auto-registration"); 1061 stop(); 1062 break; 1063 default: 1064 restartLater(); 1065 } 1066 1067 mErrorCode = errorCode; 1068 mErrorMessage = message; 1069 mProxy.onRegistrationFailed(session, errorCode, message); 1070 mMyWakeLock.release(session); 1071 } 1072 } 1073 1074 @Override 1075 public void onRegistrationTimeout(ISipSession session) { 1076 if (SAR_DBG) log("onRegistrationTimeout: " + session); 1077 synchronized (SipService.this) { 1078 if (notCurrentSession(session)) return; 1079 1080 mErrorCode = SipErrorCode.TIME_OUT; 1081 mProxy.onRegistrationTimeout(session); 1082 restartLater(); 1083 mMyWakeLock.release(session); 1084 } 1085 } 1086 1087 private void restartLater() { 1088 if (SAR_DBG) loge("restartLater"); 1089 mRegistered = false; 1090 restart(backoffDuration()); 1091 } 1092 1093 private void log(String s) { 1094 Rlog.d(SAR_TAG, s); 1095 } 1096 1097 private void loge(String s) { 1098 Rlog.e(SAR_TAG, s); 1099 } 1100 1101 private void loge(String s, Throwable e) { 1102 Rlog.e(SAR_TAG, s, e); 1103 } 1104 } 1105 1106 private class ConnectivityReceiver extends BroadcastReceiver { 1107 @Override 1108 public void onReceive(Context context, Intent intent) { 1109 Bundle bundle = intent.getExtras(); 1110 if (bundle != null) { 1111 final NetworkInfo info = (NetworkInfo) 1112 bundle.get(ConnectivityManager.EXTRA_NETWORK_INFO); 1113 1114 // Run the handler in MyExecutor to be protected by wake lock 1115 mExecutor.execute(new Runnable() { 1116 @Override 1117 public void run() { 1118 onConnectivityChanged(info); 1119 } 1120 }); 1121 } 1122 } 1123 } 1124 1125 private void registerReceivers() { 1126 mContext.registerReceiver(mConnectivityReceiver, 1127 new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)); 1128 if (DBG) log("registerReceivers:"); 1129 } 1130 1131 private void unregisterReceivers() { 1132 mContext.unregisterReceiver(mConnectivityReceiver); 1133 if (DBG) log("unregisterReceivers:"); 1134 1135 // Reset variables maintained by ConnectivityReceiver. 1136 mWifiLock.release(); 1137 mNetworkType = -1; 1138 } 1139 1140 private void updateWakeLocks() { 1141 for (SipSessionGroupExt group : mSipGroups.values()) { 1142 if (group.isOpenedToReceiveCalls()) { 1143 // Also grab the WifiLock when we are disconnected, so the 1144 // system will keep trying to reconnect. It will be released 1145 // when the system eventually connects to something else. 1146 if (mNetworkType == ConnectivityManager.TYPE_WIFI || mNetworkType == -1) { 1147 mWifiLock.acquire(); 1148 } else { 1149 mWifiLock.release(); 1150 } 1151 return; 1152 } 1153 } 1154 mWifiLock.release(); 1155 mMyWakeLock.reset(); // in case there's a leak 1156 } 1157 1158 private synchronized void onConnectivityChanged(NetworkInfo info) { 1159 // We only care about the default network, and getActiveNetworkInfo() 1160 // is the only way to distinguish them. However, as broadcasts are 1161 // delivered asynchronously, we might miss DISCONNECTED events from 1162 // getActiveNetworkInfo(), which is critical to our SIP stack. To 1163 // solve this, if it is a DISCONNECTED event to our current network, 1164 // respect it. Otherwise get a new one from getActiveNetworkInfo(). 1165 if (info == null || info.isConnected() || info.getType() != mNetworkType) { 1166 ConnectivityManager cm = (ConnectivityManager) 1167 mContext.getSystemService(Context.CONNECTIVITY_SERVICE); 1168 info = cm.getActiveNetworkInfo(); 1169 } 1170 1171 // Some devices limit SIP on Wi-Fi. In this case, if we are not on 1172 // Wi-Fi, treat it as a DISCONNECTED event. 1173 int networkType = (info != null && info.isConnected()) ? info.getType() : -1; 1174 if (mSipOnWifiOnly && networkType != ConnectivityManager.TYPE_WIFI) { 1175 networkType = -1; 1176 } 1177 1178 // Ignore the event if the current active network is not changed. 1179 if (mNetworkType == networkType) { 1180 // TODO: Maybe we need to send seq/generation number 1181 return; 1182 } 1183 if (DBG) { 1184 log("onConnectivityChanged: " + mNetworkType + 1185 " -> " + networkType); 1186 } 1187 1188 try { 1189 if (mNetworkType != -1) { 1190 mLocalIp = null; 1191 stopPortMappingMeasurement(); 1192 for (SipSessionGroupExt group : mSipGroups.values()) { 1193 group.onConnectivityChanged(false); 1194 } 1195 } 1196 mNetworkType = networkType; 1197 1198 if (mNetworkType != -1) { 1199 mLocalIp = determineLocalIp(); 1200 mKeepAliveInterval = -1; 1201 mLastGoodKeepAliveInterval = DEFAULT_KEEPALIVE_INTERVAL; 1202 for (SipSessionGroupExt group : mSipGroups.values()) { 1203 group.onConnectivityChanged(true); 1204 } 1205 } 1206 updateWakeLocks(); 1207 } catch (SipException e) { 1208 loge("onConnectivityChanged()", e); 1209 } 1210 } 1211 1212 private static Looper createLooper() { 1213 HandlerThread thread = new HandlerThread("SipService.Executor"); 1214 thread.start(); 1215 return thread.getLooper(); 1216 } 1217 1218 // Executes immediate tasks in a single thread. 1219 // Hold/release wake lock for running tasks 1220 private class MyExecutor extends Handler implements Executor { 1221 MyExecutor() { 1222 super(createLooper()); 1223 } 1224 1225 @Override 1226 public void execute(Runnable task) { 1227 mMyWakeLock.acquire(task); 1228 Message.obtain(this, 0/* don't care */, task).sendToTarget(); 1229 } 1230 1231 @Override 1232 public void handleMessage(Message msg) { 1233 if (msg.obj instanceof Runnable) { 1234 executeInternal((Runnable) msg.obj); 1235 } else { 1236 if (DBG) log("handleMessage: not Runnable ignore msg=" + msg); 1237 } 1238 } 1239 1240 private void executeInternal(Runnable task) { 1241 try { 1242 task.run(); 1243 } catch (Throwable t) { 1244 loge("run task: " + task, t); 1245 } finally { 1246 mMyWakeLock.release(task); 1247 } 1248 } 1249 } 1250 1251 private void log(String s) { 1252 Rlog.d(TAG, s); 1253 } 1254 1255 private static void slog(String s) { 1256 Rlog.d(TAG, s); 1257 } 1258 1259 private void loge(String s, Throwable e) { 1260 Rlog.e(TAG, s, e); 1261 } 1262 } 1263