private void onDisconnect(Connection conn) { Log.i(TAG, "onDisconnect"); mVoicePrivacyState = false; final Call call = getCallFromMap(mCallMap, conn, false); if (call != null) { final boolean wasConferenced = call.getState() == State.CONFERENCED; updateCallFromConnection(call, conn, false); for (int i = 0; i < mListeners.size(); ++i) { mListeners.get(i).onDisconnect(call); } // If it was a conferenced call, we need to run the entire update // to make the proper changes to parent conference calls. if (wasConferenced) { onPhoneStateChanged(null); } mCallMap.remove(conn); } if (MSimTelephonyManager.getDefault().isMultiSimEnabled() && (call != null)) { mCallManager.clearDisconnected(call.getSubscription()); } else { mCallManager.clearDisconnected(); } PhoneGlobals.getInstance().updateWakeState(); }
/* package */ Call onNewRingingConnection(Connection conn) { Log.i(TAG, "onNewRingingConnection"); final Call call = getCallFromMap(mCallMap, conn, true); if (call != null) { Phone phone = conn.getCall().getPhone(); if (phone.getPhoneType() == PhoneConstants.PHONE_TYPE_GSM && mNextGsmCallIsForwarded) { call.setForwarded(true); mNextGsmCallIsForwarded = false; } updateCallFromConnection(call, conn, false); for (int i = 0; i < mListeners.size(); ++i) { mListeners.get(i).onIncoming(call); } if (MSimTelephonyManager.getDefault().isMultiSimEnabled()) { int subscription = conn.getCall().getPhone().getSubscription(); Log.i(TAG, "Setting Active sub : '" + subscription + "'"); PhoneUtils.setActiveSubscription(subscription); // if any local hold tones are playing then they need to be stoped. final MSimCallNotifier notifier = (MSimCallNotifier) PhoneGlobals.getInstance().notifier; notifier.manageLocalCallWaitingTone(); } } PhoneGlobals.getInstance().updateWakeState(); return call; }
private void startUi(InCallState inCallState) { final Call incomingCall = mCallList.getIncomingCall(); final boolean isCallWaiting = (incomingCall != null && incomingCall.getState() == Call.State.CALL_WAITING); // If the screen is off, we need to make sure it gets turned on for incoming calls. // This normally works just fine thanks to FLAG_TURN_SCREEN_ON but that only works // when the activity is first created. Therefore, to ensure the screen is turned on // for the call waiting case, we finish() the current activity and start a new one. // There should be no jank from this since the screen is already off and will remain so // until our new activity is up. if (mProximitySensor.isScreenReallyOff() && isCallWaiting) { if (isActivityStarted()) { mInCallActivity.finish(); } mInCallActivity = null; } final PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); // If the screen is on, we'll prefer to not interrupt the user too much and slide in a card if (pm.isScreenOn()) { Intent intent = new Intent(mContext, InCallCardActivity.class); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); mContext.startActivity(intent); } else { mStatusBarNotifier.updateNotificationAndLaunchIncomingCallUi(inCallState, mCallList); } }
/** * Handles the POST_ON_DIAL_CHARS message from the Phone (see our call to * mPhone.setOnPostDialCharacter() above.) * * <p>TODO: NEED TO TEST THIS SEQUENCE now that we no longer handle "dialable" key events here in * the InCallScreen: we do directly to the Dialer UI instead. Similarly, we may now need to go * directly to the Dialer to handle POST_ON_DIAL_CHARS too. */ private void onPostDialChars(AsyncResult r, char ch) { final Connection c = (Connection) r.result; if (c != null) { final Connection.PostDialState state = (Connection.PostDialState) r.userObj; switch (state) { case WAIT: final Call call = getCallFromMap(mCallMap, c, false); if (call == null) { Log.i(TAG, "Call no longer exists. Skipping onPostDialWait()."); } else { for (Listener mListener : mListeners) { mListener.onPostDialAction( state, call.getCallId(), c.getRemainingPostDialString(), ch); } } break; default: // This is primarily to cause the DTMFTonePlayer to play local tones. // Other listeners simply perform no-ops. for (Listener mListener : mListeners) { mListener.onPostDialAction(state, 0, "", ch); } break; } } }
private void onSuppServiceNotification(AsyncResult r) { SuppServiceNotification notification = (SuppServiceNotification) r.result; Phone gsmPhone = PhoneUtils.getGsmPhone(mCallManager); Log.d(TAG, "SS Notification: " + notification); if (notification.notificationType == SuppServiceNotification.NOTIFICATION_TYPE_MT) { if (notification.code == SuppServiceNotification.MT_CODE_FORWARDED_CALL || notification.code == SuppServiceNotification.MT_CODE_DEFLECTED_CALL) { com.android.internal.telephony.Call ringing = gsmPhone.getRingingCall(); if (ringing.getState().isRinging()) { final Call call = getCallForEarliestConnection(ringing); if (call != null) { call.setForwarded(true); notifyUpdateListeners(call); } } else { mNextGsmCallIsForwarded = true; } } else if (notification.code == SuppServiceNotification.MT_CODE_CALL_ON_HOLD || notification.code == SuppServiceNotification.MT_CODE_CALL_RETRIEVED) { final Call call = getCallForEarliestConnection(gsmPhone.getForegroundCall()); if (call != null) { boolean nowHeld = notification.code == SuppServiceNotification.MT_CODE_CALL_ON_HOLD; call.setHeldRemotely(nowHeld); notifyUpdateListeners(call); } } else if (notification.code == SuppServiceNotification.MT_CODE_ADDITIONAL_CALL_FORWARDED) { com.android.internal.telephony.Call fgCall = gsmPhone.getForegroundCall(); if (fgCall.getState().isAlive()) { final Call call = getCallForEarliestConnection(fgCall); if (call != null) { call.setAdditionalCallForwarded(true); notifyUpdateListeners(call); } } } } else if (notification.notificationType == SuppServiceNotification.NOTIFICATION_TYPE_MO) { if (notification.code == SuppServiceNotification.MO_CODE_CALL_IS_WAITING) { com.android.internal.telephony.Call fgCall = gsmPhone.getForegroundCall(); if (fgCall.getState().isDialing()) { final Call call = getCallForEarliestConnection(fgCall); if (call != null) { call.setDialingIsWaiting(true); notifyUpdateListeners(call); } } } else if (notification.code == SuppServiceNotification.MO_CODE_INCOMING_CALLS_BARRED) { final Call call = getCallForEarliestConnection(gsmPhone.getForegroundCall()); if (call != null) { call.setRemoteIncomingCallBarringEnabled(true); notifyUpdateListeners(call); } } } }
private static boolean hasOutstandingActiveOrDialingCallInternal(HashMap<Connection, Call> map) { for (Call call : map.values()) { final int state = call.getState(); if (state == Call.State.ACTIVE || Call.State.isDialing(state)) { return true; } } return false; }
/* package */ void onUnsolCallModify(Connection conn) { final Call call = getCallFromMap(mCallMap, conn, false); copyDetails( conn.getCallModify().call_details, call.getCallModifyDetails(), conn.getCallModify().error + ""); for (int i = 0; i < mListeners.size(); i++) { mListeners.get(i).onModifyCall(call); } }
private boolean hasLiveCallInternal(HashMap<Connection, Call> map) { for (Call call : map.values()) { final int state = call.getState(); if (state == Call.State.ACTIVE || state == Call.State.CALL_WAITING || state == Call.State.CONFERENCED || state == Call.State.DIALING || state == Call.State.REDIALING || state == Call.State.INCOMING || state == Call.State.ONHOLD || state == Call.State.DISCONNECTING) { return true; } } return false; }
private void mapCallDetails(Call call, Connection connection) { copyDetails(connection.getCallDetails(), call.getCallDetails(), connection.errorInfo); if (connection.getCallModify() != null) { copyDetails( connection.getCallModify().call_details, call.getCallModifyDetails(), connection.errorInfo); } if (connection.getCall().getConfUriList() != null) { String[] confList = connection.getCall().getConfUriList(); call.getCallDetails().setConfUriList(confList); } call.getCallDetails().setMpty(PhoneUtils.isConferenceCall(connection.getCall())); }
/** * Sets the new call state onto the call and performs some additional logic associated with * setting the state. */ private void setNewState(Call call, int newState, Connection connection) { Preconditions.checkState(call.getState() != newState); // When starting an outgoing call, we need to grab gateway information // for the call, if available, and set it. final RawGatewayInfo info = mCallGatewayManager.getGatewayInfo(connection); if (Call.State.isDialing(newState)) { if (!info.isEmpty()) { call.setGatewayNumber(info.getFormattedGatewayNumber()); call.setGatewayPackage(info.packageName); } } else if (!Call.State.isConnected(newState)) { mCallGatewayManager.clearGatewayData(connection); } call.setState(newState); }
/** * Checks to see if the connection is the first connection in a conference call. If it is a * conference call, we will create a new Conference Call object or update the existing conference * call object for that connection. If it is not a conference call but a previous associated * conference call still exists, we mark it as idle and remove it from the map. In both cases * above, we add the Calls to be updated to the UI. * * @param connection The connection object to check. * @param updatedCalls List of 'updated' calls that will be sent to the UI. */ private boolean updateForConferenceCalls(Connection connection, List<Call> updatedCalls) { // We consider this connection a conference connection if the call it // belongs to is a multiparty call AND it is the first live connection. final boolean isConferenceCallConnection = isPartOfLiveConferenceCall(connection) && getEarliestLiveConnection(connection.getCall()) == connection; boolean changed = false; // If this connection is the main connection for the conference call, then create or update // a Call object for that conference call. if (isConferenceCallConnection) { final Call confCall = getCallFromMap(mConfCallMap, connection, true); changed = updateCallFromConnection(confCall, connection, true); if (changed) { updatedCalls.add(confCall); } if (DBG) Log.d(TAG, "Updating a conference call: " + confCall); // It is possible that through a conference call split, there may be lingering conference // calls where this connection was the main connection. We clean those up here. } else { final Call oldConfCall = getCallFromMap(mConfCallMap, connection, false); // We found a conference call for this connection, which is no longer a conference call. // Kill it! if (oldConfCall != null) { if (DBG) Log.d(TAG, "Cleaning up an old conference call: " + oldConfCall); mConfCallMap.remove(connection); oldConfCall.setState(State.IDLE); changed = true; // add to the list of calls to update updatedCalls.add(oldConfCall); } } return changed; }
/** Hangs up any active or outgoing calls. */ public void hangUpOngoingCall(Context context) { // By the time we receive this intent, we could be shut down and call list // could be null. Bail in those cases. if (mCallList == null) { if (mStatusBarNotifier == null) { // The In Call UI has crashed but the notification still stayed up. We should not // come to this stage. StatusBarNotifier.clearInCallNotification(context); } return; } Call call = mCallList.getOutgoingCall(); if (call == null) { call = mCallList.getActiveOrBackgroundCall(); } if (call != null) { CallCommandClient.getInstance().disconnectCall(call.getCallId()); } }
/** Returns a mask of capabilities for the connection such as merge, hold, etc. */ private int getCapabilitiesFor(Connection connection, Call call, boolean isForConference) { final boolean callIsActive = (call.getState() == Call.State.ACTIVE); final boolean callIsBackground = (call.getState() == Call.State.ONHOLD); final Phone phone = connection.getCall().getPhone(); boolean canAddCall = false; boolean canMergeCall = false; boolean canSwapCall = false; boolean canRespondViaText = false; boolean canMute = false; boolean canAddParticipant = false; boolean canModifyCall = false; boolean voicePrivacy = false; final boolean supportHold; final boolean canHold; final boolean genericConf = isForConference && (connection.getCall().getPhone().getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA); if (!MSimTelephonyManager.getDefault().isMultiSimEnabled()) { supportHold = PhoneUtils.okToSupportHold(mCallManager); canHold = (supportHold ? PhoneUtils.okToHoldCall(mCallManager) : false); // only applies to active calls if (callIsActive) { canMergeCall = PhoneUtils.okToMergeCalls(mCallManager); canSwapCall = PhoneUtils.okToSwapCalls(mCallManager); } canAddCall = PhoneUtils.okToAddCall(mCallManager); } else { final int subscription = call.getSubscription(); supportHold = PhoneUtils.okToSupportHold(mCallManager, subscription); canHold = (supportHold ? PhoneUtils.okToHoldCall(mCallManager, subscription) : false); // only applies to active calls if (callIsActive) { canMergeCall = PhoneUtils.okToMergeCalls(mCallManager, subscription); canSwapCall = PhoneUtils.okToSwapCalls(mCallManager, subscription); } canAddCall = PhoneUtils.okToAddCall(mCallManager, subscription); } if (callIsActive || callIsBackground) { canModifyCall = PhoneUtils.isVTModifyAllowed(connection); } canAddParticipant = PhoneUtils.canAddParticipant(mCallManager) && canAddCall; // "Mute": only enabled when the foreground call is ACTIVE. // (It's meaningless while on hold, or while DIALING/ALERTING.) // It's also explicitly disabled during emergency calls or if // emergency callback mode (ECM) is active. boolean isEmergencyCall = false; if (connection != null) { isEmergencyCall = PhoneNumberUtils.isLocalEmergencyNumber(connection.getAddress(), phone.getContext()); } boolean isECM = PhoneUtils.isPhoneInEcm(phone); if (isEmergencyCall || isECM) { // disable "Mute" item canMute = false; } else { canMute = callIsActive; } canRespondViaText = RejectWithTextMessageManager.allowRespondViaSmsForCall(call, connection); // special rules section! // CDMA always has Add if (phone.getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA) { canAddCall = true; } // Voice Privacy for CDMA if ((phone.getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA) && mVoicePrivacyState) { voicePrivacy = true; } int retval = 0x0; if (canHold) { retval |= Capabilities.HOLD; } if (supportHold) { retval |= Capabilities.SUPPORT_HOLD; } if (canAddCall) { retval |= Capabilities.ADD_CALL; } if (canMergeCall) { retval |= Capabilities.MERGE_CALLS; } if (canSwapCall) { retval |= Capabilities.SWAP_CALLS; } if (canRespondViaText) { retval |= Capabilities.RESPOND_VIA_TEXT; } if (canMute) { retval |= Capabilities.MUTE; } if (canAddParticipant) { retval |= Capabilities.ADD_PARTICIPANT; } if (genericConf) { retval |= Capabilities.GENERIC_CONFERENCE; } if (canModifyCall) { retval |= Capabilities.MODIFY_CALL; } if (voicePrivacy) { retval |= Capabilities.VOICE_PRIVACY; } return retval; }
/** * Updates the Call properties to match the state of the connection object that it represents. * * @param call The call object to update. * @param connection The connection object from which to update call. * @param isForConference There are slight differences in how we populate data for conference * calls. This boolean tells us which method to use. */ private boolean updateCallFromConnection( Call call, Connection connection, boolean isForConference) { boolean changed = false; final int newState = translateStateFromTelephony(connection, isForConference); if (call.getState() != newState) { setNewState(call, newState, connection); changed = true; } mapCallDetails(call, connection); final Call.DisconnectCause newDisconnectCause = translateDisconnectCauseFromTelephony(connection.getDisconnectCause()); if (call.getDisconnectCause() != newDisconnectCause) { call.setDisconnectCause(newDisconnectCause); changed = true; } final long oldConnectTime = call.getConnectTime(); if (oldConnectTime != connection.getConnectTime()) { call.setConnectTime(connection.getConnectTime()); changed = true; } // creation time should be fixed call.setCreateTime(connection.getCreateTime()); if (!isForConference) { // Number final String oldNumber = call.getNumber(); String newNumber = connection.getAddress(); RawGatewayInfo info = mCallGatewayManager.getGatewayInfo(connection); if (!info.isEmpty()) { newNumber = info.trueNumber; } if (TextUtils.isEmpty(oldNumber) || !oldNumber.equals(newNumber)) { call.setNumber(newNumber); changed = true; } // Number presentation final int newNumberPresentation = connection.getNumberPresentation(); if (call.getNumberPresentation() != newNumberPresentation) { call.setNumberPresentation(newNumberPresentation); changed = true; } // Name final String oldCnapName = call.getCnapName(); if (TextUtils.isEmpty(oldCnapName) || !oldCnapName.equals(connection.getCnapName())) { call.setCnapName(connection.getCnapName()); changed = true; } // Name Presentation final int newCnapNamePresentation = connection.getCnapNamePresentation(); if (call.getCnapNamePresentation() != newCnapNamePresentation) { call.setCnapNamePresentation(newCnapNamePresentation); changed = true; } } else { // update the list of children by: // 1) Saving the old set // 2) Removing all children // 3) Adding the correct children into the Call // 4) Comparing the new children set with the old children set ImmutableSortedSet<Integer> oldSet = call.getChildCallIds(); call.removeAllChildren(); if (connection.getCall() != null) { for (Connection childConn : connection.getCall().getConnections()) { final Call childCall = getCallFromMap(mCallMap, childConn, false); if (childCall != null && childConn.isAlive()) { call.addChildId(childCall.getCallId()); } } } changed |= !oldSet.equals(call.getChildCallIds()); } // Subscription id, this shall be done when Call object created. if (call.getSubscription() == MSimConstants.INVALID_SUBSCRIPTION) { call.setSubscription(connection.getCall().getPhone().getSubscription()); } /** !!! Uses values from connection and call collected above so this part must be last !!! */ final int newCapabilities = getCapabilitiesFor(connection, call, isForConference); if (call.getCapabilities() != newCapabilities) { call.setCapabilities(newCapabilities); changed = true; } return changed; }
/** * Handles the green CALL key while in-call. * * @return true if we consumed the event. */ public boolean handleCallKey() { Log.v(this, "handleCallKey"); // The green CALL button means either "Answer", "Unhold", or // "Swap calls", or can be a no-op, depending on the current state // of the Phone. /** INCOMING CALL */ final CallList calls = CallList.getInstance(); final Call incomingCall = calls.getIncomingCall(); Log.v(this, "incomingCall: " + incomingCall); // (1) Attempt to answer a call if (incomingCall != null) { CallCommandClient.getInstance().answerCall(incomingCall.getCallId()); if (mAccelerometerListener != null) { mAccelerometerListener.enableSensor(false); } return true; } /** ACTIVE CALL */ final Call activeCall = calls.getActiveCall(); if (activeCall != null) { // TODO: This logic is repeated from CallButtonPresenter.java. We should // consolidate this logic. final boolean isGeneric = activeCall.can(Capabilities.GENERIC_CONFERENCE); final boolean canMerge = activeCall.can(Capabilities.MERGE_CALLS); final boolean canSwap = activeCall.can(Capabilities.SWAP_CALLS); Log.v( this, "activeCall: " + activeCall + ", isGeneric: " + isGeneric + ", canMerge: " + canMerge + ", canSwap: " + canSwap); // (2) Attempt actions on Generic conference calls if (activeCall.isConferenceCall() && isGeneric) { if (canMerge) { CallCommandClient.getInstance().merge(); return true; } else if (canSwap) { CallCommandClient.getInstance().swap(); return true; } } // (3) Swap calls if (canSwap) { CallCommandClient.getInstance().swap(); return true; } } /** BACKGROUND CALL */ final Call heldCall = calls.getBackgroundCall(); if (heldCall != null) { // We have a hold call so presumeable it will always support HOLD...but // there is no harm in double checking. final boolean canHold = heldCall.can(Capabilities.HOLD); Log.v(this, "heldCall: " + heldCall + ", canHold: " + canHold); // (4) unhold call if (heldCall.getState() == Call.State.ONHOLD && canHold) { CallCommandClient.getInstance().hold(heldCall.getCallId(), false); return true; } } // Always consume hard keys return true; }
/** * For some disconnected causes, we show a dialog. This calls into the activity to show the dialog * if appropriate for the call. */ private void maybeShowErrorDialogOnDisconnect(Call call) { // For newly disconnected calls, we may want to show a dialog on specific error conditions if (isActivityStarted() && call.getState() == Call.State.DISCONNECTED) { mInCallActivity.maybeShowErrorDialogOnDisconnect(call.getDisconnectCause()); } }
/** * Go through the Calls from CallManager and return the list of calls that were updated. Method * also finds any orphaned Calls (Connection objects no longer returned by telephony as either * ringing, foreground, or background). For each orphaned call, it sets the call state to IDLE and * adds it to the list of calls to update. * * @param fullUpdate Add all calls to out parameter including those that have no updates. * @param out List to populate with Calls that have been updated. */ private void doUpdate(boolean fullUpdate, List<Call> out) { final List<com.android.internal.telephony.Call> telephonyCalls = Lists.newArrayList(); telephonyCalls.addAll(mCallManager.getRingingCalls()); telephonyCalls.addAll(mCallManager.getForegroundCalls()); telephonyCalls.addAll(mCallManager.getBackgroundCalls()); // orphanedConnections starts out including all connections we know about. // As we iterate through the connections we get from the telephony layer we // prune this Set down to only the connections we have but telephony no longer // recognizes. final Set<Connection> orphanedConnections = Sets.newHashSet(); orphanedConnections.addAll(mCallMap.keySet()); orphanedConnections.addAll(mConfCallMap.keySet()); // Cycle through all the Connections on all the Calls. Update our Call objects // to reflect any new state and send the updated Call objects to the handler service. for (com.android.internal.telephony.Call telephonyCall : telephonyCalls) { for (Connection connection : telephonyCall.getConnections()) { if (DBG) Log.d(TAG, "connection: " + connection + connection.getState()); if (orphanedConnections.contains(connection)) { orphanedConnections.remove(connection); } // We only send updates for live calls which are not incoming (ringing). // Disconnected and incoming calls are handled by onDisconnect and // onNewRingingConnection. final boolean shouldUpdate = connection.getState() != com.android.internal.telephony.Call.State.DISCONNECTED && connection.getState() != com.android.internal.telephony.Call.State.IDLE && !connection.getState().isRinging(); final boolean isDisconnecting = connection.getState() == com.android.internal.telephony.Call.State.DISCONNECTING; // For disconnecting calls, we still need to send the update to the UI but we do // not create a new call if the call did not exist. final boolean shouldCreate = shouldUpdate && !isDisconnecting; // New connections return a Call with INVALID state, which does not translate to // a state in the internal.telephony.Call object. This ensures that staleness // check below fails and we always add the item to the update list if it is new. final Call call = getCallFromMap(mCallMap, connection, shouldCreate /* create */); if (call == null || !shouldUpdate) { if (DBG) Log.d(TAG, "update skipped"); continue; } boolean changed = updateCallFromConnection(call, connection, false); if (fullUpdate || changed) { out.add(call); } } // We do a second loop to address conference call scenarios. We do this as a separate // loop to ensure all child calls are up to date before we start updating the parent // conference calls. for (Connection connection : telephonyCall.getConnections()) { updateForConferenceCalls(connection, out); } } // Iterate through orphaned connections, set them to idle, and remove // them from our internal structures. for (Connection orphanedConnection : orphanedConnections) { if (mCallMap.containsKey(orphanedConnection)) { final Call call = mCallMap.get(orphanedConnection); call.setState(Call.State.IDLE); out.add(call); mCallMap.remove(orphanedConnection); } if (mConfCallMap.containsKey(orphanedConnection)) { final Call call = mConfCallMap.get(orphanedConnection); call.setState(Call.State.IDLE); out.add(call); mConfCallMap.remove(orphanedConnection); } } }