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