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