Example #1
0
  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();
  }
Example #2
0
  /* 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);
    }
  }
Example #4
0
  /**
   * 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;
      }
    }
  }
Example #5
0
  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);
        }
      }
    }
  }
Example #6
0
  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;
  }
Example #7
0
 /* 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);
   }
 }
Example #8
0
 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;
 }
Example #9
0
  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()));
  }
Example #10
0
  /**
   * 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);
  }
Example #11
0
  /**
   * 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());
    }
  }
Example #13
0
  /** 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;
  }
Example #14
0
  /**
   * 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());
   }
 }
Example #17
0
  /**
   * 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);
      }
    }
  }