Renaming Telecomm to Telecom.

- Changing package from android.telecomm to android.telecom
- Changing package from com.android.telecomm to
com.android.server.telecomm.
- Renaming TelecommManager to TelecomManager.

Bug: 17364651
Change-Id: I192cb5d189f55db012ea72ee82ccc5aedbc21638
diff --git a/telecomm/java/android/telecom/AudioState.aidl b/telecomm/java/android/telecom/AudioState.aidl
new file mode 100644
index 0000000..b36e238
--- /dev/null
+++ b/telecomm/java/android/telecom/AudioState.aidl
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2014, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telecom;
+
+/**
+ * {@hide}
+ */
+parcelable AudioState;
diff --git a/telecomm/java/android/telecom/AudioState.java b/telecomm/java/android/telecom/AudioState.java
new file mode 100644
index 0000000..d0e2860
--- /dev/null
+++ b/telecomm/java/android/telecom/AudioState.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telecom;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Locale;
+
+/**
+ *  Encapsulates all audio states during a call.
+ */
+public final class AudioState implements Parcelable {
+    /** Direct the audio stream through the device's earpiece. */
+    public static final int ROUTE_EARPIECE      = 0x00000001;
+
+    /** Direct the audio stream through Bluetooth. */
+    public static final int ROUTE_BLUETOOTH     = 0x00000002;
+
+    /** Direct the audio stream through a wired headset. */
+    public static final int ROUTE_WIRED_HEADSET = 0x00000004;
+
+    /** Direct the audio stream through the device's speakerphone. */
+    public static final int ROUTE_SPEAKER       = 0x00000008;
+
+    /**
+     * Direct the audio stream through the device's earpiece or wired headset if one is
+     * connected.
+     */
+    public static final int ROUTE_WIRED_OR_EARPIECE = ROUTE_EARPIECE | ROUTE_WIRED_HEADSET;
+
+    /** Bit mask of all possible audio routes.
+     *
+     * @hide
+     */
+    public static final int ROUTE_ALL = ROUTE_EARPIECE | ROUTE_BLUETOOTH | ROUTE_WIRED_HEADSET |
+            ROUTE_SPEAKER;
+
+    /** True if the call is muted, false otherwise. */
+    public final boolean isMuted;
+
+    /** The route to use for the audio stream. */
+    public final int route;
+
+    /** Bit vector of all routes supported by this call. */
+    public final int supportedRouteMask;
+
+    public AudioState(boolean isMuted, int route, int supportedRouteMask) {
+        this.isMuted = isMuted;
+        this.route = route;
+        this.supportedRouteMask = supportedRouteMask;
+    }
+
+    public AudioState(AudioState state) {
+        isMuted = state.isMuted;
+        route = state.route;
+        supportedRouteMask = state.supportedRouteMask;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == null) {
+            return false;
+        }
+        if (!(obj instanceof AudioState)) {
+            return false;
+        }
+        AudioState state = (AudioState) obj;
+        return isMuted == state.isMuted && route == state.route &&
+                supportedRouteMask == state.supportedRouteMask;
+    }
+
+    @Override
+    public String toString() {
+        return String.format(Locale.US,
+                "[AudioState isMuted: %b, route; %s, supportedRouteMask: %s]",
+                isMuted, audioRouteToString(route), audioRouteToString(supportedRouteMask));
+    }
+
+    /** @hide */
+    public static String audioRouteToString(int route) {
+        if (route == 0 || (route & ~ROUTE_ALL) != 0x0) {
+            return "UNKNOWN";
+        }
+
+        StringBuffer buffer = new StringBuffer();
+        if ((route & ROUTE_EARPIECE) == ROUTE_EARPIECE) {
+            listAppend(buffer, "EARPIECE");
+        }
+        if ((route & ROUTE_BLUETOOTH) == ROUTE_BLUETOOTH) {
+            listAppend(buffer, "BLUETOOTH");
+        }
+        if ((route & ROUTE_WIRED_HEADSET) == ROUTE_WIRED_HEADSET) {
+            listAppend(buffer, "WIRED_HEADSET");
+        }
+        if ((route & ROUTE_SPEAKER) == ROUTE_SPEAKER) {
+            listAppend(buffer, "SPEAKER");
+        }
+
+        return buffer.toString();
+    }
+
+    private static void listAppend(StringBuffer buffer, String str) {
+        if (buffer.length() > 0) {
+            buffer.append(", ");
+        }
+        buffer.append(str);
+    }
+
+    /**
+     * Responsible for creating AudioState objects for deserialized Parcels.
+     */
+    public static final Parcelable.Creator<AudioState> CREATOR =
+            new Parcelable.Creator<AudioState> () {
+
+        @Override
+        public AudioState createFromParcel(Parcel source) {
+            boolean isMuted = source.readByte() == 0 ? false : true;
+            int route = source.readInt();
+            int supportedRouteMask = source.readInt();
+            return new AudioState(isMuted, route, supportedRouteMask);
+        }
+
+        @Override
+        public AudioState[] newArray(int size) {
+            return new AudioState[size];
+        }
+    };
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * Writes AudioState object into a serializeable Parcel.
+     */
+    @Override
+    public void writeToParcel(Parcel destination, int flags) {
+        destination.writeByte((byte) (isMuted ? 1 : 0));
+        destination.writeInt(route);
+        destination.writeInt(supportedRouteMask);
+    }
+}
diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java
new file mode 100644
index 0000000..1d33b3b
--- /dev/null
+++ b/telecomm/java/android/telecom/Call.java
@@ -0,0 +1,842 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telecom;
+
+import android.annotation.SystemApi;
+import android.net.Uri;
+import android.os.Bundle;
+import android.telephony.DisconnectCause;
+
+import java.lang.String;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+/**
+ * Represents an ongoing phone call that the in-call app should present to the user.
+ *
+ * {@hide}
+ */
+@SystemApi
+public final class Call {
+    /**
+     * The state of a {@code Call} when newly created.
+     */
+    public static final int STATE_NEW = 0;
+
+    /**
+     * The state of an outgoing {@code Call} when dialing the remote number, but not yet connected.
+     */
+    public static final int STATE_DIALING = 1;
+
+    /**
+     * The state of an incoming {@code Call} when ringing locally, but not yet connected.
+     */
+    public static final int STATE_RINGING = 2;
+
+    /**
+     * The state of a {@code Call} when in a holding state.
+     */
+    public static final int STATE_HOLDING = 3;
+
+    /**
+     * The state of a {@code Call} when actively supporting conversation.
+     */
+    public static final int STATE_ACTIVE = 4;
+
+    /**
+     * The state of a {@code Call} when no further voice or other communication is being
+     * transmitted, the remote side has been or will inevitably be informed that the {@code Call}
+     * is no longer active, and the local data transport has or inevitably will release resources
+     * associated with this {@code Call}.
+     */
+    public static final int STATE_DISCONNECTED = 7;
+
+    /**
+     * The state of an outgoing {@code Call}, but waiting for user input before proceeding.
+     */
+    public static final int STATE_PRE_DIAL_WAIT = 8;
+
+    /**
+     * The initial state of an outgoing {@code Call}.
+     * Common transitions are to {@link #STATE_DIALING} state for a successful call or
+     * {@link #STATE_DISCONNECTED} if it failed.
+     */
+    public static final int STATE_CONNECTING = 9;
+
+    public static class Details {
+        private final Uri mHandle;
+        private final int mHandlePresentation;
+        private final String mCallerDisplayName;
+        private final int mCallerDisplayNamePresentation;
+        private final PhoneAccountHandle mAccountHandle;
+        private final int mCallCapabilities;
+        private final int mCallProperties;
+        private final int mDisconnectCauseCode;
+        private final String mDisconnectCauseMessage;
+        private final long mConnectTimeMillis;
+        private final GatewayInfo mGatewayInfo;
+        private final int mVideoState;
+        private final StatusHints mStatusHints;
+        private final Bundle mExtras;
+
+        /**
+         * @return The handle (e.g., phone number) to which the {@code Call} is currently
+         * connected.
+         */
+        public Uri getHandle() {
+            return mHandle;
+        }
+
+        /**
+         * @return The presentation requirements for the handle. See
+         * {@link TelecomManager} for valid values.
+         */
+        public int getHandlePresentation() {
+            return mHandlePresentation;
+        }
+
+        /**
+         * @return The display name for the caller.
+         */
+        public String getCallerDisplayName() {
+            return mCallerDisplayName;
+        }
+
+        /**
+         * @return The presentation requirements for the caller display name. See
+         * {@link TelecomManager} for valid values.
+         */
+        public int getCallerDisplayNamePresentation() {
+            return mCallerDisplayNamePresentation;
+        }
+
+        /**
+         * @return The {@code PhoneAccountHandle} whereby the {@code Call} is currently being
+         * routed.
+         */
+        public PhoneAccountHandle getAccountHandle() {
+            return mAccountHandle;
+        }
+
+        /**
+         * @return A bitmask of the capabilities of the {@code Call}, as defined in
+         *         {@link PhoneCapabilities}.
+         */
+        public int getCallCapabilities() {
+            return mCallCapabilities;
+        }
+
+        /**
+         * @return A bitmask of the properties of the {@code Call}, as defined in
+         *         {@link CallProperties}.
+         */
+        public int getCallProperties() {
+            return mCallProperties;
+        }
+
+        /**
+         * @return For a {@link #STATE_DISCONNECTED} {@code Call}, the disconnect cause expressed
+         * as a code chosen from among those declared in {@link DisconnectCause}.
+         */
+        public int getDisconnectCauseCode() {
+            return mDisconnectCauseCode;
+        }
+
+        /**
+         * @return For a {@link #STATE_DISCONNECTED} {@code Call}, an optional reason for
+         * disconnection expressed as a free text message.
+         */
+        public String getDisconnectCauseMessage() {
+            return mDisconnectCauseMessage;
+        }
+
+        /**
+         * @return The time the {@code Call} has been connected. This information is updated
+         * periodically, but user interfaces should not rely on this to display any "call time
+         * clock".
+         */
+        public long getConnectTimeMillis() {
+            return mConnectTimeMillis;
+        }
+
+        /**
+         * @return Information about any calling gateway the {@code Call} may be using.
+         */
+        public GatewayInfo getGatewayInfo() {
+            return mGatewayInfo;
+        }
+
+        /**
+         * @return The video state of the {@code Call}.
+         */
+        public int getVideoState() {
+            return mVideoState;
+        }
+
+        /**
+         * @return The current {@link android.telecom.StatusHints}, or {@code null} if none
+         * have been set.
+         */
+        public StatusHints getStatusHints() {
+            return mStatusHints;
+        }
+
+        /**
+         * @return A bundle extras to pass with the call
+         */
+        public Bundle getExtras() {
+            return mExtras;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (o instanceof Details) {
+                Details d = (Details) o;
+                return
+                        Objects.equals(mHandle, d.mHandle) &&
+                        Objects.equals(mHandlePresentation, d.mHandlePresentation) &&
+                        Objects.equals(mCallerDisplayName, d.mCallerDisplayName) &&
+                        Objects.equals(mCallerDisplayNamePresentation,
+                                d.mCallerDisplayNamePresentation) &&
+                        Objects.equals(mAccountHandle, d.mAccountHandle) &&
+                        Objects.equals(mCallCapabilities, d.mCallCapabilities) &&
+                        Objects.equals(mCallProperties, d.mCallProperties) &&
+                        Objects.equals(mDisconnectCauseCode, d.mDisconnectCauseCode) &&
+                        Objects.equals(mDisconnectCauseMessage, d.mDisconnectCauseMessage) &&
+                        Objects.equals(mConnectTimeMillis, d.mConnectTimeMillis) &&
+                        Objects.equals(mGatewayInfo, d.mGatewayInfo) &&
+                        Objects.equals(mVideoState, d.mVideoState) &&
+                        Objects.equals(mStatusHints, d.mStatusHints) &&
+                        Objects.equals(mExtras, d.mExtras);
+            }
+            return false;
+        }
+
+        @Override
+        public int hashCode() {
+            return
+                    Objects.hashCode(mHandle) +
+                    Objects.hashCode(mHandlePresentation) +
+                    Objects.hashCode(mCallerDisplayName) +
+                    Objects.hashCode(mCallerDisplayNamePresentation) +
+                    Objects.hashCode(mAccountHandle) +
+                    Objects.hashCode(mCallCapabilities) +
+                    Objects.hashCode(mCallProperties) +
+                    Objects.hashCode(mDisconnectCauseCode) +
+                    Objects.hashCode(mDisconnectCauseMessage) +
+                    Objects.hashCode(mConnectTimeMillis) +
+                    Objects.hashCode(mGatewayInfo) +
+                    Objects.hashCode(mVideoState) +
+                    Objects.hashCode(mStatusHints) +
+                    Objects.hashCode(mExtras);
+        }
+
+        /** {@hide} */
+        public Details(
+                Uri handle,
+                int handlePresentation,
+                String callerDisplayName,
+                int callerDisplayNamePresentation,
+                PhoneAccountHandle accountHandle,
+                int capabilities,
+                int properties,
+                int disconnectCauseCode,
+                String disconnectCauseMessage,
+                long connectTimeMillis,
+                GatewayInfo gatewayInfo,
+                int videoState,
+                StatusHints statusHints,
+                Bundle extras) {
+            mHandle = handle;
+            mHandlePresentation = handlePresentation;
+            mCallerDisplayName = callerDisplayName;
+            mCallerDisplayNamePresentation = callerDisplayNamePresentation;
+            mAccountHandle = accountHandle;
+            mCallCapabilities = capabilities;
+            mCallProperties = properties;
+            mDisconnectCauseCode = disconnectCauseCode;
+            mDisconnectCauseMessage = disconnectCauseMessage;
+            mConnectTimeMillis = connectTimeMillis;
+            mGatewayInfo = gatewayInfo;
+            mVideoState = videoState;
+            mStatusHints = statusHints;
+            mExtras = extras;
+        }
+    }
+
+    public static abstract class Listener {
+        /**
+         * Invoked when the state of this {@code Call} has changed. See {@link #getState()}.
+         *
+         * @param call The {@code Call} invoking this method.
+         * @param state The new state of the {@code Call}.
+         */
+        public void onStateChanged(Call call, int state) {}
+
+        /**
+         * Invoked when the parent of this {@code Call} has changed. See {@link #getParent()}.
+         *
+         * @param call The {@code Call} invoking this method.
+         * @param parent The new parent of the {@code Call}.
+         */
+        public void onParentChanged(Call call, Call parent) {}
+
+        /**
+         * Invoked when the children of this {@code Call} have changed. See {@link #getChildren()}.
+         *
+         * @param call The {@code Call} invoking this method.
+         * @param children The new children of the {@code Call}.
+         */
+        public void onChildrenChanged(Call call, List<Call> children) {}
+
+        /**
+         * Invoked when the details of this {@code Call} have changed. See {@link #getDetails()}.
+         *
+         * @param call The {@code Call} invoking this method.
+         * @param details A {@code Details} object describing the {@code Call}.
+         */
+        public void onDetailsChanged(Call call, Details details) {}
+
+        /**
+         * Invoked when the text messages that can be used as responses to the incoming
+         * {@code Call} are loaded from the relevant database.
+         * See {@link #getCannedTextResponses()}.
+         *
+         * @param call The {@code Call} invoking this method.
+         * @param cannedTextResponses The text messages useable as responses.
+         */
+        public void onCannedTextResponsesLoaded(Call call, List<String> cannedTextResponses) {}
+
+        /**
+         * Invoked when the post-dial sequence in the outgoing {@code Call} has reached a pause
+         * character. This causes the post-dial signals to stop pending user confirmation. An
+         * implementation should present this choice to the user and invoke
+         * {@link #postDialContinue(boolean)} when the user makes the choice.
+         *
+         * @param call The {@code Call} invoking this method.
+         * @param remainingPostDialSequence The post-dial characters that remain to be sent.
+         */
+        public void onPostDialWait(Call call, String remainingPostDialSequence) {}
+
+        /**
+         * Invoked when the {@code Call.VideoCall} of the {@code Call} has changed.
+         *
+         * @param call The {@code Call} invoking this method.
+         * @param videoCall The {@code Call.VideoCall} associated with the {@code Call}.
+         * @hide
+         */
+        public void onVideoCallChanged(Call call, InCallService.VideoCall videoCall) {}
+
+        /**
+         * Invoked when the {@code Call} is destroyed. Clients should refrain from cleaning
+         * up their UI for the {@code Call} in response to state transitions. Specifically,
+         * clients should not assume that a {@link #onStateChanged(Call, int)} with a state of
+         * {@link #STATE_DISCONNECTED} is the final notification the {@code Call} will send. Rather,
+         * clients should wait for this method to be invoked.
+         *
+         * @param call The {@code Call} being destroyed.
+         */
+        public void onCallDestroyed(Call call) {}
+
+        /**
+         * Invoked upon changes to the set of {@code Call}s with which this {@code Call} can be
+         * conferenced.
+         *
+         * @param call The {@code Call} being updated.
+         * @param conferenceableCalls The {@code Call}s with which this {@code Call} can be
+         *          conferenced.
+         */
+        public void onConferenceableCallsChanged(Call call, List<Call> conferenceableCalls) {}
+    }
+
+    private final Phone mPhone;
+    private final String mTelecomCallId;
+    private final InCallAdapter mInCallAdapter;
+    private final List<String> mChildrenIds = new ArrayList<>();
+    private final List<Call> mChildren = new ArrayList<>();
+    private final List<Call> mUnmodifiableChildren = Collections.unmodifiableList(mChildren);
+    private final List<Listener> mListeners = new CopyOnWriteArrayList<>();
+    private final List<Call> mConferenceableCalls = new ArrayList<>();
+    private final List<Call> mUnmodifiableConferenceableCalls =
+            Collections.unmodifiableList(mConferenceableCalls);
+
+    private boolean mChildrenCached;
+    private String mParentId = null;
+    private int mState;
+    private List<String> mCannedTextResponses = null;
+    private String mRemainingPostDialSequence;
+    private InCallService.VideoCall mVideoCall;
+    private Details mDetails;
+
+    /**
+     * Obtains the post-dial sequence remaining to be emitted by this {@code Call}, if any.
+     *
+     * @return The remaining post-dial sequence, or {@code null} if there is no post-dial sequence
+     * remaining or this {@code Call} is not in a post-dial state.
+     */
+    public String getRemainingPostDialSequence() {
+        return mRemainingPostDialSequence;
+    }
+
+    /**
+     * Instructs this {@link #STATE_RINGING} {@code Call} to answer.
+     * @param videoState The video state in which to answer the call.
+     */
+    public void answer(int videoState) {
+        mInCallAdapter.answerCall(mTelecomCallId, videoState);
+    }
+
+    /**
+     * Instructs this {@link #STATE_RINGING} {@code Call} to reject.
+     *
+     * @param rejectWithMessage Whether to reject with a text message.
+     * @param textMessage An optional text message with which to respond.
+     */
+    public void reject(boolean rejectWithMessage, String textMessage) {
+        mInCallAdapter.rejectCall(mTelecomCallId, rejectWithMessage, textMessage);
+    }
+
+    /**
+     * Instructs this {@code Call} to disconnect.
+     */
+    public void disconnect() {
+        mInCallAdapter.disconnectCall(mTelecomCallId);
+    }
+
+    /**
+     * Instructs this {@code Call} to go on hold.
+     */
+    public void hold() {
+        mInCallAdapter.holdCall(mTelecomCallId);
+    }
+
+    /**
+     * Instructs this {@link #STATE_HOLDING} call to release from hold.
+     */
+    public void unhold() {
+        mInCallAdapter.unholdCall(mTelecomCallId);
+    }
+
+    /**
+     * Instructs this {@code Call} to play a dual-tone multi-frequency signaling (DTMF) tone.
+     *
+     * Any other currently playing DTMF tone in the specified call is immediately stopped.
+     *
+     * @param digit A character representing the DTMF digit for which to play the tone. This
+     *         value must be one of {@code '0'} through {@code '9'}, {@code '*'} or {@code '#'}.
+     */
+    public void playDtmfTone(char digit) {
+        mInCallAdapter.playDtmfTone(mTelecomCallId, digit);
+    }
+
+    /**
+     * Instructs this {@code Call} to stop any dual-tone multi-frequency signaling (DTMF) tone
+     * currently playing.
+     *
+     * DTMF tones are played by calling {@link #playDtmfTone(char)}. If no DTMF tone is
+     * currently playing, this method will do nothing.
+     */
+    public void stopDtmfTone() {
+        mInCallAdapter.stopDtmfTone(mTelecomCallId);
+    }
+
+    /**
+     * Instructs this {@code Call} to continue playing a post-dial DTMF string.
+     *
+     * A post-dial DTMF string is a string of digits entered after a phone number, when dialed,
+     * that are immediately sent as DTMF tones to the recipient as soon as the connection is made.
+     *
+     * If the DTMF string contains a {@link TelecomManager#DTMF_CHARACTER_PAUSE} symbol, this
+     * {@code Call} will temporarily pause playing the tones for a pre-defined period of time.
+     *
+     * If the DTMF string contains a {@link TelecomManager#DTMF_CHARACTER_WAIT} symbol, this
+     * {@code Call} will pause playing the tones and notify listeners via
+     * {@link Listener#onPostDialWait(Call, String)}. At this point, the in-call app
+     * should display to the user an indication of this state and an affordance to continue
+     * the postdial sequence. When the user decides to continue the postdial sequence, the in-call
+     * app should invoke the {@link #postDialContinue(boolean)} method.
+     *
+     * @param proceed Whether or not to continue with the post-dial sequence.
+     */
+    public void postDialContinue(boolean proceed) {
+        mInCallAdapter.postDialContinue(mTelecomCallId, proceed);
+    }
+
+    /**
+     * Notifies this {@code Call} that an account has been selected and to proceed with placing
+     * an outgoing call.
+     */
+    public void phoneAccountSelected(PhoneAccountHandle accountHandle) {
+        mInCallAdapter.phoneAccountSelected(mTelecomCallId, accountHandle);
+
+    }
+
+    /**
+     * Instructs this {@code Call} to enter a conference.
+     *
+     * @param callToConferenceWith The other call with which to conference.
+     */
+    public void conference(Call callToConferenceWith) {
+        if (callToConferenceWith != null) {
+            mInCallAdapter.conference(mTelecomCallId, callToConferenceWith.mTelecomCallId);
+        }
+    }
+
+    /**
+     * Instructs this {@code Call} to split from any conference call with which it may be
+     * connected.
+     */
+    public void splitFromConference() {
+        mInCallAdapter.splitFromConference(mTelecomCallId);
+    }
+
+    /**
+     * Merges the calls within this conference. See {@link PhoneCapabilities#MERGE_CONFERENCE}.
+     */
+    public void mergeConference() {
+        mInCallAdapter.mergeConference(mTelecomCallId);
+    }
+
+    /**
+     * Swaps the calls within this conference. See {@link PhoneCapabilities#SWAP_CONFERENCE}.
+     */
+    public void swapConference() {
+        mInCallAdapter.swapConference(mTelecomCallId);
+    }
+
+    /**
+     * Obtains the parent of this {@code Call} in a conference, if any.
+     *
+     * @return The parent {@code Call}, or {@code null} if this {@code Call} is not a
+     * child of any conference {@code Call}s.
+     */
+    public Call getParent() {
+        if (mParentId != null) {
+            return mPhone.internalGetCallByTelecomId(mParentId);
+        }
+        return null;
+    }
+
+    /**
+     * Obtains the children of this conference {@code Call}, if any.
+     *
+     * @return The children of this {@code Call} if this {@code Call} is a conference, or an empty
+     * {@code List} otherwise.
+     */
+    public List<Call> getChildren() {
+        if (!mChildrenCached) {
+            mChildrenCached = true;
+            mChildren.clear();
+
+            for(String id : mChildrenIds) {
+                Call call = mPhone.internalGetCallByTelecomId(id);
+                if (call == null) {
+                    // At least one child was still not found, so do not save true for "cached"
+                    mChildrenCached = false;
+                } else {
+                    mChildren.add(call);
+                }
+            }
+        }
+
+        return mUnmodifiableChildren;
+    }
+
+    /**
+     * Returns the list of {@code Call}s with which this {@code Call} is allowed to conference.
+     *
+     * @return The list of conferenceable {@code Call}s.
+     */
+    public List<Call> getConferenceableCalls() {
+        return mUnmodifiableConferenceableCalls;
+    }
+
+    /**
+     * Obtains the state of this {@code Call}.
+     *
+     * @return A state value, chosen from the {@code STATE_*} constants.
+     */
+    public int getState() {
+        return mState;
+    }
+
+    /**
+     * Obtains a list of canned, pre-configured message responses to present to the user as
+     * ways of rejecting this {@code Call} using via a text message.
+     *
+     * @see #reject(boolean, String)
+     *
+     * @return A list of canned text message responses.
+     */
+    public List<String> getCannedTextResponses() {
+        return mCannedTextResponses;
+    }
+
+    /**
+     * Obtains an object that can be used to display video from this {@code Call}.
+     *
+     * @return An {@code Call.VideoCall}.
+     * @hide
+     */
+    public InCallService.VideoCall getVideoCall() {
+        return mVideoCall;
+    }
+
+    /**
+     * Obtains an object containing call details.
+     *
+     * @return A {@link Details} object. Depending on the state of the {@code Call}, the
+     * result may be {@code null}.
+     */
+    public Details getDetails() {
+        return mDetails;
+    }
+
+    /**
+     * Adds a listener to this {@code Call}.
+     *
+     * @param listener A {@code Listener}.
+     */
+    public void addListener(Listener listener) {
+        mListeners.add(listener);
+    }
+
+    /**
+     * Removes a listener from this {@code Call}.
+     *
+     * @param listener A {@code Listener}.
+     */
+    public void removeListener(Listener listener) {
+        if (listener != null) {
+            mListeners.remove(listener);
+        }
+    }
+
+    /** {@hide} */
+    Call(Phone phone, String telecomCallId, InCallAdapter inCallAdapter) {
+        mPhone = phone;
+        mTelecomCallId = telecomCallId;
+        mInCallAdapter = inCallAdapter;
+        mState = STATE_NEW;
+    }
+
+    /** {@hide} */
+    final String internalGetCallId() {
+        return mTelecomCallId;
+    }
+
+    /** {@hide} */
+    final void internalUpdate(ParcelableCall parcelableCall, Map<String, Call> callIdMap) {
+        // First, we update the internal state as far as possible before firing any updates.
+        Details details = new Details(
+                parcelableCall.getHandle(),
+                parcelableCall.getHandlePresentation(),
+                parcelableCall.getCallerDisplayName(),
+                parcelableCall.getCallerDisplayNamePresentation(),
+                parcelableCall.getAccountHandle(),
+                parcelableCall.getCapabilities(),
+                parcelableCall.getProperties(),
+                parcelableCall.getDisconnectCauseCode(),
+                parcelableCall.getDisconnectCauseMsg(),
+                parcelableCall.getConnectTimeMillis(),
+                parcelableCall.getGatewayInfo(),
+                parcelableCall.getVideoState(),
+                parcelableCall.getStatusHints(),
+                parcelableCall.getExtras());
+        boolean detailsChanged = !Objects.equals(mDetails, details);
+        if (detailsChanged) {
+            mDetails = details;
+        }
+
+        boolean cannedTextResponsesChanged = false;
+        if (mCannedTextResponses == null && parcelableCall.getCannedSmsResponses() != null
+                && !parcelableCall.getCannedSmsResponses().isEmpty()) {
+            mCannedTextResponses =
+                    Collections.unmodifiableList(parcelableCall.getCannedSmsResponses());
+        }
+
+        boolean videoCallChanged = !Objects.equals(mVideoCall, parcelableCall.getVideoCall());
+        if (videoCallChanged) {
+            mVideoCall = parcelableCall.getVideoCall();
+        }
+
+        int state = stateFromParcelableCallState(parcelableCall.getState());
+        boolean stateChanged = mState != state;
+        if (stateChanged) {
+            mState = state;
+        }
+
+        String parentId = parcelableCall.getParentCallId();
+        boolean parentChanged = !Objects.equals(mParentId, parentId);
+        if (parentChanged) {
+            mParentId = parentId;
+        }
+
+        List<String> childCallIds = parcelableCall.getChildCallIds();
+        boolean childrenChanged = !Objects.equals(childCallIds, mChildrenIds);
+        if (childrenChanged) {
+            mChildrenIds.clear();
+            mChildrenIds.addAll(parcelableCall.getChildCallIds());
+            mChildrenCached = false;
+        }
+
+        List<String> conferenceableCallIds = parcelableCall.getConferenceableCallIds();
+        List<Call> conferenceableCalls = new ArrayList<Call>(conferenceableCallIds.size());
+        for (String otherId : conferenceableCallIds) {
+            if (callIdMap.containsKey(otherId)) {
+                conferenceableCalls.add(callIdMap.get(otherId));
+            }
+        }
+
+        if (!Objects.equals(mConferenceableCalls, conferenceableCalls)) {
+            mConferenceableCalls.clear();
+            mConferenceableCalls.addAll(conferenceableCalls);
+            fireConferenceableCallsChanged();
+        }
+
+        // Now we fire updates, ensuring that any client who listens to any of these notifications
+        // gets the most up-to-date state.
+
+        if (stateChanged) {
+            fireStateChanged(mState);
+        }
+        if (detailsChanged) {
+            fireDetailsChanged(mDetails);
+        }
+        if (cannedTextResponsesChanged) {
+            fireCannedTextResponsesLoaded(mCannedTextResponses);
+        }
+        if (videoCallChanged) {
+            fireVideoCallChanged(mVideoCall);
+        }
+        if (parentChanged) {
+            fireParentChanged(getParent());
+        }
+        if (childrenChanged) {
+            fireChildrenChanged(getChildren());
+        }
+
+        // If we have transitioned to DISCONNECTED, that means we need to notify clients and
+        // remove ourselves from the Phone. Note that we do this after completing all state updates
+        // so a client can cleanly transition all their UI to the state appropriate for a
+        // DISCONNECTED Call while still relying on the existence of that Call in the Phone's list.
+        if (mState == STATE_DISCONNECTED) {
+            fireCallDestroyed();
+            mPhone.internalRemoveCall(this);
+        }
+    }
+
+    /** {@hide} */
+    final void internalSetPostDialWait(String remaining) {
+        mRemainingPostDialSequence = remaining;
+        firePostDialWait(mRemainingPostDialSequence);
+    }
+
+    /** {@hide} */
+    final void internalSetDisconnected() {
+        if (mState != Call.STATE_DISCONNECTED) {
+            mState = Call.STATE_DISCONNECTED;
+            fireStateChanged(mState);
+            fireCallDestroyed();
+            mPhone.internalRemoveCall(this);
+        }
+    }
+
+    private void fireStateChanged(int newState) {
+        for (Listener listener : mListeners) {
+            listener.onStateChanged(this, newState);
+        }
+    }
+
+    private void fireParentChanged(Call newParent) {
+        for (Listener listener : mListeners) {
+            listener.onParentChanged(this, newParent);
+        }
+    }
+
+    private void fireChildrenChanged(List<Call> children) {
+        for (Listener listener : mListeners) {
+            listener.onChildrenChanged(this, children);
+        }
+    }
+
+    private void fireDetailsChanged(Details details) {
+        for (Listener listener : mListeners) {
+            listener.onDetailsChanged(this, details);
+        }
+    }
+
+    private void fireCannedTextResponsesLoaded(List<String> cannedTextResponses) {
+        for (Listener listener : mListeners) {
+            listener.onCannedTextResponsesLoaded(this, cannedTextResponses);
+        }
+    }
+
+    private void fireVideoCallChanged(InCallService.VideoCall videoCall) {
+        for (Listener listener : mListeners) {
+            listener.onVideoCallChanged(this, videoCall);
+        }
+    }
+
+    private void firePostDialWait(String remainingPostDialSequence) {
+        for (Listener listener : mListeners) {
+            listener.onPostDialWait(this, remainingPostDialSequence);
+        }
+    }
+
+    private void fireCallDestroyed() {
+        for (Listener listener : mListeners) {
+            listener.onCallDestroyed(this);
+        }
+    }
+
+    private void fireConferenceableCallsChanged() {
+        for (Listener listener : mListeners) {
+            listener.onConferenceableCallsChanged(this, mUnmodifiableConferenceableCalls);
+        }
+    }
+
+    private int stateFromParcelableCallState(int parcelableCallState) {
+        switch (parcelableCallState) {
+            case CallState.NEW:
+                return STATE_NEW;
+            case CallState.CONNECTING:
+                return STATE_CONNECTING;
+            case CallState.PRE_DIAL_WAIT:
+                return STATE_PRE_DIAL_WAIT;
+            case CallState.DIALING:
+                return STATE_DIALING;
+            case CallState.RINGING:
+                return STATE_RINGING;
+            case CallState.ACTIVE:
+                return STATE_ACTIVE;
+            case CallState.ON_HOLD:
+                return STATE_HOLDING;
+            case CallState.DISCONNECTED:
+                return STATE_DISCONNECTED;
+            case CallState.ABORTED:
+                return STATE_DISCONNECTED;
+            default:
+                Log.wtf(this, "Unrecognized CallState %s", parcelableCallState);
+                return STATE_NEW;
+        }
+    }
+}
diff --git a/telecomm/java/android/telecom/CallProperties.java b/telecomm/java/android/telecom/CallProperties.java
new file mode 100644
index 0000000..b1b82e2
--- /dev/null
+++ b/telecomm/java/android/telecom/CallProperties.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.telecom;
+
+/**
+ * Defines properties of a phone call which may be affected by changes to the call.
+ * @hide
+ */
+public class CallProperties {
+    /** Call is currently in a conference call. */
+    public static final int CONFERENCE                      = 0x00000001;
+}
diff --git a/telecomm/java/android/telecom/CallState.java b/telecomm/java/android/telecom/CallState.java
new file mode 100644
index 0000000..7690847
--- /dev/null
+++ b/telecomm/java/android/telecom/CallState.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright 2014, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telecom;
+
+import android.annotation.SystemApi;
+
+/**
+ * Defines call-state constants of the different states in which a call can exist. Although states
+ * have the notion of normal transitions, due to the volatile nature of telephony systems, code
+ * that uses these states should be resilient to unexpected state changes outside of what is
+ * considered traditional.
+ *
+ * {@hide}
+ */
+@SystemApi
+public final class CallState {
+
+    private CallState() {}
+
+    /**
+     * Indicates that a call is new and not connected. This is used as the default state internally
+     * within Telecom and should not be used between Telecom and call services. Call services are
+     * not expected to ever interact with NEW calls, but {@link InCallService}s will see calls in
+     * this state.
+     */
+    public static final int NEW = 0;
+
+    /**
+     * The initial state of an outgoing {@code Call}.
+     * Common transitions are to {@link #DIALING} state for a successful call or
+     * {@link #DISCONNECTED} if it failed.
+     */
+    public static final int CONNECTING = 1;
+
+    /**
+     * Indicates that the call is about to go into the outgoing and dialing state but is waiting for
+     * user input before it proceeds. For example, where no default {@link PhoneAccount} is set,
+     * this is the state where the InCallUI is waiting for the user to select a
+     * {@link PhoneAccount} to call from.
+     */
+    public static final int PRE_DIAL_WAIT = 2;
+
+    /**
+     * Indicates that a call is outgoing and in the dialing state. A call transitions to this state
+     * once an outgoing call has begun (e.g., user presses the dial button in Dialer). Calls in this
+     * state usually transition to {@link #ACTIVE} if the call was answered or {@link #DISCONNECTED}
+     * if the call was disconnected somehow (e.g., failure or cancellation of the call by the user).
+     */
+    public static final int DIALING = 3;
+
+    /**
+     * Indicates that a call is incoming and the user still has the option of answering, rejecting,
+     * or doing nothing with the call. This state is usually associated with some type of audible
+     * ringtone. Normal transitions are to {@link #ACTIVE} if answered or {@link #DISCONNECTED}
+     * otherwise.
+     */
+    public static final int RINGING = 4;
+
+    /**
+     * Indicates that a call is currently connected to another party and a communication channel is
+     * open between them. The normal transition to this state is by the user answering a
+     * {@link #DIALING} call or a {@link #RINGING} call being answered by the other party.
+     */
+    public static final int ACTIVE = 5;
+
+    /**
+     * Indicates that the call is currently on hold. In this state, the call is not terminated
+     * but no communication is allowed until the call is no longer on hold. The typical transition
+     * to this state is by the user putting an {@link #ACTIVE} call on hold by explicitly performing
+     * an action, such as clicking the hold button.
+     */
+    public static final int ON_HOLD = 6;
+
+    /**
+     * Indicates that a call is currently disconnected. All states can transition to this state
+     * by the call service giving notice that the connection has been severed. When the user
+     * explicitly ends a call, it will not transition to this state until the call service confirms
+     * the disconnection or communication was lost to the call service currently responsible for
+     * this call (e.g., call service crashes).
+     */
+    public static final int DISCONNECTED = 7;
+
+    /**
+     * Indicates that the call was attempted (mostly in the context of outgoing, at least at the
+     * time of writing) but cancelled before it was successfully connected.
+     */
+    public static final int ABORTED = 8;
+
+    public static String toString(int callState) {
+        switch (callState) {
+            case NEW:
+                return "NEW";
+            case CONNECTING:
+                return "CONNECTING";
+            case PRE_DIAL_WAIT:
+                return "PRE_DIAL_WAIT";
+            case DIALING:
+                return "DIALING";
+            case RINGING:
+                return "RINGING";
+            case ACTIVE:
+                return "ACTIVE";
+            case ON_HOLD:
+                return "ON_HOLD";
+            case DISCONNECTED:
+                return "DISCONNECTED";
+            case ABORTED:
+                return "ABORTED";
+            default:
+                return "UNKNOWN";
+        }
+    }
+}
diff --git a/telecomm/java/android/telecom/CameraCapabilities.aidl b/telecomm/java/android/telecom/CameraCapabilities.aidl
new file mode 100644
index 0000000..c8e0c5e
--- /dev/null
+++ b/telecomm/java/android/telecom/CameraCapabilities.aidl
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.telecom;
+
+/**
+ * {@hide}
+ */
+parcelable CameraCapabilities;
diff --git a/telecomm/java/android/telecom/CameraCapabilities.java b/telecomm/java/android/telecom/CameraCapabilities.java
new file mode 100644
index 0000000..f968c13
--- /dev/null
+++ b/telecomm/java/android/telecom/CameraCapabilities.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.telecom;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Represents the camera capabilities important to a Video Telephony provider.
+ * @hide
+ */
+public final class CameraCapabilities implements Parcelable {
+
+    /**
+     * Whether the camera supports zoom.
+     */
+    private final boolean mZoomSupported;
+
+    /**
+     * The maximum zoom supported by the camera.
+     */
+    private final float mMaxZoom;
+
+    /**
+     * The width of the camera video in pixels.
+     */
+    private final int mWidth;
+
+    /**
+     * The height of the camera video in pixels.
+     */
+    private final int mHeight;
+
+    /**
+     * Create a call camera capabilities instance.
+     *
+     * @param zoomSupported True when camera supports zoom.
+     * @param maxZoom Maximum zoom supported by camera.
+     * @param width The width of the camera video (in pixels).
+     * @param height The height of the camera video (in pixels).
+     */
+    public CameraCapabilities(boolean zoomSupported, float maxZoom, int width, int height) {
+        mZoomSupported = zoomSupported;
+        mMaxZoom = maxZoom;
+        mWidth = width;
+        mHeight = height;
+    }
+
+    /**
+     * Responsible for creating CallCameraCapabilities objects from deserialized Parcels.
+     **/
+    public static final Parcelable.Creator<CameraCapabilities> CREATOR =
+            new Parcelable.Creator<CameraCapabilities> () {
+                /**
+                 * Creates a CallCameraCapabilities instances from a parcel.
+                 *
+                 * @param source The parcel.
+                 * @return The CallCameraCapabilities.
+                 */
+                @Override
+                public CameraCapabilities createFromParcel(Parcel source) {
+                    boolean supportsZoom = source.readByte() != 0;
+                    float maxZoom = source.readFloat();
+                    int width = source.readInt();
+                    int height = source.readInt();
+
+                    return new CameraCapabilities(supportsZoom, maxZoom, width, height);
+                }
+
+                @Override
+                public CameraCapabilities[] newArray(int size) {
+                    return new CameraCapabilities[size];
+                }
+            };
+
+    /**
+     * Describe the kinds of special objects contained in this Parcelable's
+     * marshalled representation.
+     *
+     * @return a bitmask indicating the set of special object types marshalled
+     * by the Parcelable.
+     */
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * Flatten this object in to a Parcel.
+     *
+     * @param dest  The Parcel in which the object should be written.
+     * @param flags Additional flags about how the object should be written.
+     *              May be 0 or {@link #PARCELABLE_WRITE_RETURN_VALUE}.
+     */
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeByte((byte) (isZoomSupported() ? 1 : 0));
+        dest.writeFloat(getMaxZoom());
+        dest.writeInt(getWidth());
+        dest.writeInt(getHeight());
+    }
+
+    /**
+     * Whether the camera supports zoom.
+     */
+    public boolean isZoomSupported() {
+        return mZoomSupported;
+    }
+
+    /**
+     * The maximum zoom supported by the camera.
+     */
+    public float getMaxZoom() {
+        return mMaxZoom;
+    }
+
+    /**
+     * The width of the camera video in pixels.
+     */
+    public int getWidth() {
+        return mWidth;
+    }
+
+    /**
+     * The height of the camera video in pixels.
+     */
+    public int getHeight() {
+        return mHeight;
+    }
+}
diff --git a/telecomm/java/android/telecom/Conference.java b/telecomm/java/android/telecom/Conference.java
new file mode 100644
index 0000000..ca85446
--- /dev/null
+++ b/telecomm/java/android/telecom/Conference.java
@@ -0,0 +1,275 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telecom;
+
+import android.telephony.DisconnectCause;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.CopyOnWriteArraySet;
+
+/**
+ * Represents a conference call which can contain any number of {@link Connection} objects.
+ */
+public abstract class Conference {
+
+    /** @hide */
+    public abstract static class Listener {
+        public void onStateChanged(Conference conference, int oldState, int newState) {}
+        public void onDisconnected(Conference conference, int cause, String message) {}
+        public void onConnectionAdded(Conference conference, Connection connection) {}
+        public void onConnectionRemoved(Conference conference, Connection connection) {}
+        public void onDestroyed(Conference conference) {}
+        public void onCapabilitiesChanged(Conference conference, int capabilities) {}
+    }
+
+    private final Set<Listener> mListeners = new CopyOnWriteArraySet<>();
+    private final List<Connection> mChildConnections = new CopyOnWriteArrayList<>();
+    private final List<Connection> mUnmodifiableChildConnections =
+            Collections.unmodifiableList(mChildConnections);
+
+    private PhoneAccountHandle mPhoneAccount;
+    private int mState = Connection.STATE_NEW;
+    private int mDisconnectCause = DisconnectCause.NOT_VALID;
+    private int mCapabilities;
+    private String mDisconnectMessage;
+
+    /**
+     * Constructs a new Conference with a mandatory {@link PhoneAccountHandle}
+     *
+     * @param phoneAccount The {@code PhoneAccountHandle} associated with the conference.
+     */
+    public Conference(PhoneAccountHandle phoneAccount) {
+        mPhoneAccount = phoneAccount;
+    }
+
+    /**
+     * Returns the {@link PhoneAccountHandle} the conference call is being placed through.
+     *
+     * @return A {@code PhoneAccountHandle} object representing the PhoneAccount of the conference.
+     */
+    public final PhoneAccountHandle getPhoneAccountHandle() {
+        return mPhoneAccount;
+    }
+
+    /**
+     * Returns the list of connections currently associated with the conference call.
+     *
+     * @return A list of {@code Connection} objects which represent the children of the conference.
+     */
+    public final List<Connection> getConnections() {
+        return mUnmodifiableChildConnections;
+    }
+
+    /**
+     * Gets the state of the conference call. See {@link Connection} for valid values.
+     *
+     * @return A constant representing the state the conference call is currently in.
+     */
+    public final int getState() {
+        return mState;
+    }
+
+    /**
+     * Returns the capabilities of a conference. See {@link PhoneCapabilities} for valid values.
+     *
+     * @return A bitmask of the {@code PhoneCapabilities} of the conference call.
+     */
+    public final int getCapabilities() {
+        return mCapabilities;
+    }
+
+    /**
+     * Invoked when the Conference and all it's {@link Connection}s should be disconnected.
+     */
+    public void onDisconnect() {}
+
+    /**
+     * Invoked when the specified {@link Connection} should be separated from the conference call.
+     *
+     * @param connection The connection to separate.
+     */
+    public void onSeparate(Connection connection) {}
+
+    /**
+     * Invoked when the conference should be put on hold.
+     */
+    public void onHold() {}
+
+    /**
+     * Invoked when the conference should be moved from hold to active.
+     */
+    public void onUnhold() {}
+
+    /**
+     * Invoked when the child calls should be merged. Only invoked if the conference contains the
+     * capability {@link PhoneCapabilities#MERGE_CONFERENCE}.
+     */
+    public void onMerge() {}
+
+    /**
+     * Invoked when the child calls should be swapped. Only invoked if the conference contains the
+     * capability {@link PhoneCapabilities#SWAP_CONFERENCE}.
+     */
+    public void onSwap() {}
+
+    /**
+     * Sets state to be on hold.
+     */
+    public final void setOnHold() {
+        setState(Connection.STATE_HOLDING);
+    }
+
+    /**
+     * Sets state to be active.
+     */
+    public final void setActive() {
+        setState(Connection.STATE_ACTIVE);
+    }
+
+    /**
+     * Sets state to disconnected.
+     *
+     * @param cause The reason for the disconnection, any of
+     *         {@link android.telephony.DisconnectCause}.
+     * @param message Optional call-service-provided message about the disconnect.
+     */
+    public final void setDisconnected(int cause, String message) {
+        mDisconnectCause = cause;
+        mDisconnectMessage = message;
+        setState(Connection.STATE_DISCONNECTED);
+        for (Listener l : mListeners) {
+            l.onDisconnected(this, mDisconnectCause, mDisconnectMessage);
+        }
+    }
+
+    /**
+     * Sets the capabilities of a conference. See {@link PhoneCapabilities} for valid values.
+     *
+     * @param capabilities A bitmask of the {@code PhoneCapabilities} of the conference call.
+     */
+    public final void setCapabilities(int capabilities) {
+        if (capabilities != mCapabilities) {
+            mCapabilities = capabilities;
+
+            for (Listener l : mListeners) {
+                l.onCapabilitiesChanged(this, mCapabilities);
+            }
+        }
+    }
+
+    /**
+     * Adds the specified connection as a child of this conference.
+     *
+     * @param connection The connection to add.
+     * @return True if the connection was successfully added.
+     */
+    public final boolean addConnection(Connection connection) {
+        if (connection != null && !mChildConnections.contains(connection)) {
+            if (connection.setConference(this)) {
+                mChildConnections.add(connection);
+                for (Listener l : mListeners) {
+                    l.onConnectionAdded(this, connection);
+                }
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Removes the specified connection as a child of this conference.
+     *
+     * @param connection The connection to remove.
+     */
+    public final void removeConnection(Connection connection) {
+        Log.d(this, "removing %s from %s", connection, mChildConnections);
+        if (connection != null && mChildConnections.remove(connection)) {
+            connection.resetConference();
+            for (Listener l : mListeners) {
+                l.onConnectionRemoved(this, connection);
+            }
+        }
+    }
+
+    /**
+     * Tears down the conference object and any of its current connections.
+     */
+    public final void destroy() {
+        Log.d(this, "destroying conference : %s", this);
+        // Tear down the children.
+        for (Connection connection : mChildConnections) {
+            Log.d(this, "removing connection %s", connection);
+            removeConnection(connection);
+        }
+
+        // If not yet disconnected, set the conference call as disconnected first.
+        if (mState != Connection.STATE_DISCONNECTED) {
+            Log.d(this, "setting to disconnected");
+            setDisconnected(DisconnectCause.LOCAL, null);
+        }
+
+        // ...and notify.
+        for (Listener l : mListeners) {
+            l.onDestroyed(this);
+        }
+    }
+
+    /**
+     * Add a listener to be notified of a state change.
+     *
+     * @param listener The new listener.
+     * @return This conference.
+     * @hide
+     */
+    public final Conference addListener(Listener listener) {
+        mListeners.add(listener);
+        return this;
+    }
+
+    /**
+     * Removes the specified listener.
+     *
+     * @param listener The listener to remove.
+     * @return This conference.
+     * @hide
+     */
+    public final Conference removeListener(Listener listener) {
+        mListeners.remove(listener);
+        return this;
+    }
+
+    private void setState(int newState) {
+        if (newState != Connection.STATE_ACTIVE &&
+                newState != Connection.STATE_HOLDING &&
+                newState != Connection.STATE_DISCONNECTED) {
+            Log.w(this, "Unsupported state transition for Conference call.",
+                    Connection.stateToString(newState));
+            return;
+        }
+
+        if (mState != newState) {
+            int oldState = mState;
+            mState = newState;
+            for (Listener l : mListeners) {
+                l.onStateChanged(this, oldState, newState);
+            }
+        }
+    }
+}
diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java
new file mode 100644
index 0000000..5f63af3
--- /dev/null
+++ b/telecomm/java/android/telecom/Connection.java
@@ -0,0 +1,1129 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telecom;
+
+import com.android.internal.telecom.IVideoCallback;
+import com.android.internal.telecom.IVideoProvider;
+
+import android.net.Uri;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.RemoteException;
+import android.telephony.DisconnectCause;
+import android.view.Surface;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * Represents a connection to a remote endpoint that carries voice traffic.
+ * <p>
+ * Implementations create a custom subclass of {@code Connection} and return it to the framework
+ * as the return value of
+ * {@link ConnectionService#onCreateIncomingConnection(PhoneAccountHandle, ConnectionRequest)}
+ * or
+ * {@link ConnectionService#onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest)}.
+ * Implementations are then responsible for updating the state of the {@code Connection}, and
+ * must call {@link #destroy()} to signal to the framework that the {@code Connection} is no
+ * longer used and associated resources may be recovered.
+ */
+public abstract class Connection {
+
+    public static final int STATE_INITIALIZING = 0;
+
+    public static final int STATE_NEW = 1;
+
+    public static final int STATE_RINGING = 2;
+
+    public static final int STATE_DIALING = 3;
+
+    public static final int STATE_ACTIVE = 4;
+
+    public static final int STATE_HOLDING = 5;
+
+    public static final int STATE_DISCONNECTED = 6;
+
+    // Flag controlling whether PII is emitted into the logs
+    private static final boolean PII_DEBUG = Log.isLoggable(android.util.Log.DEBUG);
+
+    /** @hide */
+    public abstract static class Listener {
+        public void onStateChanged(Connection c, int state) {}
+        public void onAddressChanged(Connection c, Uri newAddress, int presentation) {}
+        public void onCallerDisplayNameChanged(
+                Connection c, String callerDisplayName, int presentation) {}
+        public void onVideoStateChanged(Connection c, int videoState) {}
+        public void onDisconnected(Connection c, int cause, String message) {}
+        public void onPostDialWait(Connection c, String remaining) {}
+        public void onRingbackRequested(Connection c, boolean ringback) {}
+        public void onDestroyed(Connection c) {}
+        public void onCallCapabilitiesChanged(Connection c, int callCapabilities) {}
+        public void onVideoProviderChanged(
+                Connection c, VideoProvider videoProvider) {}
+        public void onAudioModeIsVoipChanged(Connection c, boolean isVoip) {}
+        public void onStatusHintsChanged(Connection c, StatusHints statusHints) {}
+        public void onConferenceableConnectionsChanged(
+                Connection c, List<Connection> conferenceableConnections) {}
+        public void onConferenceChanged(Connection c, Conference conference) {}
+    }
+
+    /** @hide */
+    public static abstract class VideoProvider {
+
+        /**
+         * Video is not being received (no protocol pause was issued).
+         */
+        public static final int SESSION_EVENT_RX_PAUSE = 1;
+
+        /**
+         * Video reception has resumed after a SESSION_EVENT_RX_PAUSE.
+         */
+        public static final int SESSION_EVENT_RX_RESUME = 2;
+
+        /**
+         * Video transmission has begun. This occurs after a negotiated start of video transmission
+         * when the underlying protocol has actually begun transmitting video to the remote party.
+         */
+        public static final int SESSION_EVENT_TX_START = 3;
+
+        /**
+         * Video transmission has stopped. This occurs after a negotiated stop of video transmission
+         * when the underlying protocol has actually stopped transmitting video to the remote party.
+         */
+        public static final int SESSION_EVENT_TX_STOP = 4;
+
+        /**
+         * A camera failure has occurred for the selected camera.  The In-Call UI can use this as a
+         * cue to inform the user the camera is not available.
+         */
+        public static final int SESSION_EVENT_CAMERA_FAILURE = 5;
+
+        /**
+         * Issued after {@code SESSION_EVENT_CAMERA_FAILURE} when the camera is once again ready for
+         * operation.  The In-Call UI can use this as a cue to inform the user that the camera has
+         * become available again.
+         */
+        public static final int SESSION_EVENT_CAMERA_READY = 6;
+
+        /**
+         * Session modify request was successful.
+         */
+        public static final int SESSION_MODIFY_REQUEST_SUCCESS = 1;
+
+        /**
+         * Session modify request failed.
+         */
+        public static final int SESSION_MODIFY_REQUEST_FAIL = 2;
+
+        /**
+         * Session modify request ignored due to invalid parameters.
+         */
+        public static final int SESSION_MODIFY_REQUEST_INVALID = 3;
+
+        private static final int MSG_SET_VIDEO_CALLBACK = 1;
+        private static final int MSG_SET_CAMERA = 2;
+        private static final int MSG_SET_PREVIEW_SURFACE = 3;
+        private static final int MSG_SET_DISPLAY_SURFACE = 4;
+        private static final int MSG_SET_DEVICE_ORIENTATION = 5;
+        private static final int MSG_SET_ZOOM = 6;
+        private static final int MSG_SEND_SESSION_MODIFY_REQUEST = 7;
+        private static final int MSG_SEND_SESSION_MODIFY_RESPONSE = 8;
+        private static final int MSG_REQUEST_CAMERA_CAPABILITIES = 9;
+        private static final int MSG_REQUEST_CALL_DATA_USAGE = 10;
+        private static final int MSG_SET_PAUSE_IMAGE = 11;
+
+        private final VideoProvider.VideoProviderHandler
+                mMessageHandler = new VideoProvider.VideoProviderHandler();
+        private final VideoProvider.VideoProviderBinder mBinder;
+        private IVideoCallback mVideoCallback;
+
+        /**
+         * Default handler used to consolidate binder method calls onto a single thread.
+         */
+        private final class VideoProviderHandler extends Handler {
+            @Override
+            public void handleMessage(Message msg) {
+                switch (msg.what) {
+                    case MSG_SET_VIDEO_CALLBACK:
+                        mVideoCallback = IVideoCallback.Stub.asInterface((IBinder) msg.obj);
+                        break;
+                    case MSG_SET_CAMERA:
+                        onSetCamera((String) msg.obj);
+                        break;
+                    case MSG_SET_PREVIEW_SURFACE:
+                        onSetPreviewSurface((Surface) msg.obj);
+                        break;
+                    case MSG_SET_DISPLAY_SURFACE:
+                        onSetDisplaySurface((Surface) msg.obj);
+                        break;
+                    case MSG_SET_DEVICE_ORIENTATION:
+                        onSetDeviceOrientation(msg.arg1);
+                        break;
+                    case MSG_SET_ZOOM:
+                        onSetZoom((Float) msg.obj);
+                        break;
+                    case MSG_SEND_SESSION_MODIFY_REQUEST:
+                        onSendSessionModifyRequest((VideoProfile) msg.obj);
+                        break;
+                    case MSG_SEND_SESSION_MODIFY_RESPONSE:
+                        onSendSessionModifyResponse((VideoProfile) msg.obj);
+                        break;
+                    case MSG_REQUEST_CAMERA_CAPABILITIES:
+                        onRequestCameraCapabilities();
+                        break;
+                    case MSG_REQUEST_CALL_DATA_USAGE:
+                        onRequestCallDataUsage();
+                        break;
+                    case MSG_SET_PAUSE_IMAGE:
+                        onSetPauseImage((String) msg.obj);
+                        break;
+                    default:
+                        break;
+                }
+            }
+        }
+
+        /**
+         * IVideoProvider stub implementation.
+         */
+        private final class VideoProviderBinder extends IVideoProvider.Stub {
+            public void setVideoCallback(IBinder videoCallbackBinder) {
+                mMessageHandler.obtainMessage(
+                        MSG_SET_VIDEO_CALLBACK, videoCallbackBinder).sendToTarget();
+            }
+
+            public void setCamera(String cameraId) {
+                mMessageHandler.obtainMessage(MSG_SET_CAMERA, cameraId).sendToTarget();
+            }
+
+            public void setPreviewSurface(Surface surface) {
+                mMessageHandler.obtainMessage(MSG_SET_PREVIEW_SURFACE, surface).sendToTarget();
+            }
+
+            public void setDisplaySurface(Surface surface) {
+                mMessageHandler.obtainMessage(MSG_SET_DISPLAY_SURFACE, surface).sendToTarget();
+            }
+
+            public void setDeviceOrientation(int rotation) {
+                mMessageHandler.obtainMessage(MSG_SET_DEVICE_ORIENTATION, rotation).sendToTarget();
+            }
+
+            public void setZoom(float value) {
+                mMessageHandler.obtainMessage(MSG_SET_ZOOM, value).sendToTarget();
+            }
+
+            public void sendSessionModifyRequest(VideoProfile requestProfile) {
+                mMessageHandler.obtainMessage(
+                        MSG_SEND_SESSION_MODIFY_REQUEST, requestProfile).sendToTarget();
+            }
+
+            public void sendSessionModifyResponse(VideoProfile responseProfile) {
+                mMessageHandler.obtainMessage(
+                        MSG_SEND_SESSION_MODIFY_RESPONSE, responseProfile).sendToTarget();
+            }
+
+            public void requestCameraCapabilities() {
+                mMessageHandler.obtainMessage(MSG_REQUEST_CAMERA_CAPABILITIES).sendToTarget();
+            }
+
+            public void requestCallDataUsage() {
+                mMessageHandler.obtainMessage(MSG_REQUEST_CALL_DATA_USAGE).sendToTarget();
+            }
+
+            public void setPauseImage(String uri) {
+                mMessageHandler.obtainMessage(MSG_SET_PAUSE_IMAGE, uri).sendToTarget();
+            }
+        }
+
+        public VideoProvider() {
+            mBinder = new VideoProvider.VideoProviderBinder();
+        }
+
+        /**
+         * Returns binder object which can be used across IPC methods.
+         * @hide
+         */
+        public final IVideoProvider getInterface() {
+            return mBinder;
+        }
+
+        /**
+         * Sets the camera to be used for video recording in a video call.
+         *
+         * @param cameraId The id of the camera.
+         */
+        public abstract void onSetCamera(String cameraId);
+
+        /**
+         * Sets the surface to be used for displaying a preview of what the user's camera is
+         * currently capturing.  When video transmission is enabled, this is the video signal which
+         * is sent to the remote device.
+         *
+         * @param surface The surface.
+         */
+        public abstract void onSetPreviewSurface(Surface surface);
+
+        /**
+         * Sets the surface to be used for displaying the video received from the remote device.
+         *
+         * @param surface The surface.
+         */
+        public abstract void onSetDisplaySurface(Surface surface);
+
+        /**
+         * Sets the device orientation, in degrees.  Assumes that a standard portrait orientation of
+         * the device is 0 degrees.
+         *
+         * @param rotation The device orientation, in degrees.
+         */
+        public abstract void onSetDeviceOrientation(int rotation);
+
+        /**
+         * Sets camera zoom ratio.
+         *
+         * @param value The camera zoom ratio.
+         */
+        public abstract void onSetZoom(float value);
+
+        /**
+         * Issues a request to modify the properties of the current session.  The request is
+         * sent to the remote device where it it handled by the In-Call UI.
+         * Some examples of session modification requests: upgrade call from audio to video,
+         * downgrade call from video to audio, pause video.
+         *
+         * @param requestProfile The requested call video properties.
+         */
+        public abstract void onSendSessionModifyRequest(VideoProfile requestProfile);
+
+        /**te
+         * Provides a response to a request to change the current call session video
+         * properties.
+         * This is in response to a request the InCall UI has received via the InCall UI.
+         *
+         * @param responseProfile The response call video properties.
+         */
+        public abstract void onSendSessionModifyResponse(VideoProfile responseProfile);
+
+        /**
+         * Issues a request to the video provider to retrieve the camera capabilities.
+         * Camera capabilities are reported back to the caller via the In-Call UI.
+         */
+        public abstract void onRequestCameraCapabilities();
+
+        /**
+         * Issues a request to the video telephony framework to retrieve the cumulative data usage
+         * for the current call.  Data usage is reported back to the caller via the
+         * InCall UI.
+         */
+        public abstract void onRequestCallDataUsage();
+
+        /**
+         * Provides the video telephony framework with the URI of an image to be displayed to remote
+         * devices when the video signal is paused.
+         *
+         * @param uri URI of image to display.
+         */
+        public abstract void onSetPauseImage(String uri);
+
+        /**
+         * Invokes callback method defined in In-Call UI.
+         *
+         * @param videoProfile The requested video call profile.
+         */
+        public void receiveSessionModifyRequest(VideoProfile videoProfile) {
+            if (mVideoCallback != null) {
+                try {
+                    mVideoCallback.receiveSessionModifyRequest(videoProfile);
+                } catch (RemoteException ignored) {
+                }
+            }
+        }
+
+        /**
+         * Invokes callback method defined in In-Call UI.
+         *
+         * @param status Status of the session modify request.  Valid values are
+         *               {@link VideoProvider#SESSION_MODIFY_REQUEST_SUCCESS},
+         *               {@link VideoProvider#SESSION_MODIFY_REQUEST_FAIL},
+         *               {@link VideoProvider#SESSION_MODIFY_REQUEST_INVALID}
+         * @param requestedProfile The original request which was sent to the remote device.
+         * @param responseProfile The actual profile changes made by the remote device.
+         */
+        public void receiveSessionModifyResponse(int status,
+                VideoProfile requestedProfile, VideoProfile responseProfile) {
+            if (mVideoCallback != null) {
+                try {
+                    mVideoCallback.receiveSessionModifyResponse(
+                            status, requestedProfile, responseProfile);
+                } catch (RemoteException ignored) {
+                }
+            }
+        }
+
+        /**
+         * Invokes callback method defined in In-Call UI.
+         *
+         * Valid values are: {@link VideoProvider#SESSION_EVENT_RX_PAUSE},
+         * {@link VideoProvider#SESSION_EVENT_RX_RESUME},
+         * {@link VideoProvider#SESSION_EVENT_TX_START},
+         * {@link VideoProvider#SESSION_EVENT_TX_STOP}
+         *
+         * @param event The event.
+         */
+        public void handleCallSessionEvent(int event) {
+            if (mVideoCallback != null) {
+                try {
+                    mVideoCallback.handleCallSessionEvent(event);
+                } catch (RemoteException ignored) {
+                }
+            }
+        }
+
+        /**
+         * Invokes callback method defined in In-Call UI.
+         *
+         * @param width  The updated peer video width.
+         * @param height The updated peer video height.
+         */
+        public void changePeerDimensions(int width, int height) {
+            if (mVideoCallback != null) {
+                try {
+                    mVideoCallback.changePeerDimensions(width, height);
+                } catch (RemoteException ignored) {
+                }
+            }
+        }
+
+        /**
+         * Invokes callback method defined in In-Call UI.
+         *
+         * @param dataUsage The updated data usage.
+         */
+        public void changeCallDataUsage(int dataUsage) {
+            if (mVideoCallback != null) {
+                try {
+                    mVideoCallback.changeCallDataUsage(dataUsage);
+                } catch (RemoteException ignored) {
+                }
+            }
+        }
+
+        /**
+         * Invokes callback method defined in In-Call UI.
+         *
+         * @param cameraCapabilities The changed camera capabilities.
+         */
+        public void changeCameraCapabilities(CameraCapabilities cameraCapabilities) {
+            if (mVideoCallback != null) {
+                try {
+                    mVideoCallback.changeCameraCapabilities(cameraCapabilities);
+                } catch (RemoteException ignored) {
+                }
+            }
+        }
+    }
+
+    private final Listener mConnectionDeathListener = new Listener() {
+        @Override
+        public void onDestroyed(Connection c) {
+            if (mConferenceableConnections.remove(c)) {
+                fireOnConferenceableConnectionsChanged();
+            }
+        }
+    };
+
+    /**
+     * ConcurrentHashMap constructor params: 8 is initial table size, 0.9f is
+     * load factor before resizing, 1 means we only expect a single thread to
+     * access the map so make only a single shard
+     */
+    private final Set<Listener> mListeners = Collections.newSetFromMap(
+            new ConcurrentHashMap<Listener, Boolean>(8, 0.9f, 1));
+    private final List<Connection> mConferenceableConnections = new ArrayList<>();
+    private final List<Connection> mUnmodifiableConferenceableConnections =
+            Collections.unmodifiableList(mConferenceableConnections);
+
+    private int mState = STATE_NEW;
+    private AudioState mAudioState;
+    private Uri mAddress;
+    private int mAddressPresentation;
+    private String mCallerDisplayName;
+    private int mCallerDisplayNamePresentation;
+    private boolean mRingbackRequested = false;
+    private int mCallCapabilities;
+    private VideoProvider mVideoProvider;
+    private boolean mAudioModeIsVoip;
+    private StatusHints mStatusHints;
+    private int mVideoState;
+    private int mDisconnectCause;
+    private String mDisconnectMessage;
+    private Conference mConference;
+    private ConnectionService mConnectionService;
+
+    /**
+     * Create a new Connection.
+     */
+    public Connection() {}
+
+    /**
+     * @return The address (e.g., phone number) to which this Connection is currently communicating.
+     */
+    public final Uri getAddress() {
+        return mAddress;
+    }
+
+    /**
+     * @return The presentation requirements for the address.
+     *         See {@link TelecomManager} for valid values.
+     */
+    public final int getAddressPresentation() {
+        return mAddressPresentation;
+    }
+
+    /**
+     * @return The caller display name (CNAP).
+     */
+    public final String getCallerDisplayName() {
+        return mCallerDisplayName;
+    }
+
+    /**
+     * @return The presentation requirements for the handle.
+     *         See {@link TelecomManager} for valid values.
+     */
+    public final int getCallerDisplayNamePresentation() {
+        return mCallerDisplayNamePresentation;
+    }
+
+    /**
+     * @return The state of this Connection.
+     */
+    public final int getState() {
+        return mState;
+    }
+
+    /**
+     * Returns the video state of the call.
+     * Valid values: {@link VideoProfile.VideoState#AUDIO_ONLY},
+     * {@link VideoProfile.VideoState#BIDIRECTIONAL},
+     * {@link VideoProfile.VideoState#TX_ENABLED},
+     * {@link VideoProfile.VideoState#RX_ENABLED}.
+     *
+     * @return The video state of the call.
+     * @hide
+     */
+    public final int getVideoState() {
+        return mVideoState;
+    }
+
+    /**
+     * @return The audio state of the call, describing how its audio is currently
+     *         being routed by the system. This is {@code null} if this Connection
+     *         does not directly know about its audio state.
+     */
+    public final AudioState getAudioState() {
+        return mAudioState;
+    }
+
+    /**
+     * @return The conference that this connection is a part of.  Null if it is not part of any
+     *         conference.
+     */
+    public final Conference getConference() {
+        return mConference;
+    }
+
+    /**
+     * Returns whether this connection is requesting that the system play a ringback tone
+     * on its behalf.
+     */
+    public final boolean isRingbackRequested() {
+        return mRingbackRequested;
+    }
+
+    /**
+     * @return True if the connection's audio mode is VOIP.
+     */
+    public final boolean getAudioModeIsVoip() {
+        return mAudioModeIsVoip;
+    }
+
+    /**
+     * @return The status hints for this connection.
+     */
+    public final StatusHints getStatusHints() {
+        return mStatusHints;
+    }
+
+    /**
+     * Assign a listener to be notified of state changes.
+     *
+     * @param l A listener.
+     * @return This Connection.
+     *
+     * @hide
+     */
+    public final Connection addConnectionListener(Listener l) {
+        mListeners.add(l);
+        return this;
+    }
+
+    /**
+     * Remove a previously assigned listener that was being notified of state changes.
+     *
+     * @param l A Listener.
+     * @return This Connection.
+     *
+     * @hide
+     */
+    public final Connection removeConnectionListener(Listener l) {
+        if (l != null) {
+            mListeners.remove(l);
+        }
+        return this;
+    }
+
+    /**
+     * @return The {@link DisconnectCause} for this connection.
+     */
+    public final int getDisconnectCause() {
+        return mDisconnectCause;
+    }
+
+    /**
+     * @return The disconnect message for this connection.
+     */
+    public final String getDisconnectMessage() {
+        return mDisconnectMessage;
+    }
+
+    /**
+     * Inform this Connection that the state of its audio output has been changed externally.
+     *
+     * @param state The new audio state.
+     * @hide
+     */
+    final void setAudioState(AudioState state) {
+        Log.d(this, "setAudioState %s", state);
+        mAudioState = state;
+        onAudioStateChanged(state);
+    }
+
+    /**
+     * @param state An integer value of a {@code STATE_*} constant.
+     * @return A string representation of the value.
+     */
+    public static String stateToString(int state) {
+        switch (state) {
+            case STATE_INITIALIZING:
+                return "STATE_INITIALIZING";
+            case STATE_NEW:
+                return "STATE_NEW";
+            case STATE_RINGING:
+                return "STATE_RINGING";
+            case STATE_DIALING:
+                return "STATE_DIALING";
+            case STATE_ACTIVE:
+                return "STATE_ACTIVE";
+            case STATE_HOLDING:
+                return "STATE_HOLDING";
+            case STATE_DISCONNECTED:
+                return "DISCONNECTED";
+            default:
+                Log.wtf(Connection.class, "Unknown state %d", state);
+                return "UNKNOWN";
+        }
+    }
+
+    /**
+     * Returns the connection's {@link PhoneCapabilities}
+     */
+    public final int getCallCapabilities() {
+        return mCallCapabilities;
+    }
+
+    /**
+     * Sets the value of the {@link #getAddress()} property.
+     *
+     * @param address The new address.
+     * @param presentation The presentation requirements for the address.
+     *        See {@link TelecomManager} for valid values.
+     */
+    public final void setAddress(Uri address, int presentation) {
+        Log.d(this, "setAddress %s", address);
+        mAddress = address;
+        mAddressPresentation = presentation;
+        for (Listener l : mListeners) {
+            l.onAddressChanged(this, address, presentation);
+        }
+    }
+
+    /**
+     * Sets the caller display name (CNAP).
+     *
+     * @param callerDisplayName The new display name.
+     * @param presentation The presentation requirements for the handle.
+     *        See {@link TelecomManager} for valid values.
+     */
+    public final void setCallerDisplayName(String callerDisplayName, int presentation) {
+        Log.d(this, "setCallerDisplayName %s", callerDisplayName);
+        mCallerDisplayName = callerDisplayName;
+        mCallerDisplayNamePresentation = presentation;
+        for (Listener l : mListeners) {
+            l.onCallerDisplayNameChanged(this, callerDisplayName, presentation);
+        }
+    }
+
+    /**
+     * Set the video state for the connection.
+     * Valid values: {@link VideoProfile.VideoState#AUDIO_ONLY},
+     * {@link VideoProfile.VideoState#BIDIRECTIONAL},
+     * {@link VideoProfile.VideoState#TX_ENABLED},
+     * {@link VideoProfile.VideoState#RX_ENABLED}.
+     *
+     * @param videoState The new video state.
+     * @hide
+     */
+    public final void setVideoState(int videoState) {
+        Log.d(this, "setVideoState %d", videoState);
+        mVideoState = videoState;
+        for (Listener l : mListeners) {
+            l.onVideoStateChanged(this, mVideoState);
+        }
+    }
+
+    /**
+     * Sets state to active (e.g., an ongoing call where two or more parties can actively
+     * communicate).
+     */
+    public final void setActive() {
+        setRingbackRequested(false);
+        setState(STATE_ACTIVE);
+    }
+
+    /**
+     * Sets state to ringing (e.g., an inbound ringing call).
+     */
+    public final void setRinging() {
+        setState(STATE_RINGING);
+    }
+
+    /**
+     * Sets state to initializing (this Connection is not yet ready to be used).
+     */
+    public final void setInitializing() {
+        setState(STATE_INITIALIZING);
+    }
+
+    /**
+     * Sets state to initialized (the Connection has been set up and is now ready to be used).
+     */
+    public final void setInitialized() {
+        setState(STATE_NEW);
+    }
+
+    /**
+     * Sets state to dialing (e.g., dialing an outbound call).
+     */
+    public final void setDialing() {
+        setState(STATE_DIALING);
+    }
+
+    /**
+     * Sets state to be on hold.
+     */
+    public final void setOnHold() {
+        setState(STATE_HOLDING);
+    }
+
+    /**
+     * Sets the video call provider.
+     * @param videoProvider The video provider.
+     * @hide
+     */
+    public final void setVideoProvider(VideoProvider videoProvider) {
+        mVideoProvider = videoProvider;
+        for (Listener l : mListeners) {
+            l.onVideoProviderChanged(this, videoProvider);
+        }
+    }
+
+    /** @hide */
+    public final VideoProvider getVideoProvider() {
+        return mVideoProvider;
+    }
+
+    /**
+     * Sets state to disconnected.
+     *
+     * @param cause The reason for the disconnection, any of
+     *         {@link DisconnectCause}.
+     * @param message Optional call-service-provided message about the disconnect.
+     */
+    public final void setDisconnected(int cause, String message) {
+        mDisconnectCause = cause;
+        mDisconnectMessage = message;
+        setState(STATE_DISCONNECTED);
+        Log.d(this, "Disconnected with cause %d message %s", cause, message);
+        for (Listener l : mListeners) {
+            l.onDisconnected(this, cause, message);
+        }
+    }
+
+    /**
+     * TODO: Needs documentation.
+     */
+    public final void setPostDialWait(String remaining) {
+        for (Listener l : mListeners) {
+            l.onPostDialWait(this, remaining);
+        }
+    }
+
+    /**
+     * Requests that the framework play a ringback tone. This is to be invoked by implementations
+     * that do not play a ringback tone themselves in the call's audio stream.
+     *
+     * @param ringback Whether the ringback tone is to be played.
+     */
+    public final void setRingbackRequested(boolean ringback) {
+        if (mRingbackRequested != ringback) {
+            mRingbackRequested = ringback;
+            for (Listener l : mListeners) {
+                l.onRingbackRequested(this, ringback);
+            }
+        }
+    }
+
+    /**
+     * Sets the connection's {@link PhoneCapabilities}.
+     *
+     * @param callCapabilities The new call capabilities.
+     */
+    public final void setCallCapabilities(int callCapabilities) {
+        if (mCallCapabilities != callCapabilities) {
+            mCallCapabilities = callCapabilities;
+            for (Listener l : mListeners) {
+                l.onCallCapabilitiesChanged(this, mCallCapabilities);
+            }
+        }
+    }
+
+    /**
+     * Tears down the Connection object.
+     */
+    public final void destroy() {
+        for (Listener l : mListeners) {
+            l.onDestroyed(this);
+        }
+    }
+
+    /**
+     * Requests that the framework use VOIP audio mode for this connection.
+     *
+     * @param isVoip True if the audio mode is VOIP.
+     */
+    public final void setAudioModeIsVoip(boolean isVoip) {
+        mAudioModeIsVoip = isVoip;
+        for (Listener l : mListeners) {
+            l.onAudioModeIsVoipChanged(this, isVoip);
+        }
+    }
+
+    /**
+     * Sets the label and icon status to display in the in-call UI.
+     *
+     * @param statusHints The status label and icon to set.
+     */
+    public final void setStatusHints(StatusHints statusHints) {
+        mStatusHints = statusHints;
+        for (Listener l : mListeners) {
+            l.onStatusHintsChanged(this, statusHints);
+        }
+    }
+
+    /**
+     * Sets the connections with which this connection can be conferenced.
+     *
+     * @param conferenceableConnections The set of connections this connection can conference with.
+     */
+    public final void setConferenceableConnections(List<Connection> conferenceableConnections) {
+        clearConferenceableList();
+        for (Connection c : conferenceableConnections) {
+            // If statement checks for duplicates in input. It makes it N^2 but we're dealing with a
+            // small amount of items here.
+            if (!mConferenceableConnections.contains(c)) {
+                c.addConnectionListener(mConnectionDeathListener);
+                mConferenceableConnections.add(c);
+            }
+        }
+        fireOnConferenceableConnectionsChanged();
+    }
+
+    /**
+     * Obtains the connections with which this connection can be conferenced.
+     */
+    public final List<Connection> getConferenceableConnections() {
+        return mUnmodifiableConferenceableConnections;
+    }
+
+    /*
+     * @hide
+     */
+    public final void setConnectionService(ConnectionService connectionService) {
+        if (mConnectionService != null) {
+            Log.e(this, new Exception(), "Trying to set ConnectionService on a connection " +
+                    "which is already associated with another ConnectionService.");
+        } else {
+            mConnectionService = connectionService;
+        }
+    }
+
+    /**
+     * @hide
+     */
+    public final void unsetConnectionService(ConnectionService connectionService) {
+        if (mConnectionService != connectionService) {
+            Log.e(this, new Exception(), "Trying to remove ConnectionService from a Connection " +
+                    "that does not belong to the ConnectionService.");
+        } else {
+            mConnectionService = null;
+        }
+    }
+
+    /**
+     * Sets the conference that this connection is a part of. This will fail if the connection is
+     * already part of a conference call. {@link #resetConference} to un-set the conference first.
+     *
+     * @param conference The conference.
+     * @return {@code true} if the conference was successfully set.
+     * @hide
+     */
+    public final boolean setConference(Conference conference) {
+        // We check to see if it is already part of another conference.
+        if (mConference == null) {
+            mConference = conference;
+            if (mConnectionService != null && mConnectionService.containsConference(conference)) {
+                fireConferenceChanged();
+            }
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Resets the conference that this connection is a part of.
+     * @hide
+     */
+    public final void resetConference() {
+        if (mConference != null) {
+            Log.d(this, "Conference reset");
+            mConference = null;
+            fireConferenceChanged();
+        }
+    }
+
+    /**
+     * Notifies this Connection that the {@link #getAudioState()} property has a new value.
+     *
+     * @param state The new call audio state.
+     */
+    public void onAudioStateChanged(AudioState state) {}
+
+    /**
+     * Notifies this Connection of an internal state change. This method is called after the
+     * state is changed.
+     *
+     * @param state The new state, one of the {@code STATE_*} constants.
+     */
+    public void onStateChanged(int state) {}
+
+    /**
+     * Notifies this Connection of a request to play a DTMF tone.
+     *
+     * @param c A DTMF character.
+     */
+    public void onPlayDtmfTone(char c) {}
+
+    /**
+     * Notifies this Connection of a request to stop any currently playing DTMF tones.
+     */
+    public void onStopDtmfTone() {}
+
+    /**
+     * Notifies this Connection of a request to disconnect.
+     */
+    public void onDisconnect() {}
+
+    /**
+     * Notifies this Connection of a request to separate from its parent conference.
+     */
+    public void onSeparate() {}
+
+    /**
+     * Notifies this Connection of a request to abort.
+     */
+    public void onAbort() {}
+
+    /**
+     * Notifies this Connection of a request to hold.
+     */
+    public void onHold() {}
+
+    /**
+     * Notifies this Connection of a request to exit a hold state.
+     */
+    public void onUnhold() {}
+
+    /**
+     * Notifies this Connection, which is in {@link #STATE_RINGING}, of
+     * a request to accept.
+     *
+     * @param videoState The video state in which to answer the call.
+     * @hide
+     */
+    public void onAnswer(int videoState) {}
+
+    /**
+     * Notifies this Connection, which is in {@link #STATE_RINGING}, of
+     * a request to accept.
+     */
+    public void onAnswer() {
+        onAnswer(VideoProfile.VideoState.AUDIO_ONLY);
+    }
+
+    /**
+     * Notifies this Connection, which is in {@link #STATE_RINGING}, of
+     * a request to reject.
+     */
+    public void onReject() {}
+
+    /**
+     * Notifies this Connection whether the user wishes to proceed with the post-dial DTMF codes.
+     */
+    public void onPostDialContinue(boolean proceed) {}
+
+    /**
+     * Merge this connection and the specified connection into a conference call.  Once the
+     * connections are merged, the calls should be added to the an existing or new
+     * {@code Conference} instance. For new {@code Conference} instances, use
+     * {@code ConnectionService#addConference}.
+     *
+     * @param otherConnection The connection with which this connection should be conferenced.
+     */
+    public void onConferenceWith(Connection otherConnection) {}
+
+    static String toLogSafePhoneNumber(String number) {
+        // For unknown number, log empty string.
+        if (number == null) {
+            return "";
+        }
+
+        if (PII_DEBUG) {
+            // When PII_DEBUG is true we emit PII.
+            return number;
+        }
+
+        // Do exactly same thing as Uri#toSafeString() does, which will enable us to compare
+        // sanitized phone numbers.
+        StringBuilder builder = new StringBuilder();
+        for (int i = 0; i < number.length(); i++) {
+            char c = number.charAt(i);
+            if (c == '-' || c == '@' || c == '.') {
+                builder.append(c);
+            } else {
+                builder.append('x');
+            }
+        }
+        return builder.toString();
+    }
+
+    private void setState(int state) {
+        if (mState == STATE_DISCONNECTED && mState != state) {
+            Log.d(this, "Connection already DISCONNECTED; cannot transition out of this state.");
+            return;
+        }
+        if (mState != state) {
+            Log.d(this, "setState: %s", stateToString(state));
+            mState = state;
+            onStateChanged(state);
+            for (Listener l : mListeners) {
+                l.onStateChanged(this, state);
+            }
+        }
+    }
+
+    private static class FailureSignalingConnection extends Connection {
+        public FailureSignalingConnection(int cause, String message) {
+            setDisconnected(cause, message);
+        }
+    }
+
+    /**
+     * Return a {@code Connection} which represents a failed connection attempt. The returned
+     * {@code Connection} will have a {@link #getDisconnectCause()} and
+     * {@link #getDisconnectMessage()} as specified, and a {@link #getState()} of
+     * {@link #STATE_DISCONNECTED}.
+     * <p>
+     * The returned {@code Connection} can be assumed to {@link #destroy()} itself when appropriate,
+     * so users of this method need not maintain a reference to its return value to destroy it.
+     *
+     * @param cause The disconnect cause, ({@see DisconnectCause}).
+     * @param message A reason for why the connection failed (not intended to be shown to the user).
+     * @return A {@code Connection} which indicates failure.
+     */
+    public static Connection createFailedConnection(int cause, String message) {
+        return new FailureSignalingConnection(cause, message);
+    }
+
+    /**
+     * Return a {@code Connection} which represents a canceled connection attempt. The returned
+     * {@code Connection} will have state {@link #STATE_DISCONNECTED}, and cannot be moved out of
+     * that state. This connection should not be used for anything, and no other
+     * {@code Connection}s should be attempted.
+     * <p>
+     * The returned {@code Connection} can be assumed to {@link #destroy()} itself when appropriate,
+     * so users of this method need not maintain a reference to its return value to destroy it.
+     *
+     * @return A {@code Connection} which indicates that the underlying call should be canceled.
+     */
+    public static Connection createCanceledConnection() {
+        return new FailureSignalingConnection(DisconnectCause.OUTGOING_CANCELED, null);
+    }
+
+    private final void  fireOnConferenceableConnectionsChanged() {
+        for (Listener l : mListeners) {
+            l.onConferenceableConnectionsChanged(this, mConferenceableConnections);
+        }
+    }
+
+    private final void fireConferenceChanged() {
+        for (Listener l : mListeners) {
+            l.onConferenceChanged(this, mConference);
+        }
+    }
+
+    private final void clearConferenceableList() {
+        for (Connection c : mConferenceableConnections) {
+            c.removeConnectionListener(mConnectionDeathListener);
+        }
+        mConferenceableConnections.clear();
+    }
+}
diff --git a/telecomm/java/android/telecom/ConnectionRequest.aidl b/telecomm/java/android/telecom/ConnectionRequest.aidl
new file mode 100644
index 0000000..de39c67
--- /dev/null
+++ b/telecomm/java/android/telecom/ConnectionRequest.aidl
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2014, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telecom;
+
+/**
+ * {@hide}
+ */
+parcelable ConnectionRequest;
diff --git a/telecomm/java/android/telecom/ConnectionRequest.java b/telecomm/java/android/telecom/ConnectionRequest.java
new file mode 100644
index 0000000..71b481b
--- /dev/null
+++ b/telecomm/java/android/telecom/ConnectionRequest.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telecom;
+
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Simple data container encapsulating a request to some entity to
+ * create a new {@link Connection}.
+ */
+public final class ConnectionRequest implements Parcelable {
+
+    // TODO: Token to limit recursive invocations
+    private final PhoneAccountHandle mAccountHandle;
+    private final Uri mAddress;
+    private final Bundle mExtras;
+    private final int mVideoState;
+
+    /**
+     * @param accountHandle The accountHandle which should be used to place the call.
+     * @param handle The handle (e.g., phone number) to which the {@link Connection} is to connect.
+     * @param extras Application-specific extra data.
+     */
+    public ConnectionRequest(
+            PhoneAccountHandle accountHandle,
+            Uri handle,
+            Bundle extras) {
+        this(accountHandle, handle, extras, VideoProfile.VideoState.AUDIO_ONLY);
+    }
+
+    /**
+     * @param accountHandle The accountHandle which should be used to place the call.
+     * @param handle The handle (e.g., phone number) to which the {@link Connection} is to connect.
+     * @param extras Application-specific extra data.
+     * @param videoState Determines the video state for the connection.
+     * @hide
+     */
+    public ConnectionRequest(
+            PhoneAccountHandle accountHandle,
+            Uri handle,
+            Bundle extras,
+            int videoState) {
+        mAccountHandle = accountHandle;
+        mAddress = handle;
+        mExtras = extras;
+        mVideoState = videoState;
+    }
+
+    private ConnectionRequest(Parcel in) {
+        mAccountHandle = in.readParcelable(getClass().getClassLoader());
+        mAddress = in.readParcelable(getClass().getClassLoader());
+        mExtras = in.readParcelable(getClass().getClassLoader());
+        mVideoState = in.readInt();
+    }
+
+    /**
+     * The account which should be used to place the call.
+     */
+    public PhoneAccountHandle getAccountHandle() { return mAccountHandle; }
+
+    /**
+     * The handle (e.g., phone number) to which the {@link Connection} is to connect.
+     */
+    public Uri getAddress() { return mAddress; }
+
+    /**
+     * Application-specific extra data. Used for passing back information from an incoming
+     * call {@code Intent}, and for any proprietary extensions arranged between a client
+     * and servant {@code ConnectionService} which agree on a vocabulary for such data.
+     */
+    public Bundle getExtras() { return mExtras; }
+
+    /**
+     * Describes the video states supported by the client requesting the connection.
+     * Valid values: {@link VideoProfile.VideoState#AUDIO_ONLY},
+     * {@link VideoProfile.VideoState#BIDIRECTIONAL},
+     * {@link VideoProfile.VideoState#TX_ENABLED},
+     * {@link VideoProfile.VideoState#RX_ENABLED}.
+     *
+     * @return The video state for the connection.
+     * @hide
+     */
+    public int getVideoState() {
+        return mVideoState;
+    }
+
+    @Override
+    public String toString() {
+        return String.format("ConnectionRequest %s %s",
+                mAddress == null
+                        ? Uri.EMPTY
+                        : Connection.toLogSafePhoneNumber(mAddress.toString()),
+                mExtras == null ? "" : mExtras);
+    }
+
+    public static final Creator<ConnectionRequest> CREATOR = new Creator<ConnectionRequest> () {
+        @Override
+        public ConnectionRequest createFromParcel(Parcel source) {
+            return new ConnectionRequest(source);
+        }
+
+        @Override
+        public ConnectionRequest[] newArray(int size) {
+            return new ConnectionRequest[size];
+        }
+    };
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel destination, int flags) {
+        destination.writeParcelable(mAccountHandle, 0);
+        destination.writeParcelable(mAddress, 0);
+        destination.writeParcelable(mExtras, 0);
+        destination.writeInt(mVideoState);
+    }
+}
diff --git a/telecomm/java/android/telecom/ConnectionService.java b/telecomm/java/android/telecom/ConnectionService.java
new file mode 100644
index 0000000..cc80e22
--- /dev/null
+++ b/telecomm/java/android/telecom/ConnectionService.java
@@ -0,0 +1,973 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telecom;
+
+import android.annotation.SdkConstant;
+import android.app.Service;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.telephony.DisconnectCause;
+
+import com.android.internal.os.SomeArgs;
+import com.android.internal.telecom.IConnectionService;
+import com.android.internal.telecom.IConnectionServiceAdapter;
+import com.android.internal.telecom.RemoteServiceCallback;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+/**
+ * A {@link android.app.Service} that provides telephone connections to processes running on an
+ * Android device.
+ */
+public abstract class ConnectionService extends Service {
+    /**
+     * The {@link Intent} that must be declared as handled by the service.
+     */
+    @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
+    public static final String SERVICE_INTERFACE = "android.telecom.ConnectionService";
+
+    // Flag controlling whether PII is emitted into the logs
+    private static final boolean PII_DEBUG = Log.isLoggable(android.util.Log.DEBUG);
+
+    private static final int MSG_ADD_CONNECTION_SERVICE_ADAPTER = 1;
+    private static final int MSG_CREATE_CONNECTION = 2;
+    private static final int MSG_ABORT = 3;
+    private static final int MSG_ANSWER = 4;
+    private static final int MSG_REJECT = 5;
+    private static final int MSG_DISCONNECT = 6;
+    private static final int MSG_HOLD = 7;
+    private static final int MSG_UNHOLD = 8;
+    private static final int MSG_ON_AUDIO_STATE_CHANGED = 9;
+    private static final int MSG_PLAY_DTMF_TONE = 10;
+    private static final int MSG_STOP_DTMF_TONE = 11;
+    private static final int MSG_CONFERENCE = 12;
+    private static final int MSG_SPLIT_FROM_CONFERENCE = 13;
+    private static final int MSG_ON_POST_DIAL_CONTINUE = 14;
+    private static final int MSG_REMOVE_CONNECTION_SERVICE_ADAPTER = 16;
+    private static final int MSG_ANSWER_VIDEO = 17;
+    private static final int MSG_MERGE_CONFERENCE = 18;
+    private static final int MSG_SWAP_CONFERENCE = 19;
+
+    private static Connection sNullConnection;
+
+    private final Map<String, Connection> mConnectionById = new HashMap<>();
+    private final Map<Connection, String> mIdByConnection = new HashMap<>();
+    private final Map<String, Conference> mConferenceById = new HashMap<>();
+    private final Map<Conference, String> mIdByConference = new HashMap<>();
+    private final RemoteConnectionManager mRemoteConnectionManager =
+            new RemoteConnectionManager(this);
+    private final List<Runnable> mPreInitializationConnectionRequests = new ArrayList<>();
+    private final ConnectionServiceAdapter mAdapter = new ConnectionServiceAdapter();
+
+    private boolean mAreAccountsInitialized = false;
+    private Conference sNullConference;
+
+    private final IBinder mBinder = new IConnectionService.Stub() {
+        @Override
+        public void addConnectionServiceAdapter(IConnectionServiceAdapter adapter) {
+            mHandler.obtainMessage(MSG_ADD_CONNECTION_SERVICE_ADAPTER, adapter).sendToTarget();
+        }
+
+        public void removeConnectionServiceAdapter(IConnectionServiceAdapter adapter) {
+            mHandler.obtainMessage(MSG_REMOVE_CONNECTION_SERVICE_ADAPTER, adapter).sendToTarget();
+        }
+
+        @Override
+        public void createConnection(
+                PhoneAccountHandle connectionManagerPhoneAccount,
+                String id,
+                ConnectionRequest request,
+                boolean isIncoming) {
+            SomeArgs args = SomeArgs.obtain();
+            args.arg1 = connectionManagerPhoneAccount;
+            args.arg2 = id;
+            args.arg3 = request;
+            args.argi1 = isIncoming ? 1 : 0;
+            mHandler.obtainMessage(MSG_CREATE_CONNECTION, args).sendToTarget();
+        }
+
+        @Override
+        public void abort(String callId) {
+            mHandler.obtainMessage(MSG_ABORT, callId).sendToTarget();
+        }
+
+        @Override
+        /** @hide */
+        public void answerVideo(String callId, int videoState) {
+            SomeArgs args = SomeArgs.obtain();
+            args.arg1 = callId;
+            args.argi1 = videoState;
+            mHandler.obtainMessage(MSG_ANSWER_VIDEO, args).sendToTarget();
+        }
+
+        @Override
+        public void answer(String callId) {
+            mHandler.obtainMessage(MSG_ANSWER, callId).sendToTarget();
+        }
+
+        @Override
+        public void reject(String callId) {
+            mHandler.obtainMessage(MSG_REJECT, callId).sendToTarget();
+        }
+
+        @Override
+        public void disconnect(String callId) {
+            mHandler.obtainMessage(MSG_DISCONNECT, callId).sendToTarget();
+        }
+
+        @Override
+        public void hold(String callId) {
+            mHandler.obtainMessage(MSG_HOLD, callId).sendToTarget();
+        }
+
+        @Override
+        public void unhold(String callId) {
+            mHandler.obtainMessage(MSG_UNHOLD, callId).sendToTarget();
+        }
+
+        @Override
+        public void onAudioStateChanged(String callId, AudioState audioState) {
+            SomeArgs args = SomeArgs.obtain();
+            args.arg1 = callId;
+            args.arg2 = audioState;
+            mHandler.obtainMessage(MSG_ON_AUDIO_STATE_CHANGED, args).sendToTarget();
+        }
+
+        @Override
+        public void playDtmfTone(String callId, char digit) {
+            mHandler.obtainMessage(MSG_PLAY_DTMF_TONE, digit, 0, callId).sendToTarget();
+        }
+
+        @Override
+        public void stopDtmfTone(String callId) {
+            mHandler.obtainMessage(MSG_STOP_DTMF_TONE, callId).sendToTarget();
+        }
+
+        @Override
+        public void conference(String callId1, String callId2) {
+            SomeArgs args = SomeArgs.obtain();
+            args.arg1 = callId1;
+            args.arg2 = callId2;
+            mHandler.obtainMessage(MSG_CONFERENCE, args).sendToTarget();
+        }
+
+        @Override
+        public void splitFromConference(String callId) {
+            mHandler.obtainMessage(MSG_SPLIT_FROM_CONFERENCE, callId).sendToTarget();
+        }
+
+        @Override
+        public void mergeConference(String callId) {
+            mHandler.obtainMessage(MSG_MERGE_CONFERENCE, callId).sendToTarget();
+        }
+
+        @Override
+        public void swapConference(String callId) {
+            mHandler.obtainMessage(MSG_SWAP_CONFERENCE, callId).sendToTarget();
+        }
+
+        @Override
+        public void onPostDialContinue(String callId, boolean proceed) {
+            SomeArgs args = SomeArgs.obtain();
+            args.arg1 = callId;
+            args.argi1 = proceed ? 1 : 0;
+            mHandler.obtainMessage(MSG_ON_POST_DIAL_CONTINUE, args).sendToTarget();
+        }
+    };
+
+    private final Handler mHandler = new Handler(Looper.getMainLooper()) {
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_ADD_CONNECTION_SERVICE_ADAPTER:
+                    mAdapter.addAdapter((IConnectionServiceAdapter) msg.obj);
+                    onAdapterAttached();
+                    break;
+                case MSG_REMOVE_CONNECTION_SERVICE_ADAPTER:
+                    mAdapter.removeAdapter((IConnectionServiceAdapter) msg.obj);
+                    break;
+                case MSG_CREATE_CONNECTION: {
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    try {
+                        final PhoneAccountHandle connectionManagerPhoneAccount =
+                                (PhoneAccountHandle) args.arg1;
+                        final String id = (String) args.arg2;
+                        final ConnectionRequest request = (ConnectionRequest) args.arg3;
+                        final boolean isIncoming = args.argi1 == 1;
+                        if (!mAreAccountsInitialized) {
+                            Log.d(this, "Enqueueing pre-init request %s", id);
+                            mPreInitializationConnectionRequests.add(new Runnable() {
+                                @Override
+                                public void run() {
+                                    createConnection(
+                                            connectionManagerPhoneAccount,
+                                            id,
+                                            request,
+                                            isIncoming);
+                                }
+                            });
+                        } else {
+                            createConnection(
+                                    connectionManagerPhoneAccount,
+                                    id,
+                                    request,
+                                    isIncoming);
+                        }
+                    } finally {
+                        args.recycle();
+                    }
+                    break;
+                }
+                case MSG_ABORT:
+                    abort((String) msg.obj);
+                    break;
+                case MSG_ANSWER:
+                    answer((String) msg.obj);
+                    break;
+                case MSG_ANSWER_VIDEO: {
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    try {
+                        String callId = (String) args.arg1;
+                        int videoState = args.argi1;
+                        answerVideo(callId, videoState);
+                    } finally {
+                        args.recycle();
+                    }
+                    break;
+                }
+                case MSG_REJECT:
+                    reject((String) msg.obj);
+                    break;
+                case MSG_DISCONNECT:
+                    disconnect((String) msg.obj);
+                    break;
+                case MSG_HOLD:
+                    hold((String) msg.obj);
+                    break;
+                case MSG_UNHOLD:
+                    unhold((String) msg.obj);
+                    break;
+                case MSG_ON_AUDIO_STATE_CHANGED: {
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    try {
+                        String callId = (String) args.arg1;
+                        AudioState audioState = (AudioState) args.arg2;
+                        onAudioStateChanged(callId, audioState);
+                    } finally {
+                        args.recycle();
+                    }
+                    break;
+                }
+                case MSG_PLAY_DTMF_TONE:
+                    playDtmfTone((String) msg.obj, (char) msg.arg1);
+                    break;
+                case MSG_STOP_DTMF_TONE:
+                    stopDtmfTone((String) msg.obj);
+                    break;
+                case MSG_CONFERENCE: {
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    try {
+                        String callId1 = (String) args.arg1;
+                        String callId2 = (String) args.arg2;
+                        conference(callId1, callId2);
+                    } finally {
+                        args.recycle();
+                    }
+                    break;
+                }
+                case MSG_SPLIT_FROM_CONFERENCE:
+                    splitFromConference((String) msg.obj);
+                    break;
+                case MSG_MERGE_CONFERENCE:
+                    mergeConference((String) msg.obj);
+                    break;
+                case MSG_SWAP_CONFERENCE:
+                    swapConference((String) msg.obj);
+                    break;
+                case MSG_ON_POST_DIAL_CONTINUE: {
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    try {
+                        String callId = (String) args.arg1;
+                        boolean proceed = (args.argi1 == 1);
+                        onPostDialContinue(callId, proceed);
+                    } finally {
+                        args.recycle();
+                    }
+                    break;
+                }
+                default:
+                    break;
+            }
+        }
+    };
+
+    private final Conference.Listener mConferenceListener = new Conference.Listener() {
+        @Override
+        public void onStateChanged(Conference conference, int oldState, int newState) {
+            String id = mIdByConference.get(conference);
+            switch (newState) {
+                case Connection.STATE_ACTIVE:
+                    mAdapter.setActive(id);
+                    break;
+                case Connection.STATE_HOLDING:
+                    mAdapter.setOnHold(id);
+                    break;
+                case Connection.STATE_DISCONNECTED:
+                    // handled by onDisconnected
+                    break;
+            }
+        }
+
+        @Override
+        public void onDisconnected(Conference conference, int cause, String message) {
+            String id = mIdByConference.get(conference);
+            mAdapter.setDisconnected(id, cause, message);
+        }
+
+        @Override
+        public void onConnectionAdded(Conference conference, Connection connection) {
+        }
+
+        @Override
+        public void onConnectionRemoved(Conference conference, Connection connection) {
+        }
+
+        @Override
+        public void onDestroyed(Conference conference) {
+            removeConference(conference);
+        }
+
+        @Override
+        public void onCapabilitiesChanged(Conference conference, int capabilities) {
+            String id = mIdByConference.get(conference);
+            Log.d(this, "call capabilities: conference: %s",
+                    PhoneCapabilities.toString(capabilities));
+            mAdapter.setCallCapabilities(id, capabilities);
+        }
+    };
+
+    private final Connection.Listener mConnectionListener = new Connection.Listener() {
+        @Override
+        public void onStateChanged(Connection c, int state) {
+            String id = mIdByConnection.get(c);
+            Log.d(this, "Adapter set state %s %s", id, Connection.stateToString(state));
+            switch (state) {
+                case Connection.STATE_ACTIVE:
+                    mAdapter.setActive(id);
+                    break;
+                case Connection.STATE_DIALING:
+                    mAdapter.setDialing(id);
+                    break;
+                case Connection.STATE_DISCONNECTED:
+                    // Handled in onDisconnected()
+                    break;
+                case Connection.STATE_HOLDING:
+                    mAdapter.setOnHold(id);
+                    break;
+                case Connection.STATE_NEW:
+                    // Nothing to tell Telecom
+                    break;
+                case Connection.STATE_RINGING:
+                    mAdapter.setRinging(id);
+                    break;
+            }
+        }
+
+        @Override
+        public void onDisconnected(Connection c, int cause, String message) {
+            String id = mIdByConnection.get(c);
+            Log.d(this, "Adapter set disconnected %d %s", cause, message);
+            mAdapter.setDisconnected(id, cause, message);
+        }
+
+        @Override
+        public void onVideoStateChanged(Connection c, int videoState) {
+            String id = mIdByConnection.get(c);
+            Log.d(this, "Adapter set video state %d", videoState);
+            mAdapter.setVideoState(id, videoState);
+        }
+
+        @Override
+        public void onAddressChanged(Connection c, Uri address, int presentation) {
+            String id = mIdByConnection.get(c);
+            mAdapter.setAddress(id, address, presentation);
+        }
+
+        @Override
+        public void onCallerDisplayNameChanged(
+                Connection c, String callerDisplayName, int presentation) {
+            String id = mIdByConnection.get(c);
+            mAdapter.setCallerDisplayName(id, callerDisplayName, presentation);
+        }
+
+        @Override
+        public void onDestroyed(Connection c) {
+            removeConnection(c);
+        }
+
+        @Override
+        public void onPostDialWait(Connection c, String remaining) {
+            String id = mIdByConnection.get(c);
+            Log.d(this, "Adapter onPostDialWait %s, %s", c, remaining);
+            mAdapter.onPostDialWait(id, remaining);
+        }
+
+        @Override
+        public void onRingbackRequested(Connection c, boolean ringback) {
+            String id = mIdByConnection.get(c);
+            Log.d(this, "Adapter onRingback %b", ringback);
+            mAdapter.setRingbackRequested(id, ringback);
+        }
+
+        @Override
+        public void onCallCapabilitiesChanged(Connection c, int capabilities) {
+            String id = mIdByConnection.get(c);
+            Log.d(this, "capabilities: parcelableconnection: %s",
+                    PhoneCapabilities.toString(capabilities));
+            mAdapter.setCallCapabilities(id, capabilities);
+        }
+
+        @Override
+        public void onVideoProviderChanged(Connection c, Connection.VideoProvider videoProvider) {
+            String id = mIdByConnection.get(c);
+            mAdapter.setVideoProvider(id, videoProvider);
+        }
+
+        @Override
+        public void onAudioModeIsVoipChanged(Connection c, boolean isVoip) {
+            String id = mIdByConnection.get(c);
+            mAdapter.setIsVoipAudioMode(id, isVoip);
+        }
+
+        @Override
+        public void onStatusHintsChanged(Connection c, StatusHints statusHints) {
+            String id = mIdByConnection.get(c);
+            mAdapter.setStatusHints(id, statusHints);
+        }
+
+        @Override
+        public void onConferenceableConnectionsChanged(
+                Connection connection, List<Connection> conferenceableConnections) {
+            mAdapter.setConferenceableConnections(
+                    mIdByConnection.get(connection),
+                    createConnectionIdList(conferenceableConnections));
+        }
+
+        @Override
+        public void onConferenceChanged(Connection connection, Conference conference) {
+            String id = mIdByConnection.get(connection);
+            if (id != null) {
+                String conferenceId = null;
+                if (conference != null) {
+                    conferenceId = mIdByConference.get(conference);
+                }
+                mAdapter.setIsConferenced(id, conferenceId);
+            }
+        }
+    };
+
+    /** {@inheritDoc} */
+    @Override
+    public final IBinder onBind(Intent intent) {
+        return mBinder;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean onUnbind(Intent intent) {
+        endAllConnections();
+        return super.onUnbind(intent);
+    }
+
+    /**
+     * This can be used by telecom to either create a new outgoing call or attach to an existing
+     * incoming call. In either case, telecom will cycle through a set of services and call
+     * createConnection util a connection service cancels the process or completes it successfully.
+     */
+    private void createConnection(
+            final PhoneAccountHandle callManagerAccount,
+            final String callId,
+            final ConnectionRequest request,
+            boolean isIncoming) {
+        Log.d(this, "createConnection, callManagerAccount: %s, callId: %s, request: %s, " +
+                "isIncoming: %b", callManagerAccount, callId, request, isIncoming);
+
+        Connection connection = isIncoming
+                ? onCreateIncomingConnection(callManagerAccount, request)
+                : onCreateOutgoingConnection(callManagerAccount, request);
+        Log.d(this, "createConnection, connection: %s", connection);
+        if (connection == null) {
+            connection = Connection.createFailedConnection(DisconnectCause.OUTGOING_FAILURE, null);
+        }
+
+        if (connection.getState() != Connection.STATE_DISCONNECTED) {
+            addConnection(callId, connection);
+        }
+
+        Uri address = connection.getAddress();
+        String number = address == null ? "null" : address.getSchemeSpecificPart();
+        Log.v(this, "createConnection, number: %s, state: %s, capabilities: %s",
+                Connection.toLogSafePhoneNumber(number),
+                Connection.stateToString(connection.getState()),
+                PhoneCapabilities.toString(connection.getCallCapabilities()));
+
+        Log.d(this, "createConnection, calling handleCreateConnectionSuccessful %s", callId);
+        mAdapter.handleCreateConnectionComplete(
+                callId,
+                request,
+                new ParcelableConnection(
+                        request.getAccountHandle(),
+                        connection.getState(),
+                        connection.getCallCapabilities(),
+                        connection.getAddress(),
+                        connection.getAddressPresentation(),
+                        connection.getCallerDisplayName(),
+                        connection.getCallerDisplayNamePresentation(),
+                        connection.getVideoProvider() == null ?
+                                null : connection.getVideoProvider().getInterface(),
+                        connection.getVideoState(),
+                        connection.isRingbackRequested(),
+                        connection.getAudioModeIsVoip(),
+                        connection.getStatusHints(),
+                        connection.getDisconnectCause(),
+                        connection.getDisconnectMessage(),
+                        createConnectionIdList(connection.getConferenceableConnections())));
+    }
+
+    private void abort(String callId) {
+        Log.d(this, "abort %s", callId);
+        findConnectionForAction(callId, "abort").onAbort();
+    }
+
+    private void answerVideo(String callId, int videoState) {
+        Log.d(this, "answerVideo %s", callId);
+        findConnectionForAction(callId, "answer").onAnswer(videoState);
+    }
+
+    private void answer(String callId) {
+        Log.d(this, "answer %s", callId);
+        findConnectionForAction(callId, "answer").onAnswer();
+    }
+
+    private void reject(String callId) {
+        Log.d(this, "reject %s", callId);
+        findConnectionForAction(callId, "reject").onReject();
+    }
+
+    private void disconnect(String callId) {
+        Log.d(this, "disconnect %s", callId);
+        if (mConnectionById.containsKey(callId)) {
+            findConnectionForAction(callId, "disconnect").onDisconnect();
+        } else {
+            findConferenceForAction(callId, "disconnect").onDisconnect();
+        }
+    }
+
+    private void hold(String callId) {
+        Log.d(this, "hold %s", callId);
+        if (mConnectionById.containsKey(callId)) {
+            findConnectionForAction(callId, "hold").onHold();
+        } else {
+            findConferenceForAction(callId, "hold").onHold();
+        }
+    }
+
+    private void unhold(String callId) {
+        Log.d(this, "unhold %s", callId);
+        if (mConnectionById.containsKey(callId)) {
+            findConnectionForAction(callId, "unhold").onUnhold();
+        } else {
+            findConferenceForAction(callId, "unhold").onUnhold();
+        }
+    }
+
+    private void onAudioStateChanged(String callId, AudioState audioState) {
+        Log.d(this, "onAudioStateChanged %s %s", callId, audioState);
+        findConnectionForAction(callId, "onAudioStateChanged").setAudioState(audioState);
+    }
+
+    private void playDtmfTone(String callId, char digit) {
+        Log.d(this, "playDtmfTone %s %c", callId, digit);
+        findConnectionForAction(callId, "playDtmfTone").onPlayDtmfTone(digit);
+    }
+
+    private void stopDtmfTone(String callId) {
+        Log.d(this, "stopDtmfTone %s", callId);
+        findConnectionForAction(callId, "stopDtmfTone").onStopDtmfTone();
+    }
+
+    private void conference(String callId1, String callId2) {
+        Log.d(this, "conference %s, %s", callId1, callId2);
+
+        Connection connection1 = findConnectionForAction(callId1, "conference");
+        if (connection1 == getNullConnection()) {
+            Log.w(this, "Connection1 missing in conference request %s.", callId1);
+            return;
+        }
+
+        Connection connection2 = findConnectionForAction(callId2, "conference");
+        if (connection2 == getNullConnection()) {
+            Log.w(this, "Connection2 missing in conference request %s.", callId2);
+            return;
+        }
+
+        onConference(connection1, connection2);
+    }
+
+    private void splitFromConference(String callId) {
+        Log.d(this, "splitFromConference(%s)", callId);
+
+        Connection connection = findConnectionForAction(callId, "splitFromConference");
+        if (connection == getNullConnection()) {
+            Log.w(this, "Connection missing in conference request %s.", callId);
+            return;
+        }
+
+        Conference conference = connection.getConference();
+        if (conference != null) {
+            conference.onSeparate(connection);
+        }
+    }
+
+    private void mergeConference(String callId) {
+        Log.d(this, "mergeConference(%s)", callId);
+        Conference conference = findConferenceForAction(callId, "mergeConference");
+        if (conference != null) {
+            conference.onMerge();
+        }
+    }
+
+    private void swapConference(String callId) {
+        Log.d(this, "swapConference(%s)", callId);
+        Conference conference = findConferenceForAction(callId, "swapConference");
+        if (conference != null) {
+            conference.onSwap();
+        }
+    }
+
+    private void onPostDialContinue(String callId, boolean proceed) {
+        Log.d(this, "onPostDialContinue(%s)", callId);
+        findConnectionForAction(callId, "stopDtmfTone").onPostDialContinue(proceed);
+    }
+
+    private void onAdapterAttached() {
+        if (mAreAccountsInitialized) {
+            // No need to query again if we already did it.
+            return;
+        }
+
+        mAdapter.queryRemoteConnectionServices(new RemoteServiceCallback.Stub() {
+            @Override
+            public void onResult(
+                    final List<ComponentName> componentNames,
+                    final List<IBinder> services) {
+                mHandler.post(new Runnable() {
+                    @Override
+                    public void run() {
+                        for (int i = 0; i < componentNames.size() && i < services.size(); i++) {
+                            mRemoteConnectionManager.addConnectionService(
+                                    componentNames.get(i),
+                                    IConnectionService.Stub.asInterface(services.get(i)));
+                        }
+                        onAccountsInitialized();
+                        Log.d(this, "remote connection services found: " + services);
+                    }
+                });
+            }
+
+            @Override
+            public void onError() {
+                mHandler.post(new Runnable() {
+                    @Override
+                    public void run() {
+                        mAreAccountsInitialized = true;
+                    }
+                });
+            }
+        });
+    }
+
+    /**
+     * Ask some other {@code ConnectionService} to create a {@code RemoteConnection} given an
+     * incoming request. This is used to attach to existing incoming calls.
+     *
+     * @param connectionManagerPhoneAccount See description at
+     *         {@link #onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest)}.
+     * @param request Details about the incoming call.
+     * @return The {@code Connection} object to satisfy this call, or {@code null} to
+     *         not handle the call.
+     */
+    public final RemoteConnection createRemoteIncomingConnection(
+            PhoneAccountHandle connectionManagerPhoneAccount,
+            ConnectionRequest request) {
+        return mRemoteConnectionManager.createRemoteConnection(
+                connectionManagerPhoneAccount, request, true);
+    }
+
+    /**
+     * Ask some other {@code ConnectionService} to create a {@code RemoteConnection} given an
+     * outgoing request. This is used to initiate new outgoing calls.
+     *
+     * @param connectionManagerPhoneAccount See description at
+     *         {@link #onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest)}.
+     * @param request Details about the incoming call.
+     * @return The {@code Connection} object to satisfy this call, or {@code null} to
+     *         not handle the call.
+     */
+    public final RemoteConnection createRemoteOutgoingConnection(
+            PhoneAccountHandle connectionManagerPhoneAccount,
+            ConnectionRequest request) {
+        return mRemoteConnectionManager.createRemoteConnection(
+                connectionManagerPhoneAccount, request, false);
+    }
+
+    /**
+     * Adds two {@code RemoteConnection}s to some {@code RemoteConference}.
+     */
+    public final void conferenceRemoteConnections(
+            RemoteConnection a,
+            RemoteConnection b) {
+        mRemoteConnectionManager.conferenceRemoteConnections(a, b);
+    }
+
+    /**
+     * Adds a new conference call. When a conference call is created either as a result of an
+     * explicit request via {@link #onConference} or otherwise, the connection service should supply
+     * an instance of {@link Conference} by invoking this method. A conference call provided by this
+     * method will persist until {@link Conference#destroy} is invoked on the conference instance.
+     *
+     * @param conference The new conference object.
+     */
+    public final void addConference(Conference conference) {
+        String id = addConferenceInternal(conference);
+        if (id != null) {
+            List<String> connectionIds = new ArrayList<>(2);
+            for (Connection connection : conference.getConnections()) {
+                if (mIdByConnection.containsKey(connection)) {
+                    connectionIds.add(mIdByConnection.get(connection));
+                }
+            }
+            ParcelableConference parcelableConference = new ParcelableConference(
+                    conference.getPhoneAccountHandle(),
+                    conference.getState(),
+                    conference.getCapabilities(),
+                    connectionIds);
+            mAdapter.addConferenceCall(id, parcelableConference);
+
+            // Go through any child calls and set the parent.
+            for (Connection connection : conference.getConnections()) {
+                String connectionId = mIdByConnection.get(connection);
+                if (connectionId != null) {
+                    mAdapter.setIsConferenced(connectionId, id);
+                }
+            }
+        }
+    }
+
+    /**
+     * Returns all the active {@code Connection}s for which this {@code ConnectionService}
+     * has taken responsibility.
+     *
+     * @return A collection of {@code Connection}s created by this {@code ConnectionService}.
+     */
+    public final Collection<Connection> getAllConnections() {
+        return mConnectionById.values();
+    }
+
+    /**
+     * Create a {@code Connection} given an incoming request. This is used to attach to existing
+     * incoming calls.
+     *
+     * @param connectionManagerPhoneAccount See description at
+     *         {@link #onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest)}.
+     * @param request Details about the incoming call.
+     * @return The {@code Connection} object to satisfy this call, or {@code null} to
+     *         not handle the call.
+     */
+    public Connection onCreateIncomingConnection(
+            PhoneAccountHandle connectionManagerPhoneAccount,
+            ConnectionRequest request) {
+        return null;
+    }
+
+    /**
+     * Create a {@code Connection} given an outgoing request. This is used to initiate new
+     * outgoing calls.
+     *
+     * @param connectionManagerPhoneAccount The connection manager account to use for managing
+     *         this call.
+     *         <p>
+     *         If this parameter is not {@code null}, it means that this {@code ConnectionService}
+     *         has registered one or more {@code PhoneAccount}s having
+     *         {@link PhoneAccount#CAPABILITY_CONNECTION_MANAGER}. This parameter will contain
+     *         one of these {@code PhoneAccount}s, while the {@code request} will contain another
+     *         (usually but not always distinct) {@code PhoneAccount} to be used for actually
+     *         making the connection.
+     *         <p>
+     *         If this parameter is {@code null}, it means that this {@code ConnectionService} is
+     *         being asked to make a direct connection. The
+     *         {@link ConnectionRequest#getAccountHandle()} of parameter {@code request} will be
+     *         a {@code PhoneAccount} registered by this {@code ConnectionService} to use for
+     *         making the connection.
+     * @param request Details about the outgoing call.
+     * @return The {@code Connection} object to satisfy this call, or the result of an invocation
+     *         of {@link Connection#createFailedConnection(int, String)} to not handle the call.
+     */
+    public Connection onCreateOutgoingConnection(
+            PhoneAccountHandle connectionManagerPhoneAccount,
+            ConnectionRequest request) {
+        return null;
+    }
+
+    /**
+     * Conference two specified connections. Invoked when the user has made a request to merge the
+     * specified connections into a conference call. In response, the connection service should
+     * create an instance of {@link Conference} and pass it into {@link #addConference}.
+     *
+     * @param connection1 A connection to merge into a conference call.
+     * @param connection2 A connection to merge into a conference call.
+     */
+    public void onConference(Connection connection1, Connection connection2) {}
+
+    public void onRemoteConferenceAdded(RemoteConference conference) {}
+
+    /**
+     * @hide
+     */
+    public boolean containsConference(Conference conference) {
+        return mIdByConference.containsKey(conference);
+    }
+
+    /** {@hide} */
+    void addRemoteConference(RemoteConference remoteConference) {
+        onRemoteConferenceAdded(remoteConference);
+    }
+
+    private void onAccountsInitialized() {
+        mAreAccountsInitialized = true;
+        for (Runnable r : mPreInitializationConnectionRequests) {
+            r.run();
+        }
+        mPreInitializationConnectionRequests.clear();
+    }
+
+    private void addConnection(String callId, Connection connection) {
+        mConnectionById.put(callId, connection);
+        mIdByConnection.put(connection, callId);
+        connection.addConnectionListener(mConnectionListener);
+        connection.setConnectionService(this);
+    }
+
+    private void removeConnection(Connection connection) {
+        String id = mIdByConnection.get(connection);
+        connection.unsetConnectionService(this);
+        connection.removeConnectionListener(mConnectionListener);
+        mConnectionById.remove(mIdByConnection.get(connection));
+        mIdByConnection.remove(connection);
+        mAdapter.removeCall(id);
+    }
+
+    private String addConferenceInternal(Conference conference) {
+        if (mIdByConference.containsKey(conference)) {
+            Log.w(this, "Re-adding an existing conference: %s.", conference);
+        } else if (conference != null) {
+            String id = UUID.randomUUID().toString();
+            mConferenceById.put(id, conference);
+            mIdByConference.put(conference, id);
+            conference.addListener(mConferenceListener);
+            return id;
+        }
+
+        return null;
+    }
+
+    private void removeConference(Conference conference) {
+        if (mIdByConference.containsKey(conference)) {
+            conference.removeListener(mConferenceListener);
+
+            String id = mIdByConference.get(conference);
+            mConferenceById.remove(id);
+            mIdByConference.remove(conference);
+            mAdapter.removeCall(id);
+        }
+    }
+
+    private Connection findConnectionForAction(String callId, String action) {
+        if (mConnectionById.containsKey(callId)) {
+            return mConnectionById.get(callId);
+        }
+        Log.w(this, "%s - Cannot find Connection %s", action, callId);
+        return getNullConnection();
+    }
+
+    static synchronized Connection getNullConnection() {
+        if (sNullConnection == null) {
+            sNullConnection = new Connection() {};
+        }
+        return sNullConnection;
+    }
+
+    private Conference findConferenceForAction(String conferenceId, String action) {
+        if (mConferenceById.containsKey(conferenceId)) {
+            return mConferenceById.get(conferenceId);
+        }
+        Log.w(this, "%s - Cannot find conference %s", action, conferenceId);
+        return getNullConference();
+    }
+
+    private List<String> createConnectionIdList(List<Connection> connections) {
+        List<String> ids = new ArrayList<>();
+        for (Connection c : connections) {
+            if (mIdByConnection.containsKey(c)) {
+                ids.add(mIdByConnection.get(c));
+            }
+        }
+        Collections.sort(ids);
+        return ids;
+    }
+
+    private Conference getNullConference() {
+        if (sNullConference == null) {
+            sNullConference = new Conference(null) {};
+        }
+        return sNullConference;
+    }
+
+    private void endAllConnections() {
+        // Unbound from telecomm.  We should end all connections and conferences.
+        for (Connection connection : mIdByConnection.keySet()) {
+            // only operate on top-level calls. Conference calls will be removed on their own.
+            if (connection.getConference() == null) {
+                connection.onDisconnect();
+            }
+        }
+        for (Conference conference : mIdByConference.keySet()) {
+            conference.onDisconnect();
+        }
+    }
+}
diff --git a/telecomm/java/android/telecom/ConnectionServiceAdapter.java b/telecomm/java/android/telecom/ConnectionServiceAdapter.java
new file mode 100644
index 0000000..f6bcdc6
--- /dev/null
+++ b/telecomm/java/android/telecom/ConnectionServiceAdapter.java
@@ -0,0 +1,348 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telecom;
+
+import android.net.Uri;
+import android.os.IBinder.DeathRecipient;
+import android.os.RemoteException;
+
+import com.android.internal.telecom.IConnectionServiceAdapter;
+import com.android.internal.telecom.RemoteServiceCallback;
+
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * Provides methods for IConnectionService implementations to interact with the system phone app.
+ *
+ * @hide
+ */
+final class ConnectionServiceAdapter implements DeathRecipient {
+    /**
+     * ConcurrentHashMap constructor params: 8 is initial table size, 0.9f is
+     * load factor before resizing, 1 means we only expect a single thread to
+     * access the map so make only a single shard
+     */
+    private final Set<IConnectionServiceAdapter> mAdapters = Collections.newSetFromMap(
+            new ConcurrentHashMap<IConnectionServiceAdapter, Boolean>(8, 0.9f, 1));
+
+    ConnectionServiceAdapter() {
+    }
+
+    void addAdapter(IConnectionServiceAdapter adapter) {
+        if (mAdapters.add(adapter)) {
+            try {
+                adapter.asBinder().linkToDeath(this, 0);
+            } catch (RemoteException e) {
+                mAdapters.remove(adapter);
+            }
+        }
+    }
+
+    void removeAdapter(IConnectionServiceAdapter adapter) {
+        if (adapter != null && mAdapters.remove(adapter)) {
+            adapter.asBinder().unlinkToDeath(this, 0);
+        }
+    }
+
+    /** ${inheritDoc} */
+    @Override
+    public void binderDied() {
+        Iterator<IConnectionServiceAdapter> it = mAdapters.iterator();
+        while (it.hasNext()) {
+            IConnectionServiceAdapter adapter = it.next();
+            if (!adapter.asBinder().isBinderAlive()) {
+                it.remove();
+                adapter.asBinder().unlinkToDeath(this, 0);
+            }
+        }
+    }
+
+    void handleCreateConnectionComplete(
+            String id,
+            ConnectionRequest request,
+            ParcelableConnection connection) {
+        for (IConnectionServiceAdapter adapter : mAdapters) {
+            try {
+                adapter.handleCreateConnectionComplete(id, request, connection);
+            } catch (RemoteException e) {
+            }
+        }
+    }
+
+    /**
+     * Sets a call's state to active (e.g., an ongoing call where two parties can actively
+     * communicate).
+     *
+     * @param callId The unique ID of the call whose state is changing to active.
+     */
+    void setActive(String callId) {
+        for (IConnectionServiceAdapter adapter : mAdapters) {
+            try {
+                adapter.setActive(callId);
+            } catch (RemoteException e) {
+            }
+        }
+    }
+
+    /**
+     * Sets a call's state to ringing (e.g., an inbound ringing call).
+     *
+     * @param callId The unique ID of the call whose state is changing to ringing.
+     */
+    void setRinging(String callId) {
+        for (IConnectionServiceAdapter adapter : mAdapters) {
+            try {
+                adapter.setRinging(callId);
+            } catch (RemoteException e) {
+            }
+        }
+    }
+
+    /**
+     * Sets a call's state to dialing (e.g., dialing an outbound call).
+     *
+     * @param callId The unique ID of the call whose state is changing to dialing.
+     */
+    void setDialing(String callId) {
+        for (IConnectionServiceAdapter adapter : mAdapters) {
+            try {
+                adapter.setDialing(callId);
+            } catch (RemoteException e) {
+            }
+        }
+    }
+
+    /**
+     * Sets a call's state to disconnected.
+     *
+     * @param callId The unique ID of the call whose state is changing to disconnected.
+     * @param disconnectCause The reason for the disconnection, any of
+     *            {@link android.telephony.DisconnectCause}.
+     * @param disconnectMessage Optional call-service-provided message about the disconnect.
+     */
+    void setDisconnected(String callId, int disconnectCause, String disconnectMessage) {
+        for (IConnectionServiceAdapter adapter : mAdapters) {
+            try {
+                adapter.setDisconnected(callId, disconnectCause, disconnectMessage);
+            } catch (RemoteException e) {
+            }
+        }
+    }
+
+    /**
+     * Sets a call's state to be on hold.
+     *
+     * @param callId - The unique ID of the call whose state is changing to be on hold.
+     */
+    void setOnHold(String callId) {
+        for (IConnectionServiceAdapter adapter : mAdapters) {
+            try {
+                adapter.setOnHold(callId);
+            } catch (RemoteException e) {
+            }
+        }
+    }
+
+    /**
+     * Asks Telecom to start or stop a ringback tone for a call.
+     *
+     * @param callId The unique ID of the call whose ringback is being changed.
+     * @param ringback Whether Telecom should start playing a ringback tone.
+     */
+    void setRingbackRequested(String callId, boolean ringback) {
+        for (IConnectionServiceAdapter adapter : mAdapters) {
+            try {
+                adapter.setRingbackRequested(callId, ringback);
+            } catch (RemoteException e) {
+            }
+        }
+    }
+
+    void setCallCapabilities(String callId, int capabilities) {
+        for (IConnectionServiceAdapter adapter : mAdapters) {
+            try {
+                adapter.setCallCapabilities(callId, capabilities);
+            } catch (RemoteException ignored) {
+            }
+        }
+    }
+
+    /**
+     * Indicates whether or not the specified call is currently conferenced into the specified
+     * conference call.
+     *
+     * @param callId The unique ID of the call being conferenced.
+     * @param conferenceCallId The unique ID of the conference call. Null if call is not
+     *            conferenced.
+     */
+    void setIsConferenced(String callId, String conferenceCallId) {
+        for (IConnectionServiceAdapter adapter : mAdapters) {
+            try {
+                Log.d(this, "sending connection %s with conference %s", callId, conferenceCallId);
+                adapter.setIsConferenced(callId, conferenceCallId);
+            } catch (RemoteException ignored) {
+            }
+        }
+    }
+
+    /**
+     * Indicates that the call no longer exists. Can be used with either a call or a conference
+     * call.
+     *
+     * @param callId The unique ID of the call.
+     */
+    void removeCall(String callId) {
+        for (IConnectionServiceAdapter adapter : mAdapters) {
+            try {
+                adapter.removeCall(callId);
+            } catch (RemoteException ignored) {
+            }
+        }
+    }
+
+    void onPostDialWait(String callId, String remaining) {
+        for (IConnectionServiceAdapter adapter : mAdapters) {
+            try {
+                adapter.onPostDialWait(callId, remaining);
+            } catch (RemoteException ignored) {
+            }
+        }
+    }
+
+    /**
+     * Indicates that a new conference call has been created.
+     *
+     * @param callId The unique ID of the conference call.
+     */
+    void addConferenceCall(String callId, ParcelableConference parcelableConference) {
+        for (IConnectionServiceAdapter adapter : mAdapters) {
+            try {
+                adapter.addConferenceCall(callId, parcelableConference);
+            } catch (RemoteException ignored) {
+            }
+        }
+    }
+
+    /**
+     * Retrieves a list of remote connection services usable to place calls.
+     */
+    void queryRemoteConnectionServices(RemoteServiceCallback callback) {
+        // Only supported when there is only one adapter.
+        if (mAdapters.size() == 1) {
+            try {
+                mAdapters.iterator().next().queryRemoteConnectionServices(callback);
+            } catch (RemoteException e) {
+                Log.e(this, e, "Exception trying to query for remote CSs");
+            }
+        }
+    }
+
+    /**
+     * Sets the call video provider for a call.
+     *
+     * @param callId The unique ID of the call to set with the given call video provider.
+     * @param videoProvider The call video provider instance to set on the call.
+     */
+    void setVideoProvider(
+            String callId, Connection.VideoProvider videoProvider) {
+        for (IConnectionServiceAdapter adapter : mAdapters) {
+            try {
+                adapter.setVideoProvider(
+                        callId,
+                        videoProvider == null ? null : videoProvider.getInterface());
+            } catch (RemoteException e) {
+            }
+        }
+    }
+
+    /**
+     * Requests that the framework use VOIP audio mode for this connection.
+     *
+     * @param callId The unique ID of the call to set with the given call video provider.
+     * @param isVoip True if the audio mode is VOIP.
+     */
+    void setIsVoipAudioMode(String callId, boolean isVoip) {
+        for (IConnectionServiceAdapter adapter : mAdapters) {
+            try {
+                adapter.setIsVoipAudioMode(callId, isVoip);
+            } catch (RemoteException e) {
+            }
+        }
+    }
+
+    void setStatusHints(String callId, StatusHints statusHints) {
+        for (IConnectionServiceAdapter adapter : mAdapters) {
+            try {
+                adapter.setStatusHints(callId, statusHints);
+            } catch (RemoteException e) {
+            }
+        }
+    }
+
+    void setAddress(String callId, Uri address, int presentation) {
+        for (IConnectionServiceAdapter adapter : mAdapters) {
+            try {
+                adapter.setAddress(callId, address, presentation);
+            } catch (RemoteException e) {
+            }
+        }
+    }
+
+    void setCallerDisplayName(String callId, String callerDisplayName, int presentation) {
+        for (IConnectionServiceAdapter adapter : mAdapters) {
+            try {
+                adapter.setCallerDisplayName(callId, callerDisplayName, presentation);
+            } catch (RemoteException e) {
+            }
+        }
+    }
+
+    /**
+     * Sets the video state associated with a call.
+     *
+     * Valid values: {@link VideoProfile.VideoState#AUDIO_ONLY},
+     * {@link VideoProfile.VideoState#BIDIRECTIONAL},
+     * {@link VideoProfile.VideoState#TX_ENABLED},
+     * {@link VideoProfile.VideoState#RX_ENABLED}.
+     *
+     * @param callId The unique ID of the call to set the video state for.
+     * @param videoState The video state.
+     */
+    void setVideoState(String callId, int videoState) {
+        Log.v(this, "setVideoState: %d", videoState);
+        for (IConnectionServiceAdapter adapter : mAdapters) {
+            try {
+                adapter.setVideoState(callId, videoState);
+            } catch (RemoteException ignored) {
+            }
+        }
+    }
+
+    void setConferenceableConnections(String callId, List<String> conferenceableCallIds) {
+        Log.v(this, "setConferenceableConnections: %s, %s", callId, conferenceableCallIds);
+        for (IConnectionServiceAdapter adapter : mAdapters) {
+            try {
+                adapter.setConferenceableConnections(callId, conferenceableCallIds);
+            } catch (RemoteException ignored) {
+            }
+        }
+    }
+}
diff --git a/telecomm/java/android/telecom/ConnectionServiceAdapterServant.java b/telecomm/java/android/telecom/ConnectionServiceAdapterServant.java
new file mode 100644
index 0000000..ffbbc8a
--- /dev/null
+++ b/telecomm/java/android/telecom/ConnectionServiceAdapterServant.java
@@ -0,0 +1,359 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ R* limitations under the License.
+ */
+
+package android.telecom;
+
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Message;
+import android.os.RemoteException;
+
+import com.android.internal.os.SomeArgs;
+import com.android.internal.telecom.IConnectionServiceAdapter;
+import com.android.internal.telecom.IVideoProvider;
+import com.android.internal.telecom.RemoteServiceCallback;
+
+import java.util.List;
+
+/**
+ * A component that provides an RPC servant implementation of {@link IConnectionServiceAdapter},
+ * posting incoming messages on the main thread on a client-supplied delegate object.
+ *
+ * TODO: Generate this and similar classes using a compiler starting from AIDL interfaces.
+ *
+ * @hide
+ */
+final class ConnectionServiceAdapterServant {
+    private static final int MSG_HANDLE_CREATE_CONNECTION_COMPLETE = 1;
+    private static final int MSG_SET_ACTIVE = 2;
+    private static final int MSG_SET_RINGING = 3;
+    private static final int MSG_SET_DIALING = 4;
+    private static final int MSG_SET_DISCONNECTED = 5;
+    private static final int MSG_SET_ON_HOLD = 6;
+    private static final int MSG_SET_RINGBACK_REQUESTED = 7;
+    private static final int MSG_SET_CALL_CAPABILITIES = 8;
+    private static final int MSG_SET_IS_CONFERENCED = 9;
+    private static final int MSG_ADD_CONFERENCE_CALL = 10;
+    private static final int MSG_REMOVE_CALL = 11;
+    private static final int MSG_ON_POST_DIAL_WAIT = 12;
+    private static final int MSG_QUERY_REMOTE_CALL_SERVICES = 13;
+    private static final int MSG_SET_VIDEO_STATE = 14;
+    private static final int MSG_SET_VIDEO_CALL_PROVIDER = 15;
+    private static final int MSG_SET_IS_VOIP_AUDIO_MODE = 16;
+    private static final int MSG_SET_STATUS_HINTS = 17;
+    private static final int MSG_SET_ADDRESS = 18;
+    private static final int MSG_SET_CALLER_DISPLAY_NAME = 19;
+    private static final int MSG_SET_CONFERENCEABLE_CONNECTIONS = 20;
+
+    private final IConnectionServiceAdapter mDelegate;
+
+    private final Handler mHandler = new Handler() {
+        @Override
+        public void handleMessage(Message msg) {
+            try {
+                internalHandleMessage(msg);
+            } catch (RemoteException e) {
+            }
+        }
+
+        // Internal method defined to centralize handling of RemoteException
+        private void internalHandleMessage(Message msg) throws RemoteException {
+            switch (msg.what) {
+                case MSG_HANDLE_CREATE_CONNECTION_COMPLETE: {
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    try {
+                        mDelegate.handleCreateConnectionComplete(
+                                (String) args.arg1,
+                                (ConnectionRequest) args.arg2,
+                                (ParcelableConnection) args.arg3);
+                    } finally {
+                        args.recycle();
+                    }
+                    break;
+                }
+                case MSG_SET_ACTIVE:
+                    mDelegate.setActive((String) msg.obj);
+                    break;
+                case MSG_SET_RINGING:
+                    mDelegate.setRinging((String) msg.obj);
+                    break;
+                case MSG_SET_DIALING:
+                    mDelegate.setDialing((String) msg.obj);
+                    break;
+                case MSG_SET_DISCONNECTED: {
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    try {
+                        mDelegate.setDisconnected(
+                                (String) args.arg1, args.argi1, (String) args.arg2);
+                    } finally {
+                        args.recycle();
+                    }
+                    break;
+                }
+                case MSG_SET_ON_HOLD:
+                    mDelegate.setOnHold((String) msg.obj);
+                    break;
+                case MSG_SET_RINGBACK_REQUESTED:
+                    mDelegate.setRingbackRequested((String) msg.obj, msg.arg1 == 1);
+                    break;
+                case MSG_SET_CALL_CAPABILITIES:
+                    mDelegate.setCallCapabilities((String) msg.obj, msg.arg1);
+                    break;
+                case MSG_SET_IS_CONFERENCED: {
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    try {
+                        mDelegate.setIsConferenced((String) args.arg1, (String) args.arg2);
+                    } finally {
+                        args.recycle();
+                    }
+                    break;
+                }
+                case MSG_ADD_CONFERENCE_CALL: {
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    try {
+                        mDelegate.addConferenceCall(
+                                (String) args.arg1, (ParcelableConference) args.arg2);
+                    } finally {
+                        args.recycle();
+                    }
+                    break;
+                }
+                case MSG_REMOVE_CALL:
+                    mDelegate.removeCall((String) msg.obj);
+                    break;
+                case MSG_ON_POST_DIAL_WAIT: {
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    try {
+                        mDelegate.onPostDialWait((String) args.arg1, (String) args.arg2);
+                    } finally {
+                        args.recycle();
+                    }
+                    break;
+                }
+                case MSG_QUERY_REMOTE_CALL_SERVICES:
+                    mDelegate.queryRemoteConnectionServices((RemoteServiceCallback) msg.obj);
+                    break;
+                case MSG_SET_VIDEO_STATE:
+                    mDelegate.setVideoState((String) msg.obj, msg.arg1);
+                    break;
+                case MSG_SET_VIDEO_CALL_PROVIDER: {
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    try {
+                        mDelegate.setVideoProvider((String) args.arg1,
+                                (IVideoProvider) args.arg2);
+                    } finally {
+                        args.recycle();
+                    }
+                    break;
+                }
+                case MSG_SET_IS_VOIP_AUDIO_MODE:
+                    mDelegate.setIsVoipAudioMode((String) msg.obj, msg.arg1 == 1);
+                    break;
+                case MSG_SET_STATUS_HINTS: {
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    try {
+                        mDelegate.setStatusHints((String) args.arg1, (StatusHints) args.arg2);
+                    } finally {
+                        args.recycle();
+                    }
+                    break;
+                }
+                case MSG_SET_ADDRESS: {
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    try {
+                        mDelegate.setAddress((String) args.arg1, (Uri) args.arg2, args.argi1);
+                    } finally {
+                        args.recycle();
+                    }
+                    break;
+                }
+                case MSG_SET_CALLER_DISPLAY_NAME: {
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    try {
+                        mDelegate.setCallerDisplayName(
+                                (String) args.arg1, (String) args.arg2, args.argi1);
+                    } finally {
+                        args.recycle();
+                    }
+                    break;
+                }
+                case MSG_SET_CONFERENCEABLE_CONNECTIONS: {
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    try {
+                        mDelegate.setConferenceableConnections(
+                                (String) args.arg1, (List<String>) args.arg2);
+                    } finally {
+                        args.recycle();
+                    }
+                    break;
+                }
+            }
+        }
+    };
+
+    private final IConnectionServiceAdapter mStub = new IConnectionServiceAdapter.Stub() {
+        @Override
+        public void handleCreateConnectionComplete(
+                String id,
+                ConnectionRequest request,
+                ParcelableConnection connection) {
+            SomeArgs args = SomeArgs.obtain();
+            args.arg1 = id;
+            args.arg2 = request;
+            args.arg3 = connection;
+            mHandler.obtainMessage(MSG_HANDLE_CREATE_CONNECTION_COMPLETE, args).sendToTarget();
+        }
+
+        @Override
+        public void setActive(String connectionId) {
+            mHandler.obtainMessage(MSG_SET_ACTIVE, connectionId).sendToTarget();
+        }
+
+        @Override
+        public void setRinging(String connectionId) {
+            mHandler.obtainMessage(MSG_SET_RINGING, connectionId).sendToTarget();
+        }
+
+        @Override
+        public void setDialing(String connectionId) {
+            mHandler.obtainMessage(MSG_SET_DIALING, connectionId).sendToTarget();
+        }
+
+        @Override
+        public void setDisconnected(
+                String connectionId, int disconnectCause, String disconnectMessage) {
+            SomeArgs args = SomeArgs.obtain();
+            args.arg1 = connectionId;
+            args.arg2 = disconnectMessage;
+            args.argi1 = disconnectCause;
+            mHandler.obtainMessage(MSG_SET_DISCONNECTED, args).sendToTarget();
+        }
+
+        @Override
+        public void setOnHold(String connectionId) {
+            mHandler.obtainMessage(MSG_SET_ON_HOLD, connectionId).sendToTarget();
+        }
+
+        @Override
+        public void setRingbackRequested(String connectionId, boolean ringback) {
+            mHandler.obtainMessage(MSG_SET_RINGBACK_REQUESTED, ringback ? 1 : 0, 0, connectionId)
+                    .sendToTarget();
+        }
+
+        @Override
+        public void setCallCapabilities(String connectionId, int callCapabilities) {
+            mHandler.obtainMessage(MSG_SET_CALL_CAPABILITIES, callCapabilities, 0, connectionId)
+                    .sendToTarget();
+        }
+
+        @Override
+        public void setIsConferenced(String callId, String conferenceCallId) {
+            SomeArgs args = SomeArgs.obtain();
+            args.arg1 = callId;
+            args.arg2 = conferenceCallId;
+            mHandler.obtainMessage(MSG_SET_IS_CONFERENCED, args).sendToTarget();
+        }
+
+        @Override
+        public void addConferenceCall(String callId, ParcelableConference parcelableConference) {
+            SomeArgs args = SomeArgs.obtain();
+            args.arg1 = callId;
+            args.arg2 = parcelableConference;
+            mHandler.obtainMessage(MSG_ADD_CONFERENCE_CALL, args).sendToTarget();
+        }
+
+        @Override
+        public void removeCall(String connectionId) {
+            mHandler.obtainMessage(MSG_REMOVE_CALL, connectionId).sendToTarget();
+        }
+
+        @Override
+        public void onPostDialWait(String connectionId, String remainingDigits) {
+            SomeArgs args = SomeArgs.obtain();
+            args.arg1 = connectionId;
+            args.arg2 = remainingDigits;
+            mHandler.obtainMessage(MSG_ON_POST_DIAL_WAIT, args).sendToTarget();
+        }
+
+        @Override
+        public void queryRemoteConnectionServices(RemoteServiceCallback callback) {
+            mHandler.obtainMessage(MSG_QUERY_REMOTE_CALL_SERVICES, callback).sendToTarget();
+        }
+
+        @Override
+        public void setVideoState(String connectionId, int videoState) {
+            mHandler.obtainMessage(MSG_SET_VIDEO_STATE, videoState, 0, connectionId).sendToTarget();
+        }
+
+        @Override
+        public void setVideoProvider(String connectionId, IVideoProvider videoProvider) {
+            SomeArgs args = SomeArgs.obtain();
+            args.arg1 = connectionId;
+            args.arg2 = videoProvider;
+            mHandler.obtainMessage(MSG_SET_VIDEO_CALL_PROVIDER, args).sendToTarget();
+        }
+
+        @Override
+        public final void setIsVoipAudioMode(String connectionId, boolean isVoip) {
+            mHandler.obtainMessage(MSG_SET_IS_VOIP_AUDIO_MODE, isVoip ? 1 : 0, 0,
+                    connectionId).sendToTarget();
+        }
+
+        @Override
+        public final void setStatusHints(String connectionId, StatusHints statusHints) {
+            SomeArgs args = SomeArgs.obtain();
+            args.arg1 = connectionId;
+            args.arg2 = statusHints;
+            mHandler.obtainMessage(MSG_SET_STATUS_HINTS, args).sendToTarget();
+        }
+
+        @Override
+        public final void setAddress(String connectionId, Uri address, int presentation) {
+            SomeArgs args = SomeArgs.obtain();
+            args.arg1 = connectionId;
+            args.arg2 = address;
+            args.argi1 = presentation;
+            mHandler.obtainMessage(MSG_SET_ADDRESS, args).sendToTarget();
+        }
+
+        @Override
+        public final void setCallerDisplayName(
+                String connectionId, String callerDisplayName, int presentation) {
+            SomeArgs args = SomeArgs.obtain();
+            args.arg1 = connectionId;
+            args.arg2 = callerDisplayName;
+            args.argi1 = presentation;
+            mHandler.obtainMessage(MSG_SET_CALLER_DISPLAY_NAME, args).sendToTarget();
+        }
+
+        @Override
+        public final void setConferenceableConnections(
+                String connectionId, List<String> conferenceableConnectionIds) {
+            SomeArgs args = SomeArgs.obtain();
+            args.arg1 = connectionId;
+            args.arg2 = conferenceableConnectionIds;
+            mHandler.obtainMessage(MSG_SET_CONFERENCEABLE_CONNECTIONS, args).sendToTarget();
+        }
+    };
+
+    public ConnectionServiceAdapterServant(IConnectionServiceAdapter delegate) {
+        mDelegate = delegate;
+    }
+
+    public IConnectionServiceAdapter getStub() {
+        return mStub;
+    }
+}
diff --git a/telecomm/java/android/telecom/GatewayInfo.aidl b/telecomm/java/android/telecom/GatewayInfo.aidl
new file mode 100644
index 0000000..ad9858c
--- /dev/null
+++ b/telecomm/java/android/telecom/GatewayInfo.aidl
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2014, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telecom;
+
+/**
+ * {@hide}
+ */
+parcelable GatewayInfo;
diff --git a/telecomm/java/android/telecom/GatewayInfo.java b/telecomm/java/android/telecom/GatewayInfo.java
new file mode 100644
index 0000000..583c3e2
--- /dev/null
+++ b/telecomm/java/android/telecom/GatewayInfo.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2014, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telecom;
+
+import android.annotation.SystemApi;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+/**
+ * When calls are made, they may contain gateway information for services which route phone calls
+ * through their own service/numbers. The data consists of a number to call and the package name of
+ * the service. This data is used in two ways:
+ * <ol>
+ * <li> Call the appropriate routing number
+ * <li> Display information about how the call is being routed to the user
+ * </ol>
+ */
+public class GatewayInfo implements Parcelable {
+
+    private final String mGatewayProviderPackageName;
+    private final Uri mGatewayAddress;
+    private final Uri mOriginalAddress;
+
+    /** @hide */
+    @SystemApi
+    public GatewayInfo(String packageName, Uri gatewayUri, Uri originalAddress) {
+        mGatewayProviderPackageName = packageName;
+        mGatewayAddress = gatewayUri;
+        mOriginalAddress = originalAddress;
+    }
+
+    /**
+     * Package name of the gateway provider service. used to place the call with.
+     */
+    public String getGatewayProviderPackageName() {
+        return mGatewayProviderPackageName;
+    }
+
+    /**
+     * Gateway provider address to use when actually placing the call.
+     */
+    public Uri getGatewayAddress() {
+        return mGatewayAddress;
+    }
+
+    /**
+     * The actual call address that the user is trying to connect to via the gateway.
+     */
+    public Uri getOriginalAddress() {
+        return mOriginalAddress;
+    }
+
+    public boolean isEmpty() {
+        return TextUtils.isEmpty(mGatewayProviderPackageName) || mGatewayAddress == null;
+    }
+
+    /** Implement the Parcelable interface */
+    public static final Parcelable.Creator<GatewayInfo> CREATOR =
+            new Parcelable.Creator<GatewayInfo> () {
+
+        @Override
+        public GatewayInfo createFromParcel(Parcel source) {
+            String gatewayPackageName = source.readString();
+            Uri gatewayUri = Uri.CREATOR.createFromParcel(source);
+            Uri originalAddress = Uri.CREATOR.createFromParcel(source);
+            return new GatewayInfo(gatewayPackageName, gatewayUri, originalAddress);
+        }
+
+        @Override
+        public GatewayInfo[] newArray(int size) {
+            return new GatewayInfo[size];
+        }
+    };
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void writeToParcel(Parcel destination, int flags) {
+        destination.writeString(mGatewayProviderPackageName);
+        mGatewayAddress.writeToParcel(destination, 0);
+        mOriginalAddress.writeToParcel(destination, 0);
+    }
+}
diff --git a/telecomm/java/android/telecom/InCallAdapter.java b/telecomm/java/android/telecom/InCallAdapter.java
new file mode 100644
index 0000000..fd3cf2e
--- /dev/null
+++ b/telecomm/java/android/telecom/InCallAdapter.java
@@ -0,0 +1,274 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telecom;
+
+import android.os.RemoteException;
+
+import com.android.internal.telecom.IInCallAdapter;
+
+/**
+ * Receives commands from {@link InCallService} implementations which should be executed by
+ * Telecom. When Telecom binds to a {@link InCallService}, an instance of this class is given to
+ * the in-call service through which it can manipulate live (active, dialing, ringing) calls. When
+ * the in-call service is notified of new calls, it can use the
+ * given call IDs to execute commands such as {@link #answerCall} for incoming calls or
+ * {@link #disconnectCall} for active calls the user would like to end. Some commands are only
+ * appropriate for calls in certain states; please consult each method for such limitations.
+ * <p>
+ * The adapter will stop functioning when there are no more calls.
+ *
+ * {@hide}
+ */
+public final class InCallAdapter {
+    private final IInCallAdapter mAdapter;
+
+    /**
+     * {@hide}
+     */
+    public InCallAdapter(IInCallAdapter adapter) {
+        mAdapter = adapter;
+    }
+
+    /**
+     * Instructs Telecom to answer the specified call.
+     *
+     * @param callId The identifier of the call to answer.
+     * @param videoState The video state in which to answer the call.
+     */
+    public void answerCall(String callId, int videoState) {
+        try {
+            mAdapter.answerCall(callId, videoState);
+        } catch (RemoteException e) {
+        }
+    }
+
+    /**
+     * Instructs Telecom to reject the specified call.
+     *
+     * @param callId The identifier of the call to reject.
+     * @param rejectWithMessage Whether to reject with a text message.
+     * @param textMessage An optional text message with which to respond.
+     */
+    public void rejectCall(String callId, boolean rejectWithMessage, String textMessage) {
+        try {
+            mAdapter.rejectCall(callId, rejectWithMessage, textMessage);
+        } catch (RemoteException e) {
+        }
+    }
+
+    /**
+     * Instructs Telecom to disconnect the specified call.
+     *
+     * @param callId The identifier of the call to disconnect.
+     */
+    public void disconnectCall(String callId) {
+        try {
+            mAdapter.disconnectCall(callId);
+        } catch (RemoteException e) {
+        }
+    }
+
+    /**
+     * Instructs Telecom to put the specified call on hold.
+     *
+     * @param callId The identifier of the call to put on hold.
+     */
+    public void holdCall(String callId) {
+        try {
+            mAdapter.holdCall(callId);
+        } catch (RemoteException e) {
+        }
+    }
+
+    /**
+     * Instructs Telecom to release the specified call from hold.
+     *
+     * @param callId The identifier of the call to release from hold.
+     */
+    public void unholdCall(String callId) {
+        try {
+            mAdapter.unholdCall(callId);
+        } catch (RemoteException e) {
+        }
+    }
+
+    /**
+     * Mute the microphone.
+     *
+     * @param shouldMute True if the microphone should be muted.
+     */
+    public void mute(boolean shouldMute) {
+        try {
+            mAdapter.mute(shouldMute);
+        } catch (RemoteException e) {
+        }
+    }
+
+    /**
+     * Sets the audio route (speaker, bluetooth, etc...). See {@link AudioState}.
+     *
+     * @param route The audio route to use.
+     */
+    public void setAudioRoute(int route) {
+        try {
+            mAdapter.setAudioRoute(route);
+        } catch (RemoteException e) {
+        }
+    }
+
+    /**
+     * Instructs Telecom to play a dual-tone multi-frequency signaling (DTMF) tone in a call.
+     *
+     * Any other currently playing DTMF tone in the specified call is immediately stopped.
+     *
+     * @param callId The unique ID of the call in which the tone will be played.
+     * @param digit A character representing the DTMF digit for which to play the tone. This
+     *         value must be one of {@code '0'} through {@code '9'}, {@code '*'} or {@code '#'}.
+     */
+    public void playDtmfTone(String callId, char digit) {
+        try {
+            mAdapter.playDtmfTone(callId, digit);
+        } catch (RemoteException e) {
+        }
+    }
+
+    /**
+     * Instructs Telecom to stop any dual-tone multi-frequency signaling (DTMF) tone currently
+     * playing.
+     *
+     * DTMF tones are played by calling {@link #playDtmfTone(String,char)}. If no DTMF tone is
+     * currently playing, this method will do nothing.
+     *
+     * @param callId The unique ID of the call in which any currently playing tone will be stopped.
+     */
+    public void stopDtmfTone(String callId) {
+        try {
+            mAdapter.stopDtmfTone(callId);
+        } catch (RemoteException e) {
+        }
+    }
+
+    /**
+     * Instructs Telecom to continue playing a post-dial DTMF string.
+     *
+     * A post-dial DTMF string is a string of digits entered after a phone number, when dialed,
+     * that are immediately sent as DTMF tones to the recipient as soon as the connection is made.
+     * While these tones are playing, Telecom will notify the {@link InCallService} that the call
+     * is in the post dial state.
+     *
+     * If the DTMF string contains a {@link TelecomManager#DTMF_CHARACTER_PAUSE} symbol, Telecom
+     * will temporarily pause playing the tones for a pre-defined period of time.
+     *
+     * If the DTMF string contains a {@link TelecomManager#DTMF_CHARACTER_WAIT} symbol, Telecom
+     * will pause playing the tones and notify the {@link InCallService} that the call is in the
+     * post dial wait state. When the user decides to continue the postdial sequence, the
+     * {@link InCallService} should invoke the {@link #postDialContinue(String,boolean)} method.
+     *
+     * @param callId The unique ID of the call for which postdial string playing should continue.
+     * @param proceed Whether or not to continue with the post-dial sequence.
+     */
+    public void postDialContinue(String callId, boolean proceed) {
+        try {
+            mAdapter.postDialContinue(callId, proceed);
+        } catch (RemoteException e) {
+        }
+    }
+
+    /**
+     * Instructs Telecom to add a PhoneAccountHandle to the specified call
+     *
+     * @param callId The identifier of the call
+     * @param accountHandle The PhoneAccountHandle through which to place the call
+     */
+    public void phoneAccountSelected(String callId, PhoneAccountHandle accountHandle) {
+        try {
+            mAdapter.phoneAccountSelected(callId, accountHandle);
+        } catch (RemoteException e) {
+        }
+    }
+
+    /**
+     * Instructs Telecom to conference the specified call.
+     *
+     * @param callId The unique ID of the call.
+     * @hide
+     */
+    public void conference(String callId, String otherCallId) {
+        try {
+            mAdapter.conference(callId, otherCallId);
+        } catch (RemoteException ignored) {
+        }
+    }
+
+    /**
+     * Instructs Telecom to split the specified call from any conference call with which it may be
+     * connected.
+     *
+     * @param callId The unique ID of the call.
+     * @hide
+     */
+    public void splitFromConference(String callId) {
+        try {
+            mAdapter.splitFromConference(callId);
+        } catch (RemoteException ignored) {
+        }
+    }
+
+    /**
+     * Instructs Telecom to merge child calls of the specified conference call.
+     */
+    public void mergeConference(String callId) {
+        try {
+            mAdapter.mergeConference(callId);
+        } catch (RemoteException ignored) {
+        }
+    }
+
+    /**
+     * Instructs Telecom to swap the child calls of the specified conference call.
+     */
+    public void swapConference(String callId) {
+        try {
+            mAdapter.swapConference(callId);
+        } catch (RemoteException ignored) {
+        }
+    }
+
+    /**
+     * Instructs Telecom to turn the proximity sensor on.
+     */
+    public void turnProximitySensorOn() {
+        try {
+            mAdapter.turnOnProximitySensor();
+        } catch (RemoteException ignored) {
+        }
+    }
+
+    /**
+     * Instructs Telecom to turn the proximity sensor off.
+     *
+     * @param screenOnImmediately If true, the screen will be turned on immediately if it was
+     * previously off. Otherwise, the screen will only be turned on after the proximity sensor
+     * is no longer triggered.
+     */
+    public void turnProximitySensorOff(boolean screenOnImmediately) {
+        try {
+            mAdapter.turnOffProximitySensor(screenOnImmediately);
+        } catch (RemoteException ignored) {
+        }
+    }
+}
diff --git a/telecomm/java/android/telecom/InCallService.java b/telecomm/java/android/telecom/InCallService.java
new file mode 100644
index 0000000..fa12756
--- /dev/null
+++ b/telecomm/java/android/telecom/InCallService.java
@@ -0,0 +1,365 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telecom;
+
+import android.annotation.SystemApi;
+import android.annotation.SdkConstant;
+import android.app.Service;
+import android.content.Intent;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.view.Surface;
+
+import com.android.internal.os.SomeArgs;
+import com.android.internal.telecom.IInCallAdapter;
+import com.android.internal.telecom.IInCallService;
+
+import java.lang.String;
+
+/**
+ * This service is implemented by any app that wishes to provide the user-interface for managing
+ * phone calls. Telecom binds to this service while there exists a live (active or incoming) call,
+ * and uses it to notify the in-call app of any live and and recently disconnected calls.
+ *
+ * {@hide}
+ */
+@SystemApi
+public abstract class InCallService extends Service {
+
+    /**
+     * The {@link Intent} that must be declared as handled by the service.
+     */
+    @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
+    public static final String SERVICE_INTERFACE = "android.telecom.InCallService";
+
+    private static final int MSG_SET_IN_CALL_ADAPTER = 1;
+    private static final int MSG_ADD_CALL = 2;
+    private static final int MSG_UPDATE_CALL = 3;
+    private static final int MSG_SET_POST_DIAL_WAIT = 4;
+    private static final int MSG_ON_AUDIO_STATE_CHANGED = 5;
+    private static final int MSG_BRING_TO_FOREGROUND = 6;
+
+    /** Default Handler used to consolidate binder method calls onto a single thread. */
+    private final Handler mHandler = new Handler(Looper.getMainLooper()) {
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_SET_IN_CALL_ADAPTER:
+                    mPhone = new Phone(new InCallAdapter((IInCallAdapter) msg.obj));
+                    onPhoneCreated(mPhone);
+                    break;
+                case MSG_ADD_CALL:
+                    mPhone.internalAddCall((ParcelableCall) msg.obj);
+                    break;
+                case MSG_UPDATE_CALL:
+                    mPhone.internalUpdateCall((ParcelableCall) msg.obj);
+                    break;
+                case MSG_SET_POST_DIAL_WAIT: {
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    try {
+                        String callId = (String) args.arg1;
+                        String remaining = (String) args.arg2;
+                        mPhone.internalSetPostDialWait(callId, remaining);
+                    } finally {
+                        args.recycle();
+                    }
+                    break;
+                }
+                case MSG_ON_AUDIO_STATE_CHANGED:
+                    mPhone.internalAudioStateChanged((AudioState) msg.obj);
+                    break;
+                case MSG_BRING_TO_FOREGROUND:
+                    mPhone.internalBringToForeground(msg.arg1 == 1);
+                    break;
+                default:
+                    break;
+            }
+        }
+    };
+
+    /** Manages the binder calls so that the implementor does not need to deal with it. */
+    private final class InCallServiceBinder extends IInCallService.Stub {
+        @Override
+        public void setInCallAdapter(IInCallAdapter inCallAdapter) {
+            mHandler.obtainMessage(MSG_SET_IN_CALL_ADAPTER, inCallAdapter).sendToTarget();
+        }
+
+        @Override
+        public void addCall(ParcelableCall call) {
+            mHandler.obtainMessage(MSG_ADD_CALL, call).sendToTarget();
+        }
+
+        @Override
+        public void updateCall(ParcelableCall call) {
+            mHandler.obtainMessage(MSG_UPDATE_CALL, call).sendToTarget();
+        }
+
+        @Override
+        public void setPostDial(String callId, String remaining) {
+            // TODO: Unused
+        }
+
+        @Override
+        public void setPostDialWait(String callId, String remaining) {
+            SomeArgs args = SomeArgs.obtain();
+            args.arg1 = callId;
+            args.arg2 = remaining;
+            mHandler.obtainMessage(MSG_SET_POST_DIAL_WAIT, args).sendToTarget();
+        }
+
+        @Override
+        public void onAudioStateChanged(AudioState audioState) {
+            mHandler.obtainMessage(MSG_ON_AUDIO_STATE_CHANGED, audioState).sendToTarget();
+        }
+
+        @Override
+        public void bringToForeground(boolean showDialpad) {
+            mHandler.obtainMessage(MSG_BRING_TO_FOREGROUND, showDialpad ? 1 : 0, 0).sendToTarget();
+        }
+    }
+
+    private Phone mPhone;
+
+    public InCallService() {
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return new InCallServiceBinder();
+    }
+
+    @Override
+    public boolean onUnbind(Intent intent) {
+        if (mPhone != null) {
+            Phone oldPhone = mPhone;
+            mPhone = null;
+
+            oldPhone.destroy();
+            onPhoneDestroyed(oldPhone);
+        }
+        return false;
+    }
+
+    /**
+     * Obtain the {@code Phone} associated with this {@code InCallService}.
+     *
+     * @return The {@code Phone} object associated with this {@code InCallService}, or {@code null}
+     *         if the {@code InCallService} is not in a state where it has an associated
+     *         {@code Phone}.
+     */
+    public Phone getPhone() {
+        return mPhone;
+    }
+
+    /**
+     * Invoked when the {@code Phone} has been created. This is a signal to the in-call experience
+     * to start displaying in-call information to the user. Each instance of {@code InCallService}
+     * will have only one {@code Phone}, and this method will be called exactly once in the lifetime
+     * of the {@code InCallService}.
+     *
+     * @param phone The {@code Phone} object associated with this {@code InCallService}.
+     */
+    public void onPhoneCreated(Phone phone) {
+    }
+
+    /**
+     * Invoked when a {@code Phone} has been destroyed. This is a signal to the in-call experience
+     * to stop displaying in-call information to the user. This method will be called exactly once
+     * in the lifetime of the {@code InCallService}, and it will always be called after a previous
+     * call to {@link #onPhoneCreated(Phone)}.
+     *
+     * @param phone The {@code Phone} object associated with this {@code InCallService}.
+     */
+    public void onPhoneDestroyed(Phone phone) {
+    }
+
+    /**
+     * Class to invoke functionality related to video calls.
+     * @hide
+     */
+    public static abstract class VideoCall {
+
+        /**
+         * Sets a listener to invoke callback methods in the InCallUI after performing video
+         * telephony actions.
+         *
+         * @param videoCallListener The call video client.
+         */
+        public abstract void setVideoCallListener(VideoCall.Listener videoCallListener);
+
+        /**
+         * Sets the camera to be used for video recording in a video call.
+         *
+         * @param cameraId The id of the camera.
+         */
+        public abstract void setCamera(String cameraId);
+
+        /**
+         * Sets the surface to be used for displaying a preview of what the user's camera is
+         * currently capturing.  When video transmission is enabled, this is the video signal which
+         * is sent to the remote device.
+         *
+         * @param surface The surface.
+         */
+        public abstract void setPreviewSurface(Surface surface);
+
+        /**
+         * Sets the surface to be used for displaying the video received from the remote device.
+         *
+         * @param surface The surface.
+         */
+        public abstract void setDisplaySurface(Surface surface);
+
+        /**
+         * Sets the device orientation, in degrees.  Assumes that a standard portrait orientation of
+         * the device is 0 degrees.
+         *
+         * @param rotation The device orientation, in degrees.
+         */
+        public abstract void setDeviceOrientation(int rotation);
+
+        /**
+         * Sets camera zoom ratio.
+         *
+         * @param value The camera zoom ratio.
+         */
+        public abstract void setZoom(float value);
+
+        /**
+         * Issues a request to modify the properties of the current session.  The request is sent to
+         * the remote device where it it handled by
+         * {@link VideoCall.Listener#onSessionModifyRequestReceived}.
+         * Some examples of session modification requests: upgrade call from audio to video,
+         * downgrade call from video to audio, pause video.
+         *
+         * @param requestProfile The requested call video properties.
+         */
+        public abstract void sendSessionModifyRequest(VideoProfile requestProfile);
+
+        /**
+         * Provides a response to a request to change the current call session video
+         * properties.
+         * This is in response to a request the InCall UI has received via
+         * {@link VideoCall.Listener#onSessionModifyRequestReceived}.
+         * The response is handled on the remove device by
+         * {@link VideoCall.Listener#onSessionModifyResponseReceived}.
+         *
+         * @param responseProfile The response call video properties.
+         */
+        public abstract void sendSessionModifyResponse(VideoProfile responseProfile);
+
+        /**
+         * Issues a request to the video provider to retrieve the camera capabilities.
+         * Camera capabilities are reported back to the caller via
+         * {@link VideoCall.Listener#onCameraCapabilitiesChanged(CameraCapabilities)}.
+         */
+        public abstract void requestCameraCapabilities();
+
+        /**
+         * Issues a request to the video telephony framework to retrieve the cumulative data usage for
+         * the current call.  Data usage is reported back to the caller via
+         * {@link VideoCall.Listener#onCallDataUsageChanged}.
+         */
+        public abstract void requestCallDataUsage();
+
+        /**
+         * Provides the video telephony framework with the URI of an image to be displayed to remote
+         * devices when the video signal is paused.
+         *
+         * @param uri URI of image to display.
+         */
+        public abstract void setPauseImage(String uri);
+
+        /**
+         * Listener class which invokes callbacks after video call actions occur.
+         * @hide
+         */
+        public static abstract class Listener {
+            /**
+             * Called when a session modification request is received from the remote device.
+             * The remote request is sent via
+             * {@link Connection.VideoProvider#onSendSessionModifyRequest}. The InCall UI
+             * is responsible for potentially prompting the user whether they wish to accept the new
+             * call profile (e.g. prompt user if they wish to accept an upgrade from an audio to a
+             * video call) and should call
+             * {@link Connection.VideoProvider#onSendSessionModifyResponse} to indicate
+             * the video settings the user has agreed to.
+             *
+             * @param videoProfile The requested video call profile.
+             */
+            public abstract void onSessionModifyRequestReceived(VideoProfile videoProfile);
+
+            /**
+             * Called when a response to a session modification request is received from the remote
+             * device. The remote InCall UI sends the response using
+             * {@link Connection.VideoProvider#onSendSessionModifyResponse}.
+             *
+             * @param status Status of the session modify request.  Valid values are
+             *               {@link Connection.VideoProvider#SESSION_MODIFY_REQUEST_SUCCESS},
+             *               {@link Connection.VideoProvider#SESSION_MODIFY_REQUEST_FAIL},
+             *               {@link Connection.VideoProvider#SESSION_MODIFY_REQUEST_INVALID}
+             * @param requestedProfile The original request which was sent to the remote device.
+             * @param responseProfile The actual profile changes made by the remote device.
+             */
+            public abstract void onSessionModifyResponseReceived(int status,
+                    VideoProfile requestedProfile, VideoProfile responseProfile);
+
+            /**
+             * Handles events related to the current session which the client may wish to handle.
+             * These are separate from requested changes to the session due to the underlying
+             * protocol or connection.
+             *
+             * Valid values are:
+             * {@link Connection.VideoProvider#SESSION_EVENT_RX_PAUSE},
+             * {@link Connection.VideoProvider#SESSION_EVENT_RX_RESUME},
+             * {@link Connection.VideoProvider#SESSION_EVENT_TX_START},
+             * {@link Connection.VideoProvider#SESSION_EVENT_TX_STOP},
+             * {@link Connection.VideoProvider#SESSION_EVENT_CAMERA_FAILURE},
+             * {@link Connection.VideoProvider#SESSION_EVENT_CAMERA_READY}
+             *
+             * @param event The event.
+             */
+            public abstract void onCallSessionEvent(int event);
+
+            /**
+             * Handles a change to the video dimensions from the remote caller (peer). This could
+             * happen if, for example, the peer changes orientation of their device.
+             *
+             * @param width  The updated peer video width.
+             * @param height The updated peer video height.
+             */
+            public abstract void onPeerDimensionsChanged(int width, int height);
+
+            /**
+             * Handles an update to the total data used for the current session.
+             *
+             * @param dataUsage The updated data usage.
+             */
+            public abstract void onCallDataUsageChanged(int dataUsage);
+
+            /**
+             * Handles a change in camera capabilities.
+             *
+             * @param cameraCapabilities The changed camera capabilities.
+             */
+            public abstract void onCameraCapabilitiesChanged(
+                    CameraCapabilities cameraCapabilities);
+        }
+    }
+}
diff --git a/telecomm/java/android/telecom/Log.java b/telecomm/java/android/telecom/Log.java
new file mode 100644
index 0000000..73cc4a5
--- /dev/null
+++ b/telecomm/java/android/telecom/Log.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright 2014, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telecom;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.IllegalFormatException;
+import java.util.Locale;
+
+/**
+ * Manages logging for the entire module.
+ *
+ * @hide
+ */
+final public class Log {
+
+    // Generic tag for all Telecom Framework logging
+    private static final String TAG = "TelecomFramework";
+
+    public static final boolean FORCE_LOGGING = false; /* STOP SHIP if true */
+    public static final boolean DEBUG = isLoggable(android.util.Log.DEBUG);
+    public static final boolean INFO = isLoggable(android.util.Log.INFO);
+    public static final boolean VERBOSE = isLoggable(android.util.Log.VERBOSE);
+    public static final boolean WARN = isLoggable(android.util.Log.WARN);
+    public static final boolean ERROR = isLoggable(android.util.Log.ERROR);
+
+    private Log() {}
+
+    public static boolean isLoggable(int level) {
+        return FORCE_LOGGING || android.util.Log.isLoggable(TAG, level);
+    }
+
+    public static void d(String prefix, String format, Object... args) {
+        if (DEBUG) {
+            android.util.Log.d(TAG, buildMessage(prefix, format, args));
+        }
+    }
+
+    public static void d(Object objectPrefix, String format, Object... args) {
+        if (DEBUG) {
+            android.util.Log.d(TAG, buildMessage(getPrefixFromObject(objectPrefix), format, args));
+        }
+    }
+
+    public static void i(String prefix, String format, Object... args) {
+        if (INFO) {
+            android.util.Log.i(TAG, buildMessage(prefix, format, args));
+        }
+    }
+
+    public static void i(Object objectPrefix, String format, Object... args) {
+        if (INFO) {
+            android.util.Log.i(TAG, buildMessage(getPrefixFromObject(objectPrefix), format, args));
+        }
+    }
+
+    public static void v(String prefix, String format, Object... args) {
+        if (VERBOSE) {
+            android.util.Log.v(TAG, buildMessage(prefix, format, args));
+        }
+    }
+
+    public static void v(Object objectPrefix, String format, Object... args) {
+        if (VERBOSE) {
+            android.util.Log.v(TAG, buildMessage(getPrefixFromObject(objectPrefix), format, args));
+        }
+    }
+
+    public static void w(String prefix, String format, Object... args) {
+        if (WARN) {
+            android.util.Log.w(TAG, buildMessage(prefix, format, args));
+        }
+    }
+
+    public static void w(Object objectPrefix, String format, Object... args) {
+        if (WARN) {
+            android.util.Log.w(TAG, buildMessage(getPrefixFromObject(objectPrefix), format, args));
+        }
+    }
+
+    public static void e(String prefix, Throwable tr, String format, Object... args) {
+        if (ERROR) {
+            android.util.Log.e(TAG, buildMessage(prefix, format, args), tr);
+        }
+    }
+
+    public static void e(Object objectPrefix, Throwable tr, String format, Object... args) {
+        if (ERROR) {
+            android.util.Log.e(TAG, buildMessage(getPrefixFromObject(objectPrefix), format, args),
+                    tr);
+        }
+    }
+
+    public static void wtf(String prefix, Throwable tr, String format, Object... args) {
+        android.util.Log.wtf(TAG, buildMessage(prefix, format, args), tr);
+    }
+
+    public static void wtf(Object objectPrefix, Throwable tr, String format, Object... args) {
+        android.util.Log.wtf(TAG, buildMessage(getPrefixFromObject(objectPrefix), format, args),
+                tr);
+    }
+
+    public static void wtf(String prefix, String format, Object... args) {
+        String msg = buildMessage(prefix, format, args);
+        android.util.Log.wtf(TAG, msg, new IllegalStateException(msg));
+    }
+
+    public static void wtf(Object objectPrefix, String format, Object... args) {
+        String msg = buildMessage(getPrefixFromObject(objectPrefix), format, args);
+        android.util.Log.wtf(TAG, msg, new IllegalStateException(msg));
+    }
+
+    /**
+     * Redact personally identifiable information for production users.
+     * If we are running in verbose mode, return the original string, otherwise
+     * return a SHA-1 hash of the input string.
+     */
+    public static String pii(Object pii) {
+        if (pii == null || VERBOSE) {
+            return String.valueOf(pii);
+        }
+        return "[" + secureHash(String.valueOf(pii).getBytes()) + "]";
+    }
+
+    private static String secureHash(byte[] input) {
+        MessageDigest messageDigest;
+        try {
+            messageDigest = MessageDigest.getInstance("SHA-1");
+        } catch (NoSuchAlgorithmException e) {
+            return null;
+        }
+        messageDigest.update(input);
+        byte[] result = messageDigest.digest();
+        return encodeHex(result);
+    }
+
+    private static String encodeHex(byte[] bytes) {
+        StringBuffer hex = new StringBuffer(bytes.length * 2);
+
+        for (int i = 0; i < bytes.length; i++) {
+            int byteIntValue = bytes[i] & 0xff;
+            if (byteIntValue < 0x10) {
+                hex.append("0");
+            }
+            hex.append(Integer.toString(byteIntValue, 16));
+        }
+
+        return hex.toString();
+    }
+
+    private static String getPrefixFromObject(Object obj) {
+        return obj == null ? "<null>" : obj.getClass().getSimpleName();
+    }
+
+    private static String buildMessage(String prefix, String format, Object... args) {
+        String msg;
+        try {
+            msg = (args == null || args.length == 0) ? format
+                    : String.format(Locale.US, format, args);
+        } catch (IllegalFormatException ife) {
+            wtf("Log", ife, "IllegalFormatException: formatString='%s' numArgs=%d", format,
+                    args.length);
+            msg = format + " (An error occurred while formatting the message.)";
+        }
+        return String.format(Locale.US, "%s: %s", prefix, msg);
+    }
+}
diff --git a/telecomm/java/android/telecom/ParcelableCall.aidl b/telecomm/java/android/telecom/ParcelableCall.aidl
new file mode 100644
index 0000000..480e82f
--- /dev/null
+++ b/telecomm/java/android/telecom/ParcelableCall.aidl
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2014, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telecom;
+
+/**
+ * {@hide}
+ */
+parcelable ParcelableCall;
diff --git a/telecomm/java/android/telecom/ParcelableCall.java b/telecomm/java/android/telecom/ParcelableCall.java
new file mode 100644
index 0000000..838c7cf
--- /dev/null
+++ b/telecomm/java/android/telecom/ParcelableCall.java
@@ -0,0 +1,328 @@
+/*
+ * Copyright 2014, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telecom;
+
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.RemoteException;
+import android.telephony.DisconnectCause;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import com.android.internal.telecom.IVideoProvider;
+
+/**
+ * Information about a call that is used between InCallService and Telecom.
+ * @hide
+ */
+public final class ParcelableCall implements Parcelable {
+    private final String mId;
+    private final int mState;
+    private final int mDisconnectCauseCode;
+    private final String mDisconnectCauseMsg;
+    private final List<String> mCannedSmsResponses;
+    private final int mCapabilities;
+    private final int mProperties;
+    private final long mConnectTimeMillis;
+    private final Uri mHandle;
+    private final int mHandlePresentation;
+    private final String mCallerDisplayName;
+    private final int mCallerDisplayNamePresentation;
+    private final GatewayInfo mGatewayInfo;
+    private final PhoneAccountHandle mAccountHandle;
+    private final IVideoProvider mVideoCallProvider;
+    private InCallService.VideoCall mVideoCall;
+    private final String mParentCallId;
+    private final List<String> mChildCallIds;
+    private final StatusHints mStatusHints;
+    private final int mVideoState;
+    private final List<String> mConferenceableCallIds;
+    private final Bundle mExtras;
+
+    public ParcelableCall(
+            String id,
+            int state,
+            int disconnectCauseCode,
+            String disconnectCauseMsg,
+            List<String> cannedSmsResponses,
+            int capabilities,
+            int properties,
+            long connectTimeMillis,
+            Uri handle,
+            int handlePresentation,
+            String callerDisplayName,
+            int callerDisplayNamePresentation,
+            GatewayInfo gatewayInfo,
+            PhoneAccountHandle accountHandle,
+            IVideoProvider videoCallProvider,
+            String parentCallId,
+            List<String> childCallIds,
+            StatusHints statusHints,
+            int videoState,
+            List<String> conferenceableCallIds,
+            Bundle extras) {
+        mId = id;
+        mState = state;
+        mDisconnectCauseCode = disconnectCauseCode;
+        mDisconnectCauseMsg = disconnectCauseMsg;
+        mCannedSmsResponses = cannedSmsResponses;
+        mCapabilities = capabilities;
+        mProperties = properties;
+        mConnectTimeMillis = connectTimeMillis;
+        mHandle = handle;
+        mHandlePresentation = handlePresentation;
+        mCallerDisplayName = callerDisplayName;
+        mCallerDisplayNamePresentation = callerDisplayNamePresentation;
+        mGatewayInfo = gatewayInfo;
+        mAccountHandle = accountHandle;
+        mVideoCallProvider = videoCallProvider;
+        mParentCallId = parentCallId;
+        mChildCallIds = childCallIds;
+        mStatusHints = statusHints;
+        mVideoState = videoState;
+        mConferenceableCallIds = Collections.unmodifiableList(conferenceableCallIds);
+        mExtras = extras;
+    }
+
+    /** The unique ID of the call. */
+    public String getId() {
+        return mId;
+    }
+
+    /** The current state of the call. */
+    public int getState() {
+        return mState;
+    }
+
+    /**
+     * Reason for disconnection, values are defined in {@link DisconnectCause}. Valid when call
+     * state is {@link CallState#DISCONNECTED}.
+     */
+    public int getDisconnectCauseCode() {
+        return mDisconnectCauseCode;
+    }
+
+    /**
+     * Further optional textual information about the reason for disconnection. Valid when call
+     * state is {@link CallState#DISCONNECTED}.
+     */
+    public String getDisconnectCauseMsg() {
+        return mDisconnectCauseMsg;
+    }
+
+    /**
+     * The set of possible text message responses when this call is incoming.
+     */
+    public List<String> getCannedSmsResponses() {
+        return mCannedSmsResponses;
+    }
+
+    // Bit mask of actions a call supports, values are defined in {@link CallCapabilities}.
+    public int getCapabilities() {
+        return mCapabilities;
+    }
+
+    /** Bitmask of properties of the call. */
+    public int getProperties() { return mProperties; }
+
+    /** The time that the call switched to the active state. */
+    public long getConnectTimeMillis() {
+        return mConnectTimeMillis;
+    }
+
+    /** The endpoint to which the call is connected. */
+    public Uri getHandle() {
+        return mHandle;
+    }
+
+    /**
+     * The presentation requirements for the handle. See {@link TelecomManager} for valid values.
+     */
+    public int getHandlePresentation() {
+        return mHandlePresentation;
+    }
+
+    /** The endpoint to which the call is connected. */
+    public String getCallerDisplayName() {
+        return mCallerDisplayName;
+    }
+
+    /**
+     * The presentation requirements for the caller display name.
+     * See {@link TelecomManager} for valid values.
+     */
+    public int getCallerDisplayNamePresentation() {
+        return mCallerDisplayNamePresentation;
+    }
+
+    /** Gateway information for the call. */
+    public GatewayInfo getGatewayInfo() {
+        return mGatewayInfo;
+    }
+
+    /** PhoneAccountHandle information for the call. */
+    public PhoneAccountHandle getAccountHandle() {
+        return mAccountHandle;
+    }
+
+    /**
+     * Returns an object for remotely communicating through the video call provider's binder.
+     * @return The video call.
+     */
+    public InCallService.VideoCall getVideoCall() {
+        if (mVideoCall == null && mVideoCallProvider != null) {
+            try {
+                mVideoCall = new VideoCallImpl(mVideoCallProvider);
+            } catch (RemoteException ignored) {
+                // Ignore RemoteException.
+            }
+        }
+
+        return mVideoCall;
+    }
+
+    /**
+     * The conference call to which this call is conferenced. Null if not conferenced.
+     */
+    public String getParentCallId() {
+        return mParentCallId;
+    }
+
+    /**
+     * The child call-IDs if this call is a conference call. Returns an empty list if this is not
+     * a conference call or if the conference call contains no children.
+     */
+    public List<String> getChildCallIds() {
+        return mChildCallIds;
+    }
+
+    public List<String> getConferenceableCallIds() {
+        return mConferenceableCallIds;
+    }
+
+    /**
+     * The status label and icon.
+     *
+     * @return Status hints.
+     */
+    public StatusHints getStatusHints() {
+        return mStatusHints;
+    }
+
+    /**
+     * The video state.
+     * @return The video state of the call.
+     */
+    public int getVideoState() {
+        return mVideoState;
+    }
+
+    /**
+     * Any extras to pass with the call
+     *
+     * @return a bundle of extras
+     */
+    public Bundle getExtras() {
+        return mExtras;
+    }
+
+    /** Responsible for creating ParcelableCall objects for deserialized Parcels. */
+    public static final Parcelable.Creator<ParcelableCall> CREATOR =
+            new Parcelable.Creator<ParcelableCall> () {
+        @Override
+        public ParcelableCall createFromParcel(Parcel source) {
+            ClassLoader classLoader = ParcelableCall.class.getClassLoader();
+            String id = source.readString();
+            int state = source.readInt();
+            int disconnectCauseCode = source.readInt();
+            String disconnectCauseMsg = source.readString();
+            List<String> cannedSmsResponses = new ArrayList<>();
+            source.readList(cannedSmsResponses, classLoader);
+            int capabilities = source.readInt();
+            int properties = source.readInt();
+            long connectTimeMillis = source.readLong();
+            Uri handle = source.readParcelable(classLoader);
+            int handlePresentation = source.readInt();
+            String callerDisplayName = source.readString();
+            int callerDisplayNamePresentation = source.readInt();
+            GatewayInfo gatewayInfo = source.readParcelable(classLoader);
+            PhoneAccountHandle accountHandle = source.readParcelable(classLoader);
+            IVideoProvider videoCallProvider =
+                    IVideoProvider.Stub.asInterface(source.readStrongBinder());
+            String parentCallId = source.readString();
+            List<String> childCallIds = new ArrayList<>();
+            source.readList(childCallIds, classLoader);
+            StatusHints statusHints = source.readParcelable(classLoader);
+            int videoState = source.readInt();
+            List<String> conferenceableCallIds = new ArrayList<>();
+            source.readList(conferenceableCallIds, classLoader);
+            Bundle extras = source.readParcelable(classLoader);
+            return new ParcelableCall(id, state, disconnectCauseCode, disconnectCauseMsg,
+                    cannedSmsResponses, capabilities, properties, connectTimeMillis, handle,
+                    handlePresentation, callerDisplayName, callerDisplayNamePresentation,
+                    gatewayInfo, accountHandle, videoCallProvider, parentCallId, childCallIds,
+                    statusHints, videoState, conferenceableCallIds, extras);
+        }
+
+        @Override
+        public ParcelableCall[] newArray(int size) {
+            return new ParcelableCall[size];
+        }
+    };
+
+    /** {@inheritDoc} */
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /** Writes ParcelableCall object into a Parcel. */
+    @Override
+    public void writeToParcel(Parcel destination, int flags) {
+        destination.writeString(mId);
+        destination.writeInt(mState);
+        destination.writeInt(mDisconnectCauseCode);
+        destination.writeString(mDisconnectCauseMsg);
+        destination.writeList(mCannedSmsResponses);
+        destination.writeInt(mCapabilities);
+        destination.writeInt(mProperties);
+        destination.writeLong(mConnectTimeMillis);
+        destination.writeParcelable(mHandle, 0);
+        destination.writeInt(mHandlePresentation);
+        destination.writeString(mCallerDisplayName);
+        destination.writeInt(mCallerDisplayNamePresentation);
+        destination.writeParcelable(mGatewayInfo, 0);
+        destination.writeParcelable(mAccountHandle, 0);
+        destination.writeStrongBinder(
+                mVideoCallProvider != null ? mVideoCallProvider.asBinder() : null);
+        destination.writeString(mParentCallId);
+        destination.writeList(mChildCallIds);
+        destination.writeParcelable(mStatusHints, 0);
+        destination.writeInt(mVideoState);
+        destination.writeList(mConferenceableCallIds);
+        destination.writeParcelable(mExtras, 0);
+    }
+
+    @Override
+    public String toString() {
+        return String.format("[%s, parent:%s, children:%s]", mId, mParentCallId, mChildCallIds);
+    }
+}
diff --git a/telecomm/java/android/telecom/ParcelableConference.aidl b/telecomm/java/android/telecom/ParcelableConference.aidl
new file mode 100644
index 0000000..155ba94
--- /dev/null
+++ b/telecomm/java/android/telecom/ParcelableConference.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2014, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telecom;
+
+parcelable ParcelableConference;
diff --git a/telecomm/java/android/telecom/ParcelableConference.java b/telecomm/java/android/telecom/ParcelableConference.java
new file mode 100644
index 0000000..97c709c
--- /dev/null
+++ b/telecomm/java/android/telecom/ParcelableConference.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2014, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telecom;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A parcelable representation of a conference connection.
+ * @hide
+ */
+public final class ParcelableConference implements Parcelable {
+
+    private PhoneAccountHandle mPhoneAccount;
+    private int mState;
+    private int mCapabilities;
+    private List<String> mConnectionIds;
+
+    public ParcelableConference(
+            PhoneAccountHandle phoneAccount,
+            int state,
+            int capabilities,
+            List<String> connectionIds) {
+        mPhoneAccount = phoneAccount;
+        mState = state;
+        mCapabilities = capabilities;
+        mConnectionIds = connectionIds;
+    }
+
+    @Override
+    public String toString() {
+        return (new StringBuffer())
+                .append("account: ")
+                .append(mPhoneAccount)
+                .append(", state: ")
+                .append(Connection.stateToString(mState))
+                .append(", capabilities: ")
+                .append(PhoneCapabilities.toString(mCapabilities))
+                .append(", children: ")
+                .append(mConnectionIds)
+                .toString();
+    }
+
+    public PhoneAccountHandle getPhoneAccount() {
+        return mPhoneAccount;
+    }
+
+    public int getState() {
+        return mState;
+    }
+
+    public int getCapabilities() {
+        return mCapabilities;
+    }
+
+    public List<String> getConnectionIds() {
+        return mConnectionIds;
+    }
+
+    public static final Parcelable.Creator<ParcelableConference> CREATOR =
+            new Parcelable.Creator<ParcelableConference> () {
+        @Override
+        public ParcelableConference createFromParcel(Parcel source) {
+            ClassLoader classLoader = ParcelableConference.class.getClassLoader();
+            PhoneAccountHandle phoneAccount = source.readParcelable(classLoader);
+            int state = source.readInt();
+            int capabilities = source.readInt();
+            List<String> connectionIds = new ArrayList<>(2);
+            source.readList(connectionIds, classLoader);
+
+            return new ParcelableConference(phoneAccount, state, capabilities, connectionIds);
+        }
+
+        @Override
+        public ParcelableConference[] newArray(int size) {
+            return new ParcelableConference[size];
+        }
+    };
+
+    /** {@inheritDoc} */
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /** Writes ParcelableConference object into a Parcel. */
+    @Override
+    public void writeToParcel(Parcel destination, int flags) {
+        destination.writeParcelable(mPhoneAccount, 0);
+        destination.writeInt(mState);
+        destination.writeInt(mCapabilities);
+        destination.writeList(mConnectionIds);
+    }
+}
diff --git a/telecomm/java/android/telecom/ParcelableConnection.aidl b/telecomm/java/android/telecom/ParcelableConnection.aidl
new file mode 100644
index 0000000..e91ebc3
--- /dev/null
+++ b/telecomm/java/android/telecom/ParcelableConnection.aidl
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2014, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telecom;
+
+/**
+ * {@hide}
+ */
+parcelable ParcelableConnection;
diff --git a/telecomm/java/android/telecom/ParcelableConnection.java b/telecomm/java/android/telecom/ParcelableConnection.java
new file mode 100644
index 0000000..63393b2
--- /dev/null
+++ b/telecomm/java/android/telecom/ParcelableConnection.java
@@ -0,0 +1,232 @@
+/*
+ * Copyright 2014, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telecom;
+
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.telecom.IVideoProvider;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Information about a connection that is used between Telecom and the ConnectionService.
+ * This is used to send initial Connection information to Telecom when the connection is
+ * first created.
+ * @hide
+ */
+public final class ParcelableConnection implements Parcelable {
+    private final PhoneAccountHandle mPhoneAccount;
+    private final int mState;
+    private final int mCapabilities;
+    private final Uri mAddress;
+    private final int mAddressPresentation;
+    private final String mCallerDisplayName;
+    private final int mCallerDisplayNamePresentation;
+    private final IVideoProvider mVideoProvider;
+    private final int mVideoState;
+    private final boolean mRingbackRequested;
+    private final boolean mIsVoipAudioMode;
+    private final StatusHints mStatusHints;
+    private final int mDisconnectCause;
+    private final String mDisconnectMessage;
+    private final List<String> mConferenceableConnectionIds;
+
+    /** @hide */
+    public ParcelableConnection(
+            PhoneAccountHandle phoneAccount,
+            int state,
+            int capabilities,
+            Uri address,
+            int addressPresentation,
+            String callerDisplayName,
+            int callerDisplayNamePresentation,
+            IVideoProvider videoProvider,
+            int videoState,
+            boolean ringbackRequested,
+            boolean isVoipAudioMode,
+            StatusHints statusHints,
+            int disconnectCause,
+            String disconnectMessage,
+            List<String> conferenceableConnectionIds) {
+        mPhoneAccount = phoneAccount;
+        mState = state;
+        mCapabilities = capabilities;
+        mAddress = address;
+        mAddressPresentation = addressPresentation;
+        mCallerDisplayName = callerDisplayName;
+        mCallerDisplayNamePresentation = callerDisplayNamePresentation;
+        mVideoProvider = videoProvider;
+        mVideoState = videoState;
+        mRingbackRequested = ringbackRequested;
+        mIsVoipAudioMode = isVoipAudioMode;
+        mStatusHints = statusHints;
+        mDisconnectCause = disconnectCause;
+        mDisconnectMessage = disconnectMessage;
+        this.mConferenceableConnectionIds = conferenceableConnectionIds;
+    }
+
+    public PhoneAccountHandle getPhoneAccount() {
+        return mPhoneAccount;
+    }
+
+    public int getState() {
+        return mState;
+    }
+
+    // Bit mask of actions a call supports, values are defined in {@link CallCapabilities}.
+    public int getCapabilities() {
+        return mCapabilities;
+    }
+
+    public Uri getHandle() {
+        return mAddress;
+    }
+
+    public int getHandlePresentation() {
+        return mAddressPresentation;
+    }
+
+    public String getCallerDisplayName() {
+        return mCallerDisplayName;
+    }
+
+    public int getCallerDisplayNamePresentation() {
+        return mCallerDisplayNamePresentation;
+    }
+
+    public IVideoProvider getVideoProvider() {
+        return mVideoProvider;
+    }
+
+    public int getVideoState() {
+        return mVideoState;
+    }
+
+    public boolean isRingbackRequested() {
+        return mRingbackRequested;
+    }
+
+    public boolean getIsVoipAudioMode() {
+        return mIsVoipAudioMode;
+    }
+
+    public final StatusHints getStatusHints() {
+        return mStatusHints;
+    }
+
+    public final int getDisconnectCause() {
+        return mDisconnectCause;
+    }
+
+    public final String getDisconnectMessage() {
+        return mDisconnectMessage;
+    }
+
+    public final List<String> getConferenceableConnectionIds() {
+        return mConferenceableConnectionIds;
+    }
+
+    @Override
+    public String toString() {
+        return new StringBuilder()
+                .append("ParcelableConnection [act:")
+                .append(mPhoneAccount)
+                .append(", state:")
+                .append(mState)
+                .append(", capabilities:")
+                .append(PhoneCapabilities.toString(mCapabilities))
+                .toString();
+    }
+
+    public static final Parcelable.Creator<ParcelableConnection> CREATOR =
+            new Parcelable.Creator<ParcelableConnection> () {
+        @Override
+        public ParcelableConnection createFromParcel(Parcel source) {
+            ClassLoader classLoader = ParcelableConnection.class.getClassLoader();
+
+            PhoneAccountHandle phoneAccount = source.readParcelable(classLoader);
+            int state = source.readInt();
+            int capabilities = source.readInt();
+            Uri address = source.readParcelable(classLoader);
+            int addressPresentation = source.readInt();
+            String callerDisplayName = source.readString();
+            int callerDisplayNamePresentation = source.readInt();
+            IVideoProvider videoCallProvider =
+                    IVideoProvider.Stub.asInterface(source.readStrongBinder());
+            int videoState = source.readInt();
+            boolean ringbackRequested = source.readByte() == 1;
+            boolean audioModeIsVoip = source.readByte() == 1;
+            StatusHints statusHints = source.readParcelable(classLoader);
+            int disconnectCauseCode = source.readInt();
+            String disconnectCauseMessage = source.readString();
+            List<String> conferenceableConnectionIds = new ArrayList<>();
+            source.readStringList(conferenceableConnectionIds);
+
+            return new ParcelableConnection(
+                    phoneAccount,
+                    state,
+                    capabilities,
+                    address,
+                    addressPresentation,
+                    callerDisplayName,
+                    callerDisplayNamePresentation,
+                    videoCallProvider,
+                    videoState,
+                    ringbackRequested,
+                    audioModeIsVoip,
+                    statusHints,
+                    disconnectCauseCode,
+                    disconnectCauseMessage,
+                    conferenceableConnectionIds);
+        }
+
+        @Override
+        public ParcelableConnection[] newArray(int size) {
+            return new ParcelableConnection[size];
+        }
+    };
+
+    /** {@inheritDoc} */
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /** Writes ParcelableConnection object into a Parcel. */
+    @Override
+    public void writeToParcel(Parcel destination, int flags) {
+        destination.writeParcelable(mPhoneAccount, 0);
+        destination.writeInt(mState);
+        destination.writeInt(mCapabilities);
+        destination.writeParcelable(mAddress, 0);
+        destination.writeInt(mAddressPresentation);
+        destination.writeString(mCallerDisplayName);
+        destination.writeInt(mCallerDisplayNamePresentation);
+        destination.writeStrongBinder(
+                mVideoProvider != null ? mVideoProvider.asBinder() : null);
+        destination.writeInt(mVideoState);
+        destination.writeByte((byte) (mRingbackRequested ? 1 : 0));
+        destination.writeByte((byte) (mIsVoipAudioMode ? 1 : 0));
+        destination.writeParcelable(mStatusHints, 0);
+        destination.writeInt(mDisconnectCause);
+        destination.writeString(mDisconnectMessage);
+        destination.writeStringList(mConferenceableConnectionIds);
+    }
+}
diff --git a/telecomm/java/android/telecom/Phone.java b/telecomm/java/android/telecom/Phone.java
new file mode 100644
index 0000000..5131790
--- /dev/null
+++ b/telecomm/java/android/telecom/Phone.java
@@ -0,0 +1,284 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telecom;
+
+import android.annotation.SystemApi;
+import android.util.ArrayMap;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+/**
+ * A unified virtual device providing a means of voice (and other) communication on a device.
+ *
+ * {@hide}
+ */
+@SystemApi
+public final class Phone {
+
+    public abstract static class Listener {
+        /**
+         * Called when the audio state changes.
+         *
+         * @param phone The {@code Phone} calling this method.
+         * @param audioState The new {@link AudioState}.
+         */
+        public void onAudioStateChanged(Phone phone, AudioState audioState) { }
+
+        /**
+         * Called to bring the in-call screen to the foreground. The in-call experience should
+         * respond immediately by coming to the foreground to inform the user of the state of
+         * ongoing {@code Call}s.
+         *
+         * @param phone The {@code Phone} calling this method.
+         * @param showDialpad If true, put up the dialpad when the screen is shown.
+         */
+        public void onBringToForeground(Phone phone, boolean showDialpad) { }
+
+        /**
+         * Called when a {@code Call} has been added to this in-call session. The in-call user
+         * experience should add necessary state listeners to the specified {@code Call} and
+         * immediately start to show the user information about the existence
+         * and nature of this {@code Call}. Subsequent invocations of {@link #getCalls()} will
+         * include this {@code Call}.
+         *
+         * @param phone The {@code Phone} calling this method.
+         * @param call A newly added {@code Call}.
+         */
+        public void onCallAdded(Phone phone, Call call) { }
+
+        /**
+         * Called when a {@code Call} has been removed from this in-call session. The in-call user
+         * experience should remove any state listeners from the specified {@code Call} and
+         * immediately stop displaying any information about this {@code Call}.
+         * Subsequent invocations of {@link #getCalls()} will no longer include this {@code Call}.
+         *
+         * @param phone The {@code Phone} calling this method.
+         * @param call A newly removed {@code Call}.
+         */
+        public void onCallRemoved(Phone phone, Call call) { }
+    }
+
+    // A Map allows us to track each Call by its Telecom-specified call ID
+    private final Map<String, Call> mCallByTelecomCallId = new ArrayMap<>();
+
+    // A List allows us to keep the Calls in a stable iteration order so that casually developed
+    // user interface components do not incur any spurious jank
+    private final List<Call> mCalls = new CopyOnWriteArrayList<>();
+
+    // An unmodifiable view of the above List can be safely shared with subclass implementations
+    private final List<Call> mUnmodifiableCalls = Collections.unmodifiableList(mCalls);
+
+    private final InCallAdapter mInCallAdapter;
+
+    private AudioState mAudioState;
+
+    private final List<Listener> mListeners = new CopyOnWriteArrayList<>();
+
+    /** {@hide} */
+    Phone(InCallAdapter adapter) {
+        mInCallAdapter = adapter;
+    }
+
+    /** {@hide} */
+    final void internalAddCall(ParcelableCall parcelableCall) {
+        Call call = new Call(this, parcelableCall.getId(), mInCallAdapter);
+        mCallByTelecomCallId.put(parcelableCall.getId(), call);
+        mCalls.add(call);
+        checkCallTree(parcelableCall);
+        call.internalUpdate(parcelableCall, mCallByTelecomCallId);
+        fireCallAdded(call);
+     }
+
+    /** {@hide} */
+    final void internalRemoveCall(Call call) {
+        mCallByTelecomCallId.remove(call.internalGetCallId());
+        mCalls.remove(call);
+        fireCallRemoved(call);
+    }
+
+    /** {@hide} */
+    final void internalUpdateCall(ParcelableCall parcelableCall) {
+         Call call = mCallByTelecomCallId.get(parcelableCall.getId());
+         if (call != null) {
+             checkCallTree(parcelableCall);
+             call.internalUpdate(parcelableCall, mCallByTelecomCallId);
+         }
+     }
+
+    /** {@hide} */
+    final void internalSetPostDialWait(String telecomId, String remaining) {
+        Call call = mCallByTelecomCallId.get(telecomId);
+        if (call != null) {
+            call.internalSetPostDialWait(remaining);
+        }
+    }
+
+    /** {@hide} */
+    final void internalAudioStateChanged(AudioState audioState) {
+        if (!Objects.equals(mAudioState, audioState)) {
+            mAudioState = audioState;
+            fireAudioStateChanged(audioState);
+        }
+    }
+
+    /** {@hide} */
+    final Call internalGetCallByTelecomId(String telecomId) {
+        return mCallByTelecomCallId.get(telecomId);
+    }
+
+    /** {@hide} */
+    final void internalBringToForeground(boolean showDialpad) {
+        fireBringToForeground(showDialpad);
+    }
+
+    /**
+     * Called to destroy the phone and cleanup any lingering calls.
+     * @hide
+     */
+    final void destroy() {
+        for (Call call : mCalls) {
+            if (call.getState() != Call.STATE_DISCONNECTED) {
+                call.internalSetDisconnected();
+            }
+        }
+    }
+
+    /**
+     * Adds a listener to this {@code Phone}.
+     *
+     * @param listener A {@code Listener} object.
+     */
+    public final void addListener(Listener listener) {
+        mListeners.add(listener);
+    }
+
+    /**
+     * Removes a listener from this {@code Phone}.
+     *
+     * @param listener A {@code Listener} object.
+     */
+    public final void removeListener(Listener listener) {
+        if (listener != null) {
+            mListeners.remove(listener);
+        }
+    }
+
+    /**
+     * Obtains the current list of {@code Call}s to be displayed by this in-call experience.
+     *
+     * @return A list of the relevant {@code Call}s.
+     */
+    public final List<Call> getCalls() {
+        return mUnmodifiableCalls;
+    }
+
+    /**
+     * Sets the microphone mute state. When this request is honored, there will be change to
+     * the {@link #getAudioState()}.
+     *
+     * @param state {@code true} if the microphone should be muted; {@code false} otherwise.
+     */
+    public final void setMuted(boolean state) {
+        mInCallAdapter.mute(state);
+    }
+
+    /**
+     * Sets the audio route (speaker, bluetooth, etc...).  When this request is honored, there will
+     * be change to the {@link #getAudioState()}.
+     *
+     * @param route The audio route to use.
+     */
+    public final void setAudioRoute(int route) {
+        mInCallAdapter.setAudioRoute(route);
+    }
+
+    /**
+     * Turns the proximity sensor on. When this request is made, the proximity sensor will
+     * become active, and the touch screen and display will be turned off when the user's face
+     * is detected to be in close proximity to the screen. This operation is a no-op on devices
+     * that do not have a proximity sensor.
+     */
+    public final void setProximitySensorOn() {
+        mInCallAdapter.turnProximitySensorOn();
+    }
+
+    /**
+     * Turns the proximity sensor off. When this request is made, the proximity sensor will
+     * become inactive, and no longer affect the touch screen and display. This operation is a
+     * no-op on devices that do not have a proximity sensor.
+     *
+     * @param screenOnImmediately If true, the screen will be turned on immediately if it was
+     * previously off. Otherwise, the screen will only be turned on after the proximity sensor
+     * is no longer triggered.
+     */
+    public final void setProximitySensorOff(boolean screenOnImmediately) {
+        mInCallAdapter.turnProximitySensorOff(screenOnImmediately);
+    }
+
+    /**
+     * Obtains the current phone call audio state of the {@code Phone}.
+     *
+     * @return An object encapsulating the audio state.
+     */
+    public final AudioState getAudioState() {
+        return mAudioState;
+    }
+
+    private void fireCallAdded(Call call) {
+        for (Listener listener : mListeners) {
+            listener.onCallAdded(this, call);
+        }
+    }
+
+    private void fireCallRemoved(Call call) {
+        for (Listener listener : mListeners) {
+            listener.onCallRemoved(this, call);
+        }
+    }
+
+    private void fireAudioStateChanged(AudioState audioState) {
+        for (Listener listener : mListeners) {
+            listener.onAudioStateChanged(this, audioState);
+        }
+    }
+
+    private void fireBringToForeground(boolean showDialpad) {
+        for (Listener listener : mListeners) {
+            listener.onBringToForeground(this, showDialpad);
+        }
+    }
+
+    private void checkCallTree(ParcelableCall parcelableCall) {
+        if (parcelableCall.getParentCallId() != null &&
+                !mCallByTelecomCallId.containsKey(parcelableCall.getParentCallId())) {
+            Log.wtf(this, "ParcelableCall %s has nonexistent parent %s",
+                    parcelableCall.getId(), parcelableCall.getParentCallId());
+        }
+        if (parcelableCall.getChildCallIds() != null) {
+            for (int i = 0; i < parcelableCall.getChildCallIds().size(); i++) {
+                if (!mCallByTelecomCallId.containsKey(parcelableCall.getChildCallIds().get(i))) {
+                    Log.wtf(this, "ParcelableCall %s has nonexistent child %s",
+                            parcelableCall.getId(), parcelableCall.getChildCallIds().get(i));
+                }
+            }
+        }
+    }
+}
diff --git a/telecomm/java/android/telecom/PhoneAccount.aidl b/telecomm/java/android/telecom/PhoneAccount.aidl
new file mode 100644
index 0000000..d5e6058
--- /dev/null
+++ b/telecomm/java/android/telecom/PhoneAccount.aidl
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telecom;
+
+/**
+ * {@hide}
+  */
+parcelable PhoneAccount;
diff --git a/telecomm/java/android/telecom/PhoneAccount.java b/telecomm/java/android/telecom/PhoneAccount.java
new file mode 100644
index 0000000..0c233ebb
--- /dev/null
+++ b/telecomm/java/android/telecom/PhoneAccount.java
@@ -0,0 +1,490 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telecom;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.res.Resources.NotFoundException;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+import java.lang.String;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.MissingResourceException;
+
+/**
+ * Describes a distinct account, line of service or call placement method that the system
+ * can use to place phone calls.
+ */
+public class PhoneAccount implements Parcelable {
+
+    /**
+     * Flag indicating that this {@code PhoneAccount} can act as a connection manager for
+     * other connections. The {@link ConnectionService} associated with this {@code PhoneAccount}
+     * will be allowed to manage phone calls including using its own proprietary phone-call
+     * implementation (like VoIP calling) to make calls instead of the telephony stack.
+     * <p>
+     * When a user opts to place a call using the SIM-based telephony stack, the
+     * {@link ConnectionService} associated with this {@code PhoneAccount} will be attempted first
+     * if the user has explicitly selected it to be used as the default connection manager.
+     * <p>
+     * See {@link #getCapabilities}
+     */
+    public static final int CAPABILITY_CONNECTION_MANAGER = 0x1;
+
+    /**
+     * Flag indicating that this {@code PhoneAccount} can make phone calls in place of
+     * traditional SIM-based telephony calls. This account will be treated as a distinct method
+     * for placing calls alongside the traditional SIM-based telephony stack. This flag is
+     * distinct from {@link #CAPABILITY_CONNECTION_MANAGER} in that it is not allowed to manage
+     * calls from or use the built-in telephony stack to place its calls.
+     * <p>
+     * See {@link #getCapabilities}
+     * <p>
+     * {@hide}
+     */
+    public static final int CAPABILITY_CALL_PROVIDER = 0x2;
+
+    /**
+     * Flag indicating that this {@code PhoneAccount} represents a built-in PSTN SIM
+     * subscription.
+     * <p>
+     * Only the Android framework can register a {@code PhoneAccount} having this capability.
+     * <p>
+     * See {@link #getCapabilities}
+     */
+    public static final int CAPABILITY_SIM_SUBSCRIPTION = 0x4;
+
+    /**
+     * Flag indicating that this {@code PhoneAccount} is capable of placing video calls.
+     * <p>
+     * See {@link #getCapabilities}
+     * @hide
+     */
+    public static final int CAPABILITY_VIDEO_CALLING = 0x8;
+
+    /**
+     * Flag indicating that this {@code PhoneAccount} is capable of placing emergency calls.
+     * By default all PSTN {@code PhoneAccount}s are capable of placing emergency calls.
+     * <p>
+     * See {@link #getCapabilities}
+     */
+    public static final int CAPABILITY_PLACE_EMERGENCY_CALLS = 0x10;
+
+    /**
+     * Flag indicating that this {@code PhoneAccount} is always enabled and cannot be disabled by
+     * the user.
+     * This capability is reserved for important {@code PhoneAccount}s such as the emergency calling
+     * only {@code PhoneAccount}.
+     * <p>
+     * See {@link #getCapabilities}
+     * @hide
+     */
+    public static final int CAPABILITY_ALWAYS_ENABLED = 0x20;
+
+    /**
+     * URI scheme for telephone number URIs.
+     */
+    public static final String SCHEME_TEL = "tel";
+
+    /**
+     * URI scheme for voicemail URIs.
+     */
+    public static final String SCHEME_VOICEMAIL = "voicemail";
+
+    /**
+     * URI scheme for SIP URIs.
+     */
+    public static final String SCHEME_SIP = "sip";
+
+    private final PhoneAccountHandle mAccountHandle;
+    private final Uri mAddress;
+    private final Uri mSubscriptionAddress;
+    private final int mCapabilities;
+    private final int mIconResId;
+    private final CharSequence mLabel;
+    private final CharSequence mShortDescription;
+    private final List<String> mSupportedUriSchemes;
+    private final boolean mIsEnabled;
+
+    public static class Builder {
+        private PhoneAccountHandle mAccountHandle;
+        private Uri mAddress;
+        private Uri mSubscriptionAddress;
+        private int mCapabilities;
+        private int mIconResId;
+        private CharSequence mLabel;
+        private CharSequence mShortDescription;
+        private List<String> mSupportedUriSchemes = new ArrayList<String>();
+        private boolean mIsEnabled = false;
+
+        public Builder(PhoneAccountHandle accountHandle, CharSequence label) {
+            this.mAccountHandle = accountHandle;
+            this.mLabel = label;
+        }
+
+        /**
+         * Creates an instance of the {@link PhoneAccount.Builder} from an existing
+         * {@link PhoneAccount}.
+         *
+         * @param phoneAccount The {@link PhoneAccount} used to initialize the builder.
+         */
+        public Builder(PhoneAccount phoneAccount) {
+            mAccountHandle = phoneAccount.getAccountHandle();
+            mAddress = phoneAccount.getAddress();
+            mSubscriptionAddress = phoneAccount.getSubscriptionAddress();
+            mCapabilities = phoneAccount.getCapabilities();
+            mIconResId = phoneAccount.getIconResId();
+            mLabel = phoneAccount.getLabel();
+            mShortDescription = phoneAccount.getShortDescription();
+            mSupportedUriSchemes.addAll(phoneAccount.getSupportedUriSchemes());
+            mIsEnabled = phoneAccount.isEnabled();
+        }
+
+        public Builder setAddress(Uri value) {
+            this.mAddress = value;
+            return this;
+        }
+
+        public Builder setSubscriptionAddress(Uri value) {
+            this.mSubscriptionAddress = value;
+            return this;
+        }
+
+        public Builder setCapabilities(int value) {
+            this.mCapabilities = value;
+            return this;
+        }
+
+        public Builder setIconResId(int value) {
+            this.mIconResId = value;
+            return this;
+        }
+
+        public Builder setShortDescription(CharSequence value) {
+            this.mShortDescription = value;
+            return this;
+        }
+
+        /**
+         * Specifies an additional URI scheme supported by the {@link PhoneAccount}.
+         *
+         * @param uriScheme The URI scheme.
+         * @return The Builder.
+         * @hide
+         */
+        public Builder addSupportedUriScheme(String uriScheme) {
+            if (!TextUtils.isEmpty(uriScheme) && !mSupportedUriSchemes.contains(uriScheme)) {
+                this.mSupportedUriSchemes.add(uriScheme);
+            }
+            return this;
+        }
+
+        /**
+         * Specifies the URI schemes supported by the {@link PhoneAccount}.
+         *
+         * @param uriSchemes The URI schemes.
+         * @return The Builder.
+         */
+        public Builder setSupportedUriSchemes(List<String> uriSchemes) {
+            mSupportedUriSchemes.clear();
+
+            if (uriSchemes != null && !uriSchemes.isEmpty()) {
+                for (String uriScheme : uriSchemes) {
+                    addSupportedUriScheme(uriScheme);
+                }
+            }
+            return this;
+        }
+
+        /**
+         * Specifies whether the {@link PhoneAccount} is enabled or not.  {@link PhoneAccount}s are
+         * by default not enabled.
+         *
+         * @param value {@code True} if the {@link PhoneAccount} is enabled.
+         * @return The Builder.
+         * @hide
+         */
+        public Builder setEnabled(boolean value) {
+            this.mIsEnabled = value;
+            return this;
+        }
+
+        /**
+         * Creates an instance of a {@link PhoneAccount} based on the current builder settings.
+         *
+         * @return The {@link PhoneAccount}.
+         */
+        public PhoneAccount build() {
+            // If no supported URI schemes were defined, assume "tel" is supported.
+            if (mSupportedUriSchemes.isEmpty()) {
+                addSupportedUriScheme(SCHEME_TEL);
+            }
+
+            return new PhoneAccount(
+                    mAccountHandle,
+                    mAddress,
+                    mSubscriptionAddress,
+                    mCapabilities,
+                    mIconResId,
+                    mLabel,
+                    mShortDescription,
+                    mSupportedUriSchemes,
+                    mIsEnabled);
+        }
+    }
+
+    private PhoneAccount(
+            PhoneAccountHandle account,
+            Uri address,
+            Uri subscriptionAddress,
+            int capabilities,
+            int iconResId,
+            CharSequence label,
+            CharSequence shortDescription,
+            List<String> supportedUriSchemes,
+            boolean enabled) {
+        mAccountHandle = account;
+        mAddress = address;
+        mSubscriptionAddress = subscriptionAddress;
+        mCapabilities = capabilities;
+        mIconResId = iconResId;
+        mLabel = label;
+        mShortDescription = shortDescription;
+        mSupportedUriSchemes = Collections.unmodifiableList(supportedUriSchemes);
+        mIsEnabled = enabled;
+    }
+
+    public static Builder builder(
+            PhoneAccountHandle accountHandle,
+            CharSequence label) {
+        return new Builder(accountHandle, label);
+    }
+
+    /**
+     * Returns a builder initialized with the current {@link PhoneAccount} instance.
+     *
+     * @return The builder.
+     * @hide
+     */
+    public Builder toBuilder() { return new Builder(this); }
+
+    /**
+     * The unique identifier of this {@code PhoneAccount}.
+     *
+     * @return A {@code PhoneAccountHandle}.
+     */
+    public PhoneAccountHandle getAccountHandle() {
+        return mAccountHandle;
+    }
+
+    /**
+     * The address (e.g., a phone number) associated with this {@code PhoneAccount}. This
+     * represents the destination from which outgoing calls using this {@code PhoneAccount}
+     * will appear to come, if applicable, and the destination to which incoming calls using this
+     * {@code PhoneAccount} may be addressed.
+     *
+     * @return A address expressed as a {@code Uri}, for example, a phone number.
+     */
+    public Uri getAddress() {
+        return mAddress;
+    }
+
+    /**
+     * The raw callback number used for this {@code PhoneAccount}, as distinct from
+     * {@link #getAddress()}. For the majority of {@code PhoneAccount}s this should be registered
+     * as {@code null}.  It is used by the system for SIM-based {@code PhoneAccount} registration
+     * where {@link android.telephony.TelephonyManager#setLine1NumberForDisplay(String, String)}
+     * has been used to alter the callback number.
+     * <p>
+     *
+     * @return The subscription number, suitable for display to the user.
+     */
+    public Uri getSubscriptionAddress() {
+        return mSubscriptionAddress;
+    }
+
+    /**
+     * The capabilities of this {@code PhoneAccount}.
+     *
+     * @return A bit field of flags describing this {@code PhoneAccount}'s capabilities.
+     */
+    public int getCapabilities() {
+        return mCapabilities;
+    }
+
+    /**
+     * Determines if this {@code PhoneAccount} has a capabilities specified by the passed in
+     * bit mask.
+     *
+     * @param capability The capabilities to check.
+     * @return {@code True} if the phone account has the capability.
+     */
+    public boolean hasCapabilities(int capability) {
+        return (mCapabilities & capability) == capability;
+    }
+
+    /**
+     * A short label describing a {@code PhoneAccount}.
+     *
+     * @return A label for this {@code PhoneAccount}.
+     */
+    public CharSequence getLabel() {
+        return mLabel;
+    }
+
+    /**
+     * A short paragraph describing this {@code PhoneAccount}.
+     *
+     * @return A description for this {@code PhoneAccount}.
+     */
+    public CharSequence getShortDescription() {
+        return mShortDescription;
+    }
+
+    /**
+     * The URI schemes supported by this {@code PhoneAccount}.
+     *
+     * @return The URI schemes.
+     */
+    public List<String> getSupportedUriSchemes() {
+        return mSupportedUriSchemes;
+    }
+
+    /**
+     * Determines if the {@link PhoneAccount} supports calls to/from addresses with a specified URI
+     * scheme.
+     *
+     * @param uriScheme The URI scheme to check.
+     * @return {@code True} if the {@code PhoneAccount} supports calls to/from addresses with the
+     * specified URI scheme.
+     */
+    public boolean supportsUriScheme(String uriScheme) {
+        if (mSupportedUriSchemes == null || uriScheme == null) {
+            return false;
+        }
+
+        for (String scheme : mSupportedUriSchemes) {
+            if (scheme != null && scheme.equals(uriScheme)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Determines whether this {@code PhoneAccount} is enabled.
+     *
+     * @return {@code True} if this {@code PhoneAccount} is enabled..
+     */
+    public boolean isEnabled() {
+        return mIsEnabled;
+    }
+
+    /**
+     * The icon resource ID for the icon of this {@code PhoneAccount}.
+     *
+     * @return A resource ID.
+     */
+    public int getIconResId() {
+        return mIconResId;
+    }
+
+    /**
+     * An icon to represent this {@code PhoneAccount} in a user interface.
+     *
+     * @return An icon for this {@code PhoneAccount}.
+     */
+    public Drawable getIcon(Context context) {
+        return getIcon(context, mIconResId);
+    }
+
+    private Drawable getIcon(Context context, int resId) {
+        Context packageContext;
+        try {
+            packageContext = context.createPackageContext(
+                    mAccountHandle.getComponentName().getPackageName(), 0);
+        } catch (PackageManager.NameNotFoundException e) {
+            Log.w(this, "Cannot find package %s", mAccountHandle.getComponentName().getPackageName());
+            return null;
+        }
+        try {
+            return packageContext.getDrawable(resId);
+        } catch (NotFoundException|MissingResourceException e) {
+            Log.e(this, e, "Cannot find icon %d in package %s",
+                    resId, mAccountHandle.getComponentName().getPackageName());
+            return null;
+        }
+    }
+
+    //
+    // Parcelable implementation
+    //
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeParcelable(mAccountHandle, 0);
+        out.writeParcelable(mAddress, 0);
+        out.writeParcelable(mSubscriptionAddress, 0);
+        out.writeInt(mCapabilities);
+        out.writeInt(mIconResId);
+        out.writeCharSequence(mLabel);
+        out.writeCharSequence(mShortDescription);
+        out.writeList(mSupportedUriSchemes);
+        out.writeInt(mIsEnabled ? 1 : 0);
+    }
+
+    public static final Creator<PhoneAccount> CREATOR
+            = new Creator<PhoneAccount>() {
+        @Override
+        public PhoneAccount createFromParcel(Parcel in) {
+            return new PhoneAccount(in);
+        }
+
+        @Override
+        public PhoneAccount[] newArray(int size) {
+            return new PhoneAccount[size];
+        }
+    };
+
+    private PhoneAccount(Parcel in) {
+        ClassLoader classLoader = PhoneAccount.class.getClassLoader();
+
+        mAccountHandle = in.readParcelable(getClass().getClassLoader());
+        mAddress = in.readParcelable(getClass().getClassLoader());
+        mSubscriptionAddress = in.readParcelable(getClass().getClassLoader());
+        mCapabilities = in.readInt();
+        mIconResId = in.readInt();
+        mLabel = in.readCharSequence();
+        mShortDescription = in.readCharSequence();
+
+        List<String> supportedUriSchemes = new ArrayList<>();
+        in.readList(supportedUriSchemes, classLoader);
+        mSupportedUriSchemes = Collections.unmodifiableList(supportedUriSchemes);
+        mIsEnabled = in.readInt() == 1;
+    }
+}
diff --git a/telecomm/java/android/telecom/PhoneAccountHandle.aidl b/telecomm/java/android/telecom/PhoneAccountHandle.aidl
new file mode 100644
index 0000000..f8f9656
--- /dev/null
+++ b/telecomm/java/android/telecom/PhoneAccountHandle.aidl
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telecom;
+
+/**
+ * {@hide}
+  */
+parcelable PhoneAccountHandle;
diff --git a/telecomm/java/android/telecom/PhoneAccountHandle.java b/telecomm/java/android/telecom/PhoneAccountHandle.java
new file mode 100644
index 0000000..e13df76
--- /dev/null
+++ b/telecomm/java/android/telecom/PhoneAccountHandle.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telecom;
+
+import android.content.ComponentName;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * The unique identifier for a {@link PhoneAccount}.
+ */
+public class PhoneAccountHandle implements Parcelable {
+    private ComponentName mComponentName;
+    private String mId;
+
+    public PhoneAccountHandle(
+            ComponentName componentName,
+            String id) {
+        mComponentName = componentName;
+        mId = id;
+    }
+
+    /**
+     * The {@code ComponentName} of the {@link android.telecom.ConnectionService} which is
+     * responsible for making phone calls using this {@code PhoneAccountHandle}.
+     *
+     * @return A suitable {@code ComponentName}.
+     */
+    public ComponentName getComponentName() {
+        return mComponentName;
+    }
+
+    /**
+     * A string that uniquely distinguishes this particular {@code PhoneAccountHandle} from all the
+     * others supported by the {@link ConnectionService} that created it.
+     * <p>
+     * A {@code ConnectionService} must select identifiers that are stable for the lifetime of
+     * their users' relationship with their service, across many Android devices. For example, a
+     * good set of identifiers might be the email addresses with which with users registered for
+     * their accounts with a particular service. Depending on how a service chooses to operate,
+     * a bad set of identifiers might be an increasing series of integers
+     * ({@code 0}, {@code 1}, {@code 2}, ...) that are generated locally on each phone and could
+     * collide with values generated on other phones or after a data wipe of a given phone.
+     *
+     * @return A service-specific unique identifier for this {@code PhoneAccountHandle}.
+     */
+    public String getId() {
+        return mId;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hashCode(mComponentName) + Objects.hashCode(mId);
+    }
+
+    @Override
+    public String toString() {
+        return new StringBuilder().append(mComponentName)
+                    .append(", ")
+                    .append(mId)
+                    .toString();
+    }
+
+    @Override
+    public boolean equals(Object other) {
+        return other != null &&
+                other instanceof PhoneAccountHandle &&
+                Objects.equals(((PhoneAccountHandle) other).getComponentName(),
+                        getComponentName()) &&
+                Objects.equals(((PhoneAccountHandle) other).getId(), getId());
+    }
+
+    //
+    // Parcelable implementation.
+    //
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeParcelable(mComponentName, flags);
+        out.writeString(mId);
+    }
+
+    public static final Creator<PhoneAccountHandle> CREATOR = new Creator<PhoneAccountHandle>() {
+        @Override
+        public PhoneAccountHandle createFromParcel(Parcel in) {
+            return new PhoneAccountHandle(in);
+        }
+
+        @Override
+        public PhoneAccountHandle[] newArray(int size) {
+            return new PhoneAccountHandle[size];
+        }
+    };
+
+    private PhoneAccountHandle(Parcel in) {
+        mComponentName = in.readParcelable(getClass().getClassLoader());
+        mId = in.readString();
+    }
+}
diff --git a/telecomm/java/android/telecom/PhoneCapabilities.java b/telecomm/java/android/telecom/PhoneCapabilities.java
new file mode 100644
index 0000000..e73dfe2
--- /dev/null
+++ b/telecomm/java/android/telecom/PhoneCapabilities.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright 2014, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telecom;
+
+/**
+ * Defines capabilities a phone call can support, such as conference calling and video telephony.
+ * Also defines properties of a phone call, such as whether it is using VoLTE technology.
+ */
+public final class PhoneCapabilities {
+    /** Call can currently be put on hold or unheld. */
+    public static final int HOLD               = 0x00000001;
+
+    /** Call supports the hold feature. */
+    public static final int SUPPORT_HOLD       = 0x00000002;
+
+    /**
+     * Calls within a conference can be merged. Some connection services create a conference call
+     * only after two calls have been merged.  However, a conference call can also be added the
+     * moment there are more than one call. CDMA calls are implemented in this way because the call
+     * actions are more limited when more than one call exists. This flag allows merge to be exposed
+     * as a capability on the conference call instead of individual calls.
+     */
+    public static final int MERGE_CONFERENCE   = 0x00000004;
+
+    /** Calls withing a conference can be swapped between foreground and background. */
+    public static final int SWAP_CONFERENCE    = 0x00000008;
+
+    /** Call currently supports adding another call to this one. */
+    public static final int ADD_CALL           = 0x00000010;
+
+    /** Call supports responding via text option. */
+    public static final int RESPOND_VIA_TEXT   = 0x00000020;
+
+    /** Call can be muted. */
+    public static final int MUTE               = 0x00000040;
+
+    /**
+     * Call supports conference call management. This capability only applies to conference calls
+     * which can have other calls as children.
+     */
+    public static final int MANAGE_CONFERENCE = 0x00000080;
+
+    /**
+     * Local device supports video telephony.
+     * @hide
+     */
+    public static final int SUPPORTS_VT_LOCAL  = 0x00000100;
+
+    /**
+     * Remote device supports video telephony.
+     * @hide
+     */
+    public static final int SUPPORTS_VT_REMOTE = 0x00000200;
+
+    /**
+     * Call is using voice over LTE.
+     * @hide
+     */
+    public static final int VoLTE = 0x00000400;
+
+    /**
+     * Call is using voice over WIFI.
+     * @hide
+     */
+    public static final int VoWIFI = 0x00000800;
+
+    /**
+     * Call is able to be separated from its parent {@code Conference}, if any.
+     */
+    public static final int SEPARATE_FROM_CONFERENCE = 0x00001000;
+
+    /**
+     * Call is able to be individually disconnected when in a {@code Conference}.
+     */
+    public static final int DISCONNECT_FROM_CONFERENCE = 0x00002000;
+
+    public static final int ALL = HOLD | SUPPORT_HOLD | MERGE_CONFERENCE | SWAP_CONFERENCE
+            | ADD_CALL | RESPOND_VIA_TEXT | MUTE | MANAGE_CONFERENCE | SEPARATE_FROM_CONFERENCE
+            | DISCONNECT_FROM_CONFERENCE;
+
+    public static String toString(int capabilities) {
+        StringBuilder builder = new StringBuilder();
+        builder.append("[Capabilities:");
+        if ((capabilities & HOLD) != 0) {
+            builder.append(" HOLD");
+        }
+        if ((capabilities & SUPPORT_HOLD) != 0) {
+            builder.append(" SUPPORT_HOLD");
+        }
+        if ((capabilities & MERGE_CONFERENCE) != 0) {
+            builder.append(" MERGE_CONFERENCE");
+        }
+        if ((capabilities & SWAP_CONFERENCE) != 0) {
+            builder.append(" SWAP_CONFERENCE");
+        }
+        if ((capabilities & ADD_CALL) != 0) {
+            builder.append(" ADD_CALL");
+        }
+        if ((capabilities & RESPOND_VIA_TEXT) != 0) {
+            builder.append(" RESPOND_VIA_TEXT");
+        }
+        if ((capabilities & MUTE) != 0) {
+            builder.append(" MUTE");
+        }
+        if ((capabilities & MANAGE_CONFERENCE) != 0) {
+            builder.append(" MANAGE_CONFERENCE");
+        }
+        if ((capabilities & SUPPORTS_VT_LOCAL) != 0) {
+            builder.append(" SUPPORTS_VT_LOCAL");
+        }
+        if ((capabilities & SUPPORTS_VT_REMOTE) != 0) {
+            builder.append(" SUPPORTS_VT_REMOTE");
+        }
+        if ((capabilities & VoLTE) != 0) {
+            builder.append(" VoLTE");
+        }
+        if ((capabilities & VoWIFI) != 0) {
+            builder.append(" VoWIFI");
+        }
+        builder.append("]");
+        return builder.toString();
+    }
+
+    private PhoneCapabilities() {}
+}
diff --git a/telecomm/java/android/telecom/RemoteConference.java b/telecomm/java/android/telecom/RemoteConference.java
new file mode 100644
index 0000000..996e091
--- /dev/null
+++ b/telecomm/java/android/telecom/RemoteConference.java
@@ -0,0 +1,198 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telecom;
+
+import com.android.internal.telecom.IConnectionService;
+
+import android.os.RemoteException;
+import android.telephony.DisconnectCause;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.CopyOnWriteArraySet;
+
+/**
+ * Represents a conference call which can contain any number of {@link Connection} objects.
+ */
+public final class RemoteConference {
+
+    public abstract static class Callback {
+        public void onStateChanged(RemoteConference conference, int oldState, int newState) {}
+        public void onDisconnected(RemoteConference conference, int cause, String message) {}
+        public void onConnectionAdded(RemoteConference conference, RemoteConnection connection) {}
+        public void onConnectionRemoved(RemoteConference conference, RemoteConnection connection) {}
+        public void onCapabilitiesChanged(RemoteConference conference, int capabilities) {}
+        public void onDestroyed(RemoteConference conference) {}
+    }
+
+    private final String mId;
+    private final IConnectionService mConnectionService;
+
+    private final Set<Callback> mCallbacks = new CopyOnWriteArraySet<>();
+    private final List<RemoteConnection> mChildConnections = new CopyOnWriteArrayList<>();
+    private final List<RemoteConnection> mUnmodifiableChildConnections =
+            Collections.unmodifiableList(mChildConnections);
+
+    private int mState = Connection.STATE_NEW;
+    private int mDisconnectCause = DisconnectCause.NOT_VALID;
+    private int mCallCapabilities;
+    private String mDisconnectMessage;
+
+    /** {@hide} */
+    RemoteConference(String id, IConnectionService connectionService) {
+        mId = id;
+        mConnectionService = connectionService;
+    }
+
+    /** {@hide} */
+    String getId() {
+        return mId;
+    }
+
+    /** {@hide} */
+    void setDestroyed() {
+        for (RemoteConnection connection : mChildConnections) {
+            connection.setConference(null);
+        }
+        for (Callback c : mCallbacks) {
+            c.onDestroyed(this);
+        }
+    }
+
+    /** {@hide} */
+    void setState(int newState) {
+        if (newState != Connection.STATE_ACTIVE &&
+                newState != Connection.STATE_HOLDING &&
+                newState != Connection.STATE_DISCONNECTED) {
+            Log.w(this, "Unsupported state transition for Conference call.",
+                    Connection.stateToString(newState));
+            return;
+        }
+
+        if (mState != newState) {
+            int oldState = mState;
+            mState = newState;
+            for (Callback c : mCallbacks) {
+                c.onStateChanged(this, oldState, newState);
+            }
+        }
+    }
+
+    /** {@hide} */
+    void addConnection(RemoteConnection connection) {
+        if (!mChildConnections.contains(connection)) {
+            mChildConnections.add(connection);
+            connection.setConference(this);
+            for (Callback c : mCallbacks) {
+                c.onConnectionAdded(this, connection);
+            }
+        }
+    }
+
+    /** {@hide} */
+    void removeConnection(RemoteConnection connection) {
+        if (mChildConnections.contains(connection)) {
+            mChildConnections.remove(connection);
+            connection.setConference(null);
+            for (Callback c : mCallbacks) {
+                c.onConnectionRemoved(this, connection);
+            }
+        }
+    }
+
+    /** {@hide} */
+    void setCallCapabilities(int capabilities) {
+        if (mCallCapabilities != capabilities) {
+            mCallCapabilities = capabilities;
+            for (Callback c : mCallbacks) {
+                c.onCapabilitiesChanged(this, mCallCapabilities);
+            }
+        }
+    }
+
+    /** {@hide} */
+    void setDisconnected(int cause, String message) {
+        if (mState != Connection.STATE_DISCONNECTED) {
+            mDisconnectCause = cause;
+            mDisconnectMessage = message;
+            setState(Connection.STATE_DISCONNECTED);
+            for (Callback c : mCallbacks) {
+                c.onDisconnected(this, cause, message);
+            }
+        }
+    }
+
+    public final List<RemoteConnection> getConnections() {
+        return mUnmodifiableChildConnections;
+    }
+
+    public final int getState() {
+        return mState;
+    }
+
+    public final int getCallCapabilities() {
+        return mCallCapabilities;
+    }
+
+    public void disconnect() {
+        try {
+            mConnectionService.disconnect(mId);
+        } catch (RemoteException e) {
+        }
+    }
+
+    public void separate(RemoteConnection connection) {
+        if (mChildConnections.contains(connection)) {
+            try {
+                mConnectionService.splitFromConference(connection.getId());
+            } catch (RemoteException e) {
+            }
+        }
+    }
+
+    public void hold() {
+        try {
+            mConnectionService.hold(mId);
+        } catch (RemoteException e) {
+        }
+    }
+
+    public void unhold() {
+        try {
+            mConnectionService.unhold(mId);
+        } catch (RemoteException e) {
+        }
+    }
+
+    public int getDisconnectCause() {
+        return mDisconnectCause;
+    }
+
+    public String getDisconnectMessage() {
+        return mDisconnectMessage;
+    }
+
+    public final void registerCallback(Callback callback) {
+        mCallbacks.add(callback);
+    }
+
+    public final void unregisterCallback(Callback callback) {
+        mCallbacks.remove(callback);
+    }
+}
diff --git a/telecomm/java/android/telecom/RemoteConnection.java b/telecomm/java/android/telecom/RemoteConnection.java
new file mode 100644
index 0000000..bf699b3
--- /dev/null
+++ b/telecomm/java/android/telecom/RemoteConnection.java
@@ -0,0 +1,929 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telecom;
+
+import com.android.internal.telecom.IConnectionService;
+import com.android.internal.telecom.IVideoCallback;
+import com.android.internal.telecom.IVideoProvider;
+
+import android.net.Uri;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.telephony.DisconnectCause;
+import android.view.Surface;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * A connection provided to a {@link ConnectionService} by another {@code ConnectionService}
+ * running in a different process.
+ *
+ * @see ConnectionService#createRemoteOutgoingConnection(PhoneAccountHandle, ConnectionRequest)
+ * @see ConnectionService#createRemoteIncomingConnection(PhoneAccountHandle, ConnectionRequest)
+ */
+public final class RemoteConnection {
+
+    public static abstract class Callback {
+        /**
+         * Invoked when the state of this {@code RemoteConnection} has changed. See
+         * {@link #getState()}.
+         *
+         * @param connection The {@code RemoteConnection} invoking this method.
+         * @param state The new state of the {@code RemoteConnection}.
+         */
+        public void onStateChanged(RemoteConnection connection, int state) {}
+
+        /**
+         * Invoked when this {@code RemoteConnection} is disconnected.
+         *
+         * @param connection The {@code RemoteConnection} invoking this method.
+         * @param disconnectCauseCode The failure code ({@see DisconnectCause}) associated with this
+         *         failed connection.
+         * @param disconnectCauseMessage The reason for the connection failure. This will not be
+         *         displayed to the user.
+         */
+        public void onDisconnected(
+                RemoteConnection connection,
+                int disconnectCauseCode,
+                String disconnectCauseMessage) {}
+
+        /**
+         * Invoked when this {@code RemoteConnection} is requesting ringback. See
+         * {@link #isRingbackRequested()}.
+         *
+         * @param connection The {@code RemoteConnection} invoking this method.
+         * @param ringback Whether the {@code RemoteConnection} is requesting ringback.
+         */
+        public void onRingbackRequested(RemoteConnection connection, boolean ringback) {}
+
+        /**
+         * Indicates that the call capabilities of this {@code RemoteConnection} have changed.
+         * See {@link #getCallCapabilities()}.
+         *
+         * @param connection The {@code RemoteConnection} invoking this method.
+         * @param callCapabilities The new call capabilities of the {@code RemoteConnection}.
+         */
+        public void onCallCapabilitiesChanged(RemoteConnection connection, int callCapabilities) {}
+
+        /**
+         * Invoked when the post-dial sequence in the outgoing {@code Connection} has reached a
+         * pause character. This causes the post-dial signals to stop pending user confirmation. An
+         * implementation should present this choice to the user and invoke
+         * {@link RemoteConnection#postDialContinue(boolean)} when the user makes the choice.
+         *
+         * @param connection The {@code RemoteConnection} invoking this method.
+         * @param remainingPostDialSequence The post-dial characters that remain to be sent.
+         */
+        public void onPostDialWait(RemoteConnection connection, String remainingPostDialSequence) {}
+
+        /**
+         * Indicates that the VOIP audio status of this {@code RemoteConnection} has changed.
+         * See {@link #isVoipAudioMode()}.
+         *
+         * @param connection The {@code RemoteConnection} invoking this method.
+         * @param isVoip Whether the new audio state of the {@code RemoteConnection} is VOIP.
+         */
+        public void onVoipAudioChanged(RemoteConnection connection, boolean isVoip) {}
+
+        /**
+         * Indicates that the status hints of this {@code RemoteConnection} have changed. See
+         * {@link #getStatusHints()} ()}.
+         *
+         * @param connection The {@code RemoteConnection} invoking this method.
+         * @param statusHints The new status hints of the {@code RemoteConnection}.
+         */
+        public void onStatusHintsChanged(RemoteConnection connection, StatusHints statusHints) {}
+
+        /**
+         * Indicates that the address (e.g., phone number) of this {@code RemoteConnection} has
+         * changed. See {@link #getAddress()} and {@link #getAddressPresentation()}.
+         *
+         * @param connection The {@code RemoteConnection} invoking this method.
+         * @param address The new address of the {@code RemoteConnection}.
+         * @param presentation The presentation requirements for the address.
+         *        See {@link TelecomManager} for valid values.
+         */
+        public void onAddressChanged(RemoteConnection connection, Uri address, int presentation) {}
+
+        /**
+         * Indicates that the caller display name of this {@code RemoteConnection} has changed.
+         * See {@link #getCallerDisplayName()} and {@link #getCallerDisplayNamePresentation()}.
+         *
+         * @param connection The {@code RemoteConnection} invoking this method.
+         * @param callerDisplayName The new caller display name of the {@code RemoteConnection}.
+         * @param presentation The presentation requirements for the handle.
+         *        See {@link TelecomManager} for valid values.
+         */
+        public void onCallerDisplayNameChanged(
+                RemoteConnection connection, String callerDisplayName, int presentation) {}
+
+        /**
+         * Indicates that the video state of this {@code RemoteConnection} has changed.
+         * See {@link #getVideoState()}.
+         *
+         * @param connection The {@code RemoteConnection} invoking this method.
+         * @param videoState The new video state of the {@code RemoteConnection}.
+         * @hide
+         */
+        public void onVideoStateChanged(RemoteConnection connection, int videoState) {}
+
+        /**
+         * Indicates that this {@code RemoteConnection} has been destroyed. No further requests
+         * should be made to the {@code RemoteConnection}, and references to it should be cleared.
+         *
+         * @param connection The {@code RemoteConnection} invoking this method.
+         */
+        public void onDestroyed(RemoteConnection connection) {}
+
+        /**
+         * Indicates that the {@code RemoteConnection}s with which this {@code RemoteConnection}
+         * may be asked to create a conference has changed.
+         *
+         * @param connection The {@code RemoteConnection} invoking this method.
+         * @param conferenceableConnections The {@code RemoteConnection}s with which this
+         *         {@code RemoteConnection} may be asked to create a conference.
+         */
+        public void onConferenceableConnectionsChanged(
+                RemoteConnection connection,
+                List<RemoteConnection> conferenceableConnections) {}
+
+        /**
+         * Indicates that the {@code VideoProvider} associated with this {@code RemoteConnection}
+         * has changed.
+         *
+         * @param connection The {@code RemoteConnection} invoking this method.
+         * @param videoProvider The new {@code VideoProvider} associated with this
+         *         {@code RemoteConnection}.
+         * @hide
+         */
+        public void onVideoProviderChanged(
+                RemoteConnection connection, VideoProvider videoProvider) {}
+
+        /**
+         * Indicates that the {@code RemoteConference} that this {@code RemoteConnection} is a part
+         * of has changed.
+         *
+         * @param connection The {@code RemoteConnection} invoking this method.
+         * @param conference The {@code RemoteConference} of which this {@code RemoteConnection} is
+         *         a part, which may be {@code null}.
+         */
+        public void onConferenceChanged(
+                RemoteConnection connection,
+                RemoteConference conference) {}
+    }
+
+    /** {@hide} */
+    public static class VideoProvider {
+
+        public abstract static class Listener {
+            public void onReceiveSessionModifyRequest(
+                    VideoProvider videoProvider,
+                    VideoProfile videoProfile) {}
+
+            public void onReceiveSessionModifyResponse(
+                    VideoProvider videoProvider,
+                    int status,
+                    VideoProfile requestedProfile,
+                    VideoProfile responseProfile) {}
+
+            public void onHandleCallSessionEvent(VideoProvider videoProvider, int event) {}
+
+            public void onPeerDimensionsChanged(VideoProvider videoProvider, int width, int height) {}
+
+            public void onCallDataUsageChanged(VideoProvider videoProvider, int dataUsage) {}
+
+            public void onCameraCapabilitiesChanged(
+                    VideoProvider videoProvider,
+                    CameraCapabilities cameraCapabilities) {}
+        }
+
+        private final IVideoCallback mVideoCallbackDelegate = new IVideoCallback() {
+            @Override
+            public void receiveSessionModifyRequest(VideoProfile videoProfile) {
+                for (Listener l : mListeners) {
+                    l.onReceiveSessionModifyRequest(VideoProvider.this, videoProfile);
+                }
+            }
+
+            @Override
+            public void receiveSessionModifyResponse(int status, VideoProfile requestedProfile,
+                    VideoProfile responseProfile) {
+                for (Listener l : mListeners) {
+                    l.onReceiveSessionModifyResponse(
+                            VideoProvider.this,
+                            status,
+                            requestedProfile,
+                            responseProfile);
+                }
+            }
+
+            @Override
+            public void handleCallSessionEvent(int event) {
+                for (Listener l : mListeners) {
+                    l.onHandleCallSessionEvent(VideoProvider.this, event);
+                }
+            }
+
+            @Override
+            public void changePeerDimensions(int width, int height) {
+                for (Listener l : mListeners) {
+                    l.onPeerDimensionsChanged(VideoProvider.this, width, height);
+                }
+            }
+
+            @Override
+            public void changeCallDataUsage(int dataUsage) {
+                for (Listener l : mListeners) {
+                    l.onCallDataUsageChanged(VideoProvider.this, dataUsage);
+                }
+            }
+
+            @Override
+            public void changeCameraCapabilities(CameraCapabilities cameraCapabilities) {
+                for (Listener l : mListeners) {
+                    l.onCameraCapabilitiesChanged(VideoProvider.this, cameraCapabilities);
+                }
+            }
+
+            @Override
+            public IBinder asBinder() {
+                return null;
+            }
+        };
+
+        private final VideoCallbackServant mVideoCallbackServant =
+                new VideoCallbackServant(mVideoCallbackDelegate);
+
+        private final IVideoProvider mVideoProviderBinder;
+
+        /**
+         * ConcurrentHashMap constructor params: 8 is initial table size, 0.9f is
+         * load factor before resizing, 1 means we only expect a single thread to
+         * access the map so make only a single shard
+         */
+        private final Set<Listener> mListeners = Collections.newSetFromMap(
+                new ConcurrentHashMap<Listener, Boolean>(8, 0.9f, 1));
+
+        public VideoProvider(IVideoProvider videoProviderBinder) {
+            mVideoProviderBinder = videoProviderBinder;
+            try {
+                mVideoProviderBinder.setVideoCallback(mVideoCallbackServant.getStub().asBinder());
+            } catch (RemoteException e) {
+            }
+        }
+
+        public void addListener(Listener l) {
+            mListeners.add(l);
+        }
+
+        public void removeListener(Listener l) {
+            mListeners.remove(l);
+        }
+
+        public void setCamera(String cameraId) {
+            try {
+                mVideoProviderBinder.setCamera(cameraId);
+            } catch (RemoteException e) {
+            }
+        }
+
+        public void setPreviewSurface(Surface surface) {
+            try {
+                mVideoProviderBinder.setPreviewSurface(surface);
+            } catch (RemoteException e) {
+            }
+        }
+
+        public void setDisplaySurface(Surface surface) {
+            try {
+                mVideoProviderBinder.setDisplaySurface(surface);
+            } catch (RemoteException e) {
+            }
+        }
+
+        public void setDeviceOrientation(int rotation) {
+            try {
+                mVideoProviderBinder.setDeviceOrientation(rotation);
+            } catch (RemoteException e) {
+            }
+        }
+
+        public void setZoom(float value) {
+            try {
+                mVideoProviderBinder.setZoom(value);
+            } catch (RemoteException e) {
+            }
+        }
+
+        public void sendSessionModifyRequest(VideoProfile reqProfile) {
+            try {
+                mVideoProviderBinder.sendSessionModifyRequest(reqProfile);
+            } catch (RemoteException e) {
+            }
+        }
+
+        public void sendSessionModifyResponse(VideoProfile responseProfile) {
+            try {
+                mVideoProviderBinder.sendSessionModifyResponse(responseProfile);
+            } catch (RemoteException e) {
+            }
+        }
+
+        public void requestCameraCapabilities() {
+            try {
+                mVideoProviderBinder.requestCameraCapabilities();
+            } catch (RemoteException e) {
+            }
+        }
+
+        public void requestCallDataUsage() {
+            try {
+                mVideoProviderBinder.requestCallDataUsage();
+            } catch (RemoteException e) {
+            }
+        }
+
+        public void setPauseImage(String uri) {
+            try {
+                mVideoProviderBinder.setPauseImage(uri);
+            } catch (RemoteException e) {
+            }
+        }
+    }
+
+    private IConnectionService mConnectionService;
+    private final String mConnectionId;
+    /**
+     * ConcurrentHashMap constructor params: 8 is initial table size, 0.9f is
+     * load factor before resizing, 1 means we only expect a single thread to
+     * access the map so make only a single shard
+     */
+    private final Set<Callback> mCallbacks = Collections.newSetFromMap(
+            new ConcurrentHashMap<Callback, Boolean>(8, 0.9f, 1));
+    private final List<RemoteConnection> mConferenceableConnections = new ArrayList<>();
+    private final List<RemoteConnection> mUnmodifiableconferenceableConnections =
+            Collections.unmodifiableList(mConferenceableConnections);
+
+    private int mState = Connection.STATE_NEW;
+    private int mDisconnectCauseCode = DisconnectCause.NOT_VALID;
+    private String mDisconnectCauseMessage;
+    private boolean mRingbackRequested;
+    private boolean mConnected;
+    private int mCallCapabilities;
+    private int mVideoState;
+    private VideoProvider mVideoProvider;
+    private boolean mIsVoipAudioMode;
+    private StatusHints mStatusHints;
+    private Uri mAddress;
+    private int mAddressPresentation;
+    private String mCallerDisplayName;
+    private int mCallerDisplayNamePresentation;
+    private int mFailureCode;
+    private String mFailureMessage;
+    private RemoteConference mConference;
+
+    /**
+     * @hide
+     */
+    RemoteConnection(
+            String id,
+            IConnectionService connectionService,
+            ConnectionRequest request) {
+        mConnectionId = id;
+        mConnectionService = connectionService;
+        mConnected = true;
+        mState = Connection.STATE_INITIALIZING;
+    }
+
+    /**
+     * Create a RemoteConnection which is used for failed connections. Note that using it for any
+     * "real" purpose will almost certainly fail. Callers should note the failure and act
+     * accordingly (moving on to another RemoteConnection, for example)
+     *
+     * @param failureCode
+     * @param failureMessage
+     */
+    RemoteConnection(int failureCode, String failureMessage) {
+        this("NULL", null, null);
+        mConnected = false;
+        mState = Connection.STATE_DISCONNECTED;
+        mFailureCode = DisconnectCause.OUTGOING_FAILURE;
+        mFailureMessage = failureMessage + " original code = " + failureCode;
+    }
+
+    /**
+     * Adds a callback to this {@code RemoteConnection}.
+     *
+     * @param callback A {@code Callback}.
+     */
+    public void registerCallback(Callback callback) {
+        mCallbacks.add(callback);
+    }
+
+    /**
+     * Removes a callback from this {@code RemoteConnection}.
+     *
+     * @param callback A {@code Callback}.
+     */
+    public void unregisterCallback(Callback callback) {
+        if (callback != null) {
+            mCallbacks.remove(callback);
+        }
+    }
+
+    /**
+     * Obtains the state of this {@code RemoteConnection}.
+     *
+     * @return A state value, chosen from the {@code STATE_*} constants.
+     */
+    public int getState() {
+        return mState;
+    }
+
+    /**
+     * @return For a {@link Connection#STATE_DISCONNECTED} {@code RemoteConnection}, the
+     * disconnect cause expressed as a code chosen from among those declared in
+     * {@link DisconnectCause}.
+     */
+    public int getDisconnectCauseCode() {
+        return mDisconnectCauseCode;
+    }
+
+    /**
+     * @return For a {@link Connection#STATE_DISCONNECTED} {@code RemoteConnection}, an optional
+     * reason for disconnection expressed as a free text message.
+     */
+    public String getDisconnectCauseMessage() {
+        return mDisconnectCauseMessage;
+    }
+
+    /**
+     * @return A bitmask of the capabilities of the {@code RemoteConnection}, as defined in
+     *         {@link PhoneCapabilities}.
+     */
+    public int getCallCapabilities() {
+        return mCallCapabilities;
+    }
+
+    /**
+     * @return {@code true} if the {@code RemoteConnection}'s current audio mode is VOIP.
+     */
+    public boolean isVoipAudioMode() {
+        return mIsVoipAudioMode;
+    }
+
+    /**
+     * @return The current {@link StatusHints} of this {@code RemoteConnection},
+     * or {@code null} if none have been set.
+     */
+    public StatusHints getStatusHints() {
+        return mStatusHints;
+    }
+
+    /**
+     * @return The address (e.g., phone number) to which the {@code RemoteConnection} is currently
+     * connected.
+     */
+    public Uri getAddress() {
+        return mAddress;
+    }
+
+    /**
+     * @return The presentation requirements for the address. See {@link TelecomManager} for valid
+     * values.
+     */
+    public int getAddressPresentation() {
+        return mAddressPresentation;
+    }
+
+    /**
+     * @return The display name for the caller.
+     */
+    public CharSequence getCallerDisplayName() {
+        return mCallerDisplayName;
+    }
+
+    /**
+     * @return The presentation requirements for the caller display name. See
+     * {@link TelecomManager} for valid values.
+     */
+    public int getCallerDisplayNamePresentation() {
+        return mCallerDisplayNamePresentation;
+    }
+
+    /**
+     * @return The video state of the {@code RemoteConnection}. See
+     * {@link VideoProfile.VideoState}.
+     * @hide
+     */
+    public int getVideoState() {
+        return mVideoState;
+    }
+
+    /**
+     * @return The video provider associated with this {@code RemoteConnection}.
+     * @hide
+     */
+    public final VideoProvider getVideoProvider() {
+        return mVideoProvider;
+    }
+
+    /**
+     * @return The failure code ({@see DisconnectCause}) associated with this failed
+     * {@code RemoteConnection}.
+     */
+    public int getFailureCode() {
+        return mFailureCode;
+    }
+
+    /**
+     * @return The reason for the connection failure. This will not be displayed to the user.
+     */
+    public String getFailureMessage() {
+        return mFailureMessage;
+    }
+
+    /**
+     * @return Whether the {@code RemoteConnection} is requesting that the framework play a
+     * ringback tone on its behalf.
+     */
+    public boolean isRingbackRequested() {
+        return false;
+    }
+
+    /**
+     * Instructs this {@code RemoteConnection} to abort.
+     */
+    public void abort() {
+        try {
+            if (mConnected) {
+                mConnectionService.abort(mConnectionId);
+            }
+        } catch (RemoteException ignored) {
+        }
+    }
+
+    /**
+     * Instructs this {@link Connection#STATE_RINGING} {@code RemoteConnection} to answer.
+     */
+    public void answer() {
+        try {
+            if (mConnected) {
+                mConnectionService.answer(mConnectionId);
+            }
+        } catch (RemoteException ignored) {
+        }
+    }
+
+    /**
+     * Instructs this {@link Connection#STATE_RINGING} {@code RemoteConnection} to answer.
+     * @param videoState The video state in which to answer the call.
+     * @hide
+     */
+    public void answer(int videoState) {
+        try {
+            if (mConnected) {
+                mConnectionService.answerVideo(mConnectionId, videoState);
+            }
+        } catch (RemoteException ignored) {
+        }
+    }
+
+    /**
+     * Instructs this {@link Connection#STATE_RINGING} {@code RemoteConnection} to reject.
+     */
+    public void reject() {
+        try {
+            if (mConnected) {
+                mConnectionService.reject(mConnectionId);
+            }
+        } catch (RemoteException ignored) {
+        }
+    }
+
+    /**
+     * Instructs this {@code RemoteConnection} to go on hold.
+     */
+    public void hold() {
+        try {
+            if (mConnected) {
+                mConnectionService.hold(mConnectionId);
+            }
+        } catch (RemoteException ignored) {
+        }
+    }
+
+    /**
+     * Instructs this {@link Connection#STATE_HOLDING} call to release from hold.
+     */
+    public void unhold() {
+        try {
+            if (mConnected) {
+                mConnectionService.unhold(mConnectionId);
+            }
+        } catch (RemoteException ignored) {
+        }
+    }
+
+    /**
+     * Instructs this {@code RemoteConnection} to disconnect.
+     */
+    public void disconnect() {
+        try {
+            if (mConnected) {
+                mConnectionService.disconnect(mConnectionId);
+            }
+        } catch (RemoteException ignored) {
+        }
+    }
+
+    /**
+     * Instructs this {@code RemoteConnection} to play a dual-tone multi-frequency signaling
+     * (DTMF) tone.
+     *
+     * Any other currently playing DTMF tone in the specified call is immediately stopped.
+     *
+     * @param digit A character representing the DTMF digit for which to play the tone. This
+     *         value must be one of {@code '0'} through {@code '9'}, {@code '*'} or {@code '#'}.
+     */
+    public void playDtmfTone(char digit) {
+        try {
+            if (mConnected) {
+                mConnectionService.playDtmfTone(mConnectionId, digit);
+            }
+        } catch (RemoteException ignored) {
+        }
+    }
+
+    /**
+     * Instructs this {@code RemoteConnection} to stop any dual-tone multi-frequency signaling
+     * (DTMF) tone currently playing.
+     *
+     * DTMF tones are played by calling {@link #playDtmfTone(char)}. If no DTMF tone is
+     * currently playing, this method will do nothing.
+     */
+    public void stopDtmfTone() {
+        try {
+            if (mConnected) {
+                mConnectionService.stopDtmfTone(mConnectionId);
+            }
+        } catch (RemoteException ignored) {
+        }
+    }
+
+    /**
+     * Instructs this {@code RemoteConnection} to continue playing a post-dial DTMF string.
+     *
+     * A post-dial DTMF string is a string of digits following the first instance of either
+     * {@link TelecomManager#DTMF_CHARACTER_WAIT} or {@link TelecomManager#DTMF_CHARACTER_PAUSE}.
+     * These digits are immediately sent as DTMF tones to the recipient as soon as the
+     * connection is made.
+     *
+     * If the DTMF string contains a {@link TelecomManager#DTMF_CHARACTER_PAUSE} symbol, this
+     * {@code RemoteConnection} will temporarily pause playing the tones for a pre-defined period
+     * of time.
+     *
+     * If the DTMF string contains a {@link TelecomManager#DTMF_CHARACTER_WAIT} symbol, this
+     * {@code RemoteConnection} will pause playing the tones and notify callbackss via
+     * {@link Callback#onPostDialWait(RemoteConnection, String)}. At this point, the in-call app
+     * should display to the user an indication of this state and an affordance to continue
+     * the postdial sequence. When the user decides to continue the postdial sequence, the in-call
+     * app should invoke the {@link #postDialContinue(boolean)} method.
+     *
+     * @param proceed Whether or not to continue with the post-dial sequence.
+     */
+    public void postDialContinue(boolean proceed) {
+        try {
+            if (mConnected) {
+                mConnectionService.onPostDialContinue(mConnectionId, proceed);
+            }
+        } catch (RemoteException ignored) {
+        }
+    }
+
+    /**
+     * Set the audio state of this {@code RemoteConnection}.
+     *
+     * @param state The audio state of this {@code RemoteConnection}.
+     */
+    public void setAudioState(AudioState state) {
+        try {
+            if (mConnected) {
+                mConnectionService.onAudioStateChanged(mConnectionId, state);
+            }
+        } catch (RemoteException ignored) {
+        }
+    }
+
+    /**
+     * Obtain the {@code RemoteConnection}s with which this {@code RemoteConnection} may be
+     * successfully asked to create a conference with.
+     *
+     * @return The {@code RemoteConnection}s with which this {@code RemoteConnection} may be
+     *         merged into a {@link RemoteConference}.
+     */
+    public List<RemoteConnection> getConferenceableConnections() {
+        return mUnmodifiableconferenceableConnections;
+    }
+
+    /**
+     * Obtain the {@code RemoteConference} that this {@code RemoteConnection} may be a part
+     * of, or {@code null} if there is no such {@code RemoteConference}.
+     *
+     * @return A {@code RemoteConference} or {@code null};
+     */
+    public RemoteConference getConference() {
+        return mConference;
+    }
+
+    /** {@hide} */
+    String getId() {
+        return mConnectionId;
+    }
+
+    /** {@hide} */
+    IConnectionService getConnectionService() {
+        return mConnectionService;
+    }
+
+    /**
+     * @hide
+     */
+    void setState(int state) {
+        if (mState != state) {
+            mState = state;
+            for (Callback c: mCallbacks) {
+                c.onStateChanged(this, state);
+            }
+        }
+    }
+
+    /**
+     * @hide
+     */
+    void setDisconnected(int cause, String message) {
+        if (mState != Connection.STATE_DISCONNECTED) {
+            mState = Connection.STATE_DISCONNECTED;
+            mDisconnectCauseCode = cause;
+            mDisconnectCauseMessage = message;
+
+            for (Callback c : mCallbacks) {
+                c.onDisconnected(this, cause, message);
+            }
+        }
+    }
+
+    /**
+     * @hide
+     */
+    void setRingbackRequested(boolean ringback) {
+        if (mRingbackRequested != ringback) {
+            mRingbackRequested = ringback;
+            for (Callback c : mCallbacks) {
+                c.onRingbackRequested(this, ringback);
+            }
+        }
+    }
+
+    /**
+     * @hide
+     */
+    void setCallCapabilities(int callCapabilities) {
+        mCallCapabilities = callCapabilities;
+        for (Callback c : mCallbacks) {
+            c.onCallCapabilitiesChanged(this, callCapabilities);
+        }
+    }
+
+    /**
+     * @hide
+     */
+    void setDestroyed() {
+        if (!mCallbacks.isEmpty()) {
+            // Make sure that the callbacks are notified that the call is destroyed first.
+            if (mState != Connection.STATE_DISCONNECTED) {
+                setDisconnected(DisconnectCause.ERROR_UNSPECIFIED, "Connection destroyed.");
+            }
+
+            for (Callback c : mCallbacks) {
+                c.onDestroyed(this);
+            }
+            mCallbacks.clear();
+
+            mConnected = false;
+        }
+    }
+
+    /**
+     * @hide
+     */
+    void setPostDialWait(String remainingDigits) {
+        for (Callback c : mCallbacks) {
+            c.onPostDialWait(this, remainingDigits);
+        }
+    }
+
+    /**
+     * @hide
+     */
+    void setVideoState(int videoState) {
+        mVideoState = videoState;
+        for (Callback c : mCallbacks) {
+            c.onVideoStateChanged(this, videoState);
+        }
+    }
+
+    /**
+     * @hide
+     */
+    void setVideoProvider(VideoProvider videoProvider) {
+        mVideoProvider = videoProvider;
+        for (Callback c : mCallbacks) {
+            c.onVideoProviderChanged(this, videoProvider);
+        }
+    }
+
+    /** @hide */
+    void setIsVoipAudioMode(boolean isVoip) {
+        mIsVoipAudioMode = isVoip;
+        for (Callback c : mCallbacks) {
+            c.onVoipAudioChanged(this, isVoip);
+        }
+    }
+
+    /** @hide */
+    void setStatusHints(StatusHints statusHints) {
+        mStatusHints = statusHints;
+        for (Callback c : mCallbacks) {
+            c.onStatusHintsChanged(this, statusHints);
+        }
+    }
+
+    /** @hide */
+    void setAddress(Uri address, int presentation) {
+        mAddress = address;
+        mAddressPresentation = presentation;
+        for (Callback c : mCallbacks) {
+            c.onAddressChanged(this, address, presentation);
+        }
+    }
+
+    /** @hide */
+    void setCallerDisplayName(String callerDisplayName, int presentation) {
+        mCallerDisplayName = callerDisplayName;
+        mCallerDisplayNamePresentation = presentation;
+        for (Callback c : mCallbacks) {
+            c.onCallerDisplayNameChanged(this, callerDisplayName, presentation);
+        }
+    }
+
+    /** @hide */
+    void setConferenceableConnections(List<RemoteConnection> conferenceableConnections) {
+        mConferenceableConnections.clear();
+        mConferenceableConnections.addAll(conferenceableConnections);
+        for (Callback c : mCallbacks) {
+            c.onConferenceableConnectionsChanged(this, mUnmodifiableconferenceableConnections);
+        }
+    }
+
+    /** @hide */
+    void setConference(RemoteConference conference) {
+        if (mConference != conference) {
+            mConference = conference;
+            for (Callback c : mCallbacks) {
+                c.onConferenceChanged(this, conference);
+            }
+        }
+    }
+
+    /**
+     * Create a RemoteConnection represents a failure, and which will be in
+     * {@link Connection#STATE_DISCONNECTED}. Attempting to use it for anything will almost
+     * certainly result in bad things happening. Do not do this.
+     *
+     * @return a failed {@link RemoteConnection}
+     *
+     * @hide
+     */
+    public static RemoteConnection failure(int failureCode, String failureMessage) {
+        return new RemoteConnection(failureCode, failureMessage);
+    }
+}
diff --git a/telecomm/java/android/telecom/RemoteConnectionManager.java b/telecomm/java/android/telecom/RemoteConnectionManager.java
new file mode 100644
index 0000000..0366509
--- /dev/null
+++ b/telecomm/java/android/telecom/RemoteConnectionManager.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ R* limitations under the License.
+ */
+
+package android.telecom;
+
+import android.content.ComponentName;
+import android.os.RemoteException;
+
+import com.android.internal.telecom.IConnectionService;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @hide
+ */
+public class RemoteConnectionManager {
+    private final Map<ComponentName, RemoteConnectionService> mRemoteConnectionServices =
+            new HashMap<>();
+    private final ConnectionService mOurConnectionServiceImpl;
+
+    public RemoteConnectionManager(ConnectionService ourConnectionServiceImpl) {
+        mOurConnectionServiceImpl = ourConnectionServiceImpl;
+    }
+
+    void addConnectionService(
+            ComponentName componentName,
+            IConnectionService outgoingConnectionServiceRpc) {
+        if (!mRemoteConnectionServices.containsKey(componentName)) {
+            try {
+                RemoteConnectionService remoteConnectionService = new RemoteConnectionService(
+                        outgoingConnectionServiceRpc,
+                        mOurConnectionServiceImpl);
+                mRemoteConnectionServices.put(componentName, remoteConnectionService);
+            } catch (RemoteException ignored) {
+            }
+        }
+    }
+
+    public RemoteConnection createRemoteConnection(
+            PhoneAccountHandle connectionManagerPhoneAccount,
+            ConnectionRequest request,
+            boolean isIncoming) {
+        PhoneAccountHandle accountHandle = request.getAccountHandle();
+        if (accountHandle == null) {
+            throw new IllegalArgumentException("accountHandle must be specified.");
+        }
+
+        ComponentName componentName = request.getAccountHandle().getComponentName();
+        if (!mRemoteConnectionServices.containsKey(componentName)) {
+            throw new UnsupportedOperationException("accountHandle not supported: "
+                    + componentName);
+        }
+
+        RemoteConnectionService remoteService = mRemoteConnectionServices.get(componentName);
+        if (remoteService != null) {
+            return remoteService.createRemoteConnection(
+                    connectionManagerPhoneAccount, request, isIncoming);
+        }
+        return null;
+    }
+
+    public void conferenceRemoteConnections(RemoteConnection a, RemoteConnection b) {
+        if (a.getConnectionService() == b.getConnectionService()) {
+            try {
+                a.getConnectionService().conference(a.getId(), b.getId());
+            } catch (RemoteException e) {
+            }
+        } else {
+            Log.w(this, "Request to conference incompatible remote connections (%s,%s) (%s,%s)",
+                    a.getConnectionService(), a.getId(),
+                    b.getConnectionService(), b.getId());
+        }
+    }
+}
diff --git a/telecomm/java/android/telecom/RemoteConnectionService.java b/telecomm/java/android/telecom/RemoteConnectionService.java
new file mode 100644
index 0000000..bfd7c51
--- /dev/null
+++ b/telecomm/java/android/telecom/RemoteConnectionService.java
@@ -0,0 +1,385 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telecom;
+
+import android.net.Uri;
+import android.os.IBinder;
+import android.os.IBinder.DeathRecipient;
+import android.os.RemoteException;
+import android.telephony.DisconnectCause;
+
+import com.android.internal.telecom.IConnectionService;
+import com.android.internal.telecom.IConnectionServiceAdapter;
+import com.android.internal.telecom.IVideoProvider;
+import com.android.internal.telecom.RemoteServiceCallback;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.List;
+import java.util.UUID;
+
+/**
+ * Remote connection service which other connection services can use to place calls on their behalf.
+ *
+ * @hide
+ */
+final class RemoteConnectionService {
+
+    private static final RemoteConnection NULL_CONNECTION =
+            new RemoteConnection("NULL", null, null);
+
+    private static final RemoteConference NULL_CONFERENCE =
+            new RemoteConference("NULL", null);
+
+    private final IConnectionServiceAdapter mServantDelegate = new IConnectionServiceAdapter() {
+        @Override
+        public void handleCreateConnectionComplete(
+                String id,
+                ConnectionRequest request,
+                ParcelableConnection parcel) {
+            RemoteConnection connection =
+                    findConnectionForAction(id, "handleCreateConnectionSuccessful");
+            if (connection != NULL_CONNECTION && mPendingConnections.contains(connection)) {
+                mPendingConnections.remove(connection);
+                // Unconditionally initialize the connection ...
+                connection.setCallCapabilities(parcel.getCapabilities());
+                connection.setAddress(
+                        parcel.getHandle(), parcel.getHandlePresentation());
+                connection.setCallerDisplayName(
+                        parcel.getCallerDisplayName(),
+                        parcel.getCallerDisplayNamePresentation());
+                // Set state after handle so that the client can identify the connection.
+                connection.setState(parcel.getState());
+                List<RemoteConnection> conferenceable = new ArrayList<>();
+                for (String confId : parcel.getConferenceableConnectionIds()) {
+                    if (mConnectionById.containsKey(confId)) {
+                        conferenceable.add(mConnectionById.get(confId));
+                    }
+                }
+                connection.setConferenceableConnections(conferenceable);
+                connection.setVideoState(parcel.getVideoState());
+                if (connection.getState() == Connection.STATE_DISCONNECTED) {
+                    // ... then, if it was created in a disconnected state, that indicates
+                    // failure on the providing end, so immediately mark it destroyed
+                    connection.setDestroyed();
+                }
+            }
+        }
+
+        @Override
+        public void setActive(String callId) {
+            if (mConnectionById.containsKey(callId)) {
+                findConnectionForAction(callId, "setActive")
+                        .setState(Connection.STATE_ACTIVE);
+            } else {
+                findConferenceForAction(callId, "setActive")
+                        .setState(Connection.STATE_ACTIVE);
+            }
+        }
+
+        @Override
+        public void setRinging(String callId) {
+            findConnectionForAction(callId, "setRinging")
+                    .setState(Connection.STATE_RINGING);
+        }
+
+        @Override
+        public void setDialing(String callId) {
+            findConnectionForAction(callId, "setDialing")
+                    .setState(Connection.STATE_DIALING);
+        }
+
+        @Override
+        public void setDisconnected(String callId, int disconnectCause,
+                String disconnectMessage) {
+            if (mConnectionById.containsKey(callId)) {
+                findConnectionForAction(callId, "setDisconnected")
+                        .setDisconnected(disconnectCause, disconnectMessage);
+            } else {
+                findConferenceForAction(callId, "setDisconnected")
+                        .setDisconnected(disconnectCause, disconnectMessage);
+            }
+        }
+
+        @Override
+        public void setOnHold(String callId) {
+            if (mConnectionById.containsKey(callId)) {
+                findConnectionForAction(callId, "setOnHold")
+                        .setState(Connection.STATE_HOLDING);
+            } else {
+                findConferenceForAction(callId, "setOnHold")
+                        .setState(Connection.STATE_HOLDING);
+            }
+        }
+
+        @Override
+        public void setRingbackRequested(String callId, boolean ringing) {
+            findConnectionForAction(callId, "setRingbackRequested")
+                    .setRingbackRequested(ringing);
+        }
+
+        @Override
+        public void setCallCapabilities(String callId, int callCapabilities) {
+            if (mConnectionById.containsKey(callId)) {
+                findConnectionForAction(callId, "setCallCapabilities")
+                        .setCallCapabilities(callCapabilities);
+            } else {
+                findConferenceForAction(callId, "setCallCapabilities")
+                        .setCallCapabilities(callCapabilities);
+            }
+        }
+
+        @Override
+        public void setIsConferenced(String callId, String conferenceCallId) {
+            // Note: callId should not be null; conferenceCallId may be null
+            RemoteConnection connection =
+                    findConnectionForAction(callId, "setIsConferenced");
+            if (connection != NULL_CONNECTION) {
+                if (conferenceCallId == null) {
+                    // 'connection' is being split from its conference
+                    if (connection.getConference() != null) {
+                        connection.getConference().removeConnection(connection);
+                    }
+                } else {
+                    RemoteConference conference =
+                            findConferenceForAction(conferenceCallId, "setIsConferenced");
+                    if (conference != NULL_CONFERENCE) {
+                        conference.addConnection(connection);
+                    }
+                }
+            }
+        }
+
+        @Override
+        public void addConferenceCall(
+                final String callId,
+                ParcelableConference parcel) {
+            RemoteConference conference = new RemoteConference(callId,
+                    mOutgoingConnectionServiceRpc);
+
+            for (String id : parcel.getConnectionIds()) {
+                RemoteConnection c = mConnectionById.get(id);
+                if (c != null) {
+                    conference.addConnection(c);
+                }
+            }
+
+            if (conference.getConnections().size() == 0) {
+                // A conference was created, but none of its connections are ones that have been
+                // created by, and therefore being tracked by, this remote connection service. It
+                // is of no interest to us.
+                return;
+            }
+
+            conference.setState(parcel.getState());
+            conference.setCallCapabilities(parcel.getCapabilities());
+            mConferenceById.put(callId, conference);
+            conference.registerCallback(new RemoteConference.Callback() {
+                @Override
+                public void onDestroyed(RemoteConference c) {
+                    mConferenceById.remove(callId);
+                    maybeDisconnectAdapter();
+                }
+            });
+
+            mOurConnectionServiceImpl.addRemoteConference(conference);
+        }
+
+        @Override
+        public void removeCall(String callId) {
+            if (mConnectionById.containsKey(callId)) {
+                findConnectionForAction(callId, "removeCall")
+                        .setDestroyed();
+            } else {
+                findConferenceForAction(callId, "removeCall")
+                        .setDestroyed();
+            }
+        }
+
+        @Override
+        public void onPostDialWait(String callId, String remaining) {
+            findConnectionForAction(callId, "onPostDialWait")
+                    .setPostDialWait(remaining);
+        }
+
+        @Override
+        public void queryRemoteConnectionServices(RemoteServiceCallback callback) {
+            // Not supported from remote connection service.
+        }
+
+        @Override
+        public void setVideoProvider(String callId, IVideoProvider videoProvider) {
+            findConnectionForAction(callId, "setVideoProvider")
+                    .setVideoProvider(new RemoteConnection.VideoProvider(videoProvider));
+        }
+
+        @Override
+        public void setVideoState(String callId, int videoState) {
+            findConnectionForAction(callId, "setVideoState")
+                    .setVideoState(videoState);
+        }
+
+        @Override
+        public void setIsVoipAudioMode(String callId, boolean isVoip) {
+            findConnectionForAction(callId, "setIsVoipAudioMode")
+                    .setIsVoipAudioMode(isVoip);
+        }
+
+        @Override
+        public void setStatusHints(String callId, StatusHints statusHints) {
+            findConnectionForAction(callId, "setStatusHints")
+                    .setStatusHints(statusHints);
+        }
+
+        @Override
+        public void setAddress(String callId, Uri address, int presentation) {
+            findConnectionForAction(callId, "setAddress")
+                    .setAddress(address, presentation);
+        }
+
+        @Override
+        public void setCallerDisplayName(String callId, String callerDisplayName,
+                int presentation) {
+            findConnectionForAction(callId, "setCallerDisplayName")
+                    .setCallerDisplayName(callerDisplayName, presentation);
+        }
+
+        @Override
+        public IBinder asBinder() {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public final void setConferenceableConnections(
+                String callId, List<String> conferenceableConnectionIds) {
+            List<RemoteConnection> conferenceable = new ArrayList<>();
+            for (String id : conferenceableConnectionIds) {
+                if (mConnectionById.containsKey(id)) {
+                    conferenceable.add(mConnectionById.get(id));
+                }
+            }
+
+            findConnectionForAction(callId, "setConferenceableConnections")
+                    .setConferenceableConnections(conferenceable);
+        }
+    };
+
+    private final ConnectionServiceAdapterServant mServant =
+            new ConnectionServiceAdapterServant(mServantDelegate);
+
+    private final DeathRecipient mDeathRecipient = new DeathRecipient() {
+        @Override
+        public void binderDied() {
+            for (RemoteConnection c : mConnectionById.values()) {
+                c.setDestroyed();
+            }
+            for (RemoteConference c : mConferenceById.values()) {
+                c.setDestroyed();
+            }
+            mConnectionById.clear();
+            mConferenceById.clear();
+            mPendingConnections.clear();
+            mOutgoingConnectionServiceRpc.asBinder().unlinkToDeath(mDeathRecipient, 0);
+        }
+    };
+
+    private final IConnectionService mOutgoingConnectionServiceRpc;
+    private final ConnectionService mOurConnectionServiceImpl;
+    private final Map<String, RemoteConnection> mConnectionById = new HashMap<>();
+    private final Map<String, RemoteConference> mConferenceById = new HashMap<>();
+    private final Set<RemoteConnection> mPendingConnections = new HashSet<>();
+
+    RemoteConnectionService(
+            IConnectionService outgoingConnectionServiceRpc,
+            ConnectionService ourConnectionServiceImpl) throws RemoteException {
+        mOutgoingConnectionServiceRpc = outgoingConnectionServiceRpc;
+        mOutgoingConnectionServiceRpc.asBinder().linkToDeath(mDeathRecipient, 0);
+        mOurConnectionServiceImpl = ourConnectionServiceImpl;
+    }
+
+    @Override
+    public String toString() {
+        return "[RemoteCS - " + mOutgoingConnectionServiceRpc.asBinder().toString() + "]";
+    }
+
+    final RemoteConnection createRemoteConnection(
+            PhoneAccountHandle connectionManagerPhoneAccount,
+            ConnectionRequest request,
+            boolean isIncoming) {
+        final String id = UUID.randomUUID().toString();
+        final ConnectionRequest newRequest = new ConnectionRequest(
+                request.getAccountHandle(),
+                request.getAddress(),
+                request.getExtras(),
+                request.getVideoState());
+        try {
+            if (mConnectionById.isEmpty()) {
+                mOutgoingConnectionServiceRpc.addConnectionServiceAdapter(mServant.getStub());
+            }
+            RemoteConnection connection =
+                    new RemoteConnection(id, mOutgoingConnectionServiceRpc, newRequest);
+            mPendingConnections.add(connection);
+            mConnectionById.put(id, connection);
+            mOutgoingConnectionServiceRpc.createConnection(
+                    connectionManagerPhoneAccount,
+                    id,
+                    newRequest,
+                    isIncoming);
+            connection.registerCallback(new RemoteConnection.Callback() {
+                @Override
+                public void onDestroyed(RemoteConnection connection) {
+                    mConnectionById.remove(id);
+                    maybeDisconnectAdapter();
+                }
+            });
+            return connection;
+        } catch (RemoteException e) {
+            return RemoteConnection
+                    .failure(DisconnectCause.ERROR_UNSPECIFIED, e.toString());
+        }
+    }
+
+    private RemoteConnection findConnectionForAction(
+            String callId, String action) {
+        if (mConnectionById.containsKey(callId)) {
+            return mConnectionById.get(callId);
+        }
+        Log.w(this, "%s - Cannot find Connection %s", action, callId);
+        return NULL_CONNECTION;
+    }
+
+    private RemoteConference findConferenceForAction(
+            String callId, String action) {
+        if (mConferenceById.containsKey(callId)) {
+            return mConferenceById.get(callId);
+        }
+        Log.w(this, "%s - Cannot find Conference %s", action, callId);
+        return NULL_CONFERENCE;
+    }
+
+    private void maybeDisconnectAdapter() {
+        if (mConnectionById.isEmpty() && mConferenceById.isEmpty()) {
+            try {
+                mOutgoingConnectionServiceRpc.removeConnectionServiceAdapter(mServant.getStub());
+            } catch (RemoteException e) {
+            }
+        }
+    }
+}
diff --git a/telecomm/java/android/telecom/Response.java b/telecomm/java/android/telecom/Response.java
new file mode 100644
index 0000000..ce7a761
--- /dev/null
+++ b/telecomm/java/android/telecom/Response.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telecom;
+
+/**
+ * @hide
+ */
+public interface Response<IN, OUT> {
+
+    /**
+     * Provide a set of results.
+     *
+     * @param request The original request.
+     * @param result The results.
+     */
+    void onResult(IN request, OUT... result);
+
+    /**
+     * Indicates the inability to provide results.
+     *
+     * @param request The original request.
+     * @param code An integer code indicating the reason for failure.
+     * @param msg A message explaining the reason for failure.
+     */
+    void onError(IN request, int code, String msg);
+}
diff --git a/telecomm/java/android/telecom/StatusHints.aidl b/telecomm/java/android/telecom/StatusHints.aidl
new file mode 100644
index 0000000..ae7df2e
--- /dev/null
+++ b/telecomm/java/android/telecom/StatusHints.aidl
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telecom;
+
+/**
+ * {@hide}
+  */
+parcelable StatusHints;
diff --git a/telecomm/java/android/telecom/StatusHints.java b/telecomm/java/android/telecom/StatusHints.java
new file mode 100644
index 0000000..a32eae7
--- /dev/null
+++ b/telecomm/java/android/telecom/StatusHints.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telecom;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.MissingResourceException;
+import java.util.Objects;
+
+/**
+ * Contains status label and icon displayed in the in-call UI.
+ */
+public final class StatusHints implements Parcelable {
+
+    private final ComponentName mPackageName;
+    private final CharSequence mLabel;
+    private final int mIconResId;
+    private final Bundle mExtras;
+
+    public StatusHints(ComponentName packageName, CharSequence label, int iconResId,
+            Bundle extras) {
+        mPackageName = packageName;
+        mLabel = label;
+        mIconResId = iconResId;
+        mExtras = extras;
+    }
+
+    /**
+     * @return A package used to load the icon.
+     */
+    public ComponentName getPackageName() {
+        return mPackageName;
+    }
+
+    /**
+     * @return The label displayed in the in-call UI.
+     */
+    public CharSequence getLabel() {
+        return mLabel;
+    }
+
+    /**
+     * The icon resource ID for the icon to show.
+     *
+     * @return A resource ID.
+     */
+    public int getIconResId() {
+        return mIconResId;
+    }
+
+    /**
+     * @return An icon displayed in the in-call UI.
+     */
+    public Drawable getIcon(Context context) {
+        return getIcon(context, mIconResId);
+    }
+
+    /**
+     * @return Extra data used to display status.
+     */
+    public Bundle getExtras() {
+        return mExtras;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeParcelable(mPackageName, flags);
+        out.writeCharSequence(mLabel);
+        out.writeInt(mIconResId);
+        out.writeParcelable(mExtras, 0);
+    }
+
+    public static final Creator<StatusHints> CREATOR
+            = new Creator<StatusHints>() {
+        public StatusHints createFromParcel(Parcel in) {
+            return new StatusHints(in);
+        }
+
+        public StatusHints[] newArray(int size) {
+            return new StatusHints[size];
+        }
+    };
+
+    private StatusHints(Parcel in) {
+        mPackageName = in.readParcelable(getClass().getClassLoader());
+        mLabel = in.readCharSequence();
+        mIconResId = in.readInt();
+        mExtras = in.readParcelable(getClass().getClassLoader());
+    }
+
+    private Drawable getIcon(Context context, int resId) {
+        Context packageContext;
+        try {
+            packageContext = context.createPackageContext(mPackageName.getPackageName(), 0);
+        } catch (PackageManager.NameNotFoundException e) {
+            Log.e(this, e, "Cannot find package %s", mPackageName.getPackageName());
+            return null;
+        }
+        try {
+            return packageContext.getDrawable(resId);
+        } catch (MissingResourceException e) {
+            Log.e(this, e, "Cannot find icon %d in package %s",
+                    resId, mPackageName.getPackageName());
+            return null;
+        }
+    }
+
+    @Override
+    public boolean equals(Object other) {
+        if (other != null && other instanceof StatusHints) {
+            StatusHints otherHints = (StatusHints) other;
+            return Objects.equals(otherHints.getPackageName(), getPackageName()) &&
+                    Objects.equals(otherHints.getLabel(), getLabel()) &&
+                    otherHints.getIconResId() == getIconResId() &&
+                    Objects.equals(otherHints.getExtras(), getExtras());
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hashCode(mPackageName) + Objects.hashCode(mLabel) + mIconResId +
+                Objects.hashCode(mExtras);
+    }
+}
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
new file mode 100644
index 0000000..1f5be6e
--- /dev/null
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -0,0 +1,873 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package android.telecom;
+
+import android.annotation.SystemApi;
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Log;
+
+import com.android.internal.telecom.ITelecomService;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Provides access to Telecom-related functionality.
+ * TODO: Move this all into PhoneManager.
+ */
+public class TelecomManager {
+
+    /**
+     * Activity action: Starts the UI for handing an incoming call. This intent starts the in-call
+     * UI by notifying the Telecom system that an incoming call exists for a specific call service
+     * (see {@link android.telecom.ConnectionService}). Telecom reads the Intent extras to find
+     * and bind to the appropriate {@link android.telecom.ConnectionService} which Telecom will
+     * ultimately use to control and get information about the call.
+     * <p>
+     * Input: get*Extra field {@link #EXTRA_PHONE_ACCOUNT_HANDLE} contains the component name of the
+     * {@link android.telecom.ConnectionService} that Telecom should bind to. Telecom will then
+     * ask the connection service for more information about the call prior to showing any UI.
+     *
+     * @hide
+     */
+    public static final String ACTION_INCOMING_CALL = "android.telecom.action.INCOMING_CALL";
+
+    /**
+     * The {@link android.content.Intent} action used to configure a
+     * {@link android.telecom.ConnectionService}.
+     */
+    public static final String ACTION_CONNECTION_SERVICE_CONFIGURE =
+            "android.telecom.action.CONNECTION_SERVICE_CONFIGURE";
+
+    /**
+     * The {@link android.content.Intent} action used to show the call settings page.
+     */
+    public static final String ACTION_SHOW_CALL_SETTINGS =
+            "android.telecom.action.SHOW_CALL_SETTINGS";
+
+    /**
+     * The {@link android.content.Intent} action used to show the settings page used to configure
+     * {@link PhoneAccount} preferences.
+     */
+    public static final String ACTION_CHANGE_PHONE_ACCOUNTS =
+            "android.telecom.action.CHANGE_PHONE_ACCOUNTS";
+
+    /**
+     * The {@link android.content.Intent} action used to inform a
+     * {@link android.telecom.ConnectionService} that one of its {@link PhoneAccount}s has been
+     * enabled.  The {@link TelecomManager#EXTRA_PHONE_ACCOUNT_HANDLE} extra is used to indicate
+     * which {@link PhoneAccount} has been enabled.
+     */
+    public static final String ACTION_PHONE_ACCOUNT_ENABLED =
+            "android.telecom.action.PHONE_ACCOUNT_ENABLED";
+
+    /**
+     * The {@link android.content.Intent} action used to inform a
+     * {@link android.telecom.ConnectionService} that one of its {@link PhoneAccount}s has been
+     * disabled.  The {@link TelecomManager#EXTRA_PHONE_ACCOUNT_HANDLE} extra is used to indicate
+     * which {@link PhoneAccount} has been disabled.
+     */
+    public static final String ACTION_PHONE_ACCOUNT_DISABLED =
+            "android.telecom.action.PHONE_ACCOUNT_DISABLED";
+
+    /**
+     * Optional extra for {@link android.content.Intent#ACTION_CALL} containing a boolean that
+     * determines whether the speakerphone should be automatically turned on for an outgoing call.
+     */
+    public static final String EXTRA_START_CALL_WITH_SPEAKERPHONE =
+            "android.telecom.extra.START_CALL_WITH_SPEAKERPHONE";
+
+    /**
+     * Optional extra for {@link android.content.Intent#ACTION_CALL} containing an integer that
+     * determines the desired video state for an outgoing call.
+     * Valid options:
+     * {@link VideoProfile.VideoState#AUDIO_ONLY},
+     * {@link VideoProfile.VideoState#BIDIRECTIONAL},
+     * {@link VideoProfile.VideoState#RX_ENABLED},
+     * {@link VideoProfile.VideoState#TX_ENABLED}.
+     * @hide
+     */
+    public static final String EXTRA_START_CALL_WITH_VIDEO_STATE =
+            "android.telecom.extra.START_CALL_WITH_VIDEO_STATE";
+
+    /**
+     * The extra used with an {@link android.content.Intent#ACTION_CALL} and
+     * {@link android.content.Intent#ACTION_DIAL} {@code Intent} to specify a
+     * {@link PhoneAccountHandle} to use when making the call.
+     * <p class="note">
+     * Retrieve with {@link android.content.Intent#getParcelableExtra(String)}.
+     */
+    public static final String EXTRA_PHONE_ACCOUNT_HANDLE =
+            "android.telecom.extra.PHONE_ACCOUNT_HANDLE";
+
+    /**
+     * Optional extra for {@link #ACTION_INCOMING_CALL} containing a {@link Bundle} which contains
+     * metadata about the call. This {@link Bundle} will be returned to the
+     * {@link ConnectionService}.
+     *
+     * @hide
+     */
+    public static final String EXTRA_INCOMING_CALL_EXTRAS =
+            "android.telecom.extra.INCOMING_CALL_EXTRAS";
+
+    /**
+     * Optional extra for {@link android.content.Intent#ACTION_CALL} and
+     * {@link android.content.Intent#ACTION_DIAL} {@code Intent} containing a {@link Bundle}
+     * which contains metadata about the call. This {@link Bundle} will be saved into
+     * {@code Call.Details}.
+     *
+     * @hide
+     */
+    public static final String EXTRA_OUTGOING_CALL_EXTRAS =
+            "android.telecom.extra.OUTGOING_CALL_EXTRAS";
+
+    /**
+     * Optional extra for {@link android.telephony.TelephonyManager#ACTION_PHONE_STATE_CHANGED}
+     * containing the disconnect code.
+     */
+    public static final String EXTRA_CALL_DISCONNECT_CAUSE =
+            "android.telecom.extra.CALL_DISCONNECT_CAUSE";
+
+    /**
+     * Optional extra for {@link android.telephony.TelephonyManager#ACTION_PHONE_STATE_CHANGED}
+     * containing the disconnect message.
+     */
+    public static final String EXTRA_CALL_DISCONNECT_MESSAGE =
+            "android.telecom.extra.CALL_DISCONNECT_MESSAGE";
+
+    /**
+     * Optional extra for {@link android.telephony.TelephonyManager#ACTION_PHONE_STATE_CHANGED}
+     * containing the component name of the associated connection service.
+     */
+    public static final String EXTRA_CONNECTION_SERVICE =
+            "android.telecom.extra.CONNECTION_SERVICE";
+
+    /**
+     * An optional {@link android.content.Intent#ACTION_CALL} intent extra denoting the
+     * package name of the app specifying an alternative gateway for the call.
+     * The value is a string.
+     *
+     * (The following comment corresponds to the all GATEWAY_* extras)
+     * An app which sends the {@link android.content.Intent#ACTION_CALL} intent can specify an
+     * alternative address to dial which is different from the one specified and displayed to
+     * the user. This alternative address is referred to as the gateway address.
+     */
+    public static final String GATEWAY_PROVIDER_PACKAGE =
+            "android.telecom.extra.GATEWAY_PROVIDER_PACKAGE";
+
+    /**
+     * An optional {@link android.content.Intent#ACTION_CALL} intent extra corresponding to the
+     * original address to dial for the call. This is used when an alternative gateway address is
+     * provided to recall the original address.
+     * The value is a {@link android.net.Uri}.
+     *
+     * (See {@link #GATEWAY_PROVIDER_PACKAGE} for details)
+     */
+    public static final String GATEWAY_ORIGINAL_ADDRESS =
+            "android.telecom.extra.GATEWAY_ORIGINAL_ADDRESS";
+
+    /**
+     * The number which the party on the other side of the line will see (and use to return the
+     * call).
+     * <p>
+     * {@link ConnectionService}s which interact with {@link RemoteConnection}s should only populate
+     * this if the {@link android.telephony.TelephonyManager#getLine1Number()} value, as that is the
+     * user's expected caller ID.
+     */
+    public static final String EXTRA_CALL_BACK_NUMBER = "android.telecom.extra.CALL_BACK_NUMBER";
+
+    /**
+     * The dual tone multi-frequency signaling character sent to indicate the dialing system should
+     * pause for a predefined period.
+     */
+    public static final char DTMF_CHARACTER_PAUSE = ',';
+
+    /**
+     * The dual-tone multi-frequency signaling character sent to indicate the dialing system should
+     * wait for user confirmation before proceeding.
+     */
+    public static final char DTMF_CHARACTER_WAIT = ';';
+
+    /**
+     * TTY (teletypewriter) mode is off.
+     *
+     * @hide
+     */
+    public static final int TTY_MODE_OFF = 0;
+
+    /**
+     * TTY (teletypewriter) mode is on. The speaker is off and the microphone is muted. The user
+     * will communicate with the remote party by sending and receiving text messages.
+     *
+     * @hide
+     */
+    public static final int TTY_MODE_FULL = 1;
+
+    /**
+     * TTY (teletypewriter) mode is in hearing carryover mode (HCO). The microphone is muted but the
+     * speaker is on. The user will communicate with the remote party by sending text messages and
+     * hearing an audible reply.
+     *
+     * @hide
+     */
+    public static final int TTY_MODE_HCO = 2;
+
+    /**
+     * TTY (teletypewriter) mode is in voice carryover mode (VCO). The speaker is off but the
+     * microphone is still on. User will communicate with the remote party by speaking and receiving
+     * text message replies.
+     *
+     * @hide
+     */
+    public static final int TTY_MODE_VCO = 3;
+
+    /**
+     * Broadcast intent action indicating that the current TTY mode has changed. An intent extra
+     * provides this state as an int.
+     *
+     * @see #EXTRA_CURRENT_TTY_MODE
+     * @hide
+     */
+    public static final String ACTION_CURRENT_TTY_MODE_CHANGED =
+            "android.telecom.action.CURRENT_TTY_MODE_CHANGED";
+
+    /**
+     * The lookup key for an int that indicates the current TTY mode.
+     * Valid modes are:
+     * - {@link #TTY_MODE_OFF}
+     * - {@link #TTY_MODE_FULL}
+     * - {@link #TTY_MODE_HCO}
+     * - {@link #TTY_MODE_VCO}
+     *
+     * @hide
+     */
+    public static final String EXTRA_CURRENT_TTY_MODE =
+            "android.telecom.intent.extra.CURRENT_TTY_MODE";
+
+    /**
+     * Broadcast intent action indicating that the TTY preferred operating mode has changed. An
+     * intent extra provides the new mode as an int.
+     *
+     * @see #EXTRA_TTY_PREFERRED_MODE
+     * @hide
+     */
+    public static final String ACTION_TTY_PREFERRED_MODE_CHANGED =
+            "android.telecom.action.TTY_PREFERRED_MODE_CHANGED";
+
+    /**
+     * The lookup key for an int that indicates preferred TTY mode. Valid modes are: -
+     * {@link #TTY_MODE_OFF} - {@link #TTY_MODE_FULL} - {@link #TTY_MODE_HCO} -
+     * {@link #TTY_MODE_VCO}
+     *
+     * @hide
+     */
+    public static final String EXTRA_TTY_PREFERRED_MODE =
+            "android.telecom.intent.extra.TTY_PREFERRED";
+
+    /**
+     * The following 4 constants define how properties such as phone numbers and names are
+     * displayed to the user.
+     */
+
+    /** Property is displayed normally. */
+    public static final int PRESENTATION_ALLOWED = 1;
+
+    /** Property was blocked. */
+    public static final int PRESENTATION_RESTRICTED = 2;
+
+    /** Presentation was not specified or is unknown. */
+    public static final int PRESENTATION_UNKNOWN = 3;
+
+    /** Property should be displayed as a pay phone. */
+    public static final int PRESENTATION_PAYPHONE = 4;
+
+    private static final String TAG = "TelecomManager";
+
+    private final Context mContext;
+
+    /**
+     * @hide
+     */
+    public static TelecomManager from(Context context) {
+        return (TelecomManager) context.getSystemService(Context.TELECOM_SERVICE);
+    }
+
+    /**
+     * @hide
+     */
+    public TelecomManager(Context context) {
+        Context appContext = context.getApplicationContext();
+        if (appContext != null) {
+            mContext = appContext;
+        } else {
+            mContext = context;
+        }
+    }
+
+    /**
+     * Return the {@link PhoneAccount} which is the user-chosen default for making outgoing phone
+     * calls with a specified URI scheme. This {@code PhoneAccount} will always be a member of the
+     * list which is returned from calling {@link #getEnabledPhoneAccounts()}.
+     * <p>
+     * Apps must be prepared for this method to return {@code null}, indicating that there currently
+     * exists no user-chosen default {@code PhoneAccount}. In this case, apps wishing to initiate a
+     * phone call must either create their {@link android.content.Intent#ACTION_CALL} or
+     * {@link android.content.Intent#ACTION_DIAL} {@code Intent} with no
+     * {@link TelecomManager#EXTRA_PHONE_ACCOUNT_HANDLE}, or present the user with an affordance to
+     * select one of the elements of {@link #getEnabledPhoneAccounts()}.
+     * <p>
+     * An {@link android.content.Intent#ACTION_CALL} or {@link android.content.Intent#ACTION_DIAL}
+     * {@code Intent} with no {@link TelecomManager#EXTRA_PHONE_ACCOUNT_HANDLE} is valid, and
+     * subsequent steps in the phone call flow are responsible for presenting the user with an
+     * affordance, if necessary, to choose a {@code PhoneAccount}.
+     *
+     * @param uriScheme The URI scheme.
+     */
+    public PhoneAccountHandle getDefaultOutgoingPhoneAccount(String uriScheme) {
+        try {
+            if (isServiceConnected()) {
+                return getTelecomService().getDefaultOutgoingPhoneAccount(uriScheme);
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error calling ITelecomService#getDefaultOutgoingPhoneAccount", e);
+        }
+        return null;
+    }
+
+    /**
+     * Return the {@link PhoneAccount} which is the user-chosen default for making outgoing phone
+     * calls. This {@code PhoneAccount} will always be a member of the list which is returned from
+     * calling {@link #getEnabledPhoneAccounts()}
+     *
+     * Apps must be prepared for this method to return {@code null}, indicating that there currently
+     * exists no user-chosen default {@code PhoneAccount}.
+     *
+     * @return The user outgoing phone account selected by the user.
+     * @hide
+     */
+    public PhoneAccountHandle getUserSelectedOutgoingPhoneAccount() {
+        try {
+            if (isServiceConnected()) {
+                return getTelecomService().getUserSelectedOutgoingPhoneAccount();
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error calling ITelecomService#getUserSelectedOutgoingPhoneAccount", e);
+        }
+        return null;
+    }
+
+    /**
+     * Sets the default account for making outgoing phone calls.
+     * @hide
+     */
+    public void setUserSelectedOutgoingPhoneAccount(PhoneAccountHandle accountHandle) {
+        try {
+            if (isServiceConnected()) {
+                getTelecomService().setUserSelectedOutgoingPhoneAccount(accountHandle);
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error calling ITelecomService#setUserSelectedOutgoingPhoneAccount");
+        }
+    }
+
+    /**
+     * Return a list of enabled {@link PhoneAccountHandle}s which can be used to make and receive
+     * phone calls.
+     *
+     * @see #EXTRA_PHONE_ACCOUNT_HANDLE
+     * @return A list of {@code PhoneAccountHandle} objects.
+     */
+    public List<PhoneAccountHandle> getEnabledPhoneAccounts() {
+        try {
+            if (isServiceConnected()) {
+                return getTelecomService().getEnabledPhoneAccounts();
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error calling ITelecomService#getEnabledPhoneAccounts", e);
+        }
+        return new ArrayList<>();
+    }
+
+    /**
+     * Returns the current SIM call manager. Apps must be prepared for this method to return
+     * {@code null}, indicating that there currently exists no user-chosen default
+     * {@code PhoneAccount}.
+     * @return The phone account handle of the current sim call manager.
+     * @hide
+     */
+    public PhoneAccountHandle getSimCallManager() {
+        try {
+            if (isServiceConnected()) {
+                return getTelecomService().getSimCallManager();
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error calling ITelecomService#getSimCallManager");
+        }
+        return null;
+    }
+
+    /**
+     * Sets the SIM call manager to the specified phone account.
+     * @param accountHandle The phone account handle of the account to set as the sim call manager.
+     * @hide
+     */
+    public void setSimCallManager(PhoneAccountHandle accountHandle) {
+        try {
+            if (isServiceConnected()) {
+                getTelecomService().setSimCallManager(accountHandle);
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error calling ITelecomService#setSimCallManager");
+        }
+    }
+
+    /**
+     * Returns the list of registered SIM call managers.
+     * @return List of registered SIM call managers.
+     * @hide
+     */
+    public List<PhoneAccountHandle> getSimCallManagers() {
+        try {
+            if (isServiceConnected()) {
+                return getTelecomService().getSimCallManagers();
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error calling ITelecomService#getSimCallManagers");
+        }
+        return new ArrayList<>();
+    }
+
+    /**
+     * Returns the current connection manager. Apps must be prepared for this method to return
+     * {@code null}, indicating that there currently exists no user-chosen default
+     * {@code PhoneAccount}.
+     *
+     * @return The phone account handle of the current connection manager.
+     */
+    public PhoneAccountHandle getConnectionManager() {
+        return getSimCallManager();
+    }
+
+    /**
+     * Returns a list of the enabled {@link PhoneAccountHandle}s which can be used to make and
+     * receive phone calls which support the specified URI scheme.
+     * <P>
+     * For example, invoking with {@code "tel"} will find all {@link PhoneAccountHandle}s which
+     * support telephone calls (e.g. URIs such as {@code tel:555-555-1212}).  Invoking with
+     * {@code "sip"} will find all {@link PhoneAccountHandle}s which support SIP calls (e.g. URIs
+     * such as {@code sip:example@sipexample.com}).
+     *
+     * @param uriScheme The URI scheme.
+     * @return A list of {@code PhoneAccountHandle} objects supporting the URI scheme.
+     */
+    public List<PhoneAccountHandle> getPhoneAccountsSupportingScheme(String uriScheme) {
+        try {
+            if (isServiceConnected()) {
+                return getTelecomService().getPhoneAccountsSupportingScheme(uriScheme);
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error calling ITelecomService#getPhoneAccountsSupportingScheme", e);
+        }
+        return new ArrayList<>();
+    }
+
+    /**
+     * Determine whether the device has more than one account registered and enabled.
+     *
+     * @return {@code true} if the device has more than one account registered and enabled and
+     * {@code false} otherwise.
+     */
+    public boolean hasMultipleEnabledAccounts() {
+        return getEnabledPhoneAccounts().size() > 1;
+    }
+
+    /**
+     * Return the {@link PhoneAccount} for a specified {@link PhoneAccountHandle}. Object includes
+     * resources which can be used in a user interface.
+     *
+     * @param account The {@link PhoneAccountHandle}.
+     * @return The {@link PhoneAccount} object.
+     */
+    public PhoneAccount getPhoneAccount(PhoneAccountHandle account) {
+        try {
+            if (isServiceConnected()) {
+                return getTelecomService().getPhoneAccount(account);
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error calling ITelecomService#getPhoneAccount", e);
+        }
+        return null;
+    }
+
+    /**
+     * Returns a count of enabled and disabled {@link PhoneAccount}s.
+     *
+     * @return The count of enabled and disabled {@link PhoneAccount}s.
+     * @hide
+     */
+    @SystemApi
+    public int getAllPhoneAccountsCount() {
+        try {
+            if (isServiceConnected()) {
+                return getTelecomService().getAllPhoneAccountsCount();
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error calling ITelecomService#getAllPhoneAccountsCount", e);
+        }
+        return 0;
+    }
+
+    /**
+     * Returns a list of all {@link PhoneAccount}s.
+     *
+     * @return All {@link PhoneAccount}s.
+     * @hide
+     */
+    @SystemApi
+    public List<PhoneAccount> getAllPhoneAccounts() {
+        try {
+            if (isServiceConnected()) {
+                return getTelecomService().getAllPhoneAccounts();
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error calling ITelecomService#getAllPhoneAccounts", e);
+        }
+        return Collections.EMPTY_LIST;
+    }
+
+    /**
+     * Returns a list of all {@link PhoneAccountHandle}s.
+     *
+     * @return All {@link PhoneAccountHandle}s.
+     * @hide
+     */
+    @SystemApi
+    public List<PhoneAccountHandle> getAllPhoneAccountHandles() {
+        try {
+            if (isServiceConnected()) {
+                return getTelecomService().getAllPhoneAccountHandles();
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error calling ITelecomService#getAllPhoneAccountHandles", e);
+        }
+        return Collections.EMPTY_LIST;
+    }
+
+    /**
+     * Enables or disables a {@link PhoneAccount}.
+     *
+     * @param account The {@link PhoneAccountHandle} to enable or disable.
+     * @param isEnabled {@code True} if the phone account should be enabled.
+     * @hide
+     */
+    @SystemApi
+    public void setPhoneAccountEnabled(PhoneAccountHandle account, boolean isEnabled) {
+        try {
+            if (isServiceConnected()) {
+                getTelecomService().setPhoneAccountEnabled(account, isEnabled);
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error calling ITelecomService#setPhoneAccountEnabled", e);
+        }
+    }
+
+    /**
+     * Register a {@link PhoneAccount} for use by the system.
+     *
+     * @param account The complete {@link PhoneAccount}.
+     */
+    public void registerPhoneAccount(PhoneAccount account) {
+        try {
+            if (isServiceConnected()) {
+                getTelecomService().registerPhoneAccount(account);
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error calling ITelecomService#registerPhoneAccount", e);
+        }
+    }
+
+    /**
+     * Remove a {@link PhoneAccount} registration from the system.
+     *
+     * @param accountHandle A {@link PhoneAccountHandle} for the {@link PhoneAccount} to unregister.
+     */
+    public void unregisterPhoneAccount(PhoneAccountHandle accountHandle) {
+        try {
+            if (isServiceConnected()) {
+                getTelecomService().unregisterPhoneAccount(accountHandle);
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error calling ITelecomService#unregisterPhoneAccount", e);
+        }
+    }
+
+    /**
+     * Remove all Accounts that belong to the calling package from the system.
+     */
+    @SystemApi
+    public void clearAccounts() {
+        try {
+            if (isServiceConnected()) {
+                getTelecomService().clearAccounts(mContext.getPackageName());
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error calling ITelecomService#clearAccounts", e);
+        }
+    }
+
+    /**
+     * @hide
+     */
+    @SystemApi
+    public ComponentName getDefaultPhoneApp() {
+        try {
+            if (isServiceConnected()) {
+                return getTelecomService().getDefaultPhoneApp();
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "RemoteException attempting to get the default phone app.", e);
+        }
+        return null;
+    }
+
+    /**
+     * Returns whether there is an ongoing phone call (can be in dialing, ringing, active or holding
+     * states).
+     * <p>
+     * Requires permission: {@link android.Manifest.permission#READ_PHONE_STATE}
+     * </p>
+     */
+    @SystemApi
+    public boolean isInCall() {
+        try {
+            if (isServiceConnected()) {
+                return getTelecomService().isInCall();
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "RemoteException attempting to get default phone app.", e);
+        }
+        return false;
+    }
+
+    /**
+     * Returns whether there currently exists is a ringing incoming-call.
+     *
+     * @hide
+     */
+    @SystemApi
+    public boolean isRinging() {
+        try {
+            if (isServiceConnected()) {
+                return getTelecomService().isRinging();
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "RemoteException attempting to get ringing state of phone app.", e);
+        }
+        return false;
+    }
+
+    /**
+     * Ends an ongoing call.
+     * TODO: L-release - need to convert all invocations of ITelecomService#endCall to use this
+     * method (clockwork & gearhead).
+     * @hide
+     */
+    @SystemApi
+    public boolean endCall() {
+        try {
+            if (isServiceConnected()) {
+                return getTelecomService().endCall();
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error calling ITelecomService#endCall", e);
+        }
+        return false;
+    }
+
+    /**
+     * If there is a ringing incoming call, this method accepts the call on behalf of the user.
+     * TODO: L-release - need to convert all invocation of ITelecmmService#answerRingingCall to use
+     * this method (clockwork & gearhead).
+     *
+     * @hide
+     */
+    @SystemApi
+    public void acceptRingingCall() {
+        try {
+            if (isServiceConnected()) {
+                getTelecomService().acceptRingingCall();
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error calling ITelecomService#acceptRingingCall", e);
+        }
+    }
+
+    /**
+     * Silences the ringer if a ringing call exists.
+     *
+     * @hide
+     */
+    @SystemApi
+    public void silenceRinger() {
+        try {
+            if (isServiceConnected()) {
+                getTelecomService().silenceRinger();
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error calling ITelecomService#silenceRinger", e);
+        }
+    }
+
+    /**
+     * Returns whether TTY is supported on this device.
+     *
+     * @hide
+     */
+    @SystemApi
+    public boolean isTtySupported() {
+        try {
+            if (isServiceConnected()) {
+                return getTelecomService().isTtySupported();
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "RemoteException attempting to get TTY supported state.", e);
+        }
+        return false;
+    }
+
+    /**
+     * Returns the current TTY mode of the device. For TTY to be on the user must enable it in
+     * settings and have a wired headset plugged in.
+     * Valid modes are:
+     * - {@link TelecomManager#TTY_MODE_OFF}
+     * - {@link TelecomManager#TTY_MODE_FULL}
+     * - {@link TelecomManager#TTY_MODE_HCO}
+     * - {@link TelecomManager#TTY_MODE_VCO}
+     * @hide
+     */
+    public int getCurrentTtyMode() {
+        try {
+            if (isServiceConnected()) {
+                return getTelecomService().getCurrentTtyMode();
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "RemoteException attempting to get the current TTY mode.", e);
+        }
+        return TTY_MODE_OFF;
+    }
+
+    /**
+     * Registers a new incoming call. A {@link ConnectionService} should invoke this method when it
+     * has an incoming call. The specified {@link PhoneAccountHandle} must have been registered
+     * with {@link #registerPhoneAccount} and subsequently enabled by the user within the phone's
+     * settings. Once invoked, this method will cause the system to bind to the
+     * {@link ConnectionService} associated with the {@link PhoneAccountHandle} and request
+     * additional information about the call (See
+     * {@link ConnectionService#onCreateIncomingConnection}) before starting the incoming call UI.
+     *
+     * @param phoneAccount A {@link PhoneAccountHandle} registered with
+     *            {@link #registerPhoneAccount}.
+     * @param extras A bundle that will be passed through to
+     *            {@link ConnectionService#onCreateIncomingConnection}.
+     */
+    public void addNewIncomingCall(PhoneAccountHandle phoneAccount, Bundle extras) {
+        try {
+            if (isServiceConnected()) {
+                getTelecomService().addNewIncomingCall(
+                        phoneAccount, extras == null ? new Bundle() : extras);
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "RemoteException adding a new incoming call: " + phoneAccount, e);
+        }
+    }
+
+    /**
+     * Processes the specified dial string as an MMI code.
+     * MMI codes are any sequence of characters entered into the dialpad that contain a "*" or "#".
+     * Some of these sequences launch special behavior through handled by Telephony.
+     * <p>
+     * Requires that the method-caller be set as the system dialer app.
+     * </p>
+     *
+     * @param dialString The digits to dial.
+     * @return True if the digits were processed as an MMI code, false otherwise.
+     */
+    public boolean handleMmi(String dialString) {
+        ITelecomService service = getTelecomService();
+        if (service != null) {
+            try {
+                return service.handlePinMmi(dialString);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Error calling ITelecomService#handlePinMmi", e);
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Removes the missed-call notification if one is present.
+     * <p>
+     * Requires that the method-caller be set as the system dialer app.
+     * </p>
+     */
+    public void cancelMissedCallsNotification() {
+        ITelecomService service = getTelecomService();
+        if (service != null) {
+            try {
+                service.cancelMissedCallsNotification();
+            } catch (RemoteException e) {
+                Log.e(TAG, "Error calling ITelecomService#cancelMissedCallsNotification", e);
+            }
+        }
+    }
+
+    /**
+     * Brings the in-call screen to the foreground if there is an ongoing call. If there is
+     * currently no ongoing call, then this method does nothing.
+     * <p>
+     * Requires that the method-caller be set as the system dialer app or have the
+     * {@link android.Manifest.permission#READ_PHONE_STATE} permission.
+     * </p>
+     *
+     * @param showDialpad Brings up the in-call dialpad as part of showing the in-call screen.
+     */
+    public void showInCallScreen(boolean showDialpad) {
+        ITelecomService service = getTelecomService();
+        if (service != null) {
+            try {
+                service.showInCallScreen(showDialpad);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Error calling ITelecomService#showCallScreen", e);
+            }
+        }
+    }
+
+    private ITelecomService getTelecomService() {
+        return ITelecomService.Stub.asInterface(ServiceManager.getService(Context.TELECOM_SERVICE));
+    }
+
+    private boolean isServiceConnected() {
+        boolean isConnected = getTelecomService() != null;
+        if (!isConnected) {
+            Log.w(TAG, "Telecom Service not found.");
+        }
+        return isConnected;
+    }
+}
diff --git a/telecomm/java/android/telecom/VideoCallImpl.java b/telecomm/java/android/telecom/VideoCallImpl.java
new file mode 100644
index 0000000..925058e
--- /dev/null
+++ b/telecomm/java/android/telecom/VideoCallImpl.java
@@ -0,0 +1,247 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.telecom;
+
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.telecom.InCallService.VideoCall;
+import android.view.Surface;
+
+import com.android.internal.os.SomeArgs;
+import com.android.internal.telecom.IVideoCallback;
+import com.android.internal.telecom.IVideoProvider;
+
+/**
+ * Implementation of a Video Call, which allows InCallUi to communicate commands to the underlying
+ * {@link Connection.VideoProvider}, and direct callbacks from the
+ * {@link Connection.VideoProvider} to the appropriate {@link VideoCall.Listener}.
+ *
+ * {@hide}
+ */
+public class VideoCallImpl extends VideoCall {
+    private static final int MSG_RECEIVE_SESSION_MODIFY_REQUEST = 1;
+    private static final int MSG_RECEIVE_SESSION_MODIFY_RESPONSE = 2;
+    private static final int MSG_HANDLE_CALL_SESSION_EVENT = 3;
+    private static final int MSG_CHANGE_PEER_DIMENSIONS = 4;
+    private static final int MSG_CHANGE_CALL_DATA_USAGE = 5;
+    private static final int MSG_CHANGE_CAMERA_CAPABILITIES = 6;
+
+    private final IVideoProvider mVideoProvider;
+    private final VideoCallListenerBinder mBinder;
+    private VideoCall.Listener mVideoCallListener;
+
+    private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
+        @Override
+        public void binderDied() {
+            mVideoProvider.asBinder().unlinkToDeath(this, 0);
+        }
+    };
+
+    /**
+     * IVideoCallback stub implementation.
+     */
+    private final class VideoCallListenerBinder extends IVideoCallback.Stub {
+        @Override
+        public void receiveSessionModifyRequest(VideoProfile videoProfile) {
+            mHandler.obtainMessage(MSG_RECEIVE_SESSION_MODIFY_REQUEST,
+                    videoProfile).sendToTarget();
+        }
+
+        @Override
+        public void receiveSessionModifyResponse(int status, VideoProfile requestProfile,
+                VideoProfile responseProfile) {
+            SomeArgs args = SomeArgs.obtain();
+            args.arg1 = status;
+            args.arg2 = requestProfile;
+            args.arg3 = responseProfile;
+            mHandler.obtainMessage(MSG_RECEIVE_SESSION_MODIFY_RESPONSE, args).sendToTarget();
+        }
+
+        @Override
+        public void handleCallSessionEvent(int event) {
+            mHandler.obtainMessage(MSG_HANDLE_CALL_SESSION_EVENT, event).sendToTarget();
+        }
+
+        @Override
+        public void changePeerDimensions(int width, int height) {
+            SomeArgs args = SomeArgs.obtain();
+            args.arg1 = width;
+            args.arg2 = height;
+            mHandler.obtainMessage(MSG_CHANGE_PEER_DIMENSIONS, args).sendToTarget();
+        }
+
+        @Override
+        public void changeCallDataUsage(int dataUsage) {
+            mHandler.obtainMessage(MSG_CHANGE_CALL_DATA_USAGE, dataUsage).sendToTarget();
+        }
+
+        @Override
+        public void changeCameraCapabilities(CameraCapabilities cameraCapabilities) {
+            mHandler.obtainMessage(MSG_CHANGE_CAMERA_CAPABILITIES,
+                    cameraCapabilities).sendToTarget();
+        }
+    }
+
+    /** Default handler used to consolidate binder method calls onto a single thread. */
+    private final Handler mHandler = new Handler(Looper.getMainLooper()) {
+        @Override
+        public void handleMessage(Message msg) {
+            if (mVideoCallListener == null) {
+                return;
+            }
+
+            SomeArgs args;
+            switch (msg.what) {
+                case MSG_RECEIVE_SESSION_MODIFY_REQUEST:
+                    mVideoCallListener.onSessionModifyRequestReceived((VideoProfile) msg.obj);
+                    break;
+                case MSG_RECEIVE_SESSION_MODIFY_RESPONSE:
+                    args = (SomeArgs) msg.obj;
+                    try {
+                        int status = (int) args.arg1;
+                        VideoProfile requestProfile = (VideoProfile) args.arg2;
+                        VideoProfile responseProfile = (VideoProfile) args.arg3;
+
+                        mVideoCallListener.onSessionModifyResponseReceived(
+                                status, requestProfile, responseProfile);
+                    } finally {
+                        args.recycle();
+                    }
+                    break;
+                case MSG_HANDLE_CALL_SESSION_EVENT:
+                    mVideoCallListener.onCallSessionEvent((int) msg.obj);
+                    break;
+                case MSG_CHANGE_PEER_DIMENSIONS:
+                    args = (SomeArgs) msg.obj;
+                    try {
+                        int width = (int) args.arg1;
+                        int height = (int) args.arg2;
+                        mVideoCallListener.onPeerDimensionsChanged(width, height);
+                    } finally {
+                        args.recycle();
+                    }
+                    break;
+                case MSG_CHANGE_CALL_DATA_USAGE:
+                    mVideoCallListener.onCallDataUsageChanged(msg.arg1);
+                    break;
+                case MSG_CHANGE_CAMERA_CAPABILITIES:
+                    mVideoCallListener.onCameraCapabilitiesChanged(
+                            (CameraCapabilities) msg.obj);
+                    break;
+                default:
+                    break;
+            }
+        }
+    };
+
+    /** {@hide} */
+    VideoCallImpl(IVideoProvider videoProvider) throws RemoteException {
+        mVideoProvider = videoProvider;
+        mVideoProvider.asBinder().linkToDeath(mDeathRecipient, 0);
+
+        mBinder = new VideoCallListenerBinder();
+        mVideoProvider.setVideoCallback(mBinder);
+    }
+
+    /** {@inheritDoc} */
+    public void setVideoCallListener(VideoCall.Listener videoCallListener) {
+        mVideoCallListener = videoCallListener;
+    }
+
+    /** {@inheritDoc} */
+    public void setCamera(String cameraId) {
+        try {
+            mVideoProvider.setCamera(cameraId);
+        } catch (RemoteException e) {
+        }
+    }
+
+    /** {@inheritDoc} */
+    public void setPreviewSurface(Surface surface) {
+        try {
+            mVideoProvider.setPreviewSurface(surface);
+        } catch (RemoteException e) {
+        }
+    }
+
+    /** {@inheritDoc} */
+    public void setDisplaySurface(Surface surface) {
+        try {
+            mVideoProvider.setDisplaySurface(surface);
+        } catch (RemoteException e) {
+        }
+    }
+
+    /** {@inheritDoc} */
+    public void setDeviceOrientation(int rotation) {
+        try {
+            mVideoProvider.setDeviceOrientation(rotation);
+        } catch (RemoteException e) {
+        }
+    }
+
+    /** {@inheritDoc} */
+    public void setZoom(float value) {
+        try {
+            mVideoProvider.setZoom(value);
+        } catch (RemoteException e) {
+        }
+    }
+
+    /** {@inheritDoc} */
+    public void sendSessionModifyRequest(VideoProfile requestProfile) {
+        try {
+            mVideoProvider.sendSessionModifyRequest(requestProfile);
+        } catch (RemoteException e) {
+        }
+    }
+
+    /** {@inheritDoc} */
+    public void sendSessionModifyResponse(VideoProfile responseProfile) {
+        try {
+            mVideoProvider.sendSessionModifyResponse(responseProfile);
+        } catch (RemoteException e) {
+        }
+    }
+
+    /** {@inheritDoc} */
+    public void requestCameraCapabilities() {
+        try {
+            mVideoProvider.requestCameraCapabilities();
+        } catch (RemoteException e) {
+        }
+    }
+
+    /** {@inheritDoc} */
+    public void requestCallDataUsage() {
+        try {
+            mVideoProvider.requestCallDataUsage();
+        } catch (RemoteException e) {
+        }
+    }
+
+    /** {@inheritDoc} */
+    public void setPauseImage(String uri) {
+        try {
+            mVideoProvider.setPauseImage(uri);
+        } catch (RemoteException e) {
+        }
+    }
+}
\ No newline at end of file
diff --git a/telecomm/java/android/telecom/VideoCallbackServant.java b/telecomm/java/android/telecom/VideoCallbackServant.java
new file mode 100644
index 0000000..d0e3f22
--- /dev/null
+++ b/telecomm/java/android/telecom/VideoCallbackServant.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ R* limitations under the License.
+ */
+
+package android.telecom;
+
+import com.android.internal.os.SomeArgs;
+import com.android.internal.telecom.IVideoCallback;
+
+import android.os.Handler;
+import android.os.Message;
+import android.os.RemoteException;
+
+/**
+ * A component that provides an RPC servant implementation of {@link IVideoCallback},
+ * posting incoming messages on the main thread on a client-supplied delegate object.
+ *
+ * TODO: Generate this and similar classes using a compiler starting from AIDL interfaces.
+ *
+ * @hide
+ */
+final class VideoCallbackServant {
+    private static final int MSG_RECEIVE_SESSION_MODIFY_REQUEST = 0;
+    private static final int MSG_RECEIVE_SESSION_MODIFY_RESPONSE = 1;
+    private static final int MSG_HANDLE_CALL_SESSION_EVENT = 2;
+    private static final int MSG_CHANGE_PEER_DIMENSIONS = 3;
+    private static final int MSG_CHANGE_CALL_DATA_USAGE = 4;
+    private static final int MSG_CHANGE_CAMERA_CAPABILITIES = 5;
+
+    private final IVideoCallback mDelegate;
+
+    private final Handler mHandler = new Handler() {
+        @Override
+        public void handleMessage(Message msg) {
+            try {
+                internalHandleMessage(msg);
+            } catch (RemoteException e) {
+            }
+        }
+
+        // Internal method defined to centralize handling of RemoteException
+        private void internalHandleMessage(Message msg) throws RemoteException {
+            switch (msg.what) {
+                case MSG_RECEIVE_SESSION_MODIFY_REQUEST: {
+                    mDelegate.receiveSessionModifyRequest((VideoProfile) msg.obj);
+                    break;
+                }
+                case MSG_RECEIVE_SESSION_MODIFY_RESPONSE: {
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    try {
+                        mDelegate.receiveSessionModifyResponse(
+                                args.argi1,
+                                (VideoProfile) args.arg1,
+                                (VideoProfile) args.arg2);
+                    } finally {
+                        args.recycle();
+                    }
+                    break;
+                }
+                case MSG_HANDLE_CALL_SESSION_EVENT: {
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    try {
+                        mDelegate.handleCallSessionEvent(args.argi1);
+                    } finally {
+                        args.recycle();
+                    }
+                    break;
+                }
+                case MSG_CHANGE_PEER_DIMENSIONS: {
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    try {
+                        mDelegate.changePeerDimensions(args.argi1, args.argi2);
+                    } finally {
+                        args.recycle();
+                    }
+                    break;
+                }
+                case MSG_CHANGE_CALL_DATA_USAGE: {
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    try {
+                        mDelegate.changeCallDataUsage(args.argi1);
+                    } finally {
+                        args.recycle();
+                    }
+                    break;
+                }
+                case MSG_CHANGE_CAMERA_CAPABILITIES: {
+                    mDelegate.changeCameraCapabilities((CameraCapabilities) msg.obj);
+                    break;
+                }
+            }
+        }
+    };
+
+    private final IVideoCallback mStub = new IVideoCallback.Stub() {
+        @Override
+        public void receiveSessionModifyRequest(VideoProfile videoProfile) throws RemoteException {
+            mHandler.obtainMessage(MSG_RECEIVE_SESSION_MODIFY_REQUEST, videoProfile).sendToTarget();
+        }
+
+        @Override
+        public void receiveSessionModifyResponse(int status, VideoProfile requestedProfile,
+                VideoProfile responseProfile) throws RemoteException {
+            SomeArgs args = SomeArgs.obtain();
+            args.argi1 = status;
+            args.arg1 = requestedProfile;
+            args.arg2 = responseProfile;
+            mHandler.obtainMessage(MSG_RECEIVE_SESSION_MODIFY_RESPONSE, args).sendToTarget();
+        }
+
+        @Override
+        public void handleCallSessionEvent(int event) throws RemoteException {
+            SomeArgs args = SomeArgs.obtain();
+            args.argi1 = event;
+            mHandler.obtainMessage(MSG_HANDLE_CALL_SESSION_EVENT, args).sendToTarget();
+        }
+
+        @Override
+        public void changePeerDimensions(int width, int height) throws RemoteException {
+            SomeArgs args = SomeArgs.obtain();
+            args.argi1 = width;
+            args.argi2 = height;
+            mHandler.obtainMessage(MSG_CHANGE_PEER_DIMENSIONS, args).sendToTarget();
+        }
+
+        @Override
+        public void changeCallDataUsage(int dataUsage) throws RemoteException {
+            SomeArgs args = SomeArgs.obtain();
+            args.argi1 = dataUsage;
+            mHandler.obtainMessage(MSG_CHANGE_CALL_DATA_USAGE, args).sendToTarget();
+        }
+
+        @Override
+        public void changeCameraCapabilities(CameraCapabilities cameraCapabilities)
+                throws RemoteException {
+            mHandler.obtainMessage(MSG_CHANGE_CAMERA_CAPABILITIES, cameraCapabilities)
+                    .sendToTarget();
+        }
+    };
+
+    public VideoCallbackServant(IVideoCallback delegate) {
+        mDelegate = delegate;
+    }
+
+    public IVideoCallback getStub() {
+        return mStub;
+    }
+}
diff --git a/telecomm/java/android/telecom/VideoProfile.aidl b/telecomm/java/android/telecom/VideoProfile.aidl
new file mode 100644
index 0000000..091b569
--- /dev/null
+++ b/telecomm/java/android/telecom/VideoProfile.aidl
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+
+package android.telecom;
+
+/**
+ * {@hide}
+ */
+parcelable VideoProfile;
diff --git a/telecomm/java/android/telecom/VideoProfile.java b/telecomm/java/android/telecom/VideoProfile.java
new file mode 100644
index 0000000..f5cb054
--- /dev/null
+++ b/telecomm/java/android/telecom/VideoProfile.java
@@ -0,0 +1,231 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.telecom;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Represents attributes of video calls.
+ *
+ * {@hide}
+ */
+public class VideoProfile implements Parcelable {
+    /**
+     * "High" video quality.
+     */
+    public static final int QUALITY_HIGH = 1;
+
+    /**
+     * "Medium" video quality.
+     */
+    public static final int QUALITY_MEDIUM = 2;
+
+    /**
+     * "Low" video quality.
+     */
+    public static final int QUALITY_LOW = 3;
+
+    /**
+     * Use default video quality.
+     */
+    public static final int QUALITY_DEFAULT = 4;
+
+    private final int mVideoState;
+
+    private final int mQuality;
+
+    /**
+     * Creates an instance of the VideoProfile
+     *
+     * @param videoState The video state.
+     */
+    public VideoProfile(int videoState) {
+        this(videoState, QUALITY_DEFAULT);
+    }
+
+    /**
+     * Creates an instance of the VideoProfile
+     *
+     * @param videoState The video state.
+     * @param quality The video quality.
+     */
+    public VideoProfile(int videoState, int quality) {
+        mVideoState = videoState;
+        mQuality = quality;
+    }
+
+    /**
+     * The video state of the call.
+     * Valid values: {@link VideoProfile.VideoState#AUDIO_ONLY},
+     * {@link VideoProfile.VideoState#BIDIRECTIONAL},
+     * {@link VideoProfile.VideoState#TX_ENABLED},
+     * {@link VideoProfile.VideoState#RX_ENABLED},
+     * {@link VideoProfile.VideoState#PAUSED}.
+     */
+    public int getVideoState() {
+        return mVideoState;
+    }
+
+    /**
+     * The desired video quality for the call.
+     * Valid values: {@link VideoProfile#QUALITY_HIGH}, {@link VideoProfile#QUALITY_MEDIUM},
+     * {@link VideoProfile#QUALITY_LOW}, {@link VideoProfile#QUALITY_DEFAULT}.
+     */
+    public int getQuality() {
+        return mQuality;
+    }
+
+    /**
+     * Responsible for creating VideoProfile objects from deserialized Parcels.
+     **/
+    public static final Parcelable.Creator<VideoProfile> CREATOR =
+            new Parcelable.Creator<VideoProfile> () {
+                /**
+                 * Creates a MediaProfile instances from a parcel.
+                 *
+                 * @param source The parcel.
+                 * @return The MediaProfile.
+                 */
+                @Override
+                public VideoProfile createFromParcel(Parcel source) {
+                    int state = source.readInt();
+                    int quality = source.readInt();
+
+                    ClassLoader classLoader = VideoProfile.class.getClassLoader();
+                    return new VideoProfile(state, quality);
+                }
+
+                @Override
+                public VideoProfile[] newArray(int size) {
+                    return new VideoProfile[size];
+                }
+            };
+
+    /**
+     * Describe the kinds of special objects contained in this Parcelable's
+     * marshalled representation.
+     *
+     * @return a bitmask indicating the set of special object types marshalled
+     * by the Parcelable.
+     */
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * Flatten this object in to a Parcel.
+     *
+     * @param dest  The Parcel in which the object should be written.
+     * @param flags Additional flags about how the object should be written.
+     *              May be 0 or {@link #PARCELABLE_WRITE_RETURN_VALUE}.
+     */
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mVideoState);
+        dest.writeInt(mQuality);
+    }
+
+    /**
+    * The video state of the call, stored as a bit-field describing whether video transmission and
+    * receipt it enabled, as well as whether the video is currently muted.
+    */
+    public static class VideoState {
+        /**
+         * Call is currently in an audio-only mode with no video transmission or receipt.
+         */
+        public static final int AUDIO_ONLY = 0x0;
+
+        /**
+         * Video transmission is enabled.
+         */
+        public static final int TX_ENABLED = 0x1;
+
+        /**
+         * Video reception is enabled.
+         */
+        public static final int RX_ENABLED = 0x2;
+
+        /**
+         * Video signal is bi-directional.
+         */
+        public static final int BIDIRECTIONAL = TX_ENABLED | RX_ENABLED;
+
+        /**
+         * Video is paused.
+         */
+        public static final int PAUSED = 0x4;
+
+        /**
+         * Whether the video state is audio only.
+         * @param videoState The video state.
+         * @return Returns true if the video state is audio only.
+         */
+        public static boolean isAudioOnly(int videoState) {
+            return !hasState(videoState, TX_ENABLED) && !hasState(videoState, RX_ENABLED);
+        }
+
+        /**
+         * Whether the video transmission is enabled.
+         * @param videoState The video state.
+         * @return Returns true if the video transmission is enabled.
+         */
+        public static boolean isTransmissionEnabled(int videoState) {
+            return hasState(videoState, TX_ENABLED);
+        }
+
+        /**
+         * Whether the video reception is enabled.
+         * @param videoState The video state.
+         * @return Returns true if the video transmission is enabled.
+         */
+        public static boolean isReceptionEnabled(int videoState) {
+            return hasState(videoState, RX_ENABLED);
+        }
+
+        /**
+         * Whether the video signal is bi-directional.
+         * @param videoState
+         * @return Returns true if the video signal is bi-directional.
+         */
+        public static boolean isBidirectional(int videoState) {
+            return hasState(videoState, BIDIRECTIONAL);
+        }
+
+        /**
+         * Whether the video is paused.
+         * @param videoState The video state.
+         * @return Returns true if the video is paused.
+         */
+        public static boolean isPaused(int videoState) {
+            return hasState(videoState, PAUSED);
+        }
+
+        /**
+         * Determines if a specified state is set in a videoState bit-mask.
+         *
+         * @param videoState The video state bit-mask.
+         * @param state The state to check.
+         * @return {@code True} if the state is set.
+         * {@hide}
+         */
+        private static boolean hasState(int videoState, int state) {
+            return (videoState & state) == state;
+        }
+    }
+}