Add further Connection-side APIs for RTT (part 2)

Add methods and callbacks to facilitate local and remote RTT initiation
and termination in the middle of a call. Adds @hide Connection-side APIs
to communicate with the ConnectionService, as well as plumbing for
RemoteConnections.

Test: manual, through telecom testapps
Merged-In: Ia80604b7dff8586ff222dbccdbe55e91aab02178
Change-Id: Ia80604b7dff8586ff222dbccdbe55e91aab02178
diff --git a/api/current.txt b/api/current.txt
index 0c202401..1484267 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -36691,6 +36691,7 @@
     method public void onDetailsChanged(android.telecom.Call, android.telecom.Call.Details);
     method public void onParentChanged(android.telecom.Call, android.telecom.Call);
     method public void onPostDialWait(android.telecom.Call, java.lang.String);
+    method public void onRttInitiationFailure(android.telecom.Call, int);
     method public void onRttModeChanged(android.telecom.Call, int);
     method public void onRttRequest(android.telecom.Call, int);
     method public void onRttStatusChanged(android.telecom.Call, boolean, android.telecom.Call.RttCall);
@@ -36957,6 +36958,15 @@
     field public static final int STATE_RINGING = 2; // 0x2
   }
 
+  public static final class Connection.RttModifyStatus {
+    ctor public Connection.RttModifyStatus();
+    field public static final int SESSION_MODIFY_REQUEST_FAIL = 2; // 0x2
+    field public static final int SESSION_MODIFY_REQUEST_INVALID = 3; // 0x3
+    field public static final int SESSION_MODIFY_REQUEST_REJECTED_BY_REMOTE = 5; // 0x5
+    field public static final int SESSION_MODIFY_REQUEST_SUCCESS = 1; // 0x1
+    field public static final int SESSION_MODIFY_REQUEST_TIMED_OUT = 4; // 0x4
+  }
+
   public static abstract class Connection.VideoProvider {
     ctor public Connection.VideoProvider();
     method public void changeCameraCapabilities(android.telecom.VideoProfile.CameraCapabilities);
diff --git a/api/system-current.txt b/api/system-current.txt
index d6b6ad4a..0968557 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -39665,6 +39665,7 @@
     method public void onDetailsChanged(android.telecom.Call, android.telecom.Call.Details);
     method public void onParentChanged(android.telecom.Call, android.telecom.Call);
     method public void onPostDialWait(android.telecom.Call, java.lang.String);
+    method public void onRttInitiationFailure(android.telecom.Call, int);
     method public void onRttModeChanged(android.telecom.Call, int);
     method public void onRttRequest(android.telecom.Call, int);
     method public void onRttStatusChanged(android.telecom.Call, boolean, android.telecom.Call.RttCall);
@@ -39942,6 +39943,15 @@
     field public static final int STATE_RINGING = 2; // 0x2
   }
 
+  public static final class Connection.RttModifyStatus {
+    ctor public Connection.RttModifyStatus();
+    field public static final int SESSION_MODIFY_REQUEST_FAIL = 2; // 0x2
+    field public static final int SESSION_MODIFY_REQUEST_INVALID = 3; // 0x3
+    field public static final int SESSION_MODIFY_REQUEST_REJECTED_BY_REMOTE = 5; // 0x5
+    field public static final int SESSION_MODIFY_REQUEST_SUCCESS = 1; // 0x1
+    field public static final int SESSION_MODIFY_REQUEST_TIMED_OUT = 4; // 0x4
+  }
+
   public static abstract class Connection.VideoProvider {
     ctor public Connection.VideoProvider();
     method public void changeCameraCapabilities(android.telecom.VideoProfile.CameraCapabilities);
diff --git a/api/test-current.txt b/api/test-current.txt
index 7aaab5b..6a964ff 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -36773,6 +36773,7 @@
     method public void onDetailsChanged(android.telecom.Call, android.telecom.Call.Details);
     method public void onParentChanged(android.telecom.Call, android.telecom.Call);
     method public void onPostDialWait(android.telecom.Call, java.lang.String);
+    method public void onRttInitiationFailure(android.telecom.Call, int);
     method public void onRttModeChanged(android.telecom.Call, int);
     method public void onRttRequest(android.telecom.Call, int);
     method public void onRttStatusChanged(android.telecom.Call, boolean, android.telecom.Call.RttCall);
@@ -37039,6 +37040,15 @@
     field public static final int STATE_RINGING = 2; // 0x2
   }
 
+  public static final class Connection.RttModifyStatus {
+    ctor public Connection.RttModifyStatus();
+    field public static final int SESSION_MODIFY_REQUEST_FAIL = 2; // 0x2
+    field public static final int SESSION_MODIFY_REQUEST_INVALID = 3; // 0x3
+    field public static final int SESSION_MODIFY_REQUEST_REJECTED_BY_REMOTE = 5; // 0x5
+    field public static final int SESSION_MODIFY_REQUEST_SUCCESS = 1; // 0x1
+    field public static final int SESSION_MODIFY_REQUEST_TIMED_OUT = 4; // 0x4
+  }
+
   public static abstract class Connection.VideoProvider {
     ctor public Connection.VideoProvider();
     method public void changeCameraCapabilities(android.telecom.VideoProfile.CameraCapabilities);
diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java
index e939b2e..9dc7dc5 100644
--- a/telecomm/java/android/telecom/Call.java
+++ b/telecomm/java/android/telecom/Call.java
@@ -871,6 +871,16 @@
          * @param id The ID of the request.
          */
         public void onRttRequest(Call call, int id) {}
+
+        /**
+         * Invoked when the RTT session failed to initiate for some reason, including rejection
+         * by the remote party.
+         * @param call The call which the RTT initiation failure occurred on.
+         * @param reason One of the status codes defined in
+         *               {@link android.telecom.Connection.RttModifyStatus}, with the exception of
+         *               {@link android.telecom.Connection.RttModifyStatus#SESSION_MODIFY_REQUEST_SUCCESS}.
+         */
+        public void onRttInitiationFailure(Call call, int reason) {}
     }
 
     /**
@@ -913,13 +923,15 @@
         private OutputStreamWriter mTransmitStream;
         private int mRttMode;
         private final InCallAdapter mInCallAdapter;
+        private final String mTelecomCallId;
         private char[] mReadBuffer = new char[READ_BUFFER_SIZE];
 
         /**
          * @hide
          */
-        public RttCall(InputStreamReader receiveStream, OutputStreamWriter transmitStream,
-                int mode, InCallAdapter inCallAdapter) {
+        public RttCall(String telecomCallId, InputStreamReader receiveStream,
+                OutputStreamWriter transmitStream, int mode, InCallAdapter inCallAdapter) {
+            mTelecomCallId = telecomCallId;
             mReceiveStream = receiveStream;
             mTransmitStream = transmitStream;
             mRttMode = mode;
@@ -942,7 +954,7 @@
          * {@link #RTT_MODE_VCO}, or {@link #RTT_MODE_HCO}.
          */
         public void setRttMode(@RttAudioMode int mode) {
-            mInCallAdapter.setRttMode(mode);
+            mInCallAdapter.setRttMode(mTelecomCallId, mode);
         }
 
         /**
@@ -1213,7 +1225,7 @@
      * {@link Callback#onRttStatusChanged(Call, boolean, RttCall)} callback.
      */
     public void sendRttRequest() {
-        mInCallAdapter.sendRttRequest();
+        mInCallAdapter.sendRttRequest(mTelecomCallId);
     }
 
     /**
@@ -1224,7 +1236,7 @@
      * @param accept {@code true} if the RTT request should be accepted, {@code false} otherwise.
      */
     public void respondToRttRequest(int id, boolean accept) {
-        mInCallAdapter.respondToRttRequest(id, accept);
+        mInCallAdapter.respondToRttRequest(mTelecomCallId, id, accept);
     }
 
     /**
@@ -1232,7 +1244,7 @@
      * the {@link Callback#onRttStatusChanged(Call, boolean, RttCall)} callback.
      */
     public void stopRtt() {
-        mInCallAdapter.stopRtt();
+        mInCallAdapter.stopRtt(mTelecomCallId);
     }
 
     /**
@@ -1637,7 +1649,7 @@
                     new ParcelFileDescriptor.AutoCloseOutputStream(
                             parcelableRttCall.getTransmitStream()),
                     StandardCharsets.UTF_8);
-            RttCall newRttCall = new Call.RttCall(
+            RttCall newRttCall = new Call.RttCall(mTelecomCallId,
                     receiveStream, transmitStream, parcelableRttCall.getRttMode(), mInCallAdapter);
             if (mRttCall == null) {
                 isRttChanged = true;
@@ -1717,6 +1729,15 @@
         }
     }
 
+    /** @hide */
+    final void internalOnRttInitiationFailure(int reason) {
+        for (CallbackRecord<Callback> record : mCallbackRecords) {
+            final Call call = this;
+            final Callback callback = record.getCallback();
+            record.getHandler().post(() -> callback.onRttInitiationFailure(call, reason));
+        }
+    }
+
     private void fireStateChanged(final int newState) {
         for (CallbackRecord<Callback> record : mCallbackRecords) {
             final Call call = this;
diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java
index 3e690b9..c2824f4 100644
--- a/telecomm/java/android/telecom/Connection.java
+++ b/telecomm/java/android/telecom/Connection.java
@@ -20,6 +20,7 @@
 import com.android.internal.telecom.IVideoCallback;
 import com.android.internal.telecom.IVideoProvider;
 
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
@@ -39,6 +40,8 @@
 import java.io.IOException;
 import java.io.InputStreamReader;
 import java.io.OutputStreamWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -764,6 +767,10 @@
         /** @hide */
         public void onConferenceSupportedChanged(Connection c, boolean isConferenceSupported) {}
         public void onAudioRouteChanged(Connection c, int audioRoute) {}
+        public void onRttInitiationSuccess(Connection c) {}
+        public void onRttInitiationFailure(Connection c, int reason) {}
+        public void onRttSessionRemotelyTerminated(Connection c) {}
+        public void onRemoteRttRequest(Connection c) {}
     }
 
     /**
@@ -774,12 +781,16 @@
         private static final int READ_BUFFER_SIZE = 1000;
         private final InputStreamReader mPipeFromInCall;
         private final OutputStreamWriter mPipeToInCall;
+        private final ParcelFileDescriptor mFdFromInCall;
+        private final ParcelFileDescriptor mFdToInCall;
         private char[] mReadBuffer = new char[READ_BUFFER_SIZE];
 
         /**
          * @hide
          */
         public RttTextStream(ParcelFileDescriptor toInCall, ParcelFileDescriptor fromInCall) {
+            mFdFromInCall = fromInCall;
+            mFdToInCall = toInCall;
             mPipeFromInCall = new InputStreamReader(
                     new ParcelFileDescriptor.AutoCloseInputStream(fromInCall));
             mPipeToInCall = new OutputStreamWriter(
@@ -823,6 +834,47 @@
                 return null;
             }
         }
+
+        /** @hide */
+        public ParcelFileDescriptor getFdFromInCall() {
+            return mFdFromInCall;
+        }
+
+        /** @hide */
+        public ParcelFileDescriptor getFdToInCall() {
+            return mFdToInCall;
+        }
+    }
+
+    /**
+     * Provides constants to represent the results of responses to session modify requests sent via
+     * {@link Call#sendRttRequest()}
+     */
+    public static final class RttModifyStatus {
+        /**
+         * 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;
+
+        /**
+         * Session modify request timed out.
+         */
+        public static final int SESSION_MODIFY_REQUEST_TIMED_OUT = 4;
+
+        /**
+         * Session modify request rejected by remote user.
+         */
+        public static final int SESSION_MODIFY_REQUEST_REJECTED_BY_REMOTE = 5;
     }
 
     /**
@@ -2426,6 +2478,47 @@
     }
 
     /**
+     * Informs listeners that a previously requested RTT session via
+     * {@link ConnectionRequest#isRequestingRtt()} or
+     * {@link #onStartRtt(ParcelFileDescriptor, ParcelFileDescriptor)} has succeeded.
+     * @hide
+     */
+    public final void sendRttInitiationSuccess() {
+        mListeners.forEach((l) -> l.onRttInitiationSuccess(Connection.this));
+    }
+
+    /**
+     * Informs listeners that a previously requested RTT session via
+     * {@link ConnectionRequest#isRequestingRtt()} or
+     * {@link #onStartRtt(ParcelFileDescriptor, ParcelFileDescriptor)}
+     * has failed.
+     * @param reason One of the reason codes defined in {@link RttModifyStatus}, with the
+     *               exception of {@link RttModifyStatus#SESSION_MODIFY_REQUEST_SUCCESS}.
+     * @hide
+     */
+    public final void sendRttInitiationFailure(int reason) {
+        mListeners.forEach((l) -> l.onRttInitiationFailure(Connection.this, reason));
+    }
+
+    /**
+     * Informs listeners that a currently active RTT session has been terminated by the remote
+     * side of the coll.
+     * @hide
+     */
+    public final void sendRttSessionRemotelyTerminated() {
+        mListeners.forEach((l) -> l.onRttSessionRemotelyTerminated(Connection.this));
+    }
+
+    /**
+     * Informs listeners that the remote side of the call has requested an upgrade to include an
+     * RTT session in the call.
+     * @hide
+     */
+    public final void sendRemoteRttRequest() {
+        mListeners.forEach((l) -> l.onRemoteRttRequest(Connection.this));
+    }
+
+    /**
      * Notifies this Connection that the {@link #getAudioState()} property has a new value.
      *
      * @param state The new connection audio state.
@@ -2595,6 +2688,35 @@
      */
     public void onShowIncomingCallUi() {}
 
+    /**
+     * Notifies this {@link Connection} that the user has requested an RTT session.
+     * The connection service should call {@link #sendRttInitiationSuccess} or
+     * {@link #sendRttInitiationFailure} to inform Telecom of the success or failure of the
+     * request, respectively.
+     * @param rttTextStream The object that should be used to send text to or receive text from
+     *                      the in-call app.
+     * @hide
+     */
+    public void onStartRtt(@NonNull RttTextStream rttTextStream) {}
+
+    /**
+     * Notifies this {@link Connection} that it should terminate any existing RTT communication
+     * channel. No response to Telecom is needed for this method.
+     * @hide
+     */
+    public void onStopRtt() {}
+
+    /**
+     * Notifies this connection of a response to a previous remotely-initiated RTT upgrade
+     * request sent via {@link #sendRemoteRttRequest}. Acceptance of the request is
+     * indicated by the supplied {@link RttTextStream} being non-null, and rejection is
+     * indicated by {@code rttTextStream} being {@code null}
+     * @hide
+     * @param rttTextStream The object that should be used to send text to or receive text from
+     *                      the in-call app.
+     */
+    public void handleRttUpgradeResponse(@Nullable RttTextStream rttTextStream) {}
+
     static String toLogSafePhoneNumber(String number) {
         // For unknown number, log empty string.
         if (number == null) {
diff --git a/telecomm/java/android/telecom/ConnectionService.java b/telecomm/java/android/telecom/ConnectionService.java
index 6e10029..9bc77b8 100644
--- a/telecomm/java/android/telecom/ConnectionService.java
+++ b/telecomm/java/android/telecom/ConnectionService.java
@@ -26,6 +26,8 @@
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
 import android.telecom.Logging.Session;
 
 import com.android.internal.os.SomeArgs;
@@ -119,6 +121,9 @@
     private static final String SESSION_PULL_EXTERNAL_CALL = "CS.pEC";
     private static final String SESSION_SEND_CALL_EVENT = "CS.sCE";
     private static final String SESSION_EXTRAS_CHANGED = "CS.oEC";
+    private static final String SESSION_START_RTT = "CS.+RTT";
+    private static final String SESSION_STOP_RTT = "CS.-RTT";
+    private static final String SESSION_RTT_UPGRADE_RESPONSE = "CS.rTRUR";
 
     private static final int MSG_ADD_CONNECTION_SERVICE_ADAPTER = 1;
     private static final int MSG_CREATE_CONNECTION = 2;
@@ -144,6 +149,9 @@
     private static final int MSG_SEND_CALL_EVENT = 23;
     private static final int MSG_ON_EXTRAS_CHANGED = 24;
     private static final int MSG_CREATE_CONNECTION_FAILED = 25;
+    private static final int MSG_ON_START_RTT = 26;
+    private static final int MSG_ON_STOP_RTT = 27;
+    private static final int MSG_RTT_UPGRADE_RESPONSE = 28;
 
     private static Connection sNullConnection;
 
@@ -501,6 +509,53 @@
                 Log.endSession();
             }
         }
+
+        @Override
+        public void startRtt(String callId, ParcelFileDescriptor fromInCall,
+                ParcelFileDescriptor toInCall, Session.Info sessionInfo) throws RemoteException {
+            Log.startSession(sessionInfo, SESSION_START_RTT);
+            try {
+                SomeArgs args = SomeArgs.obtain();
+                args.arg1 = callId;
+                args.arg2 = new Connection.RttTextStream(toInCall, fromInCall);
+                args.arg3 = Log.createSubsession();
+                mHandler.obtainMessage(MSG_ON_START_RTT, args).sendToTarget();
+            } finally {
+                Log.endSession();
+            }
+        }
+
+        @Override
+        public void stopRtt(String callId, Session.Info sessionInfo) throws RemoteException {
+            Log.startSession(sessionInfo, SESSION_STOP_RTT);
+            try {
+                SomeArgs args = SomeArgs.obtain();
+                args.arg1 = callId;
+                args.arg2 = Log.createSubsession();
+                mHandler.obtainMessage(MSG_ON_STOP_RTT, args).sendToTarget();
+            } finally {
+                Log.endSession();
+            }
+        }
+
+        @Override
+        public void respondToRttUpgradeRequest(String callId, ParcelFileDescriptor fromInCall,
+                ParcelFileDescriptor toInCall, Session.Info sessionInfo) throws RemoteException {
+            Log.startSession(sessionInfo, SESSION_RTT_UPGRADE_RESPONSE);
+            try {
+                SomeArgs args = SomeArgs.obtain();
+                args.arg1 = callId;
+                if (toInCall == null || fromInCall == null) {
+                    args.arg2 = null;
+                } else {
+                    args.arg2 = new Connection.RttTextStream(toInCall, fromInCall);
+                }
+                args.arg3 = Log.createSubsession();
+                mHandler.obtainMessage(MSG_RTT_UPGRADE_RESPONSE, args).sendToTarget();
+            } finally {
+                Log.endSession();
+            }
+        }
     };
 
     private final Handler mHandler = new Handler(Looper.getMainLooper()) {
@@ -848,6 +903,49 @@
                     }
                     break;
                 }
+                case MSG_ON_START_RTT: {
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    try {
+                        Log.continueSession((Session) args.arg3,
+                                SESSION_HANDLER + SESSION_START_RTT);
+                        String callId = (String) args.arg1;
+                        Connection.RttTextStream rttTextStream =
+                                (Connection.RttTextStream) args.arg2;
+                        startRtt(callId, rttTextStream);
+                    } finally {
+                        args.recycle();
+                        Log.endSession();
+                    }
+                    break;
+                }
+                case MSG_ON_STOP_RTT: {
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    try {
+                        Log.continueSession((Session) args.arg2,
+                                SESSION_HANDLER + SESSION_STOP_RTT);
+                        String callId = (String) args.arg1;
+                        stopRtt(callId);
+                    } finally {
+                        args.recycle();
+                        Log.endSession();
+                    }
+                    break;
+                }
+                case MSG_RTT_UPGRADE_RESPONSE: {
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    try {
+                        Log.continueSession((Session) args.arg3,
+                                SESSION_HANDLER + SESSION_RTT_UPGRADE_RESPONSE);
+                        String callId = (String) args.arg1;
+                        Connection.RttTextStream rttTextStream =
+                                (Connection.RttTextStream) args.arg2;
+                        handleRttUpgradeResponse(callId, rttTextStream);
+                    } finally {
+                        args.recycle();
+                        Log.endSession();
+                    }
+                    break;
+                }
                 default:
                     break;
             }
@@ -1136,6 +1234,38 @@
                 mAdapter.setAudioRoute(id, audioRoute);
             }
         }
+
+        @Override
+        public void onRttInitiationSuccess(Connection c) {
+            String id = mIdByConnection.get(c);
+            if (id != null) {
+                mAdapter.onRttInitiationSuccess(id);
+            }
+        }
+
+        @Override
+        public void onRttInitiationFailure(Connection c, int reason) {
+            String id = mIdByConnection.get(c);
+            if (id != null) {
+                mAdapter.onRttInitiationFailure(id, reason);
+            }
+        }
+
+        @Override
+        public void onRttSessionRemotelyTerminated(Connection c) {
+            String id = mIdByConnection.get(c);
+            if (id != null) {
+                mAdapter.onRttSessionRemotelyTerminated(id);
+            }
+        }
+
+        @Override
+        public void onRemoteRttRequest(Connection c) {
+            String id = mIdByConnection.get(c);
+            if (id != null) {
+                mAdapter.onRemoteRttRequest(id);
+            }
+        }
     };
 
     /** {@inheritDoc} */
@@ -1430,7 +1560,6 @@
         if (connection != null) {
             connection.onCallEvent(event, extras);
         }
-
     }
 
     /**
@@ -1454,6 +1583,34 @@
         }
     }
 
+    private void startRtt(String callId, Connection.RttTextStream rttTextStream) {
+        Log.d(this, "startRtt(%s)", callId);
+        if (mConnectionById.containsKey(callId)) {
+            findConnectionForAction(callId, "startRtt").onStartRtt(rttTextStream);
+        } else if (mConferenceById.containsKey(callId)) {
+            Log.w(this, "startRtt called on a conference.");
+        }
+    }
+
+    private void stopRtt(String callId) {
+        Log.d(this, "stopRtt(%s)", callId);
+        if (mConnectionById.containsKey(callId)) {
+            findConnectionForAction(callId, "stopRtt").onStopRtt();
+        } else if (mConferenceById.containsKey(callId)) {
+            Log.w(this, "stopRtt called on a conference.");
+        }
+    }
+
+    private void handleRttUpgradeResponse(String callId, Connection.RttTextStream rttTextStream) {
+        Log.d(this, "handleRttUpgradeResponse(%s, %s)", callId, rttTextStream == null);
+        if (mConnectionById.containsKey(callId)) {
+            findConnectionForAction(callId, "handleRttUpgradeResponse")
+                    .handleRttUpgradeResponse(rttTextStream);
+        } else if (mConferenceById.containsKey(callId)) {
+            Log.w(this, "handleRttUpgradeResponse called on a conference.");
+        }
+    }
+
     private void onPostDialContinue(String callId, boolean proceed) {
         Log.d(this, "onPostDialContinue(%s)", callId);
         findConnectionForAction(callId, "stopDtmfTone").onPostDialContinue(proceed);
diff --git a/telecomm/java/android/telecom/ConnectionServiceAdapter.java b/telecomm/java/android/telecom/ConnectionServiceAdapter.java
index 9542b73..63bdf74 100644
--- a/telecomm/java/android/telecom/ConnectionServiceAdapter.java
+++ b/telecomm/java/android/telecom/ConnectionServiceAdapter.java
@@ -547,4 +547,66 @@
             }
         }
     }
+
+    /**
+     * Notifies Telecom that an RTT session was successfully established.
+     *
+     * @param callId The unique ID of the call.
+     */
+    void onRttInitiationSuccess(String callId) {
+        Log.v(this, "onRttInitiationSuccess: %s", callId);
+        for (IConnectionServiceAdapter adapter : mAdapters) {
+            try {
+                adapter.onRttInitiationSuccess(callId, Log.getExternalSession());
+            } catch (RemoteException ignored) {
+            }
+        }
+    }
+
+    /**
+     * Notifies Telecom that a requested RTT session failed to be established.
+     *
+     * @param callId The unique ID of the call.
+     */
+    void onRttInitiationFailure(String callId, int reason) {
+        Log.v(this, "onRttInitiationFailure: %s", callId);
+        for (IConnectionServiceAdapter adapter : mAdapters) {
+            try {
+                adapter.onRttInitiationFailure(callId, reason, Log.getExternalSession());
+            } catch (RemoteException ignored) {
+            }
+        }
+    }
+
+    /**
+     * Notifies Telecom that an established RTT session was terminated by the remote user on
+     * the call.
+     *
+     * @param callId The unique ID of the call.
+     */
+    void onRttSessionRemotelyTerminated(String callId) {
+        Log.v(this, "onRttSessionRemotelyTerminated: %s", callId);
+        for (IConnectionServiceAdapter adapter : mAdapters) {
+            try {
+                adapter.onRttSessionRemotelyTerminated(callId, Log.getExternalSession());
+            } catch (RemoteException ignored) {
+            }
+        }
+    }
+
+    /**
+     * Notifies Telecom that the remote user on the call has requested an upgrade to an RTT
+     * session for this call.
+     *
+     * @param callId The unique ID of the call.
+     */
+    void onRemoteRttRequest(String callId) {
+        Log.v(this, "onRemoteRttRequest: %s", callId);
+        for (IConnectionServiceAdapter adapter : mAdapters) {
+            try {
+                adapter.onRemoteRttRequest(callId, Log.getExternalSession());
+            } catch (RemoteException ignored) {
+            }
+        }
+    }
 }
diff --git a/telecomm/java/android/telecom/ConnectionServiceAdapterServant.java b/telecomm/java/android/telecom/ConnectionServiceAdapterServant.java
index cc437f9..80e3c33 100644
--- a/telecomm/java/android/telecom/ConnectionServiceAdapterServant.java
+++ b/telecomm/java/android/telecom/ConnectionServiceAdapterServant.java
@@ -68,6 +68,10 @@
     private static final int MSG_SET_CONNECTION_PROPERTIES = 27;
     private static final int MSG_SET_PULLING = 28;
     private static final int MSG_SET_AUDIO_ROUTE = 29;
+    private static final int MSG_ON_RTT_INITIATION_SUCCESS = 30;
+    private static final int MSG_ON_RTT_INITIATION_FAILURE = 31;
+    private static final int MSG_ON_RTT_REMOTELY_TERMINATED = 32;
+    private static final int MSG_ON_RTT_UPGRADE_REQUEST = 33;
 
     private final IConnectionServiceAdapter mDelegate;
 
@@ -300,6 +304,20 @@
                     }
                     break;
                 }
+                case MSG_ON_RTT_INITIATION_SUCCESS:
+                    mDelegate.onRttInitiationSuccess((String) msg.obj, null /*Session.Info*/);
+                    break;
+                case MSG_ON_RTT_INITIATION_FAILURE:
+                    mDelegate.onRttInitiationFailure((String) msg.obj, msg.arg1,
+                            null /*Session.Info*/);
+                    break;
+                case MSG_ON_RTT_REMOTELY_TERMINATED:
+                    mDelegate.onRttSessionRemotelyTerminated((String) msg.obj,
+                            null /*Session.Info*/);
+                    break;
+                case MSG_ON_RTT_UPGRADE_REQUEST:
+                    mDelegate.onRemoteRttRequest((String) msg.obj, null /*Session.Info*/);
+                    break;
             }
         }
     };
@@ -537,6 +555,32 @@
             args.arg3 = extras;
             mHandler.obtainMessage(MSG_ON_CONNECTION_EVENT, args).sendToTarget();
         }
+
+        @Override
+        public void onRttInitiationSuccess(String connectionId, Session.Info sessionInfo)
+                throws RemoteException {
+            mHandler.obtainMessage(MSG_ON_RTT_INITIATION_SUCCESS, connectionId).sendToTarget();
+        }
+
+        @Override
+        public void onRttInitiationFailure(String connectionId, int reason,
+                Session.Info sessionInfo)
+                throws RemoteException {
+            mHandler.obtainMessage(MSG_ON_RTT_INITIATION_FAILURE, reason, 0, connectionId)
+                    .sendToTarget();
+        }
+
+        @Override
+        public void onRttSessionRemotelyTerminated(String connectionId, Session.Info sessionInfo)
+                throws RemoteException {
+            mHandler.obtainMessage(MSG_ON_RTT_REMOTELY_TERMINATED, connectionId).sendToTarget();
+        }
+
+        @Override
+        public void onRemoteRttRequest(String connectionId, Session.Info sessionInfo)
+                throws RemoteException {
+            mHandler.obtainMessage(MSG_ON_RTT_UPGRADE_REQUEST, connectionId).sendToTarget();
+        }
     };
 
     public ConnectionServiceAdapterServant(IConnectionServiceAdapter delegate) {
diff --git a/telecomm/java/android/telecom/InCallAdapter.java b/telecomm/java/android/telecom/InCallAdapter.java
index d640b1d..9559a28 100644
--- a/telecomm/java/android/telecom/InCallAdapter.java
+++ b/telecomm/java/android/telecom/InCallAdapter.java
@@ -379,9 +379,9 @@
     /**
      * Sends an RTT upgrade request to the remote end of the connection.
      */
-    public void sendRttRequest() {
+    public void sendRttRequest(String callId) {
         try {
-            mAdapter.sendRttRequest();
+            mAdapter.sendRttRequest(callId);
         } catch (RemoteException ignored) {
         }
     }
@@ -392,9 +392,9 @@
      * @param id the ID of the request as specified by Telecom
      * @param accept Whether the request should be accepted.
      */
-    public void respondToRttRequest(int id, boolean accept) {
+    public void respondToRttRequest(String callId, int id, boolean accept) {
         try {
-            mAdapter.respondToRttRequest(id, accept);
+            mAdapter.respondToRttRequest(callId, id, accept);
         } catch (RemoteException ignored) {
         }
     }
@@ -402,9 +402,9 @@
     /**
      * Instructs Telecom to shut down the RTT communication channel.
      */
-    public void stopRtt() {
+    public void stopRtt(String callId) {
         try {
-            mAdapter.stopRtt();
+            mAdapter.stopRtt(callId);
         } catch (RemoteException ignored) {
         }
     }
@@ -413,9 +413,9 @@
      * Sets the RTT audio mode.
      * @param mode the desired RTT audio mode
      */
-    public void setRttMode(int mode) {
+    public void setRttMode(String callId, int mode) {
         try {
-            mAdapter.setRttMode(mode);
+            mAdapter.setRttMode(callId, mode);
         } catch (RemoteException ignored) {
         }
     }
diff --git a/telecomm/java/android/telecom/InCallService.java b/telecomm/java/android/telecom/InCallService.java
index 4bc64c0..5d8f9f6 100644
--- a/telecomm/java/android/telecom/InCallService.java
+++ b/telecomm/java/android/telecom/InCallService.java
@@ -77,6 +77,7 @@
     private static final int MSG_SILENCE_RINGER = 8;
     private static final int MSG_ON_CONNECTION_EVENT = 9;
     private static final int MSG_ON_RTT_UPGRADE_REQUEST = 10;
+    private static final int MSG_ON_RTT_INITIATION_FAILURE = 11;
 
     /** Default Handler used to consolidate binder method calls onto a single thread. */
     private final Handler mHandler = new Handler(Looper.getMainLooper()) {
@@ -140,6 +141,12 @@
                     mPhone.internalOnRttUpgradeRequest(callId, requestId);
                     break;
                 }
+                case MSG_ON_RTT_INITIATION_FAILURE: {
+                    String callId = (String) msg.obj;
+                    int reason = msg.arg1;
+                    mPhone.internalOnRttInitiationFailure(callId, reason);
+                    break;
+                }
                 default:
                     break;
             }
@@ -210,6 +217,11 @@
         public void onRttUpgradeRequest(String callId, int id) {
             mHandler.obtainMessage(MSG_ON_RTT_UPGRADE_REQUEST, id, 0, callId).sendToTarget();
         }
+
+        @Override
+        public void onRttInitiationFailure(String callId, int reason) {
+            mHandler.obtainMessage(MSG_ON_RTT_INITIATION_FAILURE, reason, 0, callId).sendToTarget();
+        }
     }
 
     private Phone.Listener mPhoneListener = new Phone.Listener() {
diff --git a/telecomm/java/android/telecom/Phone.java b/telecomm/java/android/telecom/Phone.java
index ebd04c7..db2abf3 100644
--- a/telecomm/java/android/telecom/Phone.java
+++ b/telecomm/java/android/telecom/Phone.java
@@ -208,6 +208,13 @@
         }
     }
 
+    final void internalOnRttInitiationFailure(String callId, int reason) {
+        Call call = mCallByTelecomCallId.get(callId);
+        if (call != null) {
+            call.internalOnRttInitiationFailure(reason);
+        }
+    }
+
     /**
      * Called to destroy the phone and cleanup any lingering calls.
      */
diff --git a/telecomm/java/android/telecom/RemoteConnection.java b/telecomm/java/android/telecom/RemoteConnection.java
index 77e0e54..1ca54c8 100644
--- a/telecomm/java/android/telecom/RemoteConnection.java
+++ b/telecomm/java/android/telecom/RemoteConnection.java
@@ -20,6 +20,7 @@
 import com.android.internal.telecom.IVideoCallback;
 import com.android.internal.telecom.IVideoProvider;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.hardware.camera2.CameraManager;
@@ -231,6 +232,41 @@
          * @param extras Extras associated with the event.
          */
         public void onConnectionEvent(RemoteConnection connection, String event, Bundle extras) {}
+
+        /**
+         * Indicates that a RTT session was successfully established on this
+         * {@link RemoteConnection}. See {@link Connection#sendRttInitiationSuccess()}.
+         * @hide
+         * @param connection The {@code RemoteConnection} invoking this method.
+         */
+        public void onRttInitiationSuccess(RemoteConnection connection) {}
+
+        /**
+         * Indicates that a RTT session failed to be established on this
+         * {@link RemoteConnection}. See {@link Connection#sendRttInitiationFailure()}.
+         * @hide
+         * @param connection The {@code RemoteConnection} invoking this method.
+         * @param reason One of the reason codes defined in {@link Connection.RttModifyStatus},
+         *               with the exception of
+         *               {@link Connection.RttModifyStatus#SESSION_MODIFY_REQUEST_SUCCESS}.
+         */
+        public void onRttInitiationFailure(RemoteConnection connection, int reason) {}
+
+        /**
+         * Indicates that an established RTT session was terminated remotely on this
+         * {@link RemoteConnection}. See {@link Connection#sendRttSessionRemotelyTerminated()}
+         * @hide
+         * @param connection The {@code RemoteConnection} invoking this method.
+         */
+        public void onRttSessionRemotelyTerminated(RemoteConnection connection) {}
+
+        /**
+         * Indicates that the remote user on this {@link RemoteConnection} has requested an upgrade
+         * to an RTT session. See {@link Connection#sendRemoteRttRequest()}
+         * @hide
+         * @param connection The {@code RemoteConnection} invoking this method.
+         */
+        public void onRemoteRttRequest(RemoteConnection connection) {}
     }
 
     /**
@@ -1046,6 +1082,61 @@
     }
 
     /**
+     * Notifies this {@link RemoteConnection} that the user has requested an RTT session.
+     * @param rttTextStream The object that should be used to send text to or receive text from
+     *                      the in-call app.
+     * @hide
+     */
+    public void startRtt(@NonNull Connection.RttTextStream rttTextStream) {
+        try {
+            if (mConnected) {
+                mConnectionService.startRtt(mConnectionId, rttTextStream.getFdFromInCall(),
+                        rttTextStream.getFdToInCall(), null /*Session.Info*/);
+            }
+        } catch (RemoteException ignored) {
+        }
+    }
+
+    /**
+     * Notifies this {@link RemoteConnection} that it should terminate any existing RTT
+     * session. No response to Telecom is needed for this method.
+     * @hide
+     */
+    public void stopRtt() {
+        try {
+            if (mConnected) {
+                mConnectionService.stopRtt(mConnectionId, null /*Session.Info*/);
+            }
+        } catch (RemoteException ignored) {
+        }
+    }
+
+    /**
+     * Notifies this {@link RemoteConnection} of a response to a previous remotely-initiated RTT
+     * upgrade request sent via {@link Connection#sendRemoteRttRequest}.
+     * Acceptance of the request is indicated by the supplied {@link RttTextStream} being non-null,
+     * and rejection is indicated by {@code rttTextStream} being {@code null}
+     * @hide
+     * @param rttTextStream The object that should be used to send text to or receive text from
+     *                      the in-call app.
+     */
+    public void sendRttUpgradeResponse(@Nullable Connection.RttTextStream rttTextStream) {
+        try {
+            if (mConnected) {
+                if (rttTextStream == null) {
+                    mConnectionService.respondToRttUpgradeRequest(mConnectionId,
+                            null, null, null /*Session.Info*/);
+                } else {
+                    mConnectionService.respondToRttUpgradeRequest(mConnectionId,
+                            rttTextStream.getFdFromInCall(), rttTextStream.getFdToInCall(),
+                            null /*Session.Info*/);
+                }
+            }
+        } catch (RemoteException ignored) {
+        }
+    }
+
+    /**
      * Obtain the {@code RemoteConnection}s with which this {@code RemoteConnection} may be
      * successfully asked to create a conference with.
      *
@@ -1411,6 +1502,47 @@
         }
     }
 
+    /** @hide */
+    void onRttInitiationSuccess() {
+        for (CallbackRecord record : mCallbackRecords) {
+            final RemoteConnection connection = this;
+            final Callback callback = record.getCallback();
+            record.getHandler().post(
+                    () -> callback.onRttInitiationSuccess(connection));
+        }
+    }
+
+    /** @hide */
+    void onRttInitiationFailure(int reason) {
+        for (CallbackRecord record : mCallbackRecords) {
+            final RemoteConnection connection = this;
+            final Callback callback = record.getCallback();
+            record.getHandler().post(
+                    () -> callback.onRttInitiationFailure(connection, reason));
+        }
+    }
+
+    /** @hide */
+    void onRttSessionRemotelyTerminated() {
+        for (CallbackRecord record : mCallbackRecords) {
+            final RemoteConnection connection = this;
+            final Callback callback = record.getCallback();
+            record.getHandler().post(
+                    () -> callback.onRttSessionRemotelyTerminated(connection));
+        }
+    }
+
+    /** @hide */
+    void onRemoteRttRequest() {
+        for (CallbackRecord record : mCallbackRecords) {
+            final RemoteConnection connection = this;
+            final Callback callback = record.getCallback();
+            record.getHandler().post(
+                    () -> callback.onRemoteRttRequest(connection));
+        }
+    }
+
+    /**
     /**
      * Create a RemoteConnection represents a failure, and which will be in
      * {@link Connection#STATE_DISCONNECTED}. Attempting to use it for anything will almost
diff --git a/telecomm/java/android/telecom/RemoteConnectionService.java b/telecomm/java/android/telecom/RemoteConnectionService.java
index 60a40f5..ffba93a 100644
--- a/telecomm/java/android/telecom/RemoteConnectionService.java
+++ b/telecomm/java/android/telecom/RemoteConnectionService.java
@@ -405,6 +405,50 @@
                         extras);
             }
         }
+
+        @Override
+        public void onRttInitiationSuccess(String callId, Session.Info sessionInfo)
+                throws RemoteException {
+            if (hasConnection(callId)) {
+                findConnectionForAction(callId, "onRttInitiationSuccess")
+                        .onRttInitiationSuccess();
+            } else {
+                Log.w(this, "onRttInitiationSuccess called on a remote conference");
+            }
+        }
+
+        @Override
+        public void onRttInitiationFailure(String callId, int reason, Session.Info sessionInfo)
+                throws RemoteException {
+            if (hasConnection(callId)) {
+                findConnectionForAction(callId, "onRttInitiationFailure")
+                        .onRttInitiationFailure(reason);
+            } else {
+                Log.w(this, "onRttInitiationFailure called on a remote conference");
+            }
+        }
+
+        @Override
+        public void onRttSessionRemotelyTerminated(String callId, Session.Info sessionInfo)
+                throws RemoteException {
+            if (hasConnection(callId)) {
+                findConnectionForAction(callId, "onRttSessionRemotelyTerminated")
+                        .onRttSessionRemotelyTerminated();
+            } else {
+                Log.w(this, "onRttSessionRemotelyTerminated called on a remote conference");
+            }
+        }
+
+        @Override
+        public void onRemoteRttRequest(String callId, Session.Info sessionInfo)
+                throws RemoteException {
+            if (hasConnection(callId)) {
+                findConnectionForAction(callId, "onRemoteRttRequest")
+                        .onRemoteRttRequest();
+            } else {
+                Log.w(this, "onRemoteRttRequest called on a remote conference");
+            }
+        }
     };
 
     private final ConnectionServiceAdapterServant mServant =
diff --git a/telecomm/java/com/android/internal/telecom/IConnectionService.aidl b/telecomm/java/com/android/internal/telecom/IConnectionService.aidl
index 20feba7..114949e 100644
--- a/telecomm/java/com/android/internal/telecom/IConnectionService.aidl
+++ b/telecomm/java/com/android/internal/telecom/IConnectionService.aidl
@@ -17,6 +17,7 @@
 package com.android.internal.telecom;
 
 import android.os.Bundle;
+import android.os.ParcelFileDescriptor;
 import android.telecom.CallAudioState;
 import android.telecom.ConnectionRequest;
 import android.telecom.Logging.Session;
@@ -89,4 +90,12 @@
     void sendCallEvent(String callId, String event, in Bundle extras, in Session.Info sessionInfo);
 
     void onExtrasChanged(String callId, in Bundle extras, in Session.Info sessionInfo);
+
+    void startRtt(String callId, in ParcelFileDescriptor fromInCall,
+    in ParcelFileDescriptor toInCall, in Session.Info sessionInfo);
+
+    void stopRtt(String callId, in Session.Info sessionInfo);
+
+    void respondToRttUpgradeRequest(String callId, in ParcelFileDescriptor fromInCall,
+    in ParcelFileDescriptor toInCall, in Session.Info sessionInfo);
 }
diff --git a/telecomm/java/com/android/internal/telecom/IConnectionServiceAdapter.aidl b/telecomm/java/com/android/internal/telecom/IConnectionServiceAdapter.aidl
index b58f8bc..ac9da2e 100644
--- a/telecomm/java/com/android/internal/telecom/IConnectionServiceAdapter.aidl
+++ b/telecomm/java/com/android/internal/telecom/IConnectionServiceAdapter.aidl
@@ -106,4 +106,12 @@
 
     void onConnectionEvent(String callId, String event, in Bundle extras,
     in Session.Info sessionInfo);
+
+    void onRttInitiationSuccess(String callId, in Session.Info sessionInfo);
+
+    void onRttInitiationFailure(String callId, int reason, in Session.Info sessionInfo);
+
+    void onRttSessionRemotelyTerminated(String callId, in Session.Info sessionInfo);
+
+    void onRemoteRttRequest(String callId, in Session.Info sessionInfo);
 }
diff --git a/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl b/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl
index 47c3e6c..73fa29a 100644
--- a/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl
+++ b/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl
@@ -70,11 +70,11 @@
 
     void removeExtras(String callId, in List<String> keys);
 
-    void sendRttRequest();
+    void sendRttRequest(String callId);
 
-    void respondToRttRequest(int id, boolean accept);
+    void respondToRttRequest(String callId, int id, boolean accept);
 
-    void stopRtt();
+    void stopRtt(String callId);
 
-    void setRttMode(int mode);
+    void setRttMode(String callId, int mode);
 }
diff --git a/telecomm/java/com/android/internal/telecom/IInCallService.aidl b/telecomm/java/com/android/internal/telecom/IInCallService.aidl
index 1f92e0c..e8cf8e9 100644
--- a/telecomm/java/com/android/internal/telecom/IInCallService.aidl
+++ b/telecomm/java/com/android/internal/telecom/IInCallService.aidl
@@ -52,4 +52,6 @@
     void onConnectionEvent(String callId, String event, in Bundle extras);
 
     void onRttUpgradeRequest(String callId, int id);
+
+    void onRttInitiationFailure(String callId, int reason);
 }