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