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