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