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