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