Remove dependence on Connection.Listener in Telephony.

Telephony used the Connection.Listener class a LOT, even though its an
@hide detail of the Telecom Connection API.

Summary of changes:
In TelephonyConnection, created a parallel to the Connection.Listener
for the events which telephony internally cares about. This required making
a lot of TelephonyConnection versions of the set methods so we could
trigger the right listeners.
In ImsConference, removed dependence on listener to track when a connection
is destroyed; the destroy request originates in this class so we don't
really need to go through the listener indirection.


Bug: 141576016
Test: Manual smoke test.
Test: Run unit tests.
Test: Run CTS tests.
Change-Id: I037c59a0aa75c2f4885a20642ec95c750175fe21
diff --git a/src/com/android/services/telephony/CdmaConference.java b/src/com/android/services/telephony/CdmaConference.java
index 69ff2a4..693fd16 100755
--- a/src/com/android/services/telephony/CdmaConference.java
+++ b/src/com/android/services/telephony/CdmaConference.java
@@ -18,7 +18,6 @@
 
 import android.content.Context;
 import android.os.PersistableBundle;
-import android.telecom.Conference;
 import android.telecom.Connection;
 import android.telecom.PhoneAccountHandle;
 import android.telephony.CarrierConfigManager;
@@ -32,7 +31,7 @@
 /**
  * CDMA-based conference call.
  */
-public class CdmaConference extends Conference implements Holdable {
+public class CdmaConference extends TelephonyConferenceBase implements Holdable {
     private int mCapabilities;
     private int mProperties;
     private boolean mIsHoldable;
diff --git a/src/com/android/services/telephony/CdmaConferenceController.java b/src/com/android/services/telephony/CdmaConferenceController.java
index 5d987f7..9afcd0a 100644
--- a/src/com/android/services/telephony/CdmaConferenceController.java
+++ b/src/com/android/services/telephony/CdmaConferenceController.java
@@ -48,7 +48,8 @@
  * the conference from being created for 3 seconds. This is a more pleasant experience for the user.
  */
 final class CdmaConferenceController {
-    private final Connection.Listener mConnectionListener = new Connection.Listener() {
+    private final TelephonyConnection.TelephonyConnectionListener mTelephonyConnectionListener =
+            new TelephonyConnection.TelephonyConnectionListener() {
                 @Override
                 public void onStateChanged(Connection c, int state) {
                     recalculateConference();
@@ -139,7 +140,7 @@
 
     private void addInternal(CdmaConnection connection) {
         mCdmaConnections.add(connection);
-        connection.addConnectionListener(mConnectionListener);
+        connection.addTelephonyConnectionListener(mTelephonyConnectionListener);
         recalculateConference();
     }
 
@@ -151,7 +152,7 @@
             return;
         }
 
-        connection.removeConnectionListener(mConnectionListener);
+        connection.removeTelephonyConnectionListener(mTelephonyConnectionListener);
         mCdmaConnections.remove(connection);
         recalculateConference();
     }
@@ -197,14 +198,14 @@
             for (CdmaConnection connection : conferenceConnections) {
                 if (!existingChildConnections.contains(connection)) {
                     Log.i(this, "Adding connection to conference call: %s", connection);
-                    mConference.addConnection(connection);
+                    mConference.addTelephonyConnection(connection);
                 }
                 existingChildConnections.remove(connection);
             }
 
             // 3) Remove any lingering old/disconnected/destroyed connections
             for (Connection oldConnection : existingChildConnections) {
-                mConference.removeConnection(oldConnection);
+                mConference.removeTelephonyConnection(oldConnection);
                 Log.i(this, "Removing connection from conference call: %s", oldConnection);
             }
 
@@ -212,13 +213,13 @@
             if (isNewlyCreated) {
                 Log.d(this, "Adding the conference call");
                 mConference.updateCallRadioTechAfterCreation();
-                mConnectionService.addConference(mConference);
+                mConnectionService.addTelephonyConference(mConference);
             }
         } else if (conferenceConnections.isEmpty()) {
             // There are no more connection so if we still have a conference, lets remove it.
             if (mConference != null) {
                 Log.i(this, "Destroying the CDMA conference connection.");
-                mConference.destroy();
+                mConference.destroyTelephonyConference();
                 mConference = null;
             }
         }
diff --git a/src/com/android/services/telephony/CdmaConnection.java b/src/com/android/services/telephony/CdmaConnection.java
index ca842b1..bd015e3 100644
--- a/src/com/android/services/telephony/CdmaConnection.java
+++ b/src/com/android/services/telephony/CdmaConnection.java
@@ -228,8 +228,8 @@
             } catch (CallStateException e) {
                 Log.e(this, e, "Failed to hangup call waiting call");
             }
-            setDisconnected(DisconnectCauseUtil.toTelecomDisconnectCause(telephonyDisconnectCause,
-                    null, getPhone().getPhoneId()));
+            setTelephonyConnectionDisconnected(DisconnectCauseUtil.toTelecomDisconnectCause(
+                    telephonyDisconnectCause, null, getPhone().getPhoneId()));
         }
     }
 
@@ -325,7 +325,7 @@
     }
 
     @Override
-    protected void close() {
+    public void close() {
         mIsConnectionTimeReset = false;
         if (getPhone() != null) {
             getPhone().unregisterForLineControlInfo(mHandler);
diff --git a/src/com/android/services/telephony/ImsConference.java b/src/com/android/services/telephony/ImsConference.java
index 1969b10..17549cf 100644
--- a/src/com/android/services/telephony/ImsConference.java
+++ b/src/com/android/services/telephony/ImsConference.java
@@ -21,7 +21,6 @@
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.PersistableBundle;
-import android.telecom.Conference;
 import android.telecom.ConferenceParticipant;
 import android.telecom.Connection;
 import android.telecom.Connection.VideoProvider;
@@ -68,7 +67,7 @@
  * connection and is responsible for managing the conference participant connections which represent
  * the participants.
  */
-public class ImsConference extends Conference implements Holdable {
+public class ImsConference extends TelephonyConferenceBase implements Holdable {
 
     /**
      * Abstracts out fetching a feature flag.  Makes testing easier.
@@ -78,149 +77,127 @@
     }
 
     /**
-     * Listener used to respond to changes to conference participants.  At the conference level we
-     * are most concerned with handling destruction of a conference participant.
-     */
-    private final Connection.Listener mParticipantListener = new Connection.Listener() {
-        /**
-         * Participant has been destroyed.  Remove it from the conference.
-         *
-         * @param connection The participant which was destroyed.
-         */
-        @Override
-        public void onDestroyed(Connection connection) {
-            ConferenceParticipantConnection participant =
-                    (ConferenceParticipantConnection) connection;
-            removeConferenceParticipant(participant);
-            updateManageConference();
-        }
-
-    };
-
-    /**
      * Listener used to respond to changes to the underlying radio connection for the conference
      * host connection.  Used to respond to SRVCC changes.
      */
     private final TelephonyConnection.TelephonyConnectionListener mTelephonyConnectionListener =
             new TelephonyConnection.TelephonyConnectionListener() {
 
-        @Override
-        public void onOriginalConnectionConfigured(TelephonyConnection c) {
-            if (c == mConferenceHost) {
-               handleOriginalConnectionChange();
-            }
-        }
+                /**
+                 * Updates the state of the conference based on the new state of the host.
+                 *
+                 * @param c The host connection.
+                 * @param state The new state
+                 */
+                @Override
+                public void onStateChanged(android.telecom.Connection c, int state) {
+                    setState(state);
+                }
 
-        /**
-         * Handles changes to conference participant data as reported by the conference host
-         * connection.
-         *
-         * @param c The connection.
-         * @param participants The participant information.
-         */
-        @Override
-        public void onConferenceParticipantsChanged(android.telecom.Connection c,
-                List<ConferenceParticipant> participants) {
+                /**
+                 * Disconnects the conference when its host connection disconnects.
+                 *
+                 * @param c The host connection.
+                 * @param disconnectCause The host connection disconnect cause.
+                 */
+                @Override
+                public void onDisconnected(android.telecom.Connection c,
+                        DisconnectCause disconnectCause) {
+                    setDisconnected(disconnectCause);
+                }
 
-            if (c == null || participants == null) {
-                return;
-            }
-            Log.v(this, "onConferenceParticipantsChanged: %d participants", participants.size());
-            TelephonyConnection telephonyConnection = (TelephonyConnection) c;
-            handleConferenceParticipantsUpdate(telephonyConnection, participants);
-        }
-    };
+                @Override
+                public void onVideoStateChanged(android.telecom.Connection c, int videoState) {
+                    Log.d(this, "onVideoStateChanged video state %d", videoState);
+                    setVideoState(c, videoState);
+                }
 
-    /**
-     * Listener used to respond to changes to the connection to the IMS conference server.
-     */
-    private final android.telecom.Connection.Listener mConferenceHostListener =
-            new android.telecom.Connection.Listener() {
+                @Override
+                public void onVideoProviderChanged(android.telecom.Connection c,
+                        Connection.VideoProvider videoProvider) {
+                    Log.d(this, "onVideoProviderChanged: Connection: %s, VideoProvider: %s", c,
+                            videoProvider);
+                    setVideoProvider(c, videoProvider);
+                }
 
-        /**
-         * Updates the state of the conference based on the new state of the host.
-         *
-         * @param c The host connection.
-         * @param state The new state
-         */
-        @Override
-        public void onStateChanged(android.telecom.Connection c, int state) {
-            setState(state);
-        }
+                @Override
+                public void onConnectionCapabilitiesChanged(Connection c,
+                        int connectionCapabilities) {
+                    Log.d(this, "onConnectionCapabilitiesChanged: Connection: %s,"
+                            + " connectionCapabilities: %s", c, connectionCapabilities);
+                    int capabilites = ImsConference.this.getConnectionCapabilities();
+                    boolean isVideoConferencingSupported = mConferenceHost == null ? false :
+                            mConferenceHost.isCarrierVideoConferencingSupported();
+                    setConnectionCapabilities(
+                            applyHostCapabilities(capabilites, connectionCapabilities,
+                                    isVideoConferencingSupported));
+                }
 
-        /**
-         * Disconnects the conference when its host connection disconnects.
-         *
-         * @param c The host connection.
-         * @param disconnectCause The host connection disconnect cause.
-         */
-        @Override
-        public void onDisconnected(android.telecom.Connection c, DisconnectCause disconnectCause) {
-            setDisconnected(disconnectCause);
-        }
+                @Override
+                public void onConnectionPropertiesChanged(Connection c, int connectionProperties) {
+                    Log.d(this, "onConnectionPropertiesChanged: Connection: %s,"
+                            + " connectionProperties: %s", c, connectionProperties);
+                    int properties = ImsConference.this.getConnectionProperties();
+                    setConnectionProperties(applyHostProperties(properties, connectionProperties));
+                }
 
-        @Override
-        public void onVideoStateChanged(android.telecom.Connection c, int videoState) {
-            Log.d(this, "onVideoStateChanged video state %d", videoState);
-            setVideoState(c, videoState);
-        }
+                @Override
+                public void onStatusHintsChanged(Connection c, StatusHints statusHints) {
+                    Log.v(this, "onStatusHintsChanged");
+                    updateStatusHints();
+                }
 
-        @Override
-        public void onVideoProviderChanged(android.telecom.Connection c,
-                Connection.VideoProvider videoProvider) {
-            Log.d(this, "onVideoProviderChanged: Connection: %s, VideoProvider: %s", c,
-                    videoProvider);
-            setVideoProvider(c, videoProvider);
-        }
+                @Override
+                public void onExtrasChanged(Connection c, Bundle extras) {
+                    Log.v(this, "onExtrasChanged: c=" + c + " Extras=" + extras);
+                    putExtras(extras);
+                }
 
-        @Override
-        public void onConnectionCapabilitiesChanged(Connection c, int connectionCapabilities) {
-            Log.d(this, "onConnectionCapabilitiesChanged: Connection: %s," +
-                    " connectionCapabilities: %s", c, connectionCapabilities);
-            int capabilites = ImsConference.this.getConnectionCapabilities();
-            boolean isVideoConferencingSupported = mConferenceHost == null ? false :
-                    mConferenceHost.isCarrierVideoConferencingSupported();
-            setConnectionCapabilities(applyHostCapabilities(capabilites, connectionCapabilities,
-                    isVideoConferencingSupported));
-        }
+                @Override
+                public void onExtrasRemoved(Connection c, List<String> keys) {
+                    Log.v(this, "onExtrasRemoved: c=" + c + " key=" + keys);
+                    removeExtras(keys);
+                }
 
-        @Override
-        public void onConnectionPropertiesChanged(Connection c, int connectionProperties) {
-            Log.d(this, "onConnectionPropertiesChanged: Connection: %s," +
-                    " connectionProperties: %s", c, connectionProperties);
-            int properties = ImsConference.this.getConnectionProperties();
-            setConnectionProperties(applyHostProperties(properties, connectionProperties));
-        }
+                @Override
+                public void onConnectionEvent(Connection c, String event, Bundle extras) {
+                    if (Connection.EVENT_MERGE_START.equals(event)) {
+                        // Do not pass a merge start event on the underlying host connection; only
+                        // indicate a merge has started on the connections which are merged into a
+                        // conference.
+                        return;
+                    }
 
-        @Override
-        public void onStatusHintsChanged(Connection c, StatusHints statusHints) {
-            Log.v(this, "onStatusHintsChanged");
-            updateStatusHints();
-        }
+                    sendConnectionEvent(event, extras);
+                }
 
-        @Override
-        public void onExtrasChanged(Connection c, Bundle extras) {
-            Log.v(this, "onExtrasChanged: c=" + c + " Extras=" + extras);
-            putExtras(extras);
-        }
+                @Override
+                public void onOriginalConnectionConfigured(TelephonyConnection c) {
+                    if (c == mConferenceHost) {
+                        handleOriginalConnectionChange();
+                    }
+                }
 
-        @Override
-        public void onExtrasRemoved(Connection c, List<String> keys) {
-            Log.v(this, "onExtrasRemoved: c=" + c + " key=" + keys);
-            removeExtras(keys);
-        }
+                /**
+                 * Handles changes to conference participant data as reported by the conference host
+                 * connection.
+                 *
+                 * @param c The connection.
+                 * @param participants The participant information.
+                 */
+                @Override
+                public void onConferenceParticipantsChanged(android.telecom.Connection c,
+                        List<ConferenceParticipant> participants) {
 
-        @Override
-        public void onConnectionEvent(Connection c, String event, Bundle extras) {
-            if (Connection.EVENT_MERGE_START.equals(event)) {
-                // Do not pass a merge start event on the underlying host connection; we only
-                // indicate a merge has started on the connections which are merged into a
-                // conference.
-                return;
-            }
-            sendConnectionEvent(event, extras);
-        }
-    };
+                    if (c == null || participants == null) {
+                        return;
+                    }
+                    Log.v(this, "onConferenceParticipantsChanged: %d participants",
+                            participants.size());
+                    TelephonyConnection telephonyConnection = (TelephonyConnection) c;
+                    handleConferenceParticipantsUpdate(telephonyConnection, participants);
+                }
+            };
 
     /**
      * The telephony connection service; used to add new participant connections to Telecom.
@@ -447,7 +424,7 @@
      * Invoked when the Conference and all its {@link Connection}s should be disconnected.
      * <p>
      * Hangs up the call via the conference host connection.  When the host connection has been
-     * successfully disconnected, the {@link #mConferenceHostListener} listener receives an
+     * successfully disconnected, the {@link #mTelephonyConnectionListener} listener receives an
      * {@code onDestroyed} event, which triggers the conference participant connections to be
      * disconnected.
      */
@@ -702,7 +679,6 @@
             setConnectionTime(mConferenceHost.getConnectTimeMillis());
         }
 
-        mConferenceHost.addConnectionListener(mConferenceHostListener);
         mConferenceHost.addTelephonyConnectionListener(mTelephonyConnectionListener);
         setConnectionCapabilities(applyHostCapabilities(getConnectionCapabilities(),
                 mConferenceHost.getConnectionCapabilities(),
@@ -804,6 +780,13 @@
                                 "handleConferenceParticipantsUpdate: updateState, participant = %s",
                                 participant);
                         connection.updateState(participant.getState());
+                        if (participant.getState() == Connection.STATE_DISCONNECTED) {
+                            /**
+                             * Per {@link ConferenceParticipantConnection#updateState(int)}, we will
+                             * destroy the connection when its disconnected.
+                             */
+                            handleConnectionDestruction(connection);
+                        }
                         connection.setVideoState(parent.getVideoState());
                     }
                 }
@@ -817,6 +800,13 @@
                                         newParticipant.getHandle(),
                                         newParticipant.getEndpoint()));
                         connection.updateState(newParticipant.getState());
+                        /**
+                         * Per {@link ConferenceParticipantConnection#updateState(int)}, we will
+                         * destroy the connection when its disconnected.
+                         */
+                        if (newParticipant.getState() == Connection.STATE_DISCONNECTED) {
+                            handleConnectionDestruction(connection);
+                        }
                         connection.setVideoState(parent.getVideoState());
                     }
                 }
@@ -832,9 +822,8 @@
                     if (!participantUserEntities.contains(entry.getKey())) {
                         ConferenceParticipantConnection participant = entry.getValue();
                         participant.setDisconnected(new DisconnectCause(DisconnectCause.CANCELED));
-                        participant.removeConnectionListener(mParticipantListener);
                         mTelephonyConnectionService.removeConnection(participant);
-                        removeConnection(participant);
+                        removeTelephonyConnection(participant);
                         entryIterator.remove();
                         oldParticipantsRemoved = true;
                     }
@@ -951,9 +940,8 @@
             // again anyways.
             entry.setDisconnected(new DisconnectCause(DisconnectCause.CANCELED,
                     DisconnectCause.REASON_EMULATING_SINGLE_CALL));
-            entry.removeConnectionListener(mParticipantListener);
             mTelephonyConnectionService.removeConnection(entry);
-            removeConnection(entry);
+            removeTelephonyConnection(entry);
             valueIterator.remove();
         }
 
@@ -985,7 +973,6 @@
         ConferenceParticipantConnection connection = new ConferenceParticipantConnection(
                 parent.getOriginalConnection(), participant,
                 !isConferenceHost() /* isRemotelyHosted */);
-        connection.addConnectionListener(mParticipantListener);
         if (participant.getConnectTime() == 0) {
             connection.setConnectTimeMillis(parent.getConnectTimeMillis());
             connection.setConnectionStartElapsedRealTime(parent.getConnectElapsedTimeMillis());
@@ -1007,7 +994,7 @@
 
         mTelephonyConnectionService.addExistingConnection(mConferenceHostPhoneAccountHandle,
                 connection, this);
-        addConnection(connection);
+        addTelephonyConnection(connection);
     }
 
     /**
@@ -1018,7 +1005,6 @@
     private void removeConferenceParticipant(ConferenceParticipantConnection participant) {
         Log.i(this, "removeConferenceParticipant: %s", participant);
 
-        participant.removeConnectionListener(mParticipantListener);
         synchronized(mUpdateSyncRoot) {
             mConferenceParticipantConnections.remove(new Pair<>(participant.getUserEntity(),
                     participant.getEndpoint()));
@@ -1036,12 +1022,12 @@
             for (ConferenceParticipantConnection connection :
                     mConferenceParticipantConnections.values()) {
 
-                connection.removeConnectionListener(mParticipantListener);
                 // Mark disconnect cause as cancelled to ensure that the call is not logged in the
                 // call log.
                 connection.setDisconnected(new DisconnectCause(DisconnectCause.CANCELED));
                 mTelephonyConnectionService.removeConnection(connection);
                 connection.destroy();
+                handleConnectionDestruction(connection);
             }
             mConferenceParticipantConnections.clear();
         }
@@ -1154,7 +1140,7 @@
                         mConferenceHost.isOutgoingCall());
                 // This is a newly created conference connection as a result of SRVCC
                 c.setConferenceSupported(true);
-                c.setConnectionProperties(
+                c.setTelephonyConnectionProperties(
                         c.getConnectionProperties() | Connection.PROPERTY_IS_DOWNGRADED_CONFERENCE);
                 c.updateState();
                 // Copy the connect time from the conferenceHost
@@ -1163,12 +1149,11 @@
                 mTelephonyConnectionService.addExistingConnection(phoneAccountHandle, c);
                 mTelephonyConnectionService.addConnectionToConferenceController(c);
             } // CDMA case not applicable for SRVCC
-            mConferenceHost.removeConnectionListener(mConferenceHostListener);
             mConferenceHost.removeTelephonyConnectionListener(mTelephonyConnectionListener);
             mConferenceHost = null;
             setDisconnected(new DisconnectCause(DisconnectCause.OTHER));
             disconnectConferenceParticipants();
-            destroy();
+            destroyTelephonyConference();
         }
 
         updateStatusHints();
@@ -1207,7 +1192,7 @@
                 }
                 setDisconnected(disconnectCause);
                 disconnectConferenceParticipants();
-                destroy();
+                destroyTelephonyConference();
                 break;
             case Connection.STATE_ACTIVE:
                 setActive();
@@ -1338,4 +1323,15 @@
     public boolean isEmulatingSinglePartyCall() {
         return mIsEmulatingSinglePartyCall;
     }
+
+    /**
+     * Handles destruction of a {@link ConferenceParticipantConnection}.
+     * We remove the participant from the list of tracked participants in the conference and
+     * update whether the conference can be managed.
+     * @param participant the conference participant.
+     */
+    private void handleConnectionDestruction(ConferenceParticipantConnection participant) {
+        removeConferenceParticipant(participant);
+        updateManageConference();
+    }
 }
diff --git a/src/com/android/services/telephony/ImsConferenceController.java b/src/com/android/services/telephony/ImsConferenceController.java
index b193c7f..315aaad 100644
--- a/src/com/android/services/telephony/ImsConferenceController.java
+++ b/src/com/android/services/telephony/ImsConferenceController.java
@@ -66,13 +66,7 @@
             Log.v(this, "onConferenceSupportedChanged");
             recalculate();
         }
-    };
 
-    /**
-     * Ims conference controller connection listener.  Used to respond to changes in state of the
-     * Telephony connections the controller is aware of.
-     */
-    private final Connection.Listener mConnectionListener = new Connection.Listener() {
         @Override
         public void onStateChanged(Connection c, int state) {
             Log.v(this, "onStateChanged: %s", Log.pii(c.getAddress()));
@@ -151,7 +145,6 @@
         }
 
         mTelephonyConnections.add(connection);
-        connection.addConnectionListener(mConnectionListener);
         connection.addTelephonyConnectionListener(mTelephonyConnectionListener);
         recalculateConference();
     }
@@ -179,7 +172,6 @@
             Log.v(this, "remove connection: %s", connection);
         }
 
-        connection.removeConnectionListener(mConnectionListener);
         if (connection instanceof TelephonyConnection) {
             TelephonyConnection telephonyConnection = (TelephonyConnection) connection;
             telephonyConnection.removeTelephonyConnectionListener(mTelephonyConnectionListener);
@@ -395,13 +387,11 @@
         // Cleanup TelephonyConnection which backed the original connection and remove from telecom.
         // Use the "Other" disconnect cause to ensure the call is logged to the call log but the
         // disconnect tone is not played.
-        connection.removeConnectionListener(mConnectionListener);
         connection.removeTelephonyConnectionListener(mTelephonyConnectionListener);
-        connection.clearOriginalConnection();
-        connection.setDisconnected(new DisconnectCause(DisconnectCause.OTHER,
+        connection.setTelephonyConnectionDisconnected(new DisconnectCause(DisconnectCause.OTHER,
                 android.telephony.DisconnectCause.toString(
                         android.telephony.DisconnectCause.IMS_MERGED_SUCCESSFULLY)));
-        connection.destroy();
+        connection.close();
         mImsConferences.add(conference);
         // If one of the participants failed to join the conference, recalculate will set the
         // conferenceable connections for the conference to show merge calls option.
diff --git a/src/com/android/services/telephony/TelephonyConference.java b/src/com/android/services/telephony/TelephonyConference.java
index c66d6f2..afbe89d 100644
--- a/src/com/android/services/telephony/TelephonyConference.java
+++ b/src/com/android/services/telephony/TelephonyConference.java
@@ -16,7 +16,6 @@
 
 package com.android.services.telephony;
 
-import android.telecom.Conference;
 import android.telecom.Connection;
 import android.telecom.PhoneAccountHandle;
 
@@ -30,7 +29,7 @@
  * TelephonyConnection-based conference call for GSM conferences and IMS conferences (which may
  * be either GSM-based or CDMA-based).
  */
-public class TelephonyConference extends Conference implements Holdable {
+public class TelephonyConference extends TelephonyConferenceBase implements Holdable {
 
     private boolean mIsHoldable;
 
diff --git a/src/com/android/services/telephony/TelephonyConferenceBase.java b/src/com/android/services/telephony/TelephonyConferenceBase.java
new file mode 100644
index 0000000..31d0f56
--- /dev/null
+++ b/src/com/android/services/telephony/TelephonyConferenceBase.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2019 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 com.android.services.telephony;
+
+import android.annotation.NonNull;
+import android.telecom.Conference;
+import android.telecom.Connection;
+import android.telecom.PhoneAccountHandle;
+
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * Base class for the various Telephony {@link Conference} implementations ({@link CdmaConference},
+ * {@link TelephonyConference}, and {@link ImsConference}).  Adds some common listener code which
+ * all of these conferences use.
+ */
+public class TelephonyConferenceBase extends Conference {
+    /**
+     * Listener for conference events.
+     */
+    public abstract static class TelephonyConferenceListener {
+        /**
+         * Listener called when a connection is added or removed from a conference.
+         * @param connection The connection.
+         */
+        public void onConferenceMembershipChanged(Connection connection) {};
+    }
+
+    private final Set<TelephonyConferenceListener> mListeners = Collections.newSetFromMap(
+            new ConcurrentHashMap<>(8, 0.9f, 1));
+
+    /**
+     * Adds a listener to this conference.
+     * @param listener The listener.
+     */
+    public void addListener(@NonNull TelephonyConferenceListener listener) {
+        mListeners.add(listener);
+    }
+
+    /**
+     * Removes a listener from this conference.
+     * @param listener The listener.
+     */
+    public void removeListener(@NonNull TelephonyConferenceListener listener) {
+        mListeners.remove(listener);
+    }
+
+    /**
+     * Constructs a new Conference with a mandatory {@link PhoneAccountHandle}
+     *
+     * @param phoneAccount The {@code PhoneAccountHandle} associated with the conference.
+     */
+    public TelephonyConferenceBase(PhoneAccountHandle phoneAccount) {
+        super(phoneAccount);
+    }
+
+    /**
+     * Adds a connection to this {@link Conference}.
+     * <p>
+     * Should be used in place of {@link Conference#addConnection(Connection)} to ensure
+     * {@link TelephonyConferenceListener}s are informed of the change.
+     *
+     * @param connection The connection.
+     */
+    public void addTelephonyConnection(@NonNull Connection connection) {
+        addConnection(connection);
+        notifyConferenceMembershipChanged(connection);
+    }
+
+    /**
+     * Removes a {@link Connection} from this {@link Conference}.
+     * <p>
+     * Should be used instead of {@link Conference#removeConnection(Connection)} to ensure
+     * {@link TelephonyConferenceListener}s are notified of the change.
+     *
+     * @param connection The connection.
+     */
+    public void removeTelephonyConnection(@NonNull Connection connection) {
+        removeConnection(connection);
+        notifyConferenceMembershipChanged(connection);
+    }
+
+    /**
+     * Destroys the current {@link Conference} and notifies {@link TelephonyConferenceListener}s of
+     * the change to conference membership.
+     * <p>
+     * Should be used instead of {@link Conference#destroy()} to ensure telephony listeners are
+     * notified.
+     */
+    public void destroyTelephonyConference() {
+        // Conference#removeConnection modifies the list of participants, so we need to use an
+        // iterator here to ensure all participants are removed.
+        // Technically Conference#destroy does this, but we want to notify listeners of the state
+        // change so we'll do it here first.
+        Iterator<Connection> connectionIterator = getConnections().iterator();
+        while (connectionIterator.hasNext()) {
+            removeTelephonyConnection(connectionIterator.next());
+        }
+        destroy();
+    }
+
+    /**
+     * Notifies {@link TelephonyConferenceListener}s of a connection being added or removed from
+     * the conference.
+     * @param connection The conference.
+     */
+    private void notifyConferenceMembershipChanged(@NonNull Connection connection) {
+        for (TelephonyConferenceListener listener : mListeners) {
+            listener.onConferenceMembershipChanged(connection);
+        }
+    }
+}
diff --git a/src/com/android/services/telephony/TelephonyConferenceController.java b/src/com/android/services/telephony/TelephonyConferenceController.java
index e9eef46..95281b3 100644
--- a/src/com/android/services/telephony/TelephonyConferenceController.java
+++ b/src/com/android/services/telephony/TelephonyConferenceController.java
@@ -41,25 +41,26 @@
 final class TelephonyConferenceController {
     private static final int TELEPHONY_CONFERENCE_MAX_SIZE = 5;
 
-    private final Connection.Listener mConnectionListener = new Connection.Listener() {
-        @Override
-        public void onStateChanged(Connection c, int state) {
-            Log.v(this, "onStateChange triggered in Conf Controller : connection = "+ c
-                 + " state = " + state);
-            recalculate();
-        }
+    private final TelephonyConnection.TelephonyConnectionListener mTelephonyConnectionListener =
+            new TelephonyConnection.TelephonyConnectionListener() {
+                @Override
+                public void onStateChanged(Connection c, int state) {
+                    Log.v(this, "onStateChange triggered in Conf Controller : connection = " + c
+                            + " state = " + state);
+                    recalculate();
+                }
 
-        /** ${inheritDoc} */
-        @Override
-        public void onDisconnected(Connection c, DisconnectCause disconnectCause) {
-            recalculate();
-        }
+                @Override
+                public void onDisconnected(Connection c, DisconnectCause disconnectCause) {
+                    recalculate();
+                }
 
-        @Override
-        public void onDestroyed(Connection connection) {
-            remove(connection);
-        }
-    };
+                @Override
+                public void onDestroyed(Connection connection) {
+                    // Only TelephonyConnections are added.
+                    remove((TelephonyConnection) connection);
+                }
+            };
 
     /** The known connections. */
     private final List<TelephonyConnection> mTelephonyConnections = new ArrayList<>();
@@ -85,18 +86,18 @@
             return;
         }
         mTelephonyConnections.add(connection);
-        connection.addConnectionListener(mConnectionListener);
+        connection.addTelephonyConnectionListener(mTelephonyConnectionListener);
         recalculate();
     }
 
-    void remove(Connection connection) {
+    void remove(TelephonyConnection connection) {
         if (!mTelephonyConnections.contains(connection)) {
             // Debug only since TelephonyConnectionService tries to clean up the connections tracked
             // when the original connection changes.  It does this proactively.
             Log.d(this, "remove - connection not tracked; connection=%s", connection);
             return;
         }
-        connection.removeConnectionListener(mConnectionListener);
+        connection.removeTelephonyConnectionListener(mTelephonyConnectionListener);
         mTelephonyConnections.remove(connection);
         recalculate();
     }
@@ -219,7 +220,7 @@
             // No more connections are conferenced, destroy any existing conference.
             if (mTelephonyConference != null) {
                 Log.d(this, "with a conference to destroy!");
-                mTelephonyConference.destroy();
+                mTelephonyConference.destroyTelephonyConference();
                 mTelephonyConference = null;
             }
         } else {
@@ -229,7 +230,7 @@
                 for (Connection connection : existingConnections) {
                     if (connection instanceof TelephonyConnection &&
                             !conferencedConnections.contains(connection)) {
-                        mTelephonyConference.removeConnection(connection);
+                        mTelephonyConference.removeTelephonyConnection(connection);
                     }
                 }
                 if (allConnInService) {
@@ -237,7 +238,7 @@
                     // Add any new ones
                     for (Connection connection : conferencedConnections) {
                         if (!existingConnections.contains(connection)) {
-                            mTelephonyConference.addConnection(connection);
+                            mTelephonyConference.addTelephonyConnection(connection);
                         }
                     }
                 } else {
@@ -262,7 +263,7 @@
                     for (Connection connection : conferencedConnections) {
                         Log.d(this, "Adding a connection to a conference call: %s %s",
                                 mTelephonyConference, connection);
-                        mTelephonyConference.addConnection(connection);
+                        mTelephonyConference.addTelephonyConnection(connection);
                     }
                     mTelephonyConference.updateCallRadioTechAfterCreation();
                     mConnectionService.addConference(mTelephonyConference);
diff --git a/src/com/android/services/telephony/TelephonyConnection.java b/src/com/android/services/telephony/TelephonyConnection.java
index 7f846a9..c1d2995 100644
--- a/src/com/android/services/telephony/TelephonyConnection.java
+++ b/src/com/android/services/telephony/TelephonyConnection.java
@@ -16,6 +16,8 @@
 
 package com.android.services.telephony;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.content.Context;
 import android.graphics.drawable.Icon;
 import android.net.Uri;
@@ -186,7 +188,7 @@
 
                 case MSG_SET_VIDEO_STATE:
                     int videoState = (int) msg.obj;
-                    setVideoState(videoState);
+                    setTelephonyVideoState(videoState);
 
                     // A change to the video state of the call can influence whether or not it
                     // can be part of a conference, whether another call can be added, and
@@ -198,7 +200,7 @@
 
                 case MSG_SET_VIDEO_PROVIDER:
                     VideoProvider videoProvider = (VideoProvider) msg.obj;
-                    setVideoProvider(videoProvider);
+                    setTelephonyVideoProvider(videoProvider);
                     break;
 
                 case MSG_SET_AUDIO_QUALITY:
@@ -238,9 +240,9 @@
                         // If starting the hold tone, send a connection event to Telecom which will
                         // cause it to play the on hold tone.
                         if (playTone) {
-                            sendConnectionEvent(EVENT_ON_HOLD_TONE_START, null);
+                            sendTelephonyConnectionEvent(EVENT_ON_HOLD_TONE_START, null);
                         } else {
-                            sendConnectionEvent(EVENT_ON_HOLD_TONE_END, null);
+                            sendTelephonyConnectionEvent(EVENT_ON_HOLD_TONE_END, null);
                         }
                     }
                     break;
@@ -285,7 +287,7 @@
                 case MSG_ON_CONNECTION_EVENT:
                     SomeArgs args = (SomeArgs) msg.obj;
                     try {
-                        sendConnectionEvent((String) args.arg1, (Bundle) args.arg2);
+                        sendTelephonyConnectionEvent((String) args.arg1, (Bundle) args.arg2);
 
                     } finally {
                         args.recycle();
@@ -304,7 +306,7 @@
                 ssn.code);
         if (ssn.notificationType == SuppServiceNotification.NOTIFICATION_TYPE_CODE_1
                 && ssn.code == SuppServiceNotification.CODE_1_CALL_FORWARDED) {
-            sendConnectionEvent(TelephonyManager.EVENT_CALL_FORWARDED, null);
+            sendTelephonyConnectionEvent(TelephonyManager.EVENT_CALL_FORWARDED, null);
         }
         sendSuppServiceNotificationEvent(ssn.notificationType, ssn.code);
     }
@@ -322,7 +324,8 @@
         extras.putInt(TelephonyManager.EXTRA_NOTIFICATION_CODE, code);
         extras.putCharSequence(TelephonyManager.EXTRA_NOTIFICATION_MESSAGE,
                 getSuppServiceMessage(type, code));
-        sendConnectionEvent(TelephonyManager.EVENT_SUPPLEMENTARY_SERVICE_NOTIFICATION, extras);
+        sendTelephonyConnectionEvent(TelephonyManager.EVENT_SUPPLEMENTARY_SERVICE_NOTIFICATION,
+                extras);
     }
 
     /**
@@ -423,6 +426,20 @@
                 List<ConferenceParticipant> participants) {}
         public void onConferenceStarted() {}
         public void onConferenceSupportedChanged(Connection c, boolean isConferenceSupported) {}
+
+        public void onConnectionCapabilitiesChanged(Connection c, int connectionCapabilities) {}
+        public void onConnectionEvent(Connection c, String event, Bundle extras) {}
+        public void onConnectionPropertiesChanged(Connection c, int connectionProperties) {}
+        public void onExtrasChanged(Connection c, Bundle extras) {}
+        public void onExtrasRemoved(Connection c, List<String> keys) {}
+        public void onStateChanged(android.telecom.Connection c, int state) {}
+        public void onStatusHintsChanged(Connection c, StatusHints statusHints) {}
+        public void onDestroyed(Connection c) {}
+        public void onDisconnected(android.telecom.Connection c,
+                android.telecom.DisconnectCause disconnectCause) {}
+        public void onVideoProviderChanged(android.telecom.Connection c,
+                Connection.VideoProvider videoProvider) {}
+        public void onVideoStateChanged(android.telecom.Connection c, int videoState) {}
     }
 
     private final PostDialListener mPostDialListener = new PostDialListener() {
@@ -556,7 +573,7 @@
 
             // Inform the InCallService of the fact that the call pull failed (it may choose to
             // display a message informing the user of the pull failure).
-            sendConnectionEvent(Connection.EVENT_CALL_PULL_FAILED, null);
+            sendTelephonyConnectionEvent(Connection.EVENT_CALL_PULL_FAILED, null);
 
             // Swap the ImsPhoneConnection we used to do the pull for the ImsExternalConnection
             // which originally represented the call.
@@ -571,7 +588,7 @@
          */
         @Override
         public void onHandoverToWifiFailed() {
-            sendConnectionEvent(TelephonyManager.EVENT_HANDOVER_TO_WIFI_FAILED, null);
+            sendTelephonyConnectionEvent(TelephonyManager.EVENT_HANDOVER_TO_WIFI_FAILED, null);
         }
 
         /**
@@ -1086,6 +1103,7 @@
 
         if (getConnectionCapabilities() != newCapabilities) {
             setConnectionCapabilities(newCapabilities);
+            notifyConnectionCapabilitiesChanged(newCapabilities);
         }
     }
 
@@ -1122,10 +1140,15 @@
                 isNetworkIdentifiedEmergencyCall());
 
         if (getConnectionProperties() != newProperties) {
-            setConnectionProperties(newProperties);
+            setTelephonyConnectionProperties(newProperties);
         }
     }
 
+    public void setTelephonyConnectionProperties(int newProperties) {
+        setConnectionProperties(newProperties);
+        notifyConnectionPropertiesChanged(newProperties);
+    }
+
     protected final void updateAddress() {
         updateConnectionCapabilities();
         updateConnectionProperties();
@@ -1200,11 +1223,11 @@
         mOriginalConnection.addListener(mOriginalConnectionListener);
 
         // Set video state and capabilities
-        setVideoState(mOriginalConnection.getVideoState());
+        setTelephonyVideoState(mOriginalConnection.getVideoState());
         setOriginalConnectionCapabilities(mOriginalConnection.getConnectionCapabilities());
         setIsNetworkIdentifiedEmergencyCall(mOriginalConnection.isNetworkIdentifiedEmergencyCall());
         setAudioModeIsVoip(mOriginalConnection.getAudioModeIsVoip());
-        setVideoProvider(mOriginalConnection.getVideoProvider());
+        setTelephonyVideoProvider(mOriginalConnection.getVideoProvider());
         setAudioQuality(mOriginalConnection.getAudioQuality());
         setTechnologyTypeExtra();
 
@@ -1239,8 +1262,8 @@
         } else {
             extrasToRemove.add(Connection.EXTRA_DISABLE_ADD_CALL);
         }
-        putExtras(extrasToPut);
-        removeExtras(extrasToRemove);
+        putTelephonyExtras(extrasToPut);
+        removeTelephonyExtras(extrasToRemove);
 
         // updateState can set mOriginalConnection to null if its state is DISCONNECTED, so this
         // should be executed *after* the above setters have run.
@@ -1290,7 +1313,7 @@
                 newExtras = new Bundle();
             }
             newExtras.putInt(TelecomManager.EXTRA_CALL_TECHNOLOGY_TYPE, getPhone().getPhoneType());
-            putExtras(newExtras);
+            putTelephonyExtras(newExtras);
         }
     }
 
@@ -1301,7 +1324,7 @@
                 newExtras = new Bundle();
             }
             newExtras.putBoolean(Connection.EXTRA_DISABLE_ADD_CALL, true);
-            putExtras(newExtras);
+            putTelephonyExtras(newExtras);
         } else {
             removeExtras(Connection.EXTRA_DISABLE_ADD_CALL);
         }
@@ -1483,7 +1506,7 @@
                 // There are a few cases where mOriginalConnection has not been set yet. For
                 // example, when the radio has to be turned on to make an emergency call,
                 // mOriginalConnection could not be set for many seconds.
-                setDisconnected(DisconnectCauseUtil.toTelecomDisconnectCause(
+                setTelephonyConnectionDisconnected(DisconnectCauseUtil.toTelecomDisconnectCause(
                         android.telephony.DisconnectCause.LOCAL,
                         "Local Disconnect before connection established."));
                 close();
@@ -1604,7 +1627,7 @@
                     }
 
                     // Ensure extras are propagated to Telecom.
-                    putExtras(mOriginalConnectionExtras);
+                    putTelephonyExtras(mOriginalConnectionExtras);
                 } else {
                     Log.d(this, "Extras update not required");
                 }
@@ -1673,19 +1696,19 @@
                     setActiveInternal();
                     break;
                 case HOLDING:
-                    setOnHold();
+                    setTelephonyConnectionOnHold();
                     break;
                 case DIALING:
                 case ALERTING:
                     if (mOriginalConnection != null && mOriginalConnection.isPulledCall()) {
-                        setPulling();
+                        setTelephonyConnectionPulling();
                     } else {
-                        setDialing();
+                        setTelephonyConnectionDialing();
                     }
                     break;
                 case INCOMING:
                 case WAITING:
-                    setRinging();
+                    setTelephonyConnectionRinging();
                     break;
                 case DISCONNECTED:
                     if (shouldTreatAsEmergencyCall()
@@ -1706,11 +1729,12 @@
                             preciseDisconnectCause =
                                     mOriginalConnection.getPreciseDisconnectCause();
                         }
-                        setDisconnected(DisconnectCauseUtil.toTelecomDisconnectCause(
-                                mOriginalConnection.getDisconnectCause(),
-                                preciseDisconnectCause,
-                                mOriginalConnection.getVendorDisconnectCause(),
-                                getPhone().getPhoneId()));
+                        setTelephonyConnectionDisconnected(
+                                DisconnectCauseUtil.toTelecomDisconnectCause(
+                                        mOriginalConnection.getDisconnectCause(),
+                                        preciseDisconnectCause,
+                                        mOriginalConnection.getVendorDisconnectCause(),
+                                        getPhone().getPhoneId()));
                         close();
                     }
                     break;
@@ -1800,13 +1824,14 @@
                 }
             }
         }
-        setActive();
+        setTelephonyConnectionActive();
     }
 
-    protected void close() {
+    public void close() {
         Log.v(this, "close");
         clearOriginalConnection();
         destroy();
+        notifyDestroyed();
     }
 
     /**
@@ -2157,13 +2182,13 @@
                     : R.string.status_hint_label_wifi_call;
 
             Context context = getPhone().getContext();
-            setStatusHints(new StatusHints(
+            setTelephonyStatusHints(new StatusHints(
                     context.getString(labelId),
                     Icon.createWithResource(
                             context, R.drawable.ic_signal_wifi_4_bar_24dp),
                     null /* extras */));
         } else {
-            setStatusHints(null);
+            setTelephonyStatusHints(null);
         }
     }
 
@@ -2391,12 +2416,153 @@
     }
 
     /**
-     * Notifies listeners of a change to conference participant(s).
+     * Set this {@link TelephonyConnection} to an active state.
+     * <p>
+     * Note: This should be used instead of {@link #setActive()} to ensure listeners are notified.
+     */
+    public void setTelephonyConnectionActive() {
+        setActive();
+        notifyStateChanged(getState());
+    }
+
+    /**
+     * Set this {@link TelephonyConnection} to a ringing state.
+     * <p>
+     * Note: This should be used instead of {@link #setRinging()} to ensure listeners are notified.
+     */
+    public void setTelephonyConnectionRinging() {
+        setRinging();
+        notifyStateChanged(getState());
+    }
+
+    /**
+     * Set this {@link TelephonyConnection} to an initializing state.
+     * <p>
+     * Note: This should be used instead of {@link #setInitializing()} to ensure listeners are
+     * notified.
+     */
+    public void setTelephonyConnectionInitializing() {
+        setInitializing();
+        notifyStateChanged(getState());
+    }
+
+    /**
+     * Set this {@link TelephonyConnection} to a dialing state.
+     * <p>
+     * Note: This should be used instead of {@link #setDialing()} to ensure listeners are notified.
+     */
+    public void setTelephonyConnectionDialing() {
+        setDialing();
+        notifyStateChanged(getState());
+    }
+
+    /**
+     * Set this {@link TelephonyConnection} to a pulling state.
+     * <p>
+     * Note: This should be used instead of {@link #setPulling()} to ensure listeners are notified.
+     */
+    public void setTelephonyConnectionPulling() {
+        setPulling();
+        notifyStateChanged(getState());
+    }
+
+    /**
+     * Set this {@link TelephonyConnection} to a held state.
+     * <p>
+     * Note: This should be used instead of {@link #setOnHold()} to ensure listeners are notified.
+     */
+    public void setTelephonyConnectionOnHold() {
+        setOnHold();
+        notifyStateChanged(getState());
+    }
+
+    /**
+     * Set this {@link TelephonyConnection} to a held state.
+     * <p>
+     * Note: This should be used instead of
+     * {@link #setDisconnected(android.telecom.DisconnectCause)} to ensure listeners are notified.
+     */
+    public void setTelephonyConnectionDisconnected(@NonNull
+            android.telecom.DisconnectCause disconnectCause) {
+        setDisconnected(disconnectCause);
+        notifyDisconnected(disconnectCause);
+        notifyStateChanged(getState());
+    }
+
+    /**
+     * Sends a connection event for this {@link TelephonyConnection}.
+     * <p>
+     * Note: This should be used instead of {@link #sendConnectionEvent(String, Bundle)} to ensure
+     * listeners are notified.
+     */
+    public void sendTelephonyConnectionEvent(@NonNull String event, @Nullable Bundle extras) {
+        sendConnectionEvent(event, extras);
+        notifyTelephonyConnectionEvent(event, extras);
+    }
+
+    /**
+     * Sets the extras associated with this {@link TelephonyConnection}.
+     * <p>
+     * Note: This should be used instead of {@link #putExtras(Bundle)} to ensure listeners are
+     * notified.
+     */
+    public void putTelephonyExtras(@NonNull Bundle extras) {
+        putExtras(extras);
+        notifyPutExtras(extras);
+    }
+
+    /**
+     * Removes the specified extras associated with this {@link TelephonyConnection}.
+     * <p>
+     * Note: This should be used instead of {@link #removeExtras(String...)} to ensure listeners are
+     * notified.
+     */
+    public void removeTelephonyExtras(@NonNull List<String> keys) {
+        removeExtras(keys);
+        notifyRemoveExtras(keys);
+    }
+
+    /**
+     * Sets the video state associated with this {@link TelephonyConnection}.
+     * <p>
+     * Note: This should be used instead of {@link #setVideoState(int)} to ensure listeners are
+     * notified.
+     */
+    public void setTelephonyVideoState(@VideoProfile.VideoState int videoState) {
+        setVideoState(videoState);
+        notifyVideoStateChanged(videoState);
+    }
+
+    /**
+     * Sets the video provider associated with this {@link TelephonyConnection}.
+     * <p>
+     * Note: This should be used instead of {@link #setVideoProvider(VideoProvider)} to ensure
+     * listeners are notified.
+     */
+    public void setTelephonyVideoProvider(@Nullable VideoProvider videoProvider) {
+        setVideoProvider(videoProvider);
+        notifyVideoProviderChanged(videoProvider);
+    }
+
+    /**
+     * Sets the status hints associated with this {@link TelephonyConnection}.
+     * <p>
+     * Note: This should be used instead of {@link #setStatusHints(StatusHints)} to ensure listeners
+     * are notified.
+     */
+    public void setTelephonyStatusHints(@Nullable StatusHints statusHints) {
+        setStatusHints(statusHints);
+        notifyStatusHintsChanged(statusHints);
+    }
+
+    /**
+     * Notifies {@link TelephonyConnectionListener}s of a change to conference participant data
+     * received via the {@link ImsConference} (i.e. conference event package).
      *
      * @param conferenceParticipants The participants.
      */
-    protected final void updateConferenceParticipants(
-            List<ConferenceParticipant> conferenceParticipants) {
+    private void updateConferenceParticipants(
+            @NonNull List<ConferenceParticipant> conferenceParticipants) {
         for (TelephonyConnectionListener l : mTelephonyListeners) {
             l.onConferenceParticipantsChanged(this, conferenceParticipants);
         }
@@ -2405,7 +2571,6 @@
     /**
      * Called by a {@link ConnectionService} to notify Telecom that a {@link Conference#onMerge()}
      * operation has started.
-     * <p>
      */
     protected void notifyConferenceStarted() {
         for (TelephonyConnectionListener l : mTelephonyListeners) {
@@ -2414,14 +2579,126 @@
     }
 
     /**
-     * Notifies listeners when a change has occurred to the Connection which impacts its ability to
-     * be a part of a conference call.
+     * Notifies {@link TelephonyConnectionListener}s when a change has occurred to the Connection
+     * which impacts its ability to be a part of a conference call.
      * @param isConferenceSupported {@code true} if the connection supports being part of a
      *      conference call, {@code false} otherwise.
      */
-    protected void notifyConferenceSupportedChanged(boolean isConferenceSupported) {
+    private void notifyConferenceSupportedChanged(boolean isConferenceSupported) {
         for (TelephonyConnectionListener l : mTelephonyListeners) {
             l.onConferenceSupportedChanged(this, isConferenceSupported);
         }
     }
+
+    /**
+     * Notifies {@link TelephonyConnectionListener}s of changes to the connection capabilities.
+     * @param newCapabilities the new capabilities.
+     */
+    private void notifyConnectionCapabilitiesChanged(int newCapabilities) {
+        for (TelephonyConnectionListener listener : mTelephonyListeners) {
+            listener.onConnectionCapabilitiesChanged(this, newCapabilities);
+        }
+    }
+
+    /**
+     * Notifies {@link TelephonyConnectionListener}s of changes to the connection properties.
+     * @param newProperties the new properties.
+     */
+    private void notifyConnectionPropertiesChanged(int newProperties) {
+        for (TelephonyConnectionListener listener : mTelephonyListeners) {
+            listener.onConnectionPropertiesChanged(this, newProperties);
+        }
+    }
+
+    /**
+     * Notifies {@link TelephonyConnectionListener}s when a connection is destroyed.
+     */
+    private void notifyDestroyed() {
+        for (TelephonyConnectionListener listener : mTelephonyListeners) {
+            listener.onDestroyed(this);
+        }
+    }
+
+    /**
+     * Notifies {@link TelephonyConnectionListener}s when a connection disconnects.
+     * @param cause The disconnect cause.
+     */
+    private void notifyDisconnected(android.telecom.DisconnectCause cause) {
+        for (TelephonyConnectionListener listener : mTelephonyListeners) {
+            listener.onDisconnected(this, cause);
+        }
+    }
+
+    /**
+     * Notifies {@link TelephonyConnectionListener}s of connection state changes.
+     * @param newState The new state.
+     */
+    private void notifyStateChanged(int newState) {
+        for (TelephonyConnectionListener listener : mTelephonyListeners) {
+            listener.onStateChanged(this, newState);
+        }
+    }
+
+    /**
+     * Notifies {@link TelephonyConnectionListener}s of telephony connection events.
+     * @param event The event.
+     * @param extras Any extras.
+     */
+    private void notifyTelephonyConnectionEvent(String event, Bundle extras) {
+        for (TelephonyConnectionListener listener : mTelephonyListeners) {
+            listener.onConnectionEvent(this, event, extras);
+        }
+    }
+
+    /**
+     * Notifies {@link TelephonyConnectionListener}s when extras are added to the connection.
+     * @param extras The new extras.
+     */
+    private void notifyPutExtras(Bundle extras) {
+        for (TelephonyConnectionListener listener : mTelephonyListeners) {
+            listener.onExtrasChanged(this, extras);
+        }
+    }
+
+    /**
+     * Notifies {@link TelephonyConnectionListener}s when extra keys are removed from a connection.
+     * @param keys The removed keys.
+     */
+    private void notifyRemoveExtras(List<String> keys) {
+        for (TelephonyConnectionListener listener : mTelephonyListeners) {
+            listener.onExtrasRemoved(this, keys);
+        }
+    }
+
+    /**
+     * Notifies {@link TelephonyConnectionListener}s of a change to the video state of a connection.
+     * @param videoState The new video state.
+     */
+    private void notifyVideoStateChanged(@VideoProfile.VideoState int videoState) {
+        for (TelephonyConnectionListener listener : mTelephonyListeners) {
+            listener.onVideoStateChanged(this, videoState);
+        }
+    }
+
+    /**
+     * Notifies {@link TelephonyConnectionListener}s of changes to the video provider for a
+     * connection.
+     * @param videoProvider The new video provider.
+     */
+    private void notifyVideoProviderChanged(VideoProvider videoProvider) {
+        for (TelephonyConnectionListener listener : mTelephonyListeners) {
+            listener.onVideoProviderChanged(this, videoProvider);
+        }
+    }
+
+    /**
+     * Notifies {@link TelephonyConnectionListener}s of changes to the status hints for a
+     * connection.
+     * @param statusHints The new status hints.
+     */
+    private void notifyStatusHintsChanged(StatusHints statusHints) {
+        for (TelephonyConnectionListener listener : mTelephonyListeners) {
+            listener.onStatusHintsChanged(this, statusHints);
+        }
+    }
 }
diff --git a/src/com/android/services/telephony/TelephonyConnectionService.java b/src/com/android/services/telephony/TelephonyConnectionService.java
index 5e3a899..7b55bfc 100644
--- a/src/com/android/services/telephony/TelephonyConnectionService.java
+++ b/src/com/android/services/telephony/TelephonyConnectionService.java
@@ -103,11 +103,11 @@
         }
         @Override
         public void addConference(TelephonyConference mTelephonyConference) {
-            TelephonyConnectionService.this.addConference(mTelephonyConference);
+            TelephonyConnectionService.this.addTelephonyConference(mTelephonyConference);
         }
         @Override
         public void addConference(ImsConference mImsConference) {
-            TelephonyConnectionService.this.addConference(mImsConference);
+            TelephonyConnectionService.this.addTelephonyConference(mImsConference);
         }
         @Override
         public void removeConnection(Connection connection) {
@@ -129,11 +129,9 @@
         public void addConnectionToConferenceController(TelephonyConnection connection) {
             TelephonyConnectionService.this.addConnectionToConferenceController(connection);
         }
-    };
 
-    private final Connection.Listener mConnectionListener = new Connection.Listener() {
         @Override
-        public void onConferenceChanged(Connection connection, Conference conference) {
+        public void onConferenceMembershipChanged(Connection connection) {
             mHoldTracker.updateHoldCapability(connection.getPhoneAccountHandle());
         }
     };
@@ -320,6 +318,14 @@
         }
     };
 
+    private final TelephonyConferenceBase.TelephonyConferenceListener mTelephonyConferenceListener =
+            new TelephonyConferenceBase.TelephonyConferenceListener() {
+        @Override
+        public void onConferenceMembershipChanged(Connection connection) {
+            mHoldTracker.updateHoldCapability(connection.getPhoneAccountHandle());
+        }
+    };
+
     @Override
     public void onCreate() {
         super.onCreate();
@@ -628,11 +634,10 @@
 
         } else {
             Log.w(this, "onCreateOutgoingConnection, failed to turn on radio");
-            originalConnection.setDisconnected(
+            closeOrDestroyConnection(originalConnection,
                     DisconnectCauseUtil.toTelecomDisconnectCause(
                             android.telephony.DisconnectCause.POWER_OFF,
                             "Failed to turn on radio."));
-            originalConnection.destroy();
         }
     }
 
@@ -658,12 +663,11 @@
             addExistingConnection(PhoneUtils.makePstnPhoneAccountHandleWithPrefix(
                     phone, "", isEmergencyNumber && noActiveSimCard), repConnection);
             // Remove the old connection from Telecom after.
-            connectionToEvaluate.setDisconnected(
+            closeOrDestroyConnection(connectionToEvaluate,
                     DisconnectCauseUtil.toTelecomDisconnectCause(
                             android.telephony.DisconnectCause.OUTGOING_CANCELED,
                             "Reconnecting outgoing Emergency Call.",
                             phone.getPhoneId()));
-            connectionToEvaluate.destroy();
         } else {
             placeOutgoingConnection((TelephonyConnection) connectionToEvaluate, phone, request);
         }
@@ -829,8 +833,8 @@
                             phone.getPhoneId()));
         }
         connection.setAddress(handle, PhoneConstants.PRESENTATION_ALLOWED);
-        connection.setInitializing();
-        connection.setVideoState(request.getVideoState());
+        connection.setTelephonyConnectionInitializing();
+        connection.setTelephonyVideoState(request.getVideoState());
         connection.setRttTextStream(request.getRttTextStream());
         connection.setTtyEnabled(isTtyModeEnabled);
         return connection;
@@ -1087,7 +1091,6 @@
     @Override
     public void onConnectionAdded(Connection connection) {
         if (connection instanceof Holdable && !isExternalConnection(connection)) {
-            connection.addConnectionListener(mConnectionListener);
             mHoldTracker.addHoldable(
                     connection.getPhoneAccountHandle(), (Holdable) connection);
         }
@@ -1218,9 +1221,7 @@
         } else {
             // We have run out of Phones to use. Disconnect the call and destroy the connection.
             Log.i(this, "retryOutgoingOriginalConnection, no more Phones to use. Disconnecting.");
-            c.setDisconnected(new DisconnectCause(DisconnectCause.ERROR));
-            c.clearOriginalConnection();
-            c.destroy();
+            closeOrDestroyConnection(c, new DisconnectCause(DisconnectCause.ERROR));
         }
     }
 
@@ -1282,10 +1283,10 @@
                     cause = android.telephony.DisconnectCause.OTASP_PROVISIONING_IN_PROCESS;
                     break;
             }
-            connection.setDisconnected(DisconnectCauseUtil.toTelecomDisconnectCause(
-                    cause, e.getMessage(), phone.getPhoneId()));
-            connection.clearOriginalConnection();
-            connection.destroy();
+            connection.setTelephonyConnectionDisconnected(
+                    DisconnectCauseUtil.toTelecomDisconnectCause(cause, e.getMessage(),
+                            phone.getPhoneId()));
+            connection.close();
             return;
         }
 
@@ -1306,10 +1307,10 @@
                 startActivity(intent);
             }
             Log.d(this, "placeOutgoingConnection, phone.dial returned null");
-            connection.setDisconnected(DisconnectCauseUtil.toTelecomDisconnectCause(
-                    telephonyDisconnectCause, "Connection is null", phone.getPhoneId()));
-            connection.clearOriginalConnection();
-            connection.destroy();
+            connection.setTelephonyConnectionDisconnected(
+                    DisconnectCauseUtil.toTelecomDisconnectCause(telephonyDisconnectCause,
+                            "Connection is null", phone.getPhoneId()));
+            connection.close();
         } else {
             connection.setOriginalConnection(originalConnection);
         }
@@ -1824,7 +1825,7 @@
                 // are potentially placing an international call on WFC.
                 Log.i(this, "placeOutgoingConnection - sending international call on WFC " +
                         "confirmation event");
-                telephonyConnection.sendConnectionEvent(
+                telephonyConnection.sendTelephonyConnectionEvent(
                         TelephonyManager.EVENT_NOTIFY_INTERNATIONAL_CALL_ON_WFC, null);
             }
         }
@@ -1840,4 +1841,26 @@
             }
         }
     }
+
+    private void closeOrDestroyConnection(Connection connection, DisconnectCause cause) {
+        if (connection instanceof TelephonyConnection) {
+            TelephonyConnection telephonyConnection = (TelephonyConnection) connection;
+            telephonyConnection.setTelephonyConnectionDisconnected(cause);
+            // Close destroys the connection and notifies TelephonyConnection listeners.
+            telephonyConnection.close();
+        } else {
+            connection.setDisconnected(cause);
+            connection.destroy();
+        }
+    }
+
+    /**
+     * Adds a {@link Conference} to the telephony ConnectionService and registers a listener for
+     * changes to the conference.  Should be used instead of {@link #addConference(Conference)}.
+     * @param conference The conference.
+     */
+    public void addTelephonyConference(@NonNull TelephonyConferenceBase conference) {
+        addConference(conference);
+        conference.addListener(mTelephonyConferenceListener);
+    }
 }
diff --git a/src/com/android/services/telephony/TelephonyConnectionServiceProxy.java b/src/com/android/services/telephony/TelephonyConnectionServiceProxy.java
index 1ced34a..604cf03 100644
--- a/src/com/android/services/telephony/TelephonyConnectionServiceProxy.java
+++ b/src/com/android/services/telephony/TelephonyConnectionServiceProxy.java
@@ -36,4 +36,10 @@
     void addExistingConnection(PhoneAccountHandle phoneAccountHandle,
                                Connection connection, Conference conference);
     void addConnectionToConferenceController(TelephonyConnection connection);
+
+    /**
+     * Called when a connection's conference membership changes.
+     * @param connection The connection.
+     */
+    void onConferenceMembershipChanged(Connection connection);
 }
diff --git a/tests/src/com/android/services/telephony/ImsConferenceControllerTest.java b/tests/src/com/android/services/telephony/ImsConferenceControllerTest.java
index aa832aa..edfc34f 100644
--- a/tests/src/com/android/services/telephony/ImsConferenceControllerTest.java
+++ b/tests/src/com/android/services/telephony/ImsConferenceControllerTest.java
@@ -16,14 +16,13 @@
 
 package com.android.services.telephony;
 
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
-import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.times;
-
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.assertFalse;
 
 import android.os.Looper;
 import android.test.suitebuilder.annotation.SmallTest;
@@ -32,7 +31,6 @@
 
 import org.junit.Before;
 import org.junit.Test;
-
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
@@ -85,7 +83,7 @@
         mControllerTest.add(mTestTelephonyConnectionA);
 
         mTestTelephonyConnectionA.setActive();
-        mTestTelephonyConnectionB.setOnHold();
+        mTestTelephonyConnectionB.setTelephonyConnectionOnHold();
 
         assertTrue(mTestTelephonyConnectionA.getConferenceables()
                 .contains(mTestTelephonyConnectionB));
@@ -125,10 +123,9 @@
         mControllerTest.add(mTestTelephonyConnectionA);
 
         mTestTelephonyConnectionA.setActive();
-        mTestTelephonyConnectionB.setOnHold();
+        mTestTelephonyConnectionB.setTelephonyConnectionOnHold();
 
         verify(mMockTelephonyConnectionServiceProxy, times(2))
                 .addConference(any(ImsConference.class));
-
     }
 }
diff --git a/tests/src/com/android/services/telephony/TelephonyConferenceControllerTest.java b/tests/src/com/android/services/telephony/TelephonyConferenceControllerTest.java
index 275bcc6..4fc8c02 100644
--- a/tests/src/com/android/services/telephony/TelephonyConferenceControllerTest.java
+++ b/tests/src/com/android/services/telephony/TelephonyConferenceControllerTest.java
@@ -16,6 +16,13 @@
 
 package com.android.services.telephony;
 
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
 import android.os.Looper;
 import android.telecom.Conference;
 import android.telecom.Connection;
@@ -23,18 +30,9 @@
 
 import org.junit.Before;
 import org.junit.Test;
-
+import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
-import org.mockito.ArgumentCaptor;
-
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-import static org.mockito.Mockito.any;
-
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.assertFalse;
 
 import java.util.ArrayList;
 import java.util.Collection;
@@ -94,8 +92,8 @@
         // add telephony connection A
         mControllerTest.add(mTestTelephonyConnectionA);
 
-        mTestTelephonyConnectionA.setActive();
-        mTestTelephonyConnectionB.setOnHold();
+        mTestTelephonyConnectionA.setTelephonyConnectionActive();
+        mTestTelephonyConnectionB.setTelephonyConnectionOnHold();
 
         assertTrue(mTestTelephonyConnectionA.getConferenceables()
                 .contains(mTestTelephonyConnectionB));
@@ -146,8 +144,8 @@
         // add telephony connection A
         mControllerTest.add(mTestTelephonyConnectionA);
 
-        mTestTelephonyConnectionA.setActive();
-        mTestTelephonyConnectionB.setOnHold();
+        mTestTelephonyConnectionA.setTelephonyConnectionActive();
+        mTestTelephonyConnectionB.setTelephonyConnectionOnHold();
 
         assertTrue(mTestTelephonyConnectionA.getConferenceables()
                 .contains(mTestTelephonyConnectionB));