/**
   * 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;
  }
  /**
   * 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);
      }
    }
  }