1/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.telecom;
18
19import com.android.internal.telecom.IConnectionService;
20import com.android.internal.telecom.IVideoCallback;
21import com.android.internal.telecom.IVideoProvider;
22
23import android.annotation.NonNull;
24import android.annotation.Nullable;
25import android.annotation.SystemApi;
26import android.hardware.camera2.CameraManager;
27import android.net.Uri;
28import android.os.Bundle;
29import android.os.Handler;
30import android.os.IBinder;
31import android.os.RemoteException;
32import android.view.Surface;
33
34import java.util.ArrayList;
35import java.util.Collections;
36import java.util.List;
37import java.util.Set;
38import java.util.concurrent.ConcurrentHashMap;
39
40/**
41 * A connection provided to a {@link ConnectionService} by another {@code ConnectionService}
42 * running in a different process.
43 *
44 * @see ConnectionService#createRemoteOutgoingConnection(PhoneAccountHandle, ConnectionRequest)
45 * @see ConnectionService#createRemoteIncomingConnection(PhoneAccountHandle, ConnectionRequest)
46 */
47public final class RemoteConnection {
48
49    /**
50     * Callback base class for {@link RemoteConnection}.
51     */
52    public static abstract class Callback {
53        /**
54         * Invoked when the state of this {@code RemoteConnection} has changed. See
55         * {@link #getState()}.
56         *
57         * @param connection The {@code RemoteConnection} invoking this method.
58         * @param state The new state of the {@code RemoteConnection}.
59         */
60        public void onStateChanged(RemoteConnection connection, int state) {}
61
62        /**
63         * Invoked when this {@code RemoteConnection} is disconnected.
64         *
65         * @param connection The {@code RemoteConnection} invoking this method.
66         * @param disconnectCause The ({@see DisconnectCause}) associated with this failed
67         *     connection.
68         */
69        public void onDisconnected(
70                RemoteConnection connection,
71                DisconnectCause disconnectCause) {}
72
73        /**
74         * Invoked when this {@code RemoteConnection} is requesting ringback. See
75         * {@link #isRingbackRequested()}.
76         *
77         * @param connection The {@code RemoteConnection} invoking this method.
78         * @param ringback Whether the {@code RemoteConnection} is requesting ringback.
79         */
80        public void onRingbackRequested(RemoteConnection connection, boolean ringback) {}
81
82        /**
83         * Indicates that the call capabilities of this {@code RemoteConnection} have changed.
84         * See {@link #getConnectionCapabilities()}.
85         *
86         * @param connection The {@code RemoteConnection} invoking this method.
87         * @param connectionCapabilities The new capabilities of the {@code RemoteConnection}.
88         */
89        public void onConnectionCapabilitiesChanged(
90                RemoteConnection connection,
91                int connectionCapabilities) {}
92
93        /**
94         * Indicates that the call properties of this {@code RemoteConnection} have changed.
95         * See {@link #getConnectionProperties()}.
96         *
97         * @param connection The {@code RemoteConnection} invoking this method.
98         * @param connectionProperties The new properties of the {@code RemoteConnection}.
99         */
100        public void onConnectionPropertiesChanged(
101                RemoteConnection connection,
102                int connectionProperties) {}
103
104        /**
105         * Invoked when the post-dial sequence in the outgoing {@code Connection} has reached a
106         * pause character. This causes the post-dial signals to stop pending user confirmation. An
107         * implementation should present this choice to the user and invoke
108         * {@link RemoteConnection#postDialContinue(boolean)} when the user makes the choice.
109         *
110         * @param connection The {@code RemoteConnection} invoking this method.
111         * @param remainingPostDialSequence The post-dial characters that remain to be sent.
112         */
113        public void onPostDialWait(RemoteConnection connection, String remainingPostDialSequence) {}
114
115        /**
116         * Invoked when the post-dial sequence in the outgoing {@code Connection} has processed
117         * a character.
118         *
119         * @param connection The {@code RemoteConnection} invoking this method.
120         * @param nextChar The character being processed.
121         */
122        public void onPostDialChar(RemoteConnection connection, char nextChar) {}
123
124        /**
125         * Indicates that the VOIP audio status of this {@code RemoteConnection} has changed.
126         * See {@link #isVoipAudioMode()}.
127         *
128         * @param connection The {@code RemoteConnection} invoking this method.
129         * @param isVoip Whether the new audio state of the {@code RemoteConnection} is VOIP.
130         */
131        public void onVoipAudioChanged(RemoteConnection connection, boolean isVoip) {}
132
133        /**
134         * Indicates that the status hints of this {@code RemoteConnection} have changed. See
135         * {@link #getStatusHints()} ()}.
136         *
137         * @param connection The {@code RemoteConnection} invoking this method.
138         * @param statusHints The new status hints of the {@code RemoteConnection}.
139         */
140        public void onStatusHintsChanged(RemoteConnection connection, StatusHints statusHints) {}
141
142        /**
143         * Indicates that the address (e.g., phone number) of this {@code RemoteConnection} has
144         * changed. See {@link #getAddress()} and {@link #getAddressPresentation()}.
145         *
146         * @param connection The {@code RemoteConnection} invoking this method.
147         * @param address The new address of the {@code RemoteConnection}.
148         * @param presentation The presentation requirements for the address.
149         *        See {@link TelecomManager} for valid values.
150         */
151        public void onAddressChanged(RemoteConnection connection, Uri address, int presentation) {}
152
153        /**
154         * Indicates that the caller display name of this {@code RemoteConnection} has changed.
155         * See {@link #getCallerDisplayName()} and {@link #getCallerDisplayNamePresentation()}.
156         *
157         * @param connection The {@code RemoteConnection} invoking this method.
158         * @param callerDisplayName The new caller display name of the {@code RemoteConnection}.
159         * @param presentation The presentation requirements for the handle.
160         *        See {@link TelecomManager} for valid values.
161         */
162        public void onCallerDisplayNameChanged(
163                RemoteConnection connection, String callerDisplayName, int presentation) {}
164
165        /**
166         * Indicates that the video state of this {@code RemoteConnection} has changed.
167         * See {@link #getVideoState()}.
168         *
169         * @param connection The {@code RemoteConnection} invoking this method.
170         * @param videoState The new video state of the {@code RemoteConnection}.
171         */
172        public void onVideoStateChanged(RemoteConnection connection, int videoState) {}
173
174        /**
175         * Indicates that this {@code RemoteConnection} has been destroyed. No further requests
176         * should be made to the {@code RemoteConnection}, and references to it should be cleared.
177         *
178         * @param connection The {@code RemoteConnection} invoking this method.
179         */
180        public void onDestroyed(RemoteConnection connection) {}
181
182        /**
183         * Indicates that the {@code RemoteConnection}s with which this {@code RemoteConnection}
184         * may be asked to create a conference has changed.
185         *
186         * @param connection The {@code RemoteConnection} invoking this method.
187         * @param conferenceableConnections The {@code RemoteConnection}s with which this
188         *         {@code RemoteConnection} may be asked to create a conference.
189         */
190        public void onConferenceableConnectionsChanged(
191                RemoteConnection connection,
192                List<RemoteConnection> conferenceableConnections) {}
193
194        /**
195         * Indicates that the {@code VideoProvider} associated with this {@code RemoteConnection}
196         * has changed.
197         *
198         * @param connection The {@code RemoteConnection} invoking this method.
199         * @param videoProvider The new {@code VideoProvider} associated with this
200         *         {@code RemoteConnection}.
201         */
202        public void onVideoProviderChanged(
203                RemoteConnection connection, VideoProvider videoProvider) {}
204
205        /**
206         * Indicates that the {@code RemoteConference} that this {@code RemoteConnection} is a part
207         * of has changed.
208         *
209         * @param connection The {@code RemoteConnection} invoking this method.
210         * @param conference The {@code RemoteConference} of which this {@code RemoteConnection} is
211         *         a part, which may be {@code null}.
212         */
213        public void onConferenceChanged(
214                RemoteConnection connection,
215                RemoteConference conference) {}
216
217        /**
218         * Handles changes to the {@code RemoteConnection} extras.
219         *
220         * @param connection The {@code RemoteConnection} invoking this method.
221         * @param extras The extras containing other information associated with the connection.
222         */
223        public void onExtrasChanged(RemoteConnection connection, @Nullable Bundle extras) {}
224
225        /**
226         * Handles a connection event propagated to this {@link RemoteConnection}.
227         * <p>
228         * Connection events originate from {@link Connection#sendConnectionEvent(String, Bundle)}.
229         *
230         * @param connection The {@code RemoteConnection} invoking this method.
231         * @param event The connection event.
232         * @param extras Extras associated with the event.
233         */
234        public void onConnectionEvent(RemoteConnection connection, String event, Bundle extras) {}
235
236        /**
237         * Indicates that a RTT session was successfully established on this
238         * {@link RemoteConnection}. See {@link Connection#sendRttInitiationSuccess()}.
239         * @hide
240         * @param connection The {@code RemoteConnection} invoking this method.
241         */
242        public void onRttInitiationSuccess(RemoteConnection connection) {}
243
244        /**
245         * Indicates that a RTT session failed to be established on this
246         * {@link RemoteConnection}. See {@link Connection#sendRttInitiationFailure()}.
247         * @hide
248         * @param connection The {@code RemoteConnection} invoking this method.
249         * @param reason One of the reason codes defined in {@link Connection.RttModifyStatus},
250         *               with the exception of
251         *               {@link Connection.RttModifyStatus#SESSION_MODIFY_REQUEST_SUCCESS}.
252         */
253        public void onRttInitiationFailure(RemoteConnection connection, int reason) {}
254
255        /**
256         * Indicates that an established RTT session was terminated remotely on this
257         * {@link RemoteConnection}. See {@link Connection#sendRttSessionRemotelyTerminated()}
258         * @hide
259         * @param connection The {@code RemoteConnection} invoking this method.
260         */
261        public void onRttSessionRemotelyTerminated(RemoteConnection connection) {}
262
263        /**
264         * Indicates that the remote user on this {@link RemoteConnection} has requested an upgrade
265         * to an RTT session. See {@link Connection#sendRemoteRttRequest()}
266         * @hide
267         * @param connection The {@code RemoteConnection} invoking this method.
268         */
269        public void onRemoteRttRequest(RemoteConnection connection) {}
270    }
271
272    /**
273     * {@link RemoteConnection.VideoProvider} associated with a {@link RemoteConnection}.  Used to
274     * receive video related events and control the video associated with a
275     * {@link RemoteConnection}.
276     *
277     * @see Connection.VideoProvider
278     */
279    public static class VideoProvider {
280
281        /**
282         * Callback class used by the {@link RemoteConnection.VideoProvider} to relay events from
283         * the {@link Connection.VideoProvider}.
284         */
285        public abstract static class Callback {
286            /**
287             * Reports a session modification request received from the
288             * {@link Connection.VideoProvider} associated with a {@link RemoteConnection}.
289             *
290             * @param videoProvider The {@link RemoteConnection.VideoProvider} invoking this method.
291             * @param videoProfile The requested video call profile.
292             * @see InCallService.VideoCall.Callback#onSessionModifyRequestReceived(VideoProfile)
293             * @see Connection.VideoProvider#receiveSessionModifyRequest(VideoProfile)
294             */
295            public void onSessionModifyRequestReceived(
296                    VideoProvider videoProvider,
297                    VideoProfile videoProfile) {}
298
299            /**
300             * Reports a session modification response received from the
301             * {@link Connection.VideoProvider} associated with a {@link RemoteConnection}.
302             *
303             * @param videoProvider The {@link RemoteConnection.VideoProvider} invoking this method.
304             * @param status Status of the session modify request.
305             * @param requestedProfile The original request which was sent to the peer device.
306             * @param responseProfile The actual profile changes made by the peer device.
307             * @see InCallService.VideoCall.Callback#onSessionModifyResponseReceived(int,
308             *      VideoProfile, VideoProfile)
309             * @see Connection.VideoProvider#receiveSessionModifyResponse(int, VideoProfile,
310             *      VideoProfile)
311             */
312            public void onSessionModifyResponseReceived(
313                    VideoProvider videoProvider,
314                    int status,
315                    VideoProfile requestedProfile,
316                    VideoProfile responseProfile) {}
317
318            /**
319             * Reports a call session event received from the {@link Connection.VideoProvider}
320             * associated with a {@link RemoteConnection}.
321             *
322             * @param videoProvider The {@link RemoteConnection.VideoProvider} invoking this method.
323             * @param event The event.
324             * @see InCallService.VideoCall.Callback#onCallSessionEvent(int)
325             * @see Connection.VideoProvider#handleCallSessionEvent(int)
326             */
327            public void onCallSessionEvent(VideoProvider videoProvider, int event) {}
328
329            /**
330             * Reports a change in the peer video dimensions received from the
331             * {@link Connection.VideoProvider} associated with a {@link RemoteConnection}.
332             *
333             * @param videoProvider The {@link RemoteConnection.VideoProvider} invoking this method.
334             * @param width  The updated peer video width.
335             * @param height The updated peer video height.
336             * @see InCallService.VideoCall.Callback#onPeerDimensionsChanged(int, int)
337             * @see Connection.VideoProvider#changePeerDimensions(int, int)
338             */
339            public void onPeerDimensionsChanged(VideoProvider videoProvider, int width,
340                    int height) {}
341
342            /**
343             * Reports a change in the data usage (in bytes) received from the
344             * {@link Connection.VideoProvider} associated with a {@link RemoteConnection}.
345             *
346             * @param videoProvider The {@link RemoteConnection.VideoProvider} invoking this method.
347             * @param dataUsage The updated data usage (in bytes).
348             * @see InCallService.VideoCall.Callback#onCallDataUsageChanged(long)
349             * @see Connection.VideoProvider#setCallDataUsage(long)
350             */
351            public void onCallDataUsageChanged(VideoProvider videoProvider, long dataUsage) {}
352
353            /**
354             * Reports a change in the capabilities of the current camera, received from the
355             * {@link Connection.VideoProvider} associated with a {@link RemoteConnection}.
356             *
357             * @param videoProvider The {@link RemoteConnection.VideoProvider} invoking this method.
358             * @param cameraCapabilities The changed camera capabilities.
359             * @see InCallService.VideoCall.Callback#onCameraCapabilitiesChanged(
360             *      VideoProfile.CameraCapabilities)
361             * @see Connection.VideoProvider#changeCameraCapabilities(
362             *      VideoProfile.CameraCapabilities)
363             */
364            public void onCameraCapabilitiesChanged(
365                    VideoProvider videoProvider,
366                    VideoProfile.CameraCapabilities cameraCapabilities) {}
367
368            /**
369             * Reports a change in the video quality received from the
370             * {@link Connection.VideoProvider} associated with a {@link RemoteConnection}.
371             *
372             * @param videoProvider The {@link RemoteConnection.VideoProvider} invoking this method.
373             * @param videoQuality  The updated peer video quality.
374             * @see InCallService.VideoCall.Callback#onVideoQualityChanged(int)
375             * @see Connection.VideoProvider#changeVideoQuality(int)
376             */
377            public void onVideoQualityChanged(VideoProvider videoProvider, int videoQuality) {}
378        }
379
380        private final IVideoCallback mVideoCallbackDelegate = new IVideoCallback() {
381            @Override
382            public void receiveSessionModifyRequest(VideoProfile videoProfile) {
383                for (Callback l : mCallbacks) {
384                    l.onSessionModifyRequestReceived(VideoProvider.this, videoProfile);
385                }
386            }
387
388            @Override
389            public void receiveSessionModifyResponse(int status, VideoProfile requestedProfile,
390                    VideoProfile responseProfile) {
391                for (Callback l : mCallbacks) {
392                    l.onSessionModifyResponseReceived(
393                            VideoProvider.this,
394                            status,
395                            requestedProfile,
396                            responseProfile);
397                }
398            }
399
400            @Override
401            public void handleCallSessionEvent(int event) {
402                for (Callback l : mCallbacks) {
403                    l.onCallSessionEvent(VideoProvider.this, event);
404                }
405            }
406
407            @Override
408            public void changePeerDimensions(int width, int height) {
409                for (Callback l : mCallbacks) {
410                    l.onPeerDimensionsChanged(VideoProvider.this, width, height);
411                }
412            }
413
414            @Override
415            public void changeCallDataUsage(long dataUsage) {
416                for (Callback l : mCallbacks) {
417                    l.onCallDataUsageChanged(VideoProvider.this, dataUsage);
418                }
419            }
420
421            @Override
422            public void changeCameraCapabilities(
423                    VideoProfile.CameraCapabilities cameraCapabilities) {
424                for (Callback l : mCallbacks) {
425                    l.onCameraCapabilitiesChanged(VideoProvider.this, cameraCapabilities);
426                }
427            }
428
429            @Override
430            public void changeVideoQuality(int videoQuality) {
431                for (Callback l : mCallbacks) {
432                    l.onVideoQualityChanged(VideoProvider.this, videoQuality);
433                }
434            }
435
436            @Override
437            public IBinder asBinder() {
438                return null;
439            }
440        };
441
442        private final VideoCallbackServant mVideoCallbackServant =
443                new VideoCallbackServant(mVideoCallbackDelegate);
444
445        private final IVideoProvider mVideoProviderBinder;
446
447        private final String mCallingPackage;
448
449        private final int mTargetSdkVersion;
450
451        /**
452         * ConcurrentHashMap constructor params: 8 is initial table size, 0.9f is
453         * load factor before resizing, 1 means we only expect a single thread to
454         * access the map so make only a single shard
455         */
456        private final Set<Callback> mCallbacks = Collections.newSetFromMap(
457                new ConcurrentHashMap<Callback, Boolean>(8, 0.9f, 1));
458
459        VideoProvider(IVideoProvider videoProviderBinder, String callingPackage,
460                      int targetSdkVersion) {
461
462            mVideoProviderBinder = videoProviderBinder;
463            mCallingPackage = callingPackage;
464            mTargetSdkVersion = targetSdkVersion;
465            try {
466                mVideoProviderBinder.addVideoCallback(mVideoCallbackServant.getStub().asBinder());
467            } catch (RemoteException e) {
468            }
469        }
470
471        /**
472         * Registers a callback to receive commands and state changes for video calls.
473         *
474         * @param l The video call callback.
475         */
476        public void registerCallback(Callback l) {
477            mCallbacks.add(l);
478        }
479
480        /**
481         * Clears the video call callback set via {@link #registerCallback}.
482         *
483         * @param l The video call callback to clear.
484         */
485        public void unregisterCallback(Callback l) {
486            mCallbacks.remove(l);
487        }
488
489        /**
490         * Sets the camera to be used for the outgoing video for the
491         * {@link RemoteConnection.VideoProvider}.
492         *
493         * @param cameraId The id of the camera (use ids as reported by
494         * {@link CameraManager#getCameraIdList()}).
495         * @see Connection.VideoProvider#onSetCamera(String)
496         */
497        public void setCamera(String cameraId) {
498            try {
499                mVideoProviderBinder.setCamera(cameraId, mCallingPackage, mTargetSdkVersion);
500            } catch (RemoteException e) {
501            }
502        }
503
504        /**
505         * Sets the surface to be used for displaying a preview of what the user's camera is
506         * currently capturing for the {@link RemoteConnection.VideoProvider}.
507         *
508         * @param surface The {@link Surface}.
509         * @see Connection.VideoProvider#onSetPreviewSurface(Surface)
510         */
511        public void setPreviewSurface(Surface surface) {
512            try {
513                mVideoProviderBinder.setPreviewSurface(surface);
514            } catch (RemoteException e) {
515            }
516        }
517
518        /**
519         * Sets the surface to be used for displaying the video received from the remote device for
520         * the {@link RemoteConnection.VideoProvider}.
521         *
522         * @param surface The {@link Surface}.
523         * @see Connection.VideoProvider#onSetDisplaySurface(Surface)
524         */
525        public void setDisplaySurface(Surface surface) {
526            try {
527                mVideoProviderBinder.setDisplaySurface(surface);
528            } catch (RemoteException e) {
529            }
530        }
531
532        /**
533         * Sets the device orientation, in degrees, for the {@link RemoteConnection.VideoProvider}.
534         * Assumes that a standard portrait orientation of the device is 0 degrees.
535         *
536         * @param rotation The device orientation, in degrees.
537         * @see Connection.VideoProvider#onSetDeviceOrientation(int)
538         */
539        public void setDeviceOrientation(int rotation) {
540            try {
541                mVideoProviderBinder.setDeviceOrientation(rotation);
542            } catch (RemoteException e) {
543            }
544        }
545
546        /**
547         * Sets camera zoom ratio for the {@link RemoteConnection.VideoProvider}.
548         *
549         * @param value The camera zoom ratio.
550         * @see Connection.VideoProvider#onSetZoom(float)
551         */
552        public void setZoom(float value) {
553            try {
554                mVideoProviderBinder.setZoom(value);
555            } catch (RemoteException e) {
556            }
557        }
558
559        /**
560         * Issues a request to modify the properties of the current video session for the
561         * {@link RemoteConnection.VideoProvider}.
562         *
563         * @param fromProfile The video profile prior to the request.
564         * @param toProfile The video profile with the requested changes made.
565         * @see Connection.VideoProvider#onSendSessionModifyRequest(VideoProfile, VideoProfile)
566         */
567        public void sendSessionModifyRequest(VideoProfile fromProfile, VideoProfile toProfile) {
568            try {
569                mVideoProviderBinder.sendSessionModifyRequest(fromProfile, toProfile);
570            } catch (RemoteException e) {
571            }
572        }
573
574        /**
575         * Provides a response to a request to change the current call video session
576         * properties for the {@link RemoteConnection.VideoProvider}.
577         *
578         * @param responseProfile The response call video properties.
579         * @see Connection.VideoProvider#onSendSessionModifyResponse(VideoProfile)
580         */
581        public void sendSessionModifyResponse(VideoProfile responseProfile) {
582            try {
583                mVideoProviderBinder.sendSessionModifyResponse(responseProfile);
584            } catch (RemoteException e) {
585            }
586        }
587
588        /**
589         * Issues a request to retrieve the capabilities of the current camera for the
590         * {@link RemoteConnection.VideoProvider}.
591         *
592         * @see Connection.VideoProvider#onRequestCameraCapabilities()
593         */
594        public void requestCameraCapabilities() {
595            try {
596                mVideoProviderBinder.requestCameraCapabilities();
597            } catch (RemoteException e) {
598            }
599        }
600
601        /**
602         * Issues a request to retrieve the data usage (in bytes) of the video portion of the
603         * {@link RemoteConnection} for the {@link RemoteConnection.VideoProvider}.
604         *
605         * @see Connection.VideoProvider#onRequestConnectionDataUsage()
606         */
607        public void requestCallDataUsage() {
608            try {
609                mVideoProviderBinder.requestCallDataUsage();
610            } catch (RemoteException e) {
611            }
612        }
613
614        /**
615         * Sets the {@link Uri} of an image to be displayed to the peer device when the video signal
616         * is paused, for the {@link RemoteConnection.VideoProvider}.
617         *
618         * @see Connection.VideoProvider#onSetPauseImage(Uri)
619         */
620        public void setPauseImage(Uri uri) {
621            try {
622                mVideoProviderBinder.setPauseImage(uri);
623            } catch (RemoteException e) {
624            }
625        }
626    }
627
628    private IConnectionService mConnectionService;
629    private final String mConnectionId;
630    /**
631     * ConcurrentHashMap constructor params: 8 is initial table size, 0.9f is
632     * load factor before resizing, 1 means we only expect a single thread to
633     * access the map so make only a single shard
634     */
635    private final Set<CallbackRecord> mCallbackRecords = Collections.newSetFromMap(
636            new ConcurrentHashMap<CallbackRecord, Boolean>(8, 0.9f, 1));
637    private final List<RemoteConnection> mConferenceableConnections = new ArrayList<>();
638    private final List<RemoteConnection> mUnmodifiableconferenceableConnections =
639            Collections.unmodifiableList(mConferenceableConnections);
640
641    private int mState = Connection.STATE_NEW;
642    private DisconnectCause mDisconnectCause;
643    private boolean mRingbackRequested;
644    private boolean mConnected;
645    private int mConnectionCapabilities;
646    private int mConnectionProperties;
647    private int mVideoState;
648    private VideoProvider mVideoProvider;
649    private boolean mIsVoipAudioMode;
650    private StatusHints mStatusHints;
651    private Uri mAddress;
652    private int mAddressPresentation;
653    private String mCallerDisplayName;
654    private int mCallerDisplayNamePresentation;
655    private RemoteConference mConference;
656    private Bundle mExtras;
657
658    /**
659     * @hide
660     */
661    RemoteConnection(
662            String id,
663            IConnectionService connectionService,
664            ConnectionRequest request) {
665        mConnectionId = id;
666        mConnectionService = connectionService;
667        mConnected = true;
668        mState = Connection.STATE_INITIALIZING;
669    }
670
671    /**
672     * @hide
673     */
674    RemoteConnection(String callId, IConnectionService connectionService,
675            ParcelableConnection connection, String callingPackage, int targetSdkVersion) {
676        mConnectionId = callId;
677        mConnectionService = connectionService;
678        mConnected = true;
679        mState = connection.getState();
680        mDisconnectCause = connection.getDisconnectCause();
681        mRingbackRequested = connection.isRingbackRequested();
682        mConnectionCapabilities = connection.getConnectionCapabilities();
683        mConnectionProperties = connection.getConnectionProperties();
684        mVideoState = connection.getVideoState();
685        IVideoProvider videoProvider = connection.getVideoProvider();
686        if (videoProvider != null) {
687            mVideoProvider = new RemoteConnection.VideoProvider(videoProvider, callingPackage,
688                    targetSdkVersion);
689        } else {
690            mVideoProvider = null;
691        }
692        mIsVoipAudioMode = connection.getIsVoipAudioMode();
693        mStatusHints = connection.getStatusHints();
694        mAddress = connection.getHandle();
695        mAddressPresentation = connection.getHandlePresentation();
696        mCallerDisplayName = connection.getCallerDisplayName();
697        mCallerDisplayNamePresentation = connection.getCallerDisplayNamePresentation();
698        mConference = null;
699        putExtras(connection.getExtras());
700
701        // Stash the original connection ID as it exists in the source ConnectionService.
702        // Telecom will use this to avoid adding duplicates later.
703        // See comments on Connection.EXTRA_ORIGINAL_CONNECTION_ID for more information.
704        Bundle newExtras = new Bundle();
705        newExtras.putString(Connection.EXTRA_ORIGINAL_CONNECTION_ID, callId);
706        putExtras(newExtras);
707    }
708
709    /**
710     * Create a RemoteConnection which is used for failed connections. Note that using it for any
711     * "real" purpose will almost certainly fail. Callers should note the failure and act
712     * accordingly (moving on to another RemoteConnection, for example)
713     *
714     * @param disconnectCause The reason for the failed connection.
715     * @hide
716     */
717    RemoteConnection(DisconnectCause disconnectCause) {
718        mConnectionId = "NULL";
719        mConnected = false;
720        mState = Connection.STATE_DISCONNECTED;
721        mDisconnectCause = disconnectCause;
722    }
723
724    /**
725     * Adds a callback to this {@code RemoteConnection}.
726     *
727     * @param callback A {@code Callback}.
728     */
729    public void registerCallback(Callback callback) {
730        registerCallback(callback, new Handler());
731    }
732
733    /**
734     * Adds a callback to this {@code RemoteConnection}.
735     *
736     * @param callback A {@code Callback}.
737     * @param handler A {@code Handler} which command and status changes will be delivered to.
738     */
739    public void registerCallback(Callback callback, Handler handler) {
740        unregisterCallback(callback);
741        if (callback != null && handler != null) {
742            mCallbackRecords.add(new CallbackRecord(callback, handler));
743        }
744    }
745
746    /**
747     * Removes a callback from this {@code RemoteConnection}.
748     *
749     * @param callback A {@code Callback}.
750     */
751    public void unregisterCallback(Callback callback) {
752        if (callback != null) {
753            for (CallbackRecord record : mCallbackRecords) {
754                if (record.getCallback() == callback) {
755                    mCallbackRecords.remove(record);
756                    break;
757                }
758            }
759        }
760    }
761
762    /**
763     * Obtains the state of this {@code RemoteConnection}.
764     *
765     * @return A state value, chosen from the {@code STATE_*} constants.
766     */
767    public int getState() {
768        return mState;
769    }
770
771    /**
772     * Obtains the reason why this {@code RemoteConnection} may have been disconnected.
773     *
774     * @return For a {@link Connection#STATE_DISCONNECTED} {@code RemoteConnection}, the
775     *         disconnect cause expressed as a code chosen from among those declared in
776     *         {@link DisconnectCause}.
777     */
778    public DisconnectCause getDisconnectCause() {
779        return mDisconnectCause;
780    }
781
782    /**
783     * Obtains the capabilities of this {@code RemoteConnection}.
784     *
785     * @return A bitmask of the capabilities of the {@code RemoteConnection}, as defined in
786     *         the {@code CAPABILITY_*} constants in class {@link Connection}.
787     */
788    public int getConnectionCapabilities() {
789        return mConnectionCapabilities;
790    }
791
792    /**
793     * Obtains the properties of this {@code RemoteConnection}.
794     *
795     * @return A bitmask of the properties of the {@code RemoteConnection}, as defined in the
796     *         {@code PROPERTY_*} constants in class {@link Connection}.
797     */
798    public int getConnectionProperties() {
799        return mConnectionProperties;
800    }
801
802    /**
803     * Determines if the audio mode of this {@code RemoteConnection} is VOIP.
804     *
805     * @return {@code true} if the {@code RemoteConnection}'s current audio mode is VOIP.
806     */
807    public boolean isVoipAudioMode() {
808        return mIsVoipAudioMode;
809    }
810
811    /**
812     * Obtains status hints pertaining to this {@code RemoteConnection}.
813     *
814     * @return The current {@link StatusHints} of this {@code RemoteConnection},
815     *         or {@code null} if none have been set.
816     */
817    public StatusHints getStatusHints() {
818        return mStatusHints;
819    }
820
821    /**
822     * Obtains the address of this {@code RemoteConnection}.
823     *
824     * @return The address (e.g., phone number) to which the {@code RemoteConnection}
825     *         is currently connected.
826     */
827    public Uri getAddress() {
828        return mAddress;
829    }
830
831    /**
832     * Obtains the presentation requirements for the address of this {@code RemoteConnection}.
833     *
834     * @return The presentation requirements for the address. See
835     *         {@link TelecomManager} for valid values.
836     */
837    public int getAddressPresentation() {
838        return mAddressPresentation;
839    }
840
841    /**
842     * Obtains the display name for this {@code RemoteConnection}'s caller.
843     *
844     * @return The display name for the caller.
845     */
846    public CharSequence getCallerDisplayName() {
847        return mCallerDisplayName;
848    }
849
850    /**
851     * Obtains the presentation requirements for this {@code RemoteConnection}'s
852     * caller's display name.
853     *
854     * @return The presentation requirements for the caller display name. See
855     *         {@link TelecomManager} for valid values.
856     */
857    public int getCallerDisplayNamePresentation() {
858        return mCallerDisplayNamePresentation;
859    }
860
861    /**
862     * Obtains the video state of this {@code RemoteConnection}.
863     *
864     * @return The video state of the {@code RemoteConnection}. See {@link VideoProfile}.
865     */
866    public int getVideoState() {
867        return mVideoState;
868    }
869
870    /**
871     * Obtains the video provider of this {@code RemoteConnection}.
872     * @return The video provider associated with this {@code RemoteConnection}.
873     */
874    public final VideoProvider getVideoProvider() {
875        return mVideoProvider;
876    }
877
878    /**
879     * Obtain the extras associated with this {@code RemoteConnection}.
880     *
881     * @return The extras for this connection.
882     */
883    public final Bundle getExtras() {
884        return mExtras;
885    }
886
887    /**
888     * Determines whether this {@code RemoteConnection} is requesting ringback.
889     *
890     * @return Whether the {@code RemoteConnection} is requesting that the framework play a
891     *         ringback tone on its behalf.
892     */
893    public boolean isRingbackRequested() {
894        return mRingbackRequested;
895    }
896
897    /**
898     * Instructs this {@code RemoteConnection} to abort.
899     */
900    public void abort() {
901        try {
902            if (mConnected) {
903                mConnectionService.abort(mConnectionId, null /*Session.Info*/);
904            }
905        } catch (RemoteException ignored) {
906        }
907    }
908
909    /**
910     * Instructs this {@link Connection#STATE_RINGING} {@code RemoteConnection} to answer.
911     */
912    public void answer() {
913        try {
914            if (mConnected) {
915                mConnectionService.answer(mConnectionId, null /*Session.Info*/);
916            }
917        } catch (RemoteException ignored) {
918        }
919    }
920
921    /**
922     * Instructs this {@link Connection#STATE_RINGING} {@code RemoteConnection} to answer.
923     * @param videoState The video state in which to answer the call.
924     * @hide
925     */
926    public void answer(int videoState) {
927        try {
928            if (mConnected) {
929                mConnectionService.answerVideo(mConnectionId, videoState, null /*Session.Info*/);
930            }
931        } catch (RemoteException ignored) {
932        }
933    }
934
935    /**
936     * Instructs this {@link Connection#STATE_RINGING} {@code RemoteConnection} to reject.
937     */
938    public void reject() {
939        try {
940            if (mConnected) {
941                mConnectionService.reject(mConnectionId, null /*Session.Info*/);
942            }
943        } catch (RemoteException ignored) {
944        }
945    }
946
947    /**
948     * Instructs this {@code RemoteConnection} to go on hold.
949     */
950    public void hold() {
951        try {
952            if (mConnected) {
953                mConnectionService.hold(mConnectionId, null /*Session.Info*/);
954            }
955        } catch (RemoteException ignored) {
956        }
957    }
958
959    /**
960     * Instructs this {@link Connection#STATE_HOLDING} call to release from hold.
961     */
962    public void unhold() {
963        try {
964            if (mConnected) {
965                mConnectionService.unhold(mConnectionId, null /*Session.Info*/);
966            }
967        } catch (RemoteException ignored) {
968        }
969    }
970
971    /**
972     * Instructs this {@code RemoteConnection} to disconnect.
973     */
974    public void disconnect() {
975        try {
976            if (mConnected) {
977                mConnectionService.disconnect(mConnectionId, null /*Session.Info*/);
978            }
979        } catch (RemoteException ignored) {
980        }
981    }
982
983    /**
984     * Instructs this {@code RemoteConnection} to play a dual-tone multi-frequency signaling
985     * (DTMF) tone.
986     *
987     * Any other currently playing DTMF tone in the specified call is immediately stopped.
988     *
989     * @param digit A character representing the DTMF digit for which to play the tone. This
990     *         value must be one of {@code '0'} through {@code '9'}, {@code '*'} or {@code '#'}.
991     */
992    public void playDtmfTone(char digit) {
993        try {
994            if (mConnected) {
995                mConnectionService.playDtmfTone(mConnectionId, digit, null /*Session.Info*/);
996            }
997        } catch (RemoteException ignored) {
998        }
999    }
1000
1001    /**
1002     * Instructs this {@code RemoteConnection} to stop any dual-tone multi-frequency signaling
1003     * (DTMF) tone currently playing.
1004     *
1005     * DTMF tones are played by calling {@link #playDtmfTone(char)}. If no DTMF tone is
1006     * currently playing, this method will do nothing.
1007     */
1008    public void stopDtmfTone() {
1009        try {
1010            if (mConnected) {
1011                mConnectionService.stopDtmfTone(mConnectionId, null /*Session.Info*/);
1012            }
1013        } catch (RemoteException ignored) {
1014        }
1015    }
1016
1017    /**
1018     * Instructs this {@code RemoteConnection} to continue playing a post-dial DTMF string.
1019     *
1020     * A post-dial DTMF string is a string of digits following the first instance of either
1021     * {@link TelecomManager#DTMF_CHARACTER_WAIT} or {@link TelecomManager#DTMF_CHARACTER_PAUSE}.
1022     * These digits are immediately sent as DTMF tones to the recipient as soon as the
1023     * connection is made.
1024     *
1025     * If the DTMF string contains a {@link TelecomManager#DTMF_CHARACTER_PAUSE} symbol, this
1026     * {@code RemoteConnection} will temporarily pause playing the tones for a pre-defined period
1027     * of time.
1028     *
1029     * If the DTMF string contains a {@link TelecomManager#DTMF_CHARACTER_WAIT} symbol, this
1030     * {@code RemoteConnection} will pause playing the tones and notify callbacks via
1031     * {@link Callback#onPostDialWait(RemoteConnection, String)}. At this point, the in-call app
1032     * should display to the user an indication of this state and an affordance to continue
1033     * the postdial sequence. When the user decides to continue the postdial sequence, the in-call
1034     * app should invoke the {@link #postDialContinue(boolean)} method.
1035     *
1036     * @param proceed Whether or not to continue with the post-dial sequence.
1037     */
1038    public void postDialContinue(boolean proceed) {
1039        try {
1040            if (mConnected) {
1041                mConnectionService.onPostDialContinue(mConnectionId, proceed,
1042                        null /*Session.Info*/);
1043            }
1044        } catch (RemoteException ignored) {
1045        }
1046    }
1047
1048    /**
1049     * Instructs this {@link RemoteConnection} to pull itself to the local device.
1050     * <p>
1051     * See {@link Call#pullExternalCall()} for more information.
1052     */
1053    public void pullExternalCall() {
1054        try {
1055            if (mConnected) {
1056                mConnectionService.pullExternalCall(mConnectionId, null /*Session.Info*/);
1057            }
1058        } catch (RemoteException ignored) {
1059        }
1060    }
1061
1062    /**
1063     * Set the audio state of this {@code RemoteConnection}.
1064     *
1065     * @param state The audio state of this {@code RemoteConnection}.
1066     * @hide
1067     * @deprecated Use {@link #setCallAudioState(CallAudioState) instead.
1068     */
1069    @SystemApi
1070    @Deprecated
1071    public void setAudioState(AudioState state) {
1072        setCallAudioState(new CallAudioState(state));
1073    }
1074
1075    /**
1076     * Set the audio state of this {@code RemoteConnection}.
1077     *
1078     * @param state The audio state of this {@code RemoteConnection}.
1079     */
1080    public void setCallAudioState(CallAudioState state) {
1081        try {
1082            if (mConnected) {
1083                mConnectionService.onCallAudioStateChanged(mConnectionId, state,
1084                        null /*Session.Info*/);
1085            }
1086        } catch (RemoteException ignored) {
1087        }
1088    }
1089
1090    /**
1091     * Notifies this {@link RemoteConnection} that the user has requested an RTT session.
1092     * @param rttTextStream The object that should be used to send text to or receive text from
1093     *                      the in-call app.
1094     * @hide
1095     */
1096    public void startRtt(@NonNull Connection.RttTextStream rttTextStream) {
1097        try {
1098            if (mConnected) {
1099                mConnectionService.startRtt(mConnectionId, rttTextStream.getFdFromInCall(),
1100                        rttTextStream.getFdToInCall(), null /*Session.Info*/);
1101            }
1102        } catch (RemoteException ignored) {
1103        }
1104    }
1105
1106    /**
1107     * Notifies this {@link RemoteConnection} that it should terminate any existing RTT
1108     * session. No response to Telecom is needed for this method.
1109     * @hide
1110     */
1111    public void stopRtt() {
1112        try {
1113            if (mConnected) {
1114                mConnectionService.stopRtt(mConnectionId, null /*Session.Info*/);
1115            }
1116        } catch (RemoteException ignored) {
1117        }
1118    }
1119
1120    /**
1121     * Notifies this {@link RemoteConnection} of a response to a previous remotely-initiated RTT
1122     * upgrade request sent via {@link Connection#sendRemoteRttRequest}.
1123     * Acceptance of the request is indicated by the supplied {@link RttTextStream} being non-null,
1124     * and rejection is indicated by {@code rttTextStream} being {@code null}
1125     * @hide
1126     * @param rttTextStream The object that should be used to send text to or receive text from
1127     *                      the in-call app.
1128     */
1129    public void sendRttUpgradeResponse(@Nullable Connection.RttTextStream rttTextStream) {
1130        try {
1131            if (mConnected) {
1132                if (rttTextStream == null) {
1133                    mConnectionService.respondToRttUpgradeRequest(mConnectionId,
1134                            null, null, null /*Session.Info*/);
1135                } else {
1136                    mConnectionService.respondToRttUpgradeRequest(mConnectionId,
1137                            rttTextStream.getFdFromInCall(), rttTextStream.getFdToInCall(),
1138                            null /*Session.Info*/);
1139                }
1140            }
1141        } catch (RemoteException ignored) {
1142        }
1143    }
1144
1145    /**
1146     * Obtain the {@code RemoteConnection}s with which this {@code RemoteConnection} may be
1147     * successfully asked to create a conference with.
1148     *
1149     * @return The {@code RemoteConnection}s with which this {@code RemoteConnection} may be
1150     *         merged into a {@link RemoteConference}.
1151     */
1152    public List<RemoteConnection> getConferenceableConnections() {
1153        return mUnmodifiableconferenceableConnections;
1154    }
1155
1156    /**
1157     * Obtain the {@code RemoteConference} that this {@code RemoteConnection} may be a part
1158     * of, or {@code null} if there is no such {@code RemoteConference}.
1159     *
1160     * @return A {@code RemoteConference} or {@code null};
1161     */
1162    public RemoteConference getConference() {
1163        return mConference;
1164    }
1165
1166    /** {@hide} */
1167    String getId() {
1168        return mConnectionId;
1169    }
1170
1171    /** {@hide} */
1172    IConnectionService getConnectionService() {
1173        return mConnectionService;
1174    }
1175
1176    /**
1177     * @hide
1178     */
1179    void setState(final int state) {
1180        if (mState != state) {
1181            mState = state;
1182            for (CallbackRecord record: mCallbackRecords) {
1183                final RemoteConnection connection = this;
1184                final Callback callback = record.getCallback();
1185                record.getHandler().post(new Runnable() {
1186                    @Override
1187                    public void run() {
1188                        callback.onStateChanged(connection, state);
1189                    }
1190                });
1191            }
1192        }
1193    }
1194
1195    /**
1196     * @hide
1197     */
1198    void setDisconnected(final DisconnectCause disconnectCause) {
1199        if (mState != Connection.STATE_DISCONNECTED) {
1200            mState = Connection.STATE_DISCONNECTED;
1201            mDisconnectCause = disconnectCause;
1202
1203            for (CallbackRecord record : mCallbackRecords) {
1204                final RemoteConnection connection = this;
1205                final Callback callback = record.getCallback();
1206                record.getHandler().post(new Runnable() {
1207                    @Override
1208                    public void run() {
1209                        callback.onDisconnected(connection, disconnectCause);
1210                    }
1211                });
1212            }
1213        }
1214    }
1215
1216    /**
1217     * @hide
1218     */
1219    void setRingbackRequested(final boolean ringback) {
1220        if (mRingbackRequested != ringback) {
1221            mRingbackRequested = ringback;
1222            for (CallbackRecord record : mCallbackRecords) {
1223                final RemoteConnection connection = this;
1224                final Callback callback = record.getCallback();
1225                record.getHandler().post(new Runnable() {
1226                    @Override
1227                    public void run() {
1228                        callback.onRingbackRequested(connection, ringback);
1229                    }
1230                });
1231            }
1232        }
1233    }
1234
1235    /**
1236     * @hide
1237     */
1238    void setConnectionCapabilities(final int connectionCapabilities) {
1239        mConnectionCapabilities = connectionCapabilities;
1240        for (CallbackRecord record : mCallbackRecords) {
1241            final RemoteConnection connection = this;
1242            final Callback callback = record.getCallback();
1243            record.getHandler().post(new Runnable() {
1244                @Override
1245                public void run() {
1246                    callback.onConnectionCapabilitiesChanged(connection, connectionCapabilities);
1247                }
1248            });
1249        }
1250    }
1251
1252    /**
1253     * @hide
1254     */
1255    void setConnectionProperties(final int connectionProperties) {
1256        mConnectionProperties = connectionProperties;
1257        for (CallbackRecord record : mCallbackRecords) {
1258            final RemoteConnection connection = this;
1259            final Callback callback = record.getCallback();
1260            record.getHandler().post(new Runnable() {
1261                @Override
1262                public void run() {
1263                    callback.onConnectionPropertiesChanged(connection, connectionProperties);
1264                }
1265            });
1266        }
1267    }
1268
1269    /**
1270     * @hide
1271     */
1272    void setDestroyed() {
1273        if (!mCallbackRecords.isEmpty()) {
1274            // Make sure that the callbacks are notified that the call is destroyed first.
1275            if (mState != Connection.STATE_DISCONNECTED) {
1276                setDisconnected(
1277                        new DisconnectCause(DisconnectCause.ERROR, "Connection destroyed."));
1278            }
1279
1280            for (CallbackRecord record : mCallbackRecords) {
1281                final RemoteConnection connection = this;
1282                final Callback callback = record.getCallback();
1283                record.getHandler().post(new Runnable() {
1284                    @Override
1285                    public void run() {
1286                        callback.onDestroyed(connection);
1287                    }
1288                });
1289            }
1290            mCallbackRecords.clear();
1291
1292            mConnected = false;
1293        }
1294    }
1295
1296    /**
1297     * @hide
1298     */
1299    void setPostDialWait(final String remainingDigits) {
1300        for (CallbackRecord record : mCallbackRecords) {
1301            final RemoteConnection connection = this;
1302            final Callback callback = record.getCallback();
1303            record.getHandler().post(new Runnable() {
1304                @Override
1305                public void run() {
1306                    callback.onPostDialWait(connection, remainingDigits);
1307                }
1308            });
1309        }
1310    }
1311
1312    /**
1313     * @hide
1314     */
1315    void onPostDialChar(final char nextChar) {
1316        for (CallbackRecord record : mCallbackRecords) {
1317            final RemoteConnection connection = this;
1318            final Callback callback = record.getCallback();
1319            record.getHandler().post(new Runnable() {
1320                @Override
1321                public void run() {
1322                    callback.onPostDialChar(connection, nextChar);
1323                }
1324            });
1325        }
1326    }
1327
1328    /**
1329     * @hide
1330     */
1331    void setVideoState(final int videoState) {
1332        mVideoState = videoState;
1333        for (CallbackRecord record : mCallbackRecords) {
1334            final RemoteConnection connection = this;
1335            final Callback callback = record.getCallback();
1336            record.getHandler().post(new Runnable() {
1337                @Override
1338                public void run() {
1339                    callback.onVideoStateChanged(connection, videoState);
1340                }
1341            });
1342        }
1343    }
1344
1345    /**
1346     * @hide
1347     */
1348    void setVideoProvider(final VideoProvider videoProvider) {
1349        mVideoProvider = videoProvider;
1350        for (CallbackRecord record : mCallbackRecords) {
1351            final RemoteConnection connection = this;
1352            final Callback callback = record.getCallback();
1353            record.getHandler().post(new Runnable() {
1354                @Override
1355                public void run() {
1356                    callback.onVideoProviderChanged(connection, videoProvider);
1357                }
1358            });
1359        }
1360    }
1361
1362    /** @hide */
1363    void setIsVoipAudioMode(final boolean isVoip) {
1364        mIsVoipAudioMode = isVoip;
1365        for (CallbackRecord record : mCallbackRecords) {
1366            final RemoteConnection connection = this;
1367            final Callback callback = record.getCallback();
1368            record.getHandler().post(new Runnable() {
1369                @Override
1370                public void run() {
1371                    callback.onVoipAudioChanged(connection, isVoip);
1372                }
1373            });
1374        }
1375    }
1376
1377    /** @hide */
1378    void setStatusHints(final StatusHints statusHints) {
1379        mStatusHints = statusHints;
1380        for (CallbackRecord record : mCallbackRecords) {
1381            final RemoteConnection connection = this;
1382            final Callback callback = record.getCallback();
1383            record.getHandler().post(new Runnable() {
1384                @Override
1385                public void run() {
1386                    callback.onStatusHintsChanged(connection, statusHints);
1387                }
1388            });
1389        }
1390    }
1391
1392    /** @hide */
1393    void setAddress(final Uri address, final int presentation) {
1394        mAddress = address;
1395        mAddressPresentation = presentation;
1396        for (CallbackRecord record : mCallbackRecords) {
1397            final RemoteConnection connection = this;
1398            final Callback callback = record.getCallback();
1399            record.getHandler().post(new Runnable() {
1400                @Override
1401                public void run() {
1402                    callback.onAddressChanged(connection, address, presentation);
1403                }
1404            });
1405        }
1406    }
1407
1408    /** @hide */
1409    void setCallerDisplayName(final String callerDisplayName, final int presentation) {
1410        mCallerDisplayName = callerDisplayName;
1411        mCallerDisplayNamePresentation = presentation;
1412        for (CallbackRecord record : mCallbackRecords) {
1413            final RemoteConnection connection = this;
1414            final Callback callback = record.getCallback();
1415            record.getHandler().post(new Runnable() {
1416                @Override
1417                public void run() {
1418                    callback.onCallerDisplayNameChanged(
1419                            connection, callerDisplayName, presentation);
1420                }
1421            });
1422        }
1423    }
1424
1425    /** @hide */
1426    void setConferenceableConnections(final List<RemoteConnection> conferenceableConnections) {
1427        mConferenceableConnections.clear();
1428        mConferenceableConnections.addAll(conferenceableConnections);
1429        for (CallbackRecord record : mCallbackRecords) {
1430            final RemoteConnection connection = this;
1431            final Callback callback = record.getCallback();
1432            record.getHandler().post(new Runnable() {
1433                @Override
1434                public void run() {
1435                    callback.onConferenceableConnectionsChanged(
1436                            connection, mUnmodifiableconferenceableConnections);
1437                }
1438            });
1439        }
1440    }
1441
1442    /** @hide */
1443    void setConference(final RemoteConference conference) {
1444        if (mConference != conference) {
1445            mConference = conference;
1446            for (CallbackRecord record : mCallbackRecords) {
1447                final RemoteConnection connection = this;
1448                final Callback callback = record.getCallback();
1449                record.getHandler().post(new Runnable() {
1450                    @Override
1451                    public void run() {
1452                        callback.onConferenceChanged(connection, conference);
1453                    }
1454                });
1455            }
1456        }
1457    }
1458
1459    /** @hide */
1460    void putExtras(final Bundle extras) {
1461        if (extras == null) {
1462            return;
1463        }
1464        if (mExtras == null) {
1465            mExtras = new Bundle();
1466        }
1467        mExtras.putAll(extras);
1468
1469        notifyExtrasChanged();
1470    }
1471
1472    /** @hide */
1473    void removeExtras(List<String> keys) {
1474        if (mExtras == null || keys == null || keys.isEmpty()) {
1475            return;
1476        }
1477        for (String key : keys) {
1478            mExtras.remove(key);
1479        }
1480
1481        notifyExtrasChanged();
1482    }
1483
1484    private void notifyExtrasChanged() {
1485        for (CallbackRecord record : mCallbackRecords) {
1486            final RemoteConnection connection = this;
1487            final Callback callback = record.getCallback();
1488            record.getHandler().post(new Runnable() {
1489                @Override
1490                public void run() {
1491                    callback.onExtrasChanged(connection, mExtras);
1492                }
1493            });
1494        }
1495    }
1496
1497    /** @hide */
1498    void onConnectionEvent(final String event, final Bundle extras) {
1499        for (CallbackRecord record : mCallbackRecords) {
1500            final RemoteConnection connection = this;
1501            final Callback callback = record.getCallback();
1502            record.getHandler().post(new Runnable() {
1503                @Override
1504                public void run() {
1505                    callback.onConnectionEvent(connection, event, extras);
1506                }
1507            });
1508        }
1509    }
1510
1511    /** @hide */
1512    void onRttInitiationSuccess() {
1513        for (CallbackRecord record : mCallbackRecords) {
1514            final RemoteConnection connection = this;
1515            final Callback callback = record.getCallback();
1516            record.getHandler().post(
1517                    () -> callback.onRttInitiationSuccess(connection));
1518        }
1519    }
1520
1521    /** @hide */
1522    void onRttInitiationFailure(int reason) {
1523        for (CallbackRecord record : mCallbackRecords) {
1524            final RemoteConnection connection = this;
1525            final Callback callback = record.getCallback();
1526            record.getHandler().post(
1527                    () -> callback.onRttInitiationFailure(connection, reason));
1528        }
1529    }
1530
1531    /** @hide */
1532    void onRttSessionRemotelyTerminated() {
1533        for (CallbackRecord record : mCallbackRecords) {
1534            final RemoteConnection connection = this;
1535            final Callback callback = record.getCallback();
1536            record.getHandler().post(
1537                    () -> callback.onRttSessionRemotelyTerminated(connection));
1538        }
1539    }
1540
1541    /** @hide */
1542    void onRemoteRttRequest() {
1543        for (CallbackRecord record : mCallbackRecords) {
1544            final RemoteConnection connection = this;
1545            final Callback callback = record.getCallback();
1546            record.getHandler().post(
1547                    () -> callback.onRemoteRttRequest(connection));
1548        }
1549    }
1550
1551    /**
1552    /**
1553     * Create a RemoteConnection represents a failure, and which will be in
1554     * {@link Connection#STATE_DISCONNECTED}. Attempting to use it for anything will almost
1555     * certainly result in bad things happening. Do not do this.
1556     *
1557     * @return a failed {@link RemoteConnection}
1558     *
1559     * @hide
1560     */
1561    public static RemoteConnection failure(DisconnectCause disconnectCause) {
1562        return new RemoteConnection(disconnectCause);
1563    }
1564
1565    private static final class CallbackRecord extends Callback {
1566        private final Callback mCallback;
1567        private final Handler mHandler;
1568
1569        public CallbackRecord(Callback callback, Handler handler) {
1570            mCallback = callback;
1571            mHandler = handler;
1572        }
1573
1574        public Callback getCallback() {
1575            return mCallback;
1576        }
1577
1578        public Handler getHandler() {
1579            return mHandler;
1580        }
1581    }
1582}
1583