GsmConnection.java revision c90b7d250dae1931a603cc052bd90bc34c070f1b
1/* 2 * Copyright (C) 2006 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.internal.telephony.gsm; 18import android.content.Context; 19import android.os.AsyncResult; 20import android.os.Handler; 21import android.os.Looper; 22import android.os.Message; 23import android.os.PowerManager; 24import android.os.Registrant; 25import android.os.SystemClock; 26import android.util.Log; 27import android.telephony.PhoneNumberUtils; 28import android.telephony.ServiceState; 29import android.text.TextUtils; 30 31import com.android.internal.telephony.*; 32import com.android.internal.telephony.IccCardApplicationStatus.AppState; 33import com.android.internal.telephony.uicc.UiccController; 34 35/** 36 * {@hide} 37 */ 38public class GsmConnection extends Connection { 39 static final String LOG_TAG = "GSM"; 40 41 //***** Instance Variables 42 43 GsmCallTracker owner; 44 GsmCall parent; 45 46 String address; // MAY BE NULL!!! 47 String dialString; // outgoing calls only 48 String postDialString; // outgoing calls only 49 boolean isIncoming; 50 boolean disconnected; 51 52 int index; // index in GsmCallTracker.connections[], -1 if unassigned 53 // The GSM index is 1 + this 54 55 /* 56 * These time/timespan values are based on System.currentTimeMillis(), 57 * i.e., "wall clock" time. 58 */ 59 long createTime; 60 long connectTime; 61 long disconnectTime; 62 63 /* 64 * These time/timespan values are based on SystemClock.elapsedRealTime(), 65 * i.e., time since boot. They are appropriate for comparison and 66 * calculating deltas. 67 */ 68 long connectTimeReal; 69 long duration; 70 long holdingStartTime; // The time when the Connection last transitioned 71 // into HOLDING 72 73 int nextPostDialChar; // index into postDialString 74 75 DisconnectCause cause = DisconnectCause.NOT_DISCONNECTED; 76 PostDialState postDialState = PostDialState.NOT_STARTED; 77 int numberPresentation = PhoneConstants.PRESENTATION_ALLOWED; 78 UUSInfo uusInfo; 79 80 Handler h; 81 82 private PowerManager.WakeLock mPartialWakeLock; 83 84 //***** Event Constants 85 static final int EVENT_DTMF_DONE = 1; 86 static final int EVENT_PAUSE_DONE = 2; 87 static final int EVENT_NEXT_POST_DIAL = 3; 88 static final int EVENT_WAKE_LOCK_TIMEOUT = 4; 89 90 //***** Constants 91 static final int PAUSE_DELAY_FIRST_MILLIS = 100; 92 static final int PAUSE_DELAY_MILLIS = 3 * 1000; 93 static final int WAKE_LOCK_TIMEOUT_MILLIS = 60*1000; 94 95 //***** Inner Classes 96 97 class MyHandler extends Handler { 98 MyHandler(Looper l) {super(l);} 99 100 public void 101 handleMessage(Message msg) { 102 103 switch (msg.what) { 104 case EVENT_NEXT_POST_DIAL: 105 case EVENT_DTMF_DONE: 106 case EVENT_PAUSE_DONE: 107 processNextPostDialChar(); 108 break; 109 case EVENT_WAKE_LOCK_TIMEOUT: 110 releaseWakeLock(); 111 break; 112 } 113 } 114 } 115 116 //***** Constructors 117 118 /** This is probably an MT call that we first saw in a CLCC response */ 119 /*package*/ 120 GsmConnection (Context context, DriverCall dc, GsmCallTracker ct, int index) { 121 createWakeLock(context); 122 acquireWakeLock(); 123 124 owner = ct; 125 h = new MyHandler(owner.getLooper()); 126 127 address = dc.number; 128 129 isIncoming = dc.isMT; 130 createTime = System.currentTimeMillis(); 131 cnapName = dc.name; 132 cnapNamePresentation = dc.namePresentation; 133 numberPresentation = dc.numberPresentation; 134 uusInfo = dc.uusInfo; 135 136 this.index = index; 137 138 parent = parentFromDCState (dc.state); 139 parent.attach(this, dc); 140 } 141 142 /** This is an MO call, created when dialing */ 143 /*package*/ 144 GsmConnection (Context context, String dialString, GsmCallTracker ct, GsmCall parent) { 145 createWakeLock(context); 146 acquireWakeLock(); 147 148 owner = ct; 149 h = new MyHandler(owner.getLooper()); 150 151 this.dialString = dialString; 152 153 this.address = PhoneNumberUtils.extractNetworkPortionAlt(dialString); 154 this.postDialString = PhoneNumberUtils.extractPostDialPortion(dialString); 155 156 index = -1; 157 158 isIncoming = false; 159 cnapName = null; 160 cnapNamePresentation = PhoneConstants.PRESENTATION_ALLOWED; 161 numberPresentation = PhoneConstants.PRESENTATION_ALLOWED; 162 createTime = System.currentTimeMillis(); 163 164 this.parent = parent; 165 parent.attachFake(this, GsmCall.State.DIALING); 166 } 167 168 public void dispose() { 169 } 170 171 static boolean 172 equalsHandlesNulls (Object a, Object b) { 173 return (a == null) ? (b == null) : a.equals (b); 174 } 175 176 /*package*/ boolean 177 compareTo(DriverCall c) { 178 // On mobile originated (MO) calls, the phone number may have changed 179 // due to a SIM Toolkit call control modification. 180 // 181 // We assume we know when MO calls are created (since we created them) 182 // and therefore don't need to compare the phone number anyway. 183 if (! (isIncoming || c.isMT)) return true; 184 185 // ... but we can compare phone numbers on MT calls, and we have 186 // no control over when they begin, so we might as well 187 188 String cAddress = PhoneNumberUtils.stringFromStringAndTOA(c.number, c.TOA); 189 return isIncoming == c.isMT && equalsHandlesNulls(address, cAddress); 190 } 191 192 public String getAddress() { 193 return address; 194 } 195 196 public GsmCall getCall() { 197 return parent; 198 } 199 200 public long getCreateTime() { 201 return createTime; 202 } 203 204 public long getConnectTime() { 205 return connectTime; 206 } 207 208 public long getDisconnectTime() { 209 return disconnectTime; 210 } 211 212 public long getDurationMillis() { 213 if (connectTimeReal == 0) { 214 return 0; 215 } else if (duration == 0) { 216 return SystemClock.elapsedRealtime() - connectTimeReal; 217 } else { 218 return duration; 219 } 220 } 221 222 public long getHoldDurationMillis() { 223 if (getState() != GsmCall.State.HOLDING) { 224 // If not holding, return 0 225 return 0; 226 } else { 227 return SystemClock.elapsedRealtime() - holdingStartTime; 228 } 229 } 230 231 public DisconnectCause getDisconnectCause() { 232 return cause; 233 } 234 235 public boolean isIncoming() { 236 return isIncoming; 237 } 238 239 public GsmCall.State getState() { 240 if (disconnected) { 241 return GsmCall.State.DISCONNECTED; 242 } else { 243 return super.getState(); 244 } 245 } 246 247 public void hangup() throws CallStateException { 248 if (!disconnected) { 249 owner.hangup(this); 250 } else { 251 throw new CallStateException ("disconnected"); 252 } 253 } 254 255 public void separate() throws CallStateException { 256 if (!disconnected) { 257 owner.separate(this); 258 } else { 259 throw new CallStateException ("disconnected"); 260 } 261 } 262 263 public PostDialState getPostDialState() { 264 return postDialState; 265 } 266 267 public void proceedAfterWaitChar() { 268 if (postDialState != PostDialState.WAIT) { 269 Log.w(LOG_TAG, "GsmConnection.proceedAfterWaitChar(): Expected " 270 + "getPostDialState() to be WAIT but was " + postDialState); 271 return; 272 } 273 274 setPostDialState(PostDialState.STARTED); 275 276 processNextPostDialChar(); 277 } 278 279 public void proceedAfterWildChar(String str) { 280 if (postDialState != PostDialState.WILD) { 281 Log.w(LOG_TAG, "GsmConnection.proceedAfterWaitChar(): Expected " 282 + "getPostDialState() to be WILD but was " + postDialState); 283 return; 284 } 285 286 setPostDialState(PostDialState.STARTED); 287 288 if (false) { 289 boolean playedTone = false; 290 int len = (str != null ? str.length() : 0); 291 292 for (int i=0; i<len; i++) { 293 char c = str.charAt(i); 294 Message msg = null; 295 296 if (i == len-1) { 297 msg = h.obtainMessage(EVENT_DTMF_DONE); 298 } 299 300 if (PhoneNumberUtils.is12Key(c)) { 301 owner.cm.sendDtmf(c, msg); 302 playedTone = true; 303 } 304 } 305 306 if (!playedTone) { 307 processNextPostDialChar(); 308 } 309 } else { 310 // make a new postDialString, with the wild char replacement string 311 // at the beginning, followed by the remaining postDialString. 312 313 StringBuilder buf = new StringBuilder(str); 314 buf.append(postDialString.substring(nextPostDialChar)); 315 postDialString = buf.toString(); 316 nextPostDialChar = 0; 317 if (Phone.DEBUG_PHONE) { 318 log("proceedAfterWildChar: new postDialString is " + 319 postDialString); 320 } 321 322 processNextPostDialChar(); 323 } 324 } 325 326 public void cancelPostDial() { 327 setPostDialState(PostDialState.CANCELLED); 328 } 329 330 /** 331 * Called when this Connection is being hung up locally (eg, user pressed "end") 332 * Note that at this point, the hangup request has been dispatched to the radio 333 * but no response has yet been received so update() has not yet been called 334 */ 335 void 336 onHangupLocal() { 337 cause = DisconnectCause.LOCAL; 338 } 339 340 DisconnectCause 341 disconnectCauseFromCode(int causeCode) { 342 /** 343 * See 22.001 Annex F.4 for mapping of cause codes 344 * to local tones 345 */ 346 347 switch (causeCode) { 348 case CallFailCause.USER_BUSY: 349 return DisconnectCause.BUSY; 350 351 case CallFailCause.NO_CIRCUIT_AVAIL: 352 case CallFailCause.TEMPORARY_FAILURE: 353 case CallFailCause.SWITCHING_CONGESTION: 354 case CallFailCause.CHANNEL_NOT_AVAIL: 355 case CallFailCause.QOS_NOT_AVAIL: 356 case CallFailCause.BEARER_NOT_AVAIL: 357 return DisconnectCause.CONGESTION; 358 359 case CallFailCause.ACM_LIMIT_EXCEEDED: 360 return DisconnectCause.LIMIT_EXCEEDED; 361 362 case CallFailCause.CALL_BARRED: 363 return DisconnectCause.CALL_BARRED; 364 365 case CallFailCause.FDN_BLOCKED: 366 return DisconnectCause.FDN_BLOCKED; 367 368 case CallFailCause.UNOBTAINABLE_NUMBER: 369 return DisconnectCause.UNOBTAINABLE_NUMBER; 370 371 case CallFailCause.ERROR_UNSPECIFIED: 372 case CallFailCause.NORMAL_CLEARING: 373 default: 374 GSMPhone phone = owner.phone; 375 int serviceState = phone.getServiceState().getState(); 376 UiccCardApplication cardApp = UiccController 377 .getInstance() 378 .getUiccCardApplication(UiccController.APP_FAM_3GPP); 379 AppState uiccAppState = (cardApp != null) ? cardApp.getState() : 380 AppState.APPSTATE_UNKNOWN; 381 if (serviceState == ServiceState.STATE_POWER_OFF) { 382 return DisconnectCause.POWER_OFF; 383 } else if (serviceState == ServiceState.STATE_OUT_OF_SERVICE 384 || serviceState == ServiceState.STATE_EMERGENCY_ONLY ) { 385 return DisconnectCause.OUT_OF_SERVICE; 386 } else if (uiccAppState != AppState.APPSTATE_READY) { 387 return DisconnectCause.ICC_ERROR; 388 } else if (causeCode == CallFailCause.ERROR_UNSPECIFIED) { 389 if (phone.mSST.mRestrictedState.isCsRestricted()) { 390 return DisconnectCause.CS_RESTRICTED; 391 } else if (phone.mSST.mRestrictedState.isCsEmergencyRestricted()) { 392 return DisconnectCause.CS_RESTRICTED_EMERGENCY; 393 } else if (phone.mSST.mRestrictedState.isCsNormalRestricted()) { 394 return DisconnectCause.CS_RESTRICTED_NORMAL; 395 } else { 396 return DisconnectCause.ERROR_UNSPECIFIED; 397 } 398 } else if (causeCode == CallFailCause.NORMAL_CLEARING) { 399 return DisconnectCause.NORMAL; 400 } else { 401 // If nothing else matches, report unknown call drop reason 402 // to app, not NORMAL call end. 403 return DisconnectCause.ERROR_UNSPECIFIED; 404 } 405 } 406 } 407 408 /*package*/ void 409 onRemoteDisconnect(int causeCode) { 410 onDisconnect(disconnectCauseFromCode(causeCode)); 411 } 412 413 /** Called when the radio indicates the connection has been disconnected */ 414 /*package*/ void 415 onDisconnect(DisconnectCause cause) { 416 this.cause = cause; 417 418 if (!disconnected) { 419 index = -1; 420 421 disconnectTime = System.currentTimeMillis(); 422 duration = SystemClock.elapsedRealtime() - connectTimeReal; 423 disconnected = true; 424 425 if (false) Log.d(LOG_TAG, 426 "[GSMConn] onDisconnect: cause=" + cause); 427 428 owner.phone.notifyDisconnect(this); 429 430 if (parent != null) { 431 parent.connectionDisconnected(this); 432 } 433 } 434 releaseWakeLock(); 435 } 436 437 // Returns true if state has changed, false if nothing changed 438 /*package*/ boolean 439 update (DriverCall dc) { 440 GsmCall newParent; 441 boolean changed = false; 442 boolean wasConnectingInOrOut = isConnectingInOrOut(); 443 boolean wasHolding = (getState() == GsmCall.State.HOLDING); 444 445 newParent = parentFromDCState(dc.state); 446 447 if (!equalsHandlesNulls(address, dc.number)) { 448 if (Phone.DEBUG_PHONE) log("update: phone # changed!"); 449 address = dc.number; 450 changed = true; 451 } 452 453 // A null cnapName should be the same as "" 454 if (TextUtils.isEmpty(dc.name)) { 455 if (!TextUtils.isEmpty(cnapName)) { 456 changed = true; 457 cnapName = ""; 458 } 459 } else if (!dc.name.equals(cnapName)) { 460 changed = true; 461 cnapName = dc.name; 462 } 463 464 if (Phone.DEBUG_PHONE) log("--dssds----"+cnapName); 465 cnapNamePresentation = dc.namePresentation; 466 numberPresentation = dc.numberPresentation; 467 468 if (newParent != parent) { 469 if (parent != null) { 470 parent.detach(this); 471 } 472 newParent.attach(this, dc); 473 parent = newParent; 474 changed = true; 475 } else { 476 boolean parentStateChange; 477 parentStateChange = parent.update (this, dc); 478 changed = changed || parentStateChange; 479 } 480 481 /** Some state-transition events */ 482 483 if (Phone.DEBUG_PHONE) log( 484 "update: parent=" + parent + 485 ", hasNewParent=" + (newParent != parent) + 486 ", wasConnectingInOrOut=" + wasConnectingInOrOut + 487 ", wasHolding=" + wasHolding + 488 ", isConnectingInOrOut=" + isConnectingInOrOut() + 489 ", changed=" + changed); 490 491 492 if (wasConnectingInOrOut && !isConnectingInOrOut()) { 493 onConnectedInOrOut(); 494 } 495 496 if (changed && !wasHolding && (getState() == GsmCall.State.HOLDING)) { 497 // We've transitioned into HOLDING 498 onStartedHolding(); 499 } 500 501 return changed; 502 } 503 504 /** 505 * Called when this Connection is in the foregroundCall 506 * when a dial is initiated. 507 * We know we're ACTIVE, and we know we're going to end up 508 * HOLDING in the backgroundCall 509 */ 510 void 511 fakeHoldBeforeDial() { 512 if (parent != null) { 513 parent.detach(this); 514 } 515 516 parent = owner.backgroundCall; 517 parent.attachFake(this, GsmCall.State.HOLDING); 518 519 onStartedHolding(); 520 } 521 522 /*package*/ int 523 getGSMIndex() throws CallStateException { 524 if (index >= 0) { 525 return index + 1; 526 } else { 527 throw new CallStateException ("GSM index not yet assigned"); 528 } 529 } 530 531 /** 532 * An incoming or outgoing call has connected 533 */ 534 void 535 onConnectedInOrOut() { 536 connectTime = System.currentTimeMillis(); 537 connectTimeReal = SystemClock.elapsedRealtime(); 538 duration = 0; 539 540 // bug #678474: incoming call interpreted as missed call, even though 541 // it sounds like the user has picked up the call. 542 if (Phone.DEBUG_PHONE) { 543 log("onConnectedInOrOut: connectTime=" + connectTime); 544 } 545 546 if (!isIncoming) { 547 // outgoing calls only 548 processNextPostDialChar(); 549 } 550 releaseWakeLock(); 551 } 552 553 private void 554 onStartedHolding() { 555 holdingStartTime = SystemClock.elapsedRealtime(); 556 } 557 /** 558 * Performs the appropriate action for a post-dial char, but does not 559 * notify application. returns false if the character is invalid and 560 * should be ignored 561 */ 562 private boolean 563 processPostDialChar(char c) { 564 if (PhoneNumberUtils.is12Key(c)) { 565 owner.cm.sendDtmf(c, h.obtainMessage(EVENT_DTMF_DONE)); 566 } else if (c == PhoneNumberUtils.PAUSE) { 567 // From TS 22.101: 568 569 // "The first occurrence of the "DTMF Control Digits Separator" 570 // shall be used by the ME to distinguish between the addressing 571 // digits (i.e. the phone number) and the DTMF digits...." 572 573 if (nextPostDialChar == 1) { 574 // The first occurrence. 575 // We don't need to pause here, but wait for just a bit anyway 576 h.sendMessageDelayed(h.obtainMessage(EVENT_PAUSE_DONE), 577 PAUSE_DELAY_FIRST_MILLIS); 578 } else { 579 // It continues... 580 // "Upon subsequent occurrences of the separator, the UE shall 581 // pause again for 3 seconds (\u00B1 20 %) before sending any 582 // further DTMF digits." 583 h.sendMessageDelayed(h.obtainMessage(EVENT_PAUSE_DONE), 584 PAUSE_DELAY_MILLIS); 585 } 586 } else if (c == PhoneNumberUtils.WAIT) { 587 setPostDialState(PostDialState.WAIT); 588 } else if (c == PhoneNumberUtils.WILD) { 589 setPostDialState(PostDialState.WILD); 590 } else { 591 return false; 592 } 593 594 return true; 595 } 596 597 public String 598 getRemainingPostDialString() { 599 if (postDialState == PostDialState.CANCELLED 600 || postDialState == PostDialState.COMPLETE 601 || postDialString == null 602 || postDialString.length() <= nextPostDialChar 603 ) { 604 return ""; 605 } 606 607 return postDialString.substring(nextPostDialChar); 608 } 609 610 @Override 611 protected void finalize() 612 { 613 /** 614 * It is understood that This finializer is not guaranteed 615 * to be called and the release lock call is here just in 616 * case there is some path that doesn't call onDisconnect 617 * and or onConnectedInOrOut. 618 */ 619 if (mPartialWakeLock.isHeld()) { 620 Log.e(LOG_TAG, "[GSMConn] UNEXPECTED; mPartialWakeLock is held when finalizing."); 621 } 622 releaseWakeLock(); 623 } 624 625 private void 626 processNextPostDialChar() { 627 char c = 0; 628 Registrant postDialHandler; 629 630 if (postDialState == PostDialState.CANCELLED) { 631 //Log.v("GSM", "##### processNextPostDialChar: postDialState == CANCELLED, bail"); 632 return; 633 } 634 635 if (postDialString == null || 636 postDialString.length() <= nextPostDialChar) { 637 setPostDialState(PostDialState.COMPLETE); 638 639 // notifyMessage.arg1 is 0 on complete 640 c = 0; 641 } else { 642 boolean isValid; 643 644 setPostDialState(PostDialState.STARTED); 645 646 c = postDialString.charAt(nextPostDialChar++); 647 648 isValid = processPostDialChar(c); 649 650 if (!isValid) { 651 // Will call processNextPostDialChar 652 h.obtainMessage(EVENT_NEXT_POST_DIAL).sendToTarget(); 653 // Don't notify application 654 Log.e("GSM", "processNextPostDialChar: c=" + c + " isn't valid!"); 655 return; 656 } 657 } 658 659 postDialHandler = owner.phone.mPostDialHandler; 660 661 Message notifyMessage; 662 663 if (postDialHandler != null 664 && (notifyMessage = postDialHandler.messageForRegistrant()) != null) { 665 // The AsyncResult.result is the Connection object 666 PostDialState state = postDialState; 667 AsyncResult ar = AsyncResult.forMessage(notifyMessage); 668 ar.result = this; 669 ar.userObj = state; 670 671 // arg1 is the character that was/is being processed 672 notifyMessage.arg1 = c; 673 674 //Log.v("GSM", "##### processNextPostDialChar: send msg to postDialHandler, arg1=" + c); 675 notifyMessage.sendToTarget(); 676 } 677 } 678 679 680 /** "connecting" means "has never been ACTIVE" for both incoming 681 * and outgoing calls 682 */ 683 private boolean 684 isConnectingInOrOut() { 685 return parent == null || parent == owner.ringingCall 686 || parent.state == GsmCall.State.DIALING 687 || parent.state == GsmCall.State.ALERTING; 688 } 689 690 private GsmCall 691 parentFromDCState (DriverCall.State state) { 692 switch (state) { 693 case ACTIVE: 694 case DIALING: 695 case ALERTING: 696 return owner.foregroundCall; 697 //break; 698 699 case HOLDING: 700 return owner.backgroundCall; 701 //break; 702 703 case INCOMING: 704 case WAITING: 705 return owner.ringingCall; 706 //break; 707 708 default: 709 throw new RuntimeException("illegal call state: " + state); 710 } 711 } 712 713 /** 714 * Set post dial state and acquire wake lock while switching to "started" 715 * state, the wake lock will be released if state switches out of "started" 716 * state or after WAKE_LOCK_TIMEOUT_MILLIS. 717 * @param s new PostDialState 718 */ 719 private void setPostDialState(PostDialState s) { 720 if (postDialState != PostDialState.STARTED 721 && s == PostDialState.STARTED) { 722 acquireWakeLock(); 723 Message msg = h.obtainMessage(EVENT_WAKE_LOCK_TIMEOUT); 724 h.sendMessageDelayed(msg, WAKE_LOCK_TIMEOUT_MILLIS); 725 } else if (postDialState == PostDialState.STARTED 726 && s != PostDialState.STARTED) { 727 h.removeMessages(EVENT_WAKE_LOCK_TIMEOUT); 728 releaseWakeLock(); 729 } 730 postDialState = s; 731 } 732 733 private void 734 createWakeLock(Context context) { 735 PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE); 736 mPartialWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, LOG_TAG); 737 } 738 739 private void 740 acquireWakeLock() { 741 log("acquireWakeLock"); 742 mPartialWakeLock.acquire(); 743 } 744 745 private void 746 releaseWakeLock() { 747 synchronized(mPartialWakeLock) { 748 if (mPartialWakeLock.isHeld()) { 749 log("releaseWakeLock"); 750 mPartialWakeLock.release(); 751 } 752 } 753 } 754 755 private void log(String msg) { 756 Log.d(LOG_TAG, "[GSMConn] " + msg); 757 } 758 759 @Override 760 public int getNumberPresentation() { 761 return numberPresentation; 762 } 763 764 @Override 765 public UUSInfo getUUSInfo() { 766 return uusInfo; 767 } 768} 769