SipService.java revision aa562ffdb8f728569e6957b742f271eb7303f878
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.Process; 43import android.os.RemoteException; 44import android.os.ServiceManager; 45import android.os.SystemClock; 46import android.text.TextUtils; 47import android.util.Log; 48 49import java.io.IOException; 50import java.net.DatagramSocket; 51import java.net.InetAddress; 52import java.net.UnknownHostException; 53import java.util.ArrayList; 54import java.util.Collection; 55import java.util.Comparator; 56import java.util.HashMap; 57import java.util.Iterator; 58import java.util.Map; 59import java.util.Timer; 60import java.util.TimerTask; 61import java.util.TreeSet; 62import javax.sip.SipException; 63 64/** 65 * @hide 66 */ 67public final class SipService extends ISipService.Stub { 68 private static final String TAG = "SipService"; 69 private static final boolean DEBUGV = false; 70 private static final boolean DEBUG = true; 71 private static final boolean DEBUG_TIMER = 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 76 private Context mContext; 77 private String mLocalIp; 78 private String mNetworkType; 79 private boolean mConnected; 80 private WakeupTimer mTimer; 81 private WifiManager.WifiLock mWifiLock; 82 private boolean mWifiOnly; 83 84 private MyExecutor mExecutor; 85 86 // SipProfile URI --> group 87 private Map<String, SipSessionGroupExt> mSipGroups = 88 new HashMap<String, SipSessionGroupExt>(); 89 90 // session ID --> session 91 private Map<String, ISipSession> mPendingSessions = 92 new HashMap<String, ISipSession>(); 93 94 private ConnectivityReceiver mConnectivityReceiver; 95 96 /** 97 * Starts the SIP service. Do nothing if the SIP API is not supported on the 98 * device. 99 */ 100 public static void start(Context context) { 101 if (SipManager.isApiSupported(context)) { 102 ServiceManager.addService("sip", new SipService(context)); 103 context.sendBroadcast(new Intent(SipManager.ACTION_SIP_SERVICE_UP)); 104 Log.i(TAG, "SIP service started"); 105 } 106 } 107 108 private SipService(Context context) { 109 if (DEBUG) Log.d(TAG, " service started!"); 110 mContext = context; 111 mConnectivityReceiver = new ConnectivityReceiver(); 112 context.registerReceiver(mConnectivityReceiver, 113 new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)); 114 115 mTimer = new WakeupTimer(context); 116 mWifiOnly = SipManager.isSipWifiOnly(context); 117 } 118 119 private MyExecutor getExecutor() { 120 // create mExecutor lazily 121 if (mExecutor == null) mExecutor = new MyExecutor(); 122 return mExecutor; 123 } 124 125 public synchronized SipProfile[] getListOfProfiles() { 126 mContext.enforceCallingOrSelfPermission( 127 android.Manifest.permission.USE_SIP, null); 128 boolean isCallerRadio = isCallerRadio(); 129 ArrayList<SipProfile> profiles = new ArrayList<SipProfile>(); 130 for (SipSessionGroupExt group : mSipGroups.values()) { 131 if (isCallerRadio || isCallerCreator(group)) { 132 profiles.add(group.getLocalProfile()); 133 } 134 } 135 return profiles.toArray(new SipProfile[profiles.size()]); 136 } 137 138 public 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 Log.e(TAG, "openToMakeCalls()", e); 146 // TODO: how to send the exception back 147 } 148 } 149 150 public synchronized void open3(SipProfile localProfile, 151 PendingIntent incomingCallPendingIntent, 152 ISipSessionListener listener) { 153 mContext.enforceCallingOrSelfPermission( 154 android.Manifest.permission.USE_SIP, null); 155 localProfile.setCallingUid(Binder.getCallingUid()); 156 if (incomingCallPendingIntent == null) { 157 Log.w(TAG, "incomingCallPendingIntent cannot be null; " 158 + "the profile is not opened"); 159 return; 160 } 161 if (DEBUG) Log.d(TAG, "open3: " + localProfile.getUriString() + ": " 162 + incomingCallPendingIntent + ": " + listener); 163 try { 164 SipSessionGroupExt group = createGroup(localProfile, 165 incomingCallPendingIntent, listener); 166 if (localProfile.getAutoRegistration()) { 167 group.openToReceiveCalls(); 168 if (isWifiActive()) grabWifiLock(); 169 } 170 } catch (SipException e) { 171 Log.e(TAG, "openToReceiveCalls()", e); 172 // TODO: how to send the exception back 173 } 174 } 175 176 private boolean isCallerCreator(SipSessionGroupExt group) { 177 SipProfile profile = group.getLocalProfile(); 178 return (profile.getCallingUid() == Binder.getCallingUid()); 179 } 180 181 private boolean isCallerCreatorOrRadio(SipSessionGroupExt group) { 182 return (isCallerRadio() || isCallerCreator(group)); 183 } 184 185 private boolean isCallerRadio() { 186 return (Binder.getCallingUid() == Process.PHONE_UID); 187 } 188 189 public synchronized void close(String localProfileUri) { 190 mContext.enforceCallingOrSelfPermission( 191 android.Manifest.permission.USE_SIP, null); 192 SipSessionGroupExt group = mSipGroups.get(localProfileUri); 193 if (group == null) return; 194 if (!isCallerCreatorOrRadio(group)) { 195 Log.d(TAG, "only creator or radio can close this profile"); 196 return; 197 } 198 199 group = mSipGroups.remove(localProfileUri); 200 notifyProfileRemoved(group.getLocalProfile()); 201 group.close(); 202 if (isWifiActive() && !anyOpened()) releaseWifiLock(); 203 } 204 205 public synchronized boolean isOpened(String localProfileUri) { 206 mContext.enforceCallingOrSelfPermission( 207 android.Manifest.permission.USE_SIP, null); 208 SipSessionGroupExt group = mSipGroups.get(localProfileUri); 209 if (group == null) return false; 210 if (isCallerCreatorOrRadio(group)) { 211 return group.isOpened(); 212 } else { 213 Log.i(TAG, "only creator or radio can query on the profile"); 214 return false; 215 } 216 } 217 218 public synchronized boolean isRegistered(String localProfileUri) { 219 mContext.enforceCallingOrSelfPermission( 220 android.Manifest.permission.USE_SIP, null); 221 SipSessionGroupExt group = mSipGroups.get(localProfileUri); 222 if (group == null) return false; 223 if (isCallerCreatorOrRadio(group)) { 224 return group.isRegistered(); 225 } else { 226 Log.i(TAG, "only creator or radio can query on the profile"); 227 return false; 228 } 229 } 230 231 public synchronized void setRegistrationListener(String localProfileUri, 232 ISipSessionListener listener) { 233 mContext.enforceCallingOrSelfPermission( 234 android.Manifest.permission.USE_SIP, null); 235 SipSessionGroupExt group = mSipGroups.get(localProfileUri); 236 if (group == null) return; 237 if (isCallerCreator(group)) { 238 group.setListener(listener); 239 } else { 240 Log.i(TAG, "only creator can set listener on the profile"); 241 } 242 } 243 244 public synchronized ISipSession createSession(SipProfile localProfile, 245 ISipSessionListener listener) { 246 mContext.enforceCallingOrSelfPermission( 247 android.Manifest.permission.USE_SIP, null); 248 localProfile.setCallingUid(Binder.getCallingUid()); 249 if (!mConnected) return null; 250 try { 251 SipSessionGroupExt group = createGroup(localProfile); 252 return group.createSession(listener); 253 } catch (SipException e) { 254 Log.w(TAG, "createSession()", e); 255 return null; 256 } 257 } 258 259 public synchronized ISipSession getPendingSession(String callId) { 260 mContext.enforceCallingOrSelfPermission( 261 android.Manifest.permission.USE_SIP, null); 262 if (callId == null) return null; 263 return mPendingSessions.get(callId); 264 } 265 266 private String determineLocalIp() { 267 try { 268 DatagramSocket s = new DatagramSocket(); 269 s.connect(InetAddress.getByName("192.168.1.1"), 80); 270 return s.getLocalAddress().getHostAddress(); 271 } catch (IOException e) { 272 Log.w(TAG, "determineLocalIp()", e); 273 // dont do anything; there should be a connectivity change going 274 return null; 275 } 276 } 277 278 private SipSessionGroupExt createGroup(SipProfile localProfile) 279 throws SipException { 280 String key = localProfile.getUriString(); 281 SipSessionGroupExt group = mSipGroups.get(key); 282 if (group == null) { 283 group = new SipSessionGroupExt(localProfile, null, null); 284 mSipGroups.put(key, group); 285 notifyProfileAdded(localProfile); 286 } else if (!isCallerCreator(group)) { 287 throw new SipException("only creator can access the profile"); 288 } 289 return group; 290 } 291 292 private SipSessionGroupExt createGroup(SipProfile localProfile, 293 PendingIntent incomingCallPendingIntent, 294 ISipSessionListener listener) throws SipException { 295 String key = localProfile.getUriString(); 296 SipSessionGroupExt group = mSipGroups.get(key); 297 if (group != null) { 298 if (!isCallerCreator(group)) { 299 throw new SipException("only creator can access the profile"); 300 } 301 group.setIncomingCallPendingIntent(incomingCallPendingIntent); 302 group.setListener(listener); 303 } else { 304 group = new SipSessionGroupExt(localProfile, 305 incomingCallPendingIntent, listener); 306 mSipGroups.put(key, group); 307 notifyProfileAdded(localProfile); 308 } 309 return group; 310 } 311 312 private void notifyProfileAdded(SipProfile localProfile) { 313 if (DEBUG) Log.d(TAG, "notify: profile added: " + localProfile); 314 Intent intent = new Intent(SipManager.ACTION_SIP_ADD_PHONE); 315 intent.putExtra(SipManager.EXTRA_LOCAL_URI, localProfile.getUriString()); 316 mContext.sendBroadcast(intent); 317 } 318 319 private void notifyProfileRemoved(SipProfile localProfile) { 320 if (DEBUG) Log.d(TAG, "notify: profile removed: " + localProfile); 321 Intent intent = new Intent(SipManager.ACTION_SIP_REMOVE_PHONE); 322 intent.putExtra(SipManager.EXTRA_LOCAL_URI, localProfile.getUriString()); 323 mContext.sendBroadcast(intent); 324 } 325 326 private boolean anyOpened() { 327 for (SipSessionGroupExt group : mSipGroups.values()) { 328 if (group.isOpened()) return true; 329 } 330 return false; 331 } 332 333 private void grabWifiLock() { 334 if (mWifiLock == null) { 335 if (DEBUG) Log.d(TAG, "acquire wifi lock"); 336 mWifiLock = ((WifiManager) 337 mContext.getSystemService(Context.WIFI_SERVICE)) 338 .createWifiLock(WifiManager.WIFI_MODE_FULL, TAG); 339 mWifiLock.acquire(); 340 } 341 } 342 343 private void releaseWifiLock() { 344 if (mWifiLock != null) { 345 if (DEBUG) Log.d(TAG, "release wifi lock"); 346 mWifiLock.release(); 347 mWifiLock = null; 348 } 349 } 350 351 private boolean isWifiActive() { 352 return "WIFI".equalsIgnoreCase(mNetworkType); 353 } 354 355 private synchronized void onConnectivityChanged( 356 String type, boolean connected) { 357 if (DEBUG) Log.d(TAG, "onConnectivityChanged(): " 358 + mNetworkType + (mConnected? " CONNECTED" : " DISCONNECTED") 359 + " --> " + type + (connected? " CONNECTED" : " DISCONNECTED")); 360 361 boolean sameType = type.equals(mNetworkType); 362 if (!sameType && !connected) return; 363 364 boolean wasWifi = "WIFI".equalsIgnoreCase(mNetworkType); 365 boolean isWifi = "WIFI".equalsIgnoreCase(type); 366 boolean wifiOff = (isWifi && !connected) || (wasWifi && !sameType); 367 boolean wifiOn = isWifi && connected; 368 if (wifiOff) { 369 releaseWifiLock(); 370 } else if (wifiOn) { 371 if (anyOpened()) grabWifiLock(); 372 } 373 374 try { 375 boolean wasConnected = mConnected; 376 mNetworkType = type; 377 mConnected = connected; 378 379 if (wasConnected) { 380 mLocalIp = null; 381 for (SipSessionGroupExt group : mSipGroups.values()) { 382 group.onConnectivityChanged(false); 383 } 384 } 385 386 if (connected) { 387 mLocalIp = determineLocalIp(); 388 for (SipSessionGroupExt group : mSipGroups.values()) { 389 group.onConnectivityChanged(true); 390 } 391 } 392 393 } catch (SipException e) { 394 Log.e(TAG, "onConnectivityChanged()", e); 395 } 396 } 397 398 private synchronized void addPendingSession(ISipSession session) { 399 try { 400 mPendingSessions.put(session.getCallId(), session); 401 } catch (RemoteException e) { 402 // should not happen with a local call 403 Log.e(TAG, "addPendingSession()", e); 404 } 405 } 406 407 private class SipSessionGroupExt extends SipSessionAdapter { 408 private SipSessionGroup mSipGroup; 409 private PendingIntent mIncomingCallPendingIntent; 410 private boolean mOpened; 411 412 private AutoRegistrationProcess mAutoRegistration = 413 new AutoRegistrationProcess(); 414 415 public SipSessionGroupExt(SipProfile localProfile, 416 PendingIntent incomingCallPendingIntent, 417 ISipSessionListener listener) throws SipException { 418 String password = localProfile.getPassword(); 419 SipProfile p = duplicate(localProfile); 420 mSipGroup = createSipSessionGroup(mLocalIp, p, password); 421 mIncomingCallPendingIntent = incomingCallPendingIntent; 422 mAutoRegistration.setListener(listener); 423 } 424 425 public SipProfile getLocalProfile() { 426 return mSipGroup.getLocalProfile(); 427 } 428 429 // network connectivity is tricky because network can be disconnected 430 // at any instant so need to deal with exceptions carefully even when 431 // you think you are connected 432 private SipSessionGroup createSipSessionGroup(String localIp, 433 SipProfile localProfile, String password) throws SipException { 434 try { 435 return new SipSessionGroup(localIp, localProfile, password); 436 } catch (IOException e) { 437 // network disconnected 438 Log.w(TAG, "createSipSessionGroup(): network disconnected?"); 439 if (localIp != null) { 440 return createSipSessionGroup(null, localProfile, password); 441 } else { 442 // recursive 443 Log.wtf(TAG, "impossible!"); 444 throw new RuntimeException("createSipSessionGroup"); 445 } 446 } 447 } 448 449 private SipProfile duplicate(SipProfile p) { 450 try { 451 return new SipProfile.Builder(p).setPassword("*").build(); 452 } catch (Exception e) { 453 Log.wtf(TAG, "duplicate()", e); 454 throw new RuntimeException("duplicate profile", e); 455 } 456 } 457 458 public void setListener(ISipSessionListener listener) { 459 mAutoRegistration.setListener(listener); 460 } 461 462 public void setIncomingCallPendingIntent(PendingIntent pIntent) { 463 mIncomingCallPendingIntent = pIntent; 464 } 465 466 public void openToReceiveCalls() throws SipException { 467 mOpened = true; 468 if (mConnected) { 469 mSipGroup.openToReceiveCalls(this); 470 mAutoRegistration.start(mSipGroup); 471 } 472 if (DEBUG) Log.d(TAG, " openToReceiveCalls: " + getUri() + ": " 473 + mIncomingCallPendingIntent); 474 } 475 476 public void onConnectivityChanged(boolean connected) 477 throws SipException { 478 mSipGroup.onConnectivityChanged(); 479 if (connected) { 480 resetGroup(mLocalIp); 481 if (mOpened) openToReceiveCalls(); 482 } else { 483 // close mSipGroup but remember mOpened 484 if (DEBUG) Log.d(TAG, " close auto reg temporarily: " 485 + getUri() + ": " + mIncomingCallPendingIntent); 486 mSipGroup.close(); 487 mAutoRegistration.stop(); 488 } 489 } 490 491 private void resetGroup(String localIp) throws SipException { 492 try { 493 mSipGroup.reset(localIp); 494 } catch (IOException e) { 495 // network disconnected 496 Log.w(TAG, "resetGroup(): network disconnected?"); 497 if (localIp != null) { 498 resetGroup(null); // reset w/o local IP 499 } else { 500 // recursive 501 Log.wtf(TAG, "impossible!"); 502 throw new RuntimeException("resetGroup"); 503 } 504 } 505 } 506 507 public void close() { 508 mOpened = false; 509 mSipGroup.close(); 510 mAutoRegistration.stop(); 511 if (DEBUG) Log.d(TAG, " close: " + getUri() + ": " 512 + mIncomingCallPendingIntent); 513 } 514 515 public ISipSession createSession(ISipSessionListener listener) { 516 return mSipGroup.createSession(listener); 517 } 518 519 @Override 520 public void onRinging(ISipSession s, SipProfile caller, 521 String sessionDescription) { 522 SipSessionGroup.SipSessionImpl session = 523 (SipSessionGroup.SipSessionImpl) s; 524 synchronized (SipService.this) { 525 try { 526 if (!isRegistered()) { 527 session.endCall(); 528 return; 529 } 530 531 // send out incoming call broadcast 532 addPendingSession(session); 533 Intent intent = SipManager.createIncomingCallBroadcast( 534 session.getCallId(), sessionDescription); 535 if (DEBUG) Log.d(TAG, " ringing~~ " + getUri() + ": " 536 + caller.getUri() + ": " + session.getCallId() 537 + " " + mIncomingCallPendingIntent); 538 mIncomingCallPendingIntent.send(mContext, 539 SipManager.INCOMING_CALL_RESULT_CODE, intent); 540 } catch (PendingIntent.CanceledException e) { 541 Log.w(TAG, "pendingIntent is canceled, drop incoming call"); 542 session.endCall(); 543 } 544 } 545 } 546 547 @Override 548 public void onError(ISipSession session, int errorCode, 549 String message) { 550 if (DEBUG) Log.d(TAG, "sip session error: " 551 + SipErrorCode.toString(errorCode) + ": " + message); 552 } 553 554 public boolean isOpened() { 555 return mOpened; 556 } 557 558 public boolean isRegistered() { 559 return mAutoRegistration.isRegistered(); 560 } 561 562 private String getUri() { 563 return mSipGroup.getLocalProfileUri(); 564 } 565 } 566 567 // KeepAliveProcess is controlled by AutoRegistrationProcess. 568 // All methods will be invoked in sync with SipService.this except realRun() 569 private class KeepAliveProcess implements Runnable { 570 private static final String TAG = "\\KEEPALIVE/"; 571 private static final int INTERVAL = 10; 572 private SipSessionGroup.SipSessionImpl mSession; 573 private boolean mRunning = false; 574 575 public KeepAliveProcess(SipSessionGroup.SipSessionImpl session) { 576 mSession = session; 577 } 578 579 public void start() { 580 if (mRunning) return; 581 mRunning = true; 582 mTimer.set(INTERVAL * 1000, this); 583 } 584 585 // timeout handler 586 public void run() { 587 if (!mRunning) return; 588 final SipSessionGroup.SipSessionImpl session = mSession; 589 590 // delegate to mExecutor 591 getExecutor().addTask(new Runnable() { 592 public void run() { 593 realRun(session); 594 } 595 }); 596 } 597 598 // real timeout handler 599 private void realRun(SipSessionGroup.SipSessionImpl session) { 600 synchronized (SipService.this) { 601 if (notCurrentSession(session)) return; 602 603 session = session.duplicate(); 604 if (DEBUGV) Log.v(TAG, "~~~ keepalive"); 605 mTimer.cancel(this); 606 session.sendKeepAlive(); 607 if (session.isReRegisterRequired()) { 608 mSession.register(EXPIRY_TIME); 609 } else { 610 mTimer.set(INTERVAL * 1000, this); 611 } 612 } 613 } 614 615 public void stop() { 616 mRunning = false; 617 mSession = null; 618 mTimer.cancel(this); 619 } 620 621 private boolean notCurrentSession(ISipSession session) { 622 return (session != mSession) || !mRunning; 623 } 624 } 625 626 private class AutoRegistrationProcess extends SipSessionAdapter 627 implements Runnable { 628 private SipSessionGroup.SipSessionImpl mSession; 629 private SipSessionListenerProxy mProxy = new SipSessionListenerProxy(); 630 private KeepAliveProcess mKeepAliveProcess; 631 private int mBackoff = 1; 632 private boolean mRegistered; 633 private long mExpiryTime; 634 private int mErrorCode; 635 private String mErrorMessage; 636 private boolean mRunning = false; 637 638 private String getAction() { 639 return toString(); 640 } 641 642 public void start(SipSessionGroup group) { 643 if (!mRunning) { 644 mRunning = true; 645 mBackoff = 1; 646 mSession = (SipSessionGroup.SipSessionImpl) 647 group.createSession(this); 648 // return right away if no active network connection. 649 if (mSession == null) return; 650 651 // start unregistration to clear up old registration at server 652 // TODO: when rfc5626 is deployed, use reg-id and sip.instance 653 // in registration to avoid adding duplicate entries to server 654 mSession.unregister(); 655 if (DEBUG) Log.d(TAG, "start AutoRegistrationProcess for " 656 + mSession.getLocalProfile().getUriString()); 657 } 658 } 659 660 public void stop() { 661 if (!mRunning) return; 662 mRunning = false; 663 mSession.setListener(null); 664 if (mConnected && mRegistered) mSession.unregister(); 665 666 mTimer.cancel(this); 667 if (mKeepAliveProcess != null) { 668 mKeepAliveProcess.stop(); 669 mKeepAliveProcess = null; 670 } 671 672 mRegistered = false; 673 setListener(mProxy.getListener()); 674 } 675 676 public void setListener(ISipSessionListener listener) { 677 synchronized (SipService.this) { 678 mProxy.setListener(listener); 679 680 try { 681 int state = (mSession == null) 682 ? SipSession.State.READY_TO_CALL 683 : mSession.getState(); 684 if ((state == SipSession.State.REGISTERING) 685 || (state == SipSession.State.DEREGISTERING)) { 686 mProxy.onRegistering(mSession); 687 } else if (mRegistered) { 688 int duration = (int) 689 (mExpiryTime - SystemClock.elapsedRealtime()); 690 mProxy.onRegistrationDone(mSession, duration); 691 } else if (mErrorCode != SipErrorCode.NO_ERROR) { 692 if (mErrorCode == SipErrorCode.TIME_OUT) { 693 mProxy.onRegistrationTimeout(mSession); 694 } else { 695 mProxy.onRegistrationFailed(mSession, mErrorCode, 696 mErrorMessage); 697 } 698 } else if (!mConnected) { 699 mProxy.onRegistrationFailed(mSession, 700 SipErrorCode.DATA_CONNECTION_LOST, 701 "no data connection"); 702 } else if (!mRunning) { 703 mProxy.onRegistrationFailed(mSession, 704 SipErrorCode.CLIENT_ERROR, 705 "registration not running"); 706 } else { 707 mProxy.onRegistrationFailed(mSession, 708 SipErrorCode.IN_PROGRESS, 709 String.valueOf(state)); 710 } 711 } catch (Throwable t) { 712 Log.w(TAG, "setListener(): " + t); 713 } 714 } 715 } 716 717 public boolean isRegistered() { 718 return mRegistered; 719 } 720 721 // timeout handler 722 public void run() { 723 synchronized (SipService.this) { 724 if (!mRunning) return; 725 final SipSessionGroup.SipSessionImpl session = mSession; 726 727 // delegate to mExecutor 728 getExecutor().addTask(new Runnable() { 729 public void run() { 730 realRun(session); 731 } 732 }); 733 } 734 } 735 736 // real timeout handler 737 private void realRun(SipSessionGroup.SipSessionImpl session) { 738 synchronized (SipService.this) { 739 if (notCurrentSession(session)) return; 740 mErrorCode = SipErrorCode.NO_ERROR; 741 mErrorMessage = null; 742 if (DEBUG) Log.d(TAG, "~~~ registering"); 743 if (mConnected) session.register(EXPIRY_TIME); 744 } 745 } 746 747 private boolean isBehindNAT(String address) { 748 try { 749 byte[] d = InetAddress.getByName(address).getAddress(); 750 if ((d[0] == 10) || 751 (((0x000000FF & ((int)d[0])) == 172) && 752 ((0x000000F0 & ((int)d[1])) == 16)) || 753 (((0x000000FF & ((int)d[0])) == 192) && 754 ((0x000000FF & ((int)d[1])) == 168))) { 755 return true; 756 } 757 } catch (UnknownHostException e) { 758 Log.e(TAG, "isBehindAT()" + address, e); 759 } 760 return false; 761 } 762 763 private void restart(int duration) { 764 if (DEBUG) Log.d(TAG, "Refresh registration " + duration + "s later."); 765 mTimer.cancel(this); 766 mTimer.set(duration * 1000, this); 767 } 768 769 private int backoffDuration() { 770 int duration = SHORT_EXPIRY_TIME * mBackoff; 771 if (duration > 3600) { 772 duration = 3600; 773 } else { 774 mBackoff *= 2; 775 } 776 return duration; 777 } 778 779 @Override 780 public void onRegistering(ISipSession session) { 781 if (DEBUG) Log.d(TAG, "onRegistering(): " + session); 782 synchronized (SipService.this) { 783 if (notCurrentSession(session)) return; 784 785 mRegistered = false; 786 mProxy.onRegistering(session); 787 } 788 } 789 790 private boolean notCurrentSession(ISipSession session) { 791 if (session != mSession) { 792 ((SipSessionGroup.SipSessionImpl) session).setListener(null); 793 return true; 794 } 795 return !mRunning; 796 } 797 798 @Override 799 public void onRegistrationDone(ISipSession session, int duration) { 800 if (DEBUG) Log.d(TAG, "onRegistrationDone(): " + session); 801 synchronized (SipService.this) { 802 if (notCurrentSession(session)) return; 803 804 mProxy.onRegistrationDone(session, duration); 805 806 if (duration > 0) { 807 mSession.clearReRegisterRequired(); 808 mExpiryTime = SystemClock.elapsedRealtime() 809 + (duration * 1000); 810 811 if (!mRegistered) { 812 mRegistered = true; 813 // allow some overlap to avoid call drop during renew 814 duration -= MIN_EXPIRY_TIME; 815 if (duration < MIN_EXPIRY_TIME) { 816 duration = MIN_EXPIRY_TIME; 817 } 818 restart(duration); 819 820 if (isBehindNAT(mLocalIp) || 821 mSession.getLocalProfile().getSendKeepAlive()) { 822 if (mKeepAliveProcess == null) { 823 mKeepAliveProcess = 824 new KeepAliveProcess(mSession); 825 } 826 mKeepAliveProcess.start(); 827 } 828 } 829 } else { 830 mRegistered = false; 831 mExpiryTime = -1L; 832 if (DEBUG) Log.d(TAG, "Refresh registration immediately"); 833 run(); 834 } 835 } 836 } 837 838 @Override 839 public void onRegistrationFailed(ISipSession session, int errorCode, 840 String message) { 841 if (DEBUG) Log.d(TAG, "onRegistrationFailed(): " + session + ": " 842 + SipErrorCode.toString(errorCode) + ": " + message); 843 synchronized (SipService.this) { 844 if (notCurrentSession(session)) return; 845 846 switch (errorCode) { 847 case SipErrorCode.INVALID_CREDENTIALS: 848 case SipErrorCode.SERVER_UNREACHABLE: 849 if (DEBUG) Log.d(TAG, " pause auto-registration"); 850 stop(); 851 default: 852 restartLater(); 853 } 854 855 mErrorCode = errorCode; 856 mErrorMessage = message; 857 mProxy.onRegistrationFailed(session, errorCode, message); 858 } 859 } 860 861 @Override 862 public void onRegistrationTimeout(ISipSession session) { 863 if (DEBUG) Log.d(TAG, "onRegistrationTimeout(): " + session); 864 synchronized (SipService.this) { 865 if (notCurrentSession(session)) return; 866 867 mErrorCode = SipErrorCode.TIME_OUT; 868 mProxy.onRegistrationTimeout(session); 869 restartLater(); 870 } 871 } 872 873 private void restartLater() { 874 mRegistered = false; 875 restart(backoffDuration()); 876 if (mKeepAliveProcess != null) { 877 mKeepAliveProcess.stop(); 878 mKeepAliveProcess = null; 879 } 880 } 881 } 882 883 private class ConnectivityReceiver extends BroadcastReceiver { 884 private Timer mTimer = new Timer(); 885 private MyTimerTask mTask; 886 887 @Override 888 public void onReceive(Context context, Intent intent) { 889 String action = intent.getAction(); 890 if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) { 891 Bundle b = intent.getExtras(); 892 if (b != null) { 893 NetworkInfo netInfo = (NetworkInfo) 894 b.get(ConnectivityManager.EXTRA_NETWORK_INFO); 895 String type = netInfo.getTypeName(); 896 NetworkInfo.State state = netInfo.getState(); 897 898 if (mWifiOnly && (netInfo.getType() != 899 ConnectivityManager.TYPE_WIFI)) { 900 if (DEBUG) { 901 Log.d(TAG, "Wifi only, other connectivity ignored: " 902 + type); 903 } 904 return; 905 } 906 907 NetworkInfo activeNetInfo = getActiveNetworkInfo(); 908 if (DEBUG) { 909 if (activeNetInfo != null) { 910 Log.d(TAG, "active network: " 911 + activeNetInfo.getTypeName() 912 + ((activeNetInfo.getState() == NetworkInfo.State.CONNECTED) 913 ? " CONNECTED" : " DISCONNECTED")); 914 } else { 915 Log.d(TAG, "active network: null"); 916 } 917 } 918 if ((state == NetworkInfo.State.CONNECTED) 919 && (activeNetInfo != null) 920 && (activeNetInfo.getType() != netInfo.getType())) { 921 if (DEBUG) Log.d(TAG, "ignore connect event: " + type 922 + ", active: " + activeNetInfo.getTypeName()); 923 return; 924 } 925 926 if (state == NetworkInfo.State.CONNECTED) { 927 if (DEBUG) Log.d(TAG, "Connectivity alert: CONNECTED " + type); 928 onChanged(type, true); 929 } else if (state == NetworkInfo.State.DISCONNECTED) { 930 if (DEBUG) Log.d(TAG, "Connectivity alert: DISCONNECTED " + type); 931 onChanged(type, false); 932 } else { 933 if (DEBUG) Log.d(TAG, "Connectivity alert not processed: " 934 + state + " " + type); 935 } 936 } 937 } 938 } 939 940 private NetworkInfo getActiveNetworkInfo() { 941 ConnectivityManager cm = (ConnectivityManager) 942 mContext.getSystemService(Context.CONNECTIVITY_SERVICE); 943 return cm.getActiveNetworkInfo(); 944 } 945 946 private void onChanged(String type, boolean connected) { 947 synchronized (SipService.this) { 948 // When turning on WIFI, it needs some time for network 949 // connectivity to get stabile so we defer good news (because 950 // we want to skip the interim ones) but deliver bad news 951 // immediately 952 if (connected) { 953 if (mTask != null) mTask.cancel(); 954 mTask = new MyTimerTask(type, connected); 955 mTimer.schedule(mTask, 2 * 1000L); 956 // TODO: hold wakup lock so that we can finish change before 957 // the device goes to sleep 958 } else { 959 if ((mTask != null) && mTask.mNetworkType.equals(type)) { 960 mTask.cancel(); 961 } 962 onConnectivityChanged(type, false); 963 } 964 } 965 } 966 967 private class MyTimerTask extends TimerTask { 968 private boolean mConnected; 969 private String mNetworkType; 970 971 public MyTimerTask(String type, boolean connected) { 972 mNetworkType = type; 973 mConnected = connected; 974 } 975 976 // timeout handler 977 @Override 978 public void run() { 979 // delegate to mExecutor 980 getExecutor().addTask(new Runnable() { 981 public void run() { 982 realRun(); 983 } 984 }); 985 } 986 987 private void realRun() { 988 synchronized (SipService.this) { 989 if (mTask != this) { 990 Log.w(TAG, " unexpected task: " + mNetworkType 991 + (mConnected ? " CONNECTED" : "DISCONNECTED")); 992 return; 993 } 994 mTask = null; 995 if (DEBUG) Log.d(TAG, " deliver change for " + mNetworkType 996 + (mConnected ? " CONNECTED" : "DISCONNECTED")); 997 onConnectivityChanged(mNetworkType, mConnected); 998 } 999 } 1000 } 1001 } 1002 1003 // TODO: clean up pending SipSession(s) periodically 1004 1005 1006 /** 1007 * Timer that can schedule events to occur even when the device is in sleep. 1008 * Only used internally in this package. 1009 */ 1010 class WakeupTimer extends BroadcastReceiver { 1011 private static final String TAG = "_SIP.WkTimer_"; 1012 private static final String TRIGGER_TIME = "TriggerTime"; 1013 1014 private Context mContext; 1015 private AlarmManager mAlarmManager; 1016 1017 // runnable --> time to execute in SystemClock 1018 private TreeSet<MyEvent> mEventQueue = 1019 new TreeSet<MyEvent>(new MyEventComparator()); 1020 1021 private PendingIntent mPendingIntent; 1022 1023 public WakeupTimer(Context context) { 1024 mContext = context; 1025 mAlarmManager = (AlarmManager) 1026 context.getSystemService(Context.ALARM_SERVICE); 1027 1028 IntentFilter filter = new IntentFilter(getAction()); 1029 context.registerReceiver(this, filter); 1030 } 1031 1032 /** 1033 * Stops the timer. No event can be scheduled after this method is called. 1034 */ 1035 public synchronized void stop() { 1036 mContext.unregisterReceiver(this); 1037 if (mPendingIntent != null) { 1038 mAlarmManager.cancel(mPendingIntent); 1039 mPendingIntent = null; 1040 } 1041 mEventQueue.clear(); 1042 mEventQueue = null; 1043 } 1044 1045 private synchronized boolean stopped() { 1046 if (mEventQueue == null) { 1047 Log.w(TAG, "Timer stopped"); 1048 return true; 1049 } else { 1050 return false; 1051 } 1052 } 1053 1054 private void cancelAlarm() { 1055 mAlarmManager.cancel(mPendingIntent); 1056 mPendingIntent = null; 1057 } 1058 1059 private void recalculatePeriods() { 1060 if (mEventQueue.isEmpty()) return; 1061 1062 MyEvent firstEvent = mEventQueue.first(); 1063 int minPeriod = firstEvent.mMaxPeriod; 1064 long minTriggerTime = firstEvent.mTriggerTime; 1065 for (MyEvent e : mEventQueue) { 1066 e.mPeriod = e.mMaxPeriod / minPeriod * minPeriod; 1067 int interval = (int) (e.mLastTriggerTime + e.mMaxPeriod 1068 - minTriggerTime); 1069 interval = interval / minPeriod * minPeriod; 1070 e.mTriggerTime = minTriggerTime + interval; 1071 } 1072 TreeSet<MyEvent> newQueue = new TreeSet<MyEvent>( 1073 mEventQueue.comparator()); 1074 newQueue.addAll((Collection<MyEvent>) mEventQueue); 1075 mEventQueue.clear(); 1076 mEventQueue = newQueue; 1077 if (DEBUG_TIMER) { 1078 Log.d(TAG, "queue re-calculated"); 1079 printQueue(); 1080 } 1081 } 1082 1083 // Determines the period and the trigger time of the new event and insert it 1084 // to the queue. 1085 private void insertEvent(MyEvent event) { 1086 long now = SystemClock.elapsedRealtime(); 1087 if (mEventQueue.isEmpty()) { 1088 event.mTriggerTime = now + event.mPeriod; 1089 mEventQueue.add(event); 1090 return; 1091 } 1092 MyEvent firstEvent = mEventQueue.first(); 1093 int minPeriod = firstEvent.mPeriod; 1094 if (minPeriod <= event.mMaxPeriod) { 1095 event.mPeriod = event.mMaxPeriod / minPeriod * minPeriod; 1096 int interval = event.mMaxPeriod; 1097 interval -= (int) (firstEvent.mTriggerTime - now); 1098 interval = interval / minPeriod * minPeriod; 1099 event.mTriggerTime = firstEvent.mTriggerTime + interval; 1100 mEventQueue.add(event); 1101 } else { 1102 long triggerTime = now + event.mPeriod; 1103 if (firstEvent.mTriggerTime < triggerTime) { 1104 event.mTriggerTime = firstEvent.mTriggerTime; 1105 event.mLastTriggerTime -= event.mPeriod; 1106 } else { 1107 event.mTriggerTime = triggerTime; 1108 } 1109 mEventQueue.add(event); 1110 recalculatePeriods(); 1111 } 1112 } 1113 1114 /** 1115 * Sets a periodic timer. 1116 * 1117 * @param period the timer period; in milli-second 1118 * @param callback is called back when the timer goes off; the same callback 1119 * can be specified in multiple timer events 1120 */ 1121 public synchronized void set(int period, Runnable callback) { 1122 if (stopped()) return; 1123 1124 long now = SystemClock.elapsedRealtime(); 1125 MyEvent event = new MyEvent(period, callback, now); 1126 insertEvent(event); 1127 1128 if (mEventQueue.first() == event) { 1129 if (mEventQueue.size() > 1) cancelAlarm(); 1130 scheduleNext(); 1131 } 1132 1133 long triggerTime = event.mTriggerTime; 1134 if (DEBUG_TIMER) { 1135 Log.d(TAG, " add event " + event + " scheduled at " 1136 + showTime(triggerTime) + " at " + showTime(now) 1137 + ", #events=" + mEventQueue.size()); 1138 printQueue(); 1139 } 1140 } 1141 1142 /** 1143 * Cancels all the timer events with the specified callback. 1144 * 1145 * @param callback the callback 1146 */ 1147 public synchronized void cancel(Runnable callback) { 1148 if (stopped() || mEventQueue.isEmpty()) return; 1149 if (DEBUG_TIMER) Log.d(TAG, "cancel:" + callback); 1150 1151 MyEvent firstEvent = mEventQueue.first(); 1152 for (Iterator<MyEvent> iter = mEventQueue.iterator(); 1153 iter.hasNext();) { 1154 MyEvent event = iter.next(); 1155 if (event.mCallback == callback) { 1156 iter.remove(); 1157 if (DEBUG_TIMER) Log.d(TAG, " cancel found:" + event); 1158 } 1159 } 1160 if (mEventQueue.isEmpty()) { 1161 cancelAlarm(); 1162 } else if (mEventQueue.first() != firstEvent) { 1163 cancelAlarm(); 1164 firstEvent = mEventQueue.first(); 1165 firstEvent.mPeriod = firstEvent.mMaxPeriod; 1166 firstEvent.mTriggerTime = firstEvent.mLastTriggerTime 1167 + firstEvent.mPeriod; 1168 recalculatePeriods(); 1169 scheduleNext(); 1170 } 1171 if (DEBUG_TIMER) { 1172 Log.d(TAG, "after cancel:"); 1173 printQueue(); 1174 } 1175 } 1176 1177 private void scheduleNext() { 1178 if (stopped() || mEventQueue.isEmpty()) return; 1179 1180 if (mPendingIntent != null) { 1181 throw new RuntimeException("pendingIntent is not null!"); 1182 } 1183 1184 MyEvent event = mEventQueue.first(); 1185 Intent intent = new Intent(getAction()); 1186 intent.putExtra(TRIGGER_TIME, event.mTriggerTime); 1187 PendingIntent pendingIntent = mPendingIntent = 1188 PendingIntent.getBroadcast(mContext, 0, intent, 1189 PendingIntent.FLAG_UPDATE_CURRENT); 1190 mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, 1191 event.mTriggerTime, pendingIntent); 1192 } 1193 1194 @Override 1195 public synchronized void onReceive(Context context, Intent intent) { 1196 String action = intent.getAction(); 1197 if (getAction().equals(action) 1198 && intent.getExtras().containsKey(TRIGGER_TIME)) { 1199 mPendingIntent = null; 1200 long triggerTime = intent.getLongExtra(TRIGGER_TIME, -1L); 1201 execute(triggerTime); 1202 } else { 1203 Log.d(TAG, "unrecognized intent: " + intent); 1204 } 1205 } 1206 1207 private void printQueue() { 1208 int count = 0; 1209 for (MyEvent event : mEventQueue) { 1210 Log.d(TAG, " " + event + ": scheduled at " 1211 + showTime(event.mTriggerTime) + ": last at " 1212 + showTime(event.mLastTriggerTime)); 1213 if (++count >= 5) break; 1214 } 1215 if (mEventQueue.size() > count) { 1216 Log.d(TAG, " ....."); 1217 } else if (count == 0) { 1218 Log.d(TAG, " <empty>"); 1219 } 1220 } 1221 1222 private void execute(long triggerTime) { 1223 if (DEBUG_TIMER) Log.d(TAG, "time's up, triggerTime = " 1224 + showTime(triggerTime) + ": " + mEventQueue.size()); 1225 if (stopped() || mEventQueue.isEmpty()) return; 1226 1227 for (MyEvent event : mEventQueue) { 1228 if (event.mTriggerTime != triggerTime) break; 1229 if (DEBUG_TIMER) Log.d(TAG, "execute " + event); 1230 1231 event.mLastTriggerTime = event.mTriggerTime; 1232 event.mTriggerTime += event.mPeriod; 1233 1234 // run the callback in a new thread to prevent deadlock 1235 new Thread(event.mCallback, "SipServiceTimerCallbackThread") 1236 .start(); 1237 } 1238 if (DEBUG_TIMER) { 1239 Log.d(TAG, "after timeout execution"); 1240 printQueue(); 1241 } 1242 scheduleNext(); 1243 } 1244 1245 private String getAction() { 1246 return toString(); 1247 } 1248 1249 private String showTime(long time) { 1250 int ms = (int) (time % 1000); 1251 int s = (int) (time / 1000); 1252 int m = s / 60; 1253 s %= 60; 1254 return String.format("%d.%d.%d", m, s, ms); 1255 } 1256 } 1257 1258 private static class MyEvent { 1259 int mPeriod; 1260 int mMaxPeriod; 1261 long mTriggerTime; 1262 long mLastTriggerTime; 1263 Runnable mCallback; 1264 1265 MyEvent(int period, Runnable callback, long now) { 1266 mPeriod = mMaxPeriod = period; 1267 mCallback = callback; 1268 mLastTriggerTime = now; 1269 } 1270 1271 @Override 1272 public String toString() { 1273 String s = super.toString(); 1274 s = s.substring(s.indexOf("@")); 1275 return s + ":" + (mPeriod / 1000) + ":" + (mMaxPeriod / 1000) + ":" 1276 + toString(mCallback); 1277 } 1278 1279 private String toString(Object o) { 1280 String s = o.toString(); 1281 int index = s.indexOf("$"); 1282 if (index > 0) s = s.substring(index + 1); 1283 return s; 1284 } 1285 } 1286 1287 private static class MyEventComparator implements Comparator<MyEvent> { 1288 public int compare(MyEvent e1, MyEvent e2) { 1289 if (e1 == e2) return 0; 1290 int diff = e1.mMaxPeriod - e2.mMaxPeriod; 1291 if (diff == 0) diff = -1; 1292 return diff; 1293 } 1294 1295 public boolean equals(Object that) { 1296 return (this == that); 1297 } 1298 } 1299 1300 // Single-threaded executor 1301 private static class MyExecutor extends Handler { 1302 MyExecutor() { 1303 super(createLooper()); 1304 } 1305 1306 private static Looper createLooper() { 1307 HandlerThread thread = new HandlerThread("SipService"); 1308 thread.start(); 1309 return thread.getLooper(); 1310 } 1311 1312 void addTask(Runnable task) { 1313 Message.obtain(this, 0/* don't care */, task).sendToTarget(); 1314 } 1315 1316 @Override 1317 public void handleMessage(Message msg) { 1318 if (msg.obj instanceof Runnable) { 1319 ((Runnable) msg.obj).run(); 1320 } else { 1321 Log.w(TAG, "can't handle msg: " + msg); 1322 } 1323 } 1324 } 1325} 1326