GsmConnection.java revision e287feac673ff68565b766e0e463d105fa9cef9d
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 AppState uiccAppState = UiccController 377 .getInstance() 378 .getUiccCardApplication(UiccController.APP_FAM_3GPP) 379 .getState(); 380 if (serviceState == ServiceState.STATE_POWER_OFF) { 381 return DisconnectCause.POWER_OFF; 382 } else if (serviceState == ServiceState.STATE_OUT_OF_SERVICE 383 || serviceState == ServiceState.STATE_EMERGENCY_ONLY ) { 384 return DisconnectCause.OUT_OF_SERVICE; 385 } else if (uiccAppState != AppState.APPSTATE_READY) { 386 return DisconnectCause.ICC_ERROR; 387 } else if (causeCode == CallFailCause.ERROR_UNSPECIFIED) { 388 if (phone.mSST.mRestrictedState.isCsRestricted()) { 389 return DisconnectCause.CS_RESTRICTED; 390 } else if (phone.mSST.mRestrictedState.isCsEmergencyRestricted()) { 391 return DisconnectCause.CS_RESTRICTED_EMERGENCY; 392 } else if (phone.mSST.mRestrictedState.isCsNormalRestricted()) { 393 return DisconnectCause.CS_RESTRICTED_NORMAL; 394 } else { 395 return DisconnectCause.ERROR_UNSPECIFIED; 396 } 397 } else if (causeCode == CallFailCause.NORMAL_CLEARING) { 398 return DisconnectCause.NORMAL; 399 } else { 400 // If nothing else matches, report unknown call drop reason 401 // to app, not NORMAL call end. 402 return DisconnectCause.ERROR_UNSPECIFIED; 403 } 404 } 405 } 406 407 /*package*/ void 408 onRemoteDisconnect(int causeCode) { 409 onDisconnect(disconnectCauseFromCode(causeCode)); 410 } 411 412 /** Called when the radio indicates the connection has been disconnected */ 413 /*package*/ void 414 onDisconnect(DisconnectCause cause) { 415 this.cause = cause; 416 417 if (!disconnected) { 418 index = -1; 419 420 disconnectTime = System.currentTimeMillis(); 421 duration = SystemClock.elapsedRealtime() - connectTimeReal; 422 disconnected = true; 423 424 if (false) Log.d(LOG_TAG, 425 "[GSMConn] onDisconnect: cause=" + cause); 426 427 owner.phone.notifyDisconnect(this); 428 429 if (parent != null) { 430 parent.connectionDisconnected(this); 431 } 432 } 433 releaseWakeLock(); 434 } 435 436 // Returns true if state has changed, false if nothing changed 437 /*package*/ boolean 438 update (DriverCall dc) { 439 GsmCall newParent; 440 boolean changed = false; 441 boolean wasConnectingInOrOut = isConnectingInOrOut(); 442 boolean wasHolding = (getState() == GsmCall.State.HOLDING); 443 444 newParent = parentFromDCState(dc.state); 445 446 if (!equalsHandlesNulls(address, dc.number)) { 447 if (Phone.DEBUG_PHONE) log("update: phone # changed!"); 448 address = dc.number; 449 changed = true; 450 } 451 452 // A null cnapName should be the same as "" 453 if (TextUtils.isEmpty(dc.name)) { 454 if (!TextUtils.isEmpty(cnapName)) { 455 changed = true; 456 cnapName = ""; 457 } 458 } else if (!dc.name.equals(cnapName)) { 459 changed = true; 460 cnapName = dc.name; 461 } 462 463 if (Phone.DEBUG_PHONE) log("--dssds----"+cnapName); 464 cnapNamePresentation = dc.namePresentation; 465 numberPresentation = dc.numberPresentation; 466 467 if (newParent != parent) { 468 if (parent != null) { 469 parent.detach(this); 470 } 471 newParent.attach(this, dc); 472 parent = newParent; 473 changed = true; 474 } else { 475 boolean parentStateChange; 476 parentStateChange = parent.update (this, dc); 477 changed = changed || parentStateChange; 478 } 479 480 /** Some state-transition events */ 481 482 if (Phone.DEBUG_PHONE) log( 483 "update: parent=" + parent + 484 ", hasNewParent=" + (newParent != parent) + 485 ", wasConnectingInOrOut=" + wasConnectingInOrOut + 486 ", wasHolding=" + wasHolding + 487 ", isConnectingInOrOut=" + isConnectingInOrOut() + 488 ", changed=" + changed); 489 490 491 if (wasConnectingInOrOut && !isConnectingInOrOut()) { 492 onConnectedInOrOut(); 493 } 494 495 if (changed && !wasHolding && (getState() == GsmCall.State.HOLDING)) { 496 // We've transitioned into HOLDING 497 onStartedHolding(); 498 } 499 500 return changed; 501 } 502 503 /** 504 * Called when this Connection is in the foregroundCall 505 * when a dial is initiated. 506 * We know we're ACTIVE, and we know we're going to end up 507 * HOLDING in the backgroundCall 508 */ 509 void 510 fakeHoldBeforeDial() { 511 if (parent != null) { 512 parent.detach(this); 513 } 514 515 parent = owner.backgroundCall; 516 parent.attachFake(this, GsmCall.State.HOLDING); 517 518 onStartedHolding(); 519 } 520 521 /*package*/ int 522 getGSMIndex() throws CallStateException { 523 if (index >= 0) { 524 return index + 1; 525 } else { 526 throw new CallStateException ("GSM index not yet assigned"); 527 } 528 } 529 530 /** 531 * An incoming or outgoing call has connected 532 */ 533 void 534 onConnectedInOrOut() { 535 connectTime = System.currentTimeMillis(); 536 connectTimeReal = SystemClock.elapsedRealtime(); 537 duration = 0; 538 539 // bug #678474: incoming call interpreted as missed call, even though 540 // it sounds like the user has picked up the call. 541 if (Phone.DEBUG_PHONE) { 542 log("onConnectedInOrOut: connectTime=" + connectTime); 543 } 544 545 if (!isIncoming) { 546 // outgoing calls only 547 processNextPostDialChar(); 548 } 549 releaseWakeLock(); 550 } 551 552 private void 553 onStartedHolding() { 554 holdingStartTime = SystemClock.elapsedRealtime(); 555 } 556 /** 557 * Performs the appropriate action for a post-dial char, but does not 558 * notify application. returns false if the character is invalid and 559 * should be ignored 560 */ 561 private boolean 562 processPostDialChar(char c) { 563 if (PhoneNumberUtils.is12Key(c)) { 564 owner.cm.sendDtmf(c, h.obtainMessage(EVENT_DTMF_DONE)); 565 } else if (c == PhoneNumberUtils.PAUSE) { 566 // From TS 22.101: 567 568 // "The first occurrence of the "DTMF Control Digits Separator" 569 // shall be used by the ME to distinguish between the addressing 570 // digits (i.e. the phone number) and the DTMF digits...." 571 572 if (nextPostDialChar == 1) { 573 // The first occurrence. 574 // We don't need to pause here, but wait for just a bit anyway 575 h.sendMessageDelayed(h.obtainMessage(EVENT_PAUSE_DONE), 576 PAUSE_DELAY_FIRST_MILLIS); 577 } else { 578 // It continues... 579 // "Upon subsequent occurrences of the separator, the UE shall 580 // pause again for 3 seconds (\u00B1 20 %) before sending any 581 // further DTMF digits." 582 h.sendMessageDelayed(h.obtainMessage(EVENT_PAUSE_DONE), 583 PAUSE_DELAY_MILLIS); 584 } 585 } else if (c == PhoneNumberUtils.WAIT) { 586 setPostDialState(PostDialState.WAIT); 587 } else if (c == PhoneNumberUtils.WILD) { 588 setPostDialState(PostDialState.WILD); 589 } else { 590 return false; 591 } 592 593 return true; 594 } 595 596 public String 597 getRemainingPostDialString() { 598 if (postDialState == PostDialState.CANCELLED 599 || postDialState == PostDialState.COMPLETE 600 || postDialString == null 601 || postDialString.length() <= nextPostDialChar 602 ) { 603 return ""; 604 } 605 606 return postDialString.substring(nextPostDialChar); 607 } 608 609 @Override 610 protected void finalize() 611 { 612 /** 613 * It is understood that This finializer is not guaranteed 614 * to be called and the release lock call is here just in 615 * case there is some path that doesn't call onDisconnect 616 * and or onConnectedInOrOut. 617 */ 618 if (mPartialWakeLock.isHeld()) { 619 Log.e(LOG_TAG, "[GSMConn] UNEXPECTED; mPartialWakeLock is held when finalizing."); 620 } 621 releaseWakeLock(); 622 } 623 624 private void 625 processNextPostDialChar() { 626 char c = 0; 627 Registrant postDialHandler; 628 629 if (postDialState == PostDialState.CANCELLED) { 630 //Log.v("GSM", "##### processNextPostDialChar: postDialState == CANCELLED, bail"); 631 return; 632 } 633 634 if (postDialString == null || 635 postDialString.length() <= nextPostDialChar) { 636 setPostDialState(PostDialState.COMPLETE); 637 638 // notifyMessage.arg1 is 0 on complete 639 c = 0; 640 } else { 641 boolean isValid; 642 643 setPostDialState(PostDialState.STARTED); 644 645 c = postDialString.charAt(nextPostDialChar++); 646 647 isValid = processPostDialChar(c); 648 649 if (!isValid) { 650 // Will call processNextPostDialChar 651 h.obtainMessage(EVENT_NEXT_POST_DIAL).sendToTarget(); 652 // Don't notify application 653 Log.e("GSM", "processNextPostDialChar: c=" + c + " isn't valid!"); 654 return; 655 } 656 } 657 658 postDialHandler = owner.phone.mPostDialHandler; 659 660 Message notifyMessage; 661 662 if (postDialHandler != null 663 && (notifyMessage = postDialHandler.messageForRegistrant()) != null) { 664 // The AsyncResult.result is the Connection object 665 PostDialState state = postDialState; 666 AsyncResult ar = AsyncResult.forMessage(notifyMessage); 667 ar.result = this; 668 ar.userObj = state; 669 670 // arg1 is the character that was/is being processed 671 notifyMessage.arg1 = c; 672 673 //Log.v("GSM", "##### processNextPostDialChar: send msg to postDialHandler, arg1=" + c); 674 notifyMessage.sendToTarget(); 675 } 676 } 677 678 679 /** "connecting" means "has never been ACTIVE" for both incoming 680 * and outgoing calls 681 */ 682 private boolean 683 isConnectingInOrOut() { 684 return parent == null || parent == owner.ringingCall 685 || parent.state == GsmCall.State.DIALING 686 || parent.state == GsmCall.State.ALERTING; 687 } 688 689 private GsmCall 690 parentFromDCState (DriverCall.State state) { 691 switch (state) { 692 case ACTIVE: 693 case DIALING: 694 case ALERTING: 695 return owner.foregroundCall; 696 //break; 697 698 case HOLDING: 699 return owner.backgroundCall; 700 //break; 701 702 case INCOMING: 703 case WAITING: 704 return owner.ringingCall; 705 //break; 706 707 default: 708 throw new RuntimeException("illegal call state: " + state); 709 } 710 } 711 712 /** 713 * Set post dial state and acquire wake lock while switching to "started" 714 * state, the wake lock will be released if state switches out of "started" 715 * state or after WAKE_LOCK_TIMEOUT_MILLIS. 716 * @param s new PostDialState 717 */ 718 private void setPostDialState(PostDialState s) { 719 if (postDialState != PostDialState.STARTED 720 && s == PostDialState.STARTED) { 721 acquireWakeLock(); 722 Message msg = h.obtainMessage(EVENT_WAKE_LOCK_TIMEOUT); 723 h.sendMessageDelayed(msg, WAKE_LOCK_TIMEOUT_MILLIS); 724 } else if (postDialState == PostDialState.STARTED 725 && s != PostDialState.STARTED) { 726 h.removeMessages(EVENT_WAKE_LOCK_TIMEOUT); 727 releaseWakeLock(); 728 } 729 postDialState = s; 730 } 731 732 private void 733 createWakeLock(Context context) { 734 PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE); 735 mPartialWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, LOG_TAG); 736 } 737 738 private void 739 acquireWakeLock() { 740 log("acquireWakeLock"); 741 mPartialWakeLock.acquire(); 742 } 743 744 private void 745 releaseWakeLock() { 746 synchronized(mPartialWakeLock) { 747 if (mPartialWakeLock.isHeld()) { 748 log("releaseWakeLock"); 749 mPartialWakeLock.release(); 750 } 751 } 752 } 753 754 private void log(String msg) { 755 Log.d(LOG_TAG, "[GSMConn] " + msg); 756 } 757 758 @Override 759 public int getNumberPresentation() { 760 return numberPresentation; 761 } 762 763 @Override 764 public UUSInfo getUUSInfo() { 765 return uusInfo; 766 } 767} 768