/** * Some dial strings in GSM are defined to do non-call setup things, such as modify or query * supplementary service settings (eg, call forwarding). These are generally referred to as "MMI * codes". We look to see if the dial string contains a valid MMI code (potentially with a dial * string at the end as well) and return info here. * * <p>If the dial string contains no MMI code, we return an instance with only "dialingNumber" set * * <p>Please see flow chart in TS 22.030 6.5.3.2 */ static GsmMmiCode newFromDialString(String dialString, GSMPhone phone, UiccCardApplication app) { Matcher m; GsmMmiCode ret = null; if (SystemProperties.getBoolean("ro.config.multimode_cdma", false)) { m = sPatternSuppServiceGlobalDev.matcher(dialString); if (m.matches()) { ret = new GsmMmiCode(phone, app); ret.action = makeEmptyNull(m.group(MATCH_GROUP_ACTION)); String DialCode = makeEmptyNull(m.group(MATCH_GROUP_SERVICE_CODE)); if (DialCode.equals(SC_GLOBALDEV_VM)) { ret.sc = SC_GLOBALDEV_VM; ret.dialingNumber = "+1" + phone.getMdn(); return ret; } else if (DialCode.equals(SC_GLOBALDEV_CS)) { ret.sc = SC_GLOBALDEV_CS; ret.dialingNumber = GLOBALDEV_CS; return ret; } else if (DialCode.length() >= 3 && DialCode.startsWith(SC_GLOBALDEV_CLIR_INVK)) { // Dial "#31#PhoneNum" to invoke CLIR temporarily dialString = ACTION_DEACTIVATE + SC_CLIR + ACTION_DEACTIVATE + DialCode.substring(2); } else if (DialCode.length() >= 3 && DialCode.startsWith(SC_GLOBALDEV_CLIR_SUPP)) { // Dial "*31#PhoneNum" to suppress CLIR temporarily dialString = ACTION_ACTIVATE + SC_CLIR + ACTION_DEACTIVATE + DialCode.substring(2); } } } m = sPatternSuppService.matcher(dialString); // Is this formatted like a standard supplementary service code? if (m.matches()) { ret = new GsmMmiCode(phone, app); ret.poundString = makeEmptyNull(m.group(MATCH_GROUP_POUND_STRING)); ret.action = makeEmptyNull(m.group(MATCH_GROUP_ACTION)); ret.sc = makeEmptyNull(m.group(MATCH_GROUP_SERVICE_CODE)); ret.sia = makeEmptyNull(m.group(MATCH_GROUP_SIA)); ret.sib = makeEmptyNull(m.group(MATCH_GROUP_SIB)); ret.sic = makeEmptyNull(m.group(MATCH_GROUP_SIC)); ret.pwd = makeEmptyNull(m.group(MATCH_GROUP_PWD_CONFIRM)); ret.dialingNumber = makeEmptyNull(m.group(MATCH_GROUP_DIALING_NUMBER)); } else if (dialString.endsWith("#")) { // TS 22.030 sec 6.5.3.2 // "Entry of any characters defined in the 3GPP TS 23.038 [8] Default Alphabet // (up to the maximum defined in 3GPP TS 24.080 [10]), followed by #SEND". ret = new GsmMmiCode(phone, app); ret.poundString = dialString; } else if (isTwoDigitShortCode(phone.getContext(), dialString)) { // Is a country-specific exception to short codes as defined in TS 22.030, 6.5.3.2 ret = null; } else if (isShortCode(dialString, phone)) { // this may be a short code, as defined in TS 22.030, 6.5.3.2 ret = new GsmMmiCode(phone, app); ret.dialingNumber = dialString; } return ret; }
GsmMmiCode(GSMPhone phone, UiccCardApplication app) { // The telephony unit-test cases may create GsmMmiCode's // in secondary threads super(phone.getHandler().getLooper()); this.phone = phone; this.context = phone.getContext(); mUiccApp = app; }
/** clirMode is one of the CLIR_ constants */ synchronized Connection dial(String dialString, int clirMode, UUSInfo uusInfo) throws CallStateException { // note that this triggers call state changed notif clearDisconnected(); if (!canDial()) { throw new CallStateException("cannot dial in current state"); } // The new call must be assigned to the foreground call. // That call must be idle, so place anything that's // there on hold if (foregroundCall.getState() == GsmCall.State.ACTIVE) { // this will probably be done by the radio anyway // but the dial might fail before this happens // and we need to make sure the foreground call is clear // for the newly dialed connection switchWaitingOrHoldingAndActive(); // Fake local state so that // a) foregroundCall is empty for the newly dialed connection // b) hasNonHangupStateChanged remains false in the // next poll, so that we don't clear a failed dialing call fakeHoldForegroundBeforeDial(); } if (foregroundCall.getState() != GsmCall.State.IDLE) { // we should have failed in !canDial() above before we get here throw new CallStateException("cannot dial in current state"); } pendingMO = new GsmConnection( phone.getContext(), checkForTestEmergencyNumber(dialString), this, foregroundCall); hangupPendingMO = false; if (pendingMO.address == null || pendingMO.address.length() == 0 || pendingMO.address.indexOf(PhoneNumberUtils.WILD) >= 0) { // Phone number is invalid pendingMO.cause = Connection.DisconnectCause.INVALID_NUMBER; // handlePollCalls() will notice this call not present // and will mark it as dropped. pollCallsWhenSafe(); } else { // Always unmute when initiating a new call setMute(false); cm.dial(pendingMO.address, clirMode, uusInfo, obtainCompleteMessage()); } updatePhoneState(); phone.notifyPreciseCallStateChanged(); return pendingMO; }
protected synchronized void handlePollCalls(AsyncResult ar) { List polledCalls; if (ar.exception == null) { polledCalls = (List) ar.result; } else if (isCommandExceptionRadioNotAvailable(ar.exception)) { // just a dummy empty ArrayList to cause the loop // to hang up all the calls polledCalls = new ArrayList(); } else { // Radio probably wasn't ready--try again in a bit // But don't keep polling if the channel is closed pollCallsAfterDelay(); return; } Connection newRinging = null; // or waiting boolean hasNonHangupStateChanged = false; // Any change besides // a dropped connection boolean needsPollDelay = false; boolean unknownConnectionAppeared = false; for (int i = 0, curDC = 0, dcSize = polledCalls.size(); i < connections.length; i++) { GsmConnection conn = connections[i]; DriverCall dc = null; // polledCall list is sparse if (curDC < dcSize) { dc = (DriverCall) polledCalls.get(curDC); if (dc.index == i + 1) { curDC++; } else { dc = null; } } if (DBG_POLL) log("poll: conn[i=" + i + "]=" + conn + ", dc=" + dc); if (conn == null && dc != null) { // Connection appeared in CLCC response that we don't know about if (pendingMO != null && pendingMO.compareTo(dc)) { if (DBG_POLL) log("poll: pendingMO=" + pendingMO); // It's our pending mobile originating call connections[i] = pendingMO; pendingMO.index = i; pendingMO.update(dc); pendingMO = null; // Someone has already asked to hangup this call if (hangupPendingMO) { hangupPendingMO = false; try { if (Phone.DEBUG_PHONE) log("poll: hangupPendingMO, hangup conn " + i); hangup(connections[i]); } catch (CallStateException ex) { Rlog.e(LOG_TAG, "unexpected error on hangup"); } // Do not continue processing this poll // Wait for hangup and repoll return; } } else { connections[i] = new GsmConnection(phone.getContext(), dc, this, i); // it's a ringing call if (connections[i].getCall() == ringingCall) { newRinging = connections[i]; } else { // Something strange happened: a call appeared // which is neither a ringing call or one we created. // Either we've crashed and re-attached to an existing // call, or something else (eg, SIM) initiated the call. Rlog.i(LOG_TAG, "Phantom call appeared " + dc); // If it's a connected call, set the connect time so that // it's non-zero. It may not be accurate, but at least // it won't appear as a Missed Call. if (dc.state != DriverCall.State.ALERTING && dc.state != DriverCall.State.DIALING) { connections[i].onConnectedInOrOut(); if (dc.state == DriverCall.State.HOLDING) { // We've transitioned into HOLDING connections[i].onStartedHolding(); } } unknownConnectionAppeared = true; } } hasNonHangupStateChanged = true; } else if (conn != null && dc == null) { // Connection missing in CLCC response that we were // tracking. droppedDuringPoll.add(conn); // Dropped connections are removed from the CallTracker // list but kept in the GsmCall list connections[i] = null; } else if (conn != null && dc != null && !conn.compareTo(dc)) { // Connection in CLCC response does not match what // we were tracking. Assume dropped call and new call droppedDuringPoll.add(conn); connections[i] = new GsmConnection(phone.getContext(), dc, this, i); if (connections[i].getCall() == ringingCall) { newRinging = connections[i]; } // else something strange happened hasNonHangupStateChanged = true; } else if (conn != null && dc != null) { /* implicit conn.compareTo(dc) */ boolean changed; changed = conn.update(dc); hasNonHangupStateChanged = hasNonHangupStateChanged || changed; } if (REPEAT_POLLING) { if (dc != null) { // FIXME with RIL, we should not need this anymore if ((dc.state == DriverCall.State.DIALING /*&& cm.getOption(cm.OPTION_POLL_DIALING)*/ ) || (dc.state == DriverCall.State.ALERTING /*&& cm.getOption(cm.OPTION_POLL_ALERTING)*/ ) || (dc.state == DriverCall.State.INCOMING /*&& cm.getOption(cm.OPTION_POLL_INCOMING)*/ ) || (dc.state == DriverCall.State.WAITING /*&& cm.getOption(cm.OPTION_POLL_WAITING)*/ )) { // Sometimes there's no unsolicited notification // for state transitions needsPollDelay = true; } } } } // This is the first poll after an ATD. // We expect the pending call to appear in the list // If it does not, we land here if (pendingMO != null) { Rlog.d(LOG_TAG, "Pending MO dropped before poll fg state:" + foregroundCall.getState()); droppedDuringPoll.add(pendingMO); pendingMO = null; hangupPendingMO = false; } if (newRinging != null) { phone.notifyNewRingingConnection(newRinging); } // clear the "local hangup" and "missed/rejected call" // cases from the "dropped during poll" list // These cases need no "last call fail" reason for (int i = droppedDuringPoll.size() - 1; i >= 0; i--) { GsmConnection conn = droppedDuringPoll.get(i); if (conn.isIncoming() && conn.getConnectTime() == 0) { // Missed or rejected call Connection.DisconnectCause cause; if (conn.cause == Connection.DisconnectCause.LOCAL) { cause = Connection.DisconnectCause.INCOMING_REJECTED; } else { cause = Connection.DisconnectCause.INCOMING_MISSED; } if (Phone.DEBUG_PHONE) { log("missed/rejected call, conn.cause=" + conn.cause); log("setting cause to " + cause); } droppedDuringPoll.remove(i); conn.onDisconnect(cause); } else if (conn.cause == Connection.DisconnectCause.LOCAL) { // Local hangup droppedDuringPoll.remove(i); conn.onDisconnect(Connection.DisconnectCause.LOCAL); } else if (conn.cause == Connection.DisconnectCause.INVALID_NUMBER) { droppedDuringPoll.remove(i); conn.onDisconnect(Connection.DisconnectCause.INVALID_NUMBER); } } // Any non-local disconnects: determine cause if (droppedDuringPoll.size() > 0) { cm.getLastCallFailCause(obtainNoPollCompleteMessage(EVENT_GET_LAST_CALL_FAIL_CAUSE)); } if (needsPollDelay) { pollCallsAfterDelay(); } // Cases when we can no longer keep disconnected Connection's // with their previous calls // 1) the phone has started to ring // 2) A Call/Connection object has changed state... // we may have switched or held or answered (but not hung up) if (newRinging != null || hasNonHangupStateChanged) { internalClearDisconnected(); } updatePhoneState(); if (unknownConnectionAppeared) { phone.notifyUnknownConnection(); } if (hasNonHangupStateChanged || newRinging != null) { phone.notifyPreciseCallStateChanged(); } // dumpState(); }