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