RemoteConnection.java revision 4af5935c71f1e31ef1aec27661c4ef60545a0924
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.SystemApi;
24import android.net.Uri;
25import android.os.Handler;
26import android.os.IBinder;
27import android.os.RemoteException;
28import android.view.Surface;
29
30import java.util.ArrayList;
31import java.util.Collections;
32import java.util.List;
33import java.util.Set;
34import java.util.concurrent.ConcurrentHashMap;
35
36/**
37 * A connection provided to a {@link ConnectionService} by another {@code ConnectionService}
38 * running in a different process.
39 *
40 * @see ConnectionService#createRemoteOutgoingConnection(PhoneAccountHandle, ConnectionRequest)
41 * @see ConnectionService#createRemoteIncomingConnection(PhoneAccountHandle, ConnectionRequest)
42 */
43public final class RemoteConnection {
44
45    public static abstract class Callback {
46        /**
47         * Invoked when the state of this {@code RemoteConnection} has changed. See
48         * {@link #getState()}.
49         *
50         * @param connection The {@code RemoteConnection} invoking this method.
51         * @param state The new state of the {@code RemoteConnection}.
52         */
53        public void onStateChanged(RemoteConnection connection, int state) {}
54
55        /**
56         * Invoked when this {@code RemoteConnection} is disconnected.
57         *
58         * @param connection The {@code RemoteConnection} invoking this method.
59         * @param disconnectCause The ({@see DisconnectCause}) associated with this failed
60         *     connection.
61         */
62        public void onDisconnected(
63                RemoteConnection connection,
64                DisconnectCause disconnectCause) {}
65
66        /**
67         * Invoked when this {@code RemoteConnection} is requesting ringback. See
68         * {@link #isRingbackRequested()}.
69         *
70         * @param connection The {@code RemoteConnection} invoking this method.
71         * @param ringback Whether the {@code RemoteConnection} is requesting ringback.
72         */
73        public void onRingbackRequested(RemoteConnection connection, boolean ringback) {}
74
75        /**
76         * Indicates that the call capabilities of this {@code RemoteConnection} have changed.
77         * See {@link #getConnectionCapabilities()}.
78         *
79         * @param connection The {@code RemoteConnection} invoking this method.
80         * @param connectionCapabilities The new capabilities of the {@code RemoteConnection}.
81         */
82        public void onConnectionCapabilitiesChanged(
83                RemoteConnection connection,
84                int connectionCapabilities) {}
85
86        /**
87         * Invoked when the post-dial sequence in the outgoing {@code Connection} has reached a
88         * pause character. This causes the post-dial signals to stop pending user confirmation. An
89         * implementation should present this choice to the user and invoke
90         * {@link RemoteConnection#postDialContinue(boolean)} when the user makes the choice.
91         *
92         * @param connection The {@code RemoteConnection} invoking this method.
93         * @param remainingPostDialSequence The post-dial characters that remain to be sent.
94         */
95        public void onPostDialWait(RemoteConnection connection, String remainingPostDialSequence) {}
96
97        /**
98         * Invoked when the post-dial sequence in the outgoing {@code Connection} has processed
99         * a character.
100         *
101         * @param connection The {@code RemoteConnection} invoking this method.
102         * @param nextChar The character being processed.
103         */
104        public void onPostDialChar(RemoteConnection connection, char nextChar) {}
105
106        /**
107         * Indicates that the VOIP audio status of this {@code RemoteConnection} has changed.
108         * See {@link #isVoipAudioMode()}.
109         *
110         * @param connection The {@code RemoteConnection} invoking this method.
111         * @param isVoip Whether the new audio state of the {@code RemoteConnection} is VOIP.
112         */
113        public void onVoipAudioChanged(RemoteConnection connection, boolean isVoip) {}
114
115        /**
116         * Indicates that the status hints of this {@code RemoteConnection} have changed. See
117         * {@link #getStatusHints()} ()}.
118         *
119         * @param connection The {@code RemoteConnection} invoking this method.
120         * @param statusHints The new status hints of the {@code RemoteConnection}.
121         */
122        public void onStatusHintsChanged(RemoteConnection connection, StatusHints statusHints) {}
123
124        /**
125         * Indicates that the address (e.g., phone number) of this {@code RemoteConnection} has
126         * changed. See {@link #getAddress()} and {@link #getAddressPresentation()}.
127         *
128         * @param connection The {@code RemoteConnection} invoking this method.
129         * @param address The new address of the {@code RemoteConnection}.
130         * @param presentation The presentation requirements for the address.
131         *        See {@link TelecomManager} for valid values.
132         */
133        public void onAddressChanged(RemoteConnection connection, Uri address, int presentation) {}
134
135        /**
136         * Indicates that the caller display name of this {@code RemoteConnection} has changed.
137         * See {@link #getCallerDisplayName()} and {@link #getCallerDisplayNamePresentation()}.
138         *
139         * @param connection The {@code RemoteConnection} invoking this method.
140         * @param callerDisplayName The new caller display name of the {@code RemoteConnection}.
141         * @param presentation The presentation requirements for the handle.
142         *        See {@link TelecomManager} for valid values.
143         */
144        public void onCallerDisplayNameChanged(
145                RemoteConnection connection, String callerDisplayName, int presentation) {}
146
147        /**
148         * Indicates that the video state of this {@code RemoteConnection} has changed.
149         * See {@link #getVideoState()}.
150         *
151         * @param connection The {@code RemoteConnection} invoking this method.
152         * @param videoState The new video state of the {@code RemoteConnection}.
153         * @hide
154         */
155        public void onVideoStateChanged(RemoteConnection connection, int videoState) {}
156
157        /**
158         * Indicates that this {@code RemoteConnection} has been destroyed. No further requests
159         * should be made to the {@code RemoteConnection}, and references to it should be cleared.
160         *
161         * @param connection The {@code RemoteConnection} invoking this method.
162         */
163        public void onDestroyed(RemoteConnection connection) {}
164
165        /**
166         * Indicates that the {@code RemoteConnection}s with which this {@code RemoteConnection}
167         * may be asked to create a conference has changed.
168         *
169         * @param connection The {@code RemoteConnection} invoking this method.
170         * @param conferenceableConnections The {@code RemoteConnection}s with which this
171         *         {@code RemoteConnection} may be asked to create a conference.
172         */
173        public void onConferenceableConnectionsChanged(
174                RemoteConnection connection,
175                List<RemoteConnection> conferenceableConnections) {}
176
177        /**
178         * Indicates that the {@code VideoProvider} associated with this {@code RemoteConnection}
179         * has changed.
180         *
181         * @param connection The {@code RemoteConnection} invoking this method.
182         * @param videoProvider The new {@code VideoProvider} associated with this
183         *         {@code RemoteConnection}.
184         * @hide
185         */
186        public void onVideoProviderChanged(
187                RemoteConnection connection, VideoProvider videoProvider) {}
188
189        /**
190         * Indicates that the {@code RemoteConference} that this {@code RemoteConnection} is a part
191         * of has changed.
192         *
193         * @param connection The {@code RemoteConnection} invoking this method.
194         * @param conference The {@code RemoteConference} of which this {@code RemoteConnection} is
195         *         a part, which may be {@code null}.
196         */
197        public void onConferenceChanged(
198                RemoteConnection connection,
199                RemoteConference conference) {}
200    }
201
202    /** {@hide} */
203    public static class VideoProvider {
204
205        public abstract static class Listener {
206            public void onReceiveSessionModifyRequest(
207                    VideoProvider videoProvider,
208                    VideoProfile videoProfile) {}
209
210            public void onReceiveSessionModifyResponse(
211                    VideoProvider videoProvider,
212                    int status,
213                    VideoProfile requestedProfile,
214                    VideoProfile responseProfile) {}
215
216            public void onHandleCallSessionEvent(VideoProvider videoProvider, int event) {}
217
218            public void onPeerDimensionsChanged(VideoProvider videoProvider, int width, int height) {}
219
220            public void onCallDataUsageChanged(VideoProvider videoProvider, long dataUsage) {}
221
222            public void onCameraCapabilitiesChanged(
223                    VideoProvider videoProvider,
224                    VideoProfile.CameraCapabilities cameraCapabilities) {}
225
226            public void onVideoQualityChanged(VideoProvider videoProvider, int videoQuality) {}
227        }
228
229        private final IVideoCallback mVideoCallbackDelegate = new IVideoCallback() {
230            @Override
231            public void receiveSessionModifyRequest(VideoProfile videoProfile) {
232                for (Listener l : mListeners) {
233                    l.onReceiveSessionModifyRequest(VideoProvider.this, videoProfile);
234                }
235            }
236
237            @Override
238            public void receiveSessionModifyResponse(int status, VideoProfile requestedProfile,
239                    VideoProfile responseProfile) {
240                for (Listener l : mListeners) {
241                    l.onReceiveSessionModifyResponse(
242                            VideoProvider.this,
243                            status,
244                            requestedProfile,
245                            responseProfile);
246                }
247            }
248
249            @Override
250            public void handleCallSessionEvent(int event) {
251                for (Listener l : mListeners) {
252                    l.onHandleCallSessionEvent(VideoProvider.this, event);
253                }
254            }
255
256            @Override
257            public void changePeerDimensions(int width, int height) {
258                for (Listener l : mListeners) {
259                    l.onPeerDimensionsChanged(VideoProvider.this, width, height);
260                }
261            }
262
263            @Override
264            public void changeCallDataUsage(long dataUsage) {
265                for (Listener l : mListeners) {
266                    l.onCallDataUsageChanged(VideoProvider.this, dataUsage);
267                }
268            }
269
270            @Override
271            public void changeCameraCapabilities(
272                    VideoProfile.CameraCapabilities cameraCapabilities) {
273                for (Listener l : mListeners) {
274                    l.onCameraCapabilitiesChanged(VideoProvider.this, cameraCapabilities);
275                }
276            }
277
278            @Override
279            public void changeVideoQuality(int videoQuality) {
280                for (Listener l : mListeners) {
281                    l.onVideoQualityChanged(VideoProvider.this, videoQuality);
282                }
283            }
284
285            @Override
286            public IBinder asBinder() {
287                return null;
288            }
289        };
290
291        private final VideoCallbackServant mVideoCallbackServant =
292                new VideoCallbackServant(mVideoCallbackDelegate);
293
294        private final IVideoProvider mVideoProviderBinder;
295
296        /**
297         * ConcurrentHashMap constructor params: 8 is initial table size, 0.9f is
298         * load factor before resizing, 1 means we only expect a single thread to
299         * access the map so make only a single shard
300         */
301        private final Set<Listener> mListeners = Collections.newSetFromMap(
302                new ConcurrentHashMap<Listener, Boolean>(8, 0.9f, 1));
303
304        public VideoProvider(IVideoProvider videoProviderBinder) {
305            mVideoProviderBinder = videoProviderBinder;
306            try {
307                mVideoProviderBinder.addVideoCallback(mVideoCallbackServant.getStub().asBinder());
308            } catch (RemoteException e) {
309            }
310        }
311
312        public void addListener(Listener l) {
313            mListeners.add(l);
314        }
315
316        public void removeListener(Listener l) {
317            mListeners.remove(l);
318        }
319
320        public void setCamera(String cameraId) {
321            try {
322                mVideoProviderBinder.setCamera(cameraId);
323            } catch (RemoteException e) {
324            }
325        }
326
327        public void setPreviewSurface(Surface surface) {
328            try {
329                mVideoProviderBinder.setPreviewSurface(surface);
330            } catch (RemoteException e) {
331            }
332        }
333
334        public void setDisplaySurface(Surface surface) {
335            try {
336                mVideoProviderBinder.setDisplaySurface(surface);
337            } catch (RemoteException e) {
338            }
339        }
340
341        public void setDeviceOrientation(int rotation) {
342            try {
343                mVideoProviderBinder.setDeviceOrientation(rotation);
344            } catch (RemoteException e) {
345            }
346        }
347
348        public void setZoom(float value) {
349            try {
350                mVideoProviderBinder.setZoom(value);
351            } catch (RemoteException e) {
352            }
353        }
354
355        public void sendSessionModifyRequest(VideoProfile fromProfile, VideoProfile toProfile) {
356            try {
357                mVideoProviderBinder.sendSessionModifyRequest(fromProfile, toProfile);
358            } catch (RemoteException e) {
359            }
360        }
361
362        public void sendSessionModifyResponse(VideoProfile responseProfile) {
363            try {
364                mVideoProviderBinder.sendSessionModifyResponse(responseProfile);
365            } catch (RemoteException e) {
366            }
367        }
368
369        public void requestCameraCapabilities() {
370            try {
371                mVideoProviderBinder.requestCameraCapabilities();
372            } catch (RemoteException e) {
373            }
374        }
375
376        public void requestCallDataUsage() {
377            try {
378                mVideoProviderBinder.requestCallDataUsage();
379            } catch (RemoteException e) {
380            }
381        }
382
383        public void setPauseImage(Uri uri) {
384            try {
385                mVideoProviderBinder.setPauseImage(uri);
386            } catch (RemoteException e) {
387            }
388        }
389    }
390
391    private IConnectionService mConnectionService;
392    private final String mConnectionId;
393    /**
394     * ConcurrentHashMap constructor params: 8 is initial table size, 0.9f is
395     * load factor before resizing, 1 means we only expect a single thread to
396     * access the map so make only a single shard
397     */
398    private final Set<CallbackRecord> mCallbackRecords = Collections.newSetFromMap(
399            new ConcurrentHashMap<CallbackRecord, Boolean>(8, 0.9f, 1));
400    private final List<RemoteConnection> mConferenceableConnections = new ArrayList<>();
401    private final List<RemoteConnection> mUnmodifiableconferenceableConnections =
402            Collections.unmodifiableList(mConferenceableConnections);
403
404    private int mState = Connection.STATE_NEW;
405    private DisconnectCause mDisconnectCause;
406    private boolean mRingbackRequested;
407    private boolean mConnected;
408    private int mConnectionCapabilities;
409    private int mVideoState;
410    private VideoProvider mVideoProvider;
411    private boolean mIsVoipAudioMode;
412    private StatusHints mStatusHints;
413    private Uri mAddress;
414    private int mAddressPresentation;
415    private String mCallerDisplayName;
416    private int mCallerDisplayNamePresentation;
417    private RemoteConference mConference;
418
419    /**
420     * @hide
421     */
422    RemoteConnection(
423            String id,
424            IConnectionService connectionService,
425            ConnectionRequest request) {
426        mConnectionId = id;
427        mConnectionService = connectionService;
428        mConnected = true;
429        mState = Connection.STATE_INITIALIZING;
430    }
431
432    /**
433     * @hide
434     */
435    RemoteConnection(String callId, IConnectionService connectionService,
436            ParcelableConnection connection) {
437        mConnectionId = callId;
438        mConnectionService = connectionService;
439        mConnected = true;
440        mState = connection.getState();
441        mDisconnectCause = connection.getDisconnectCause();
442        mRingbackRequested = connection.isRingbackRequested();
443        mConnectionCapabilities = connection.getConnectionCapabilities();
444        mVideoState = connection.getVideoState();
445        mVideoProvider = new RemoteConnection.VideoProvider(connection.getVideoProvider());
446        mIsVoipAudioMode = connection.getIsVoipAudioMode();
447        mStatusHints = connection.getStatusHints();
448        mAddress = connection.getHandle();
449        mAddressPresentation = connection.getHandlePresentation();
450        mCallerDisplayName = connection.getCallerDisplayName();
451        mCallerDisplayNamePresentation = connection.getCallerDisplayNamePresentation();
452        mConference = null;
453    }
454
455    /**
456     * Create a RemoteConnection which is used for failed connections. Note that using it for any
457     * "real" purpose will almost certainly fail. Callers should note the failure and act
458     * accordingly (moving on to another RemoteConnection, for example)
459     *
460     * @param disconnectCause The reason for the failed connection.
461     * @hide
462     */
463    RemoteConnection(DisconnectCause disconnectCause) {
464        mConnectionId = "NULL";
465        mConnected = false;
466        mState = Connection.STATE_DISCONNECTED;
467        mDisconnectCause = disconnectCause;
468    }
469
470    /**
471     * Adds a callback to this {@code RemoteConnection}.
472     *
473     * @param callback A {@code Callback}.
474     */
475    public void registerCallback(Callback callback) {
476        registerCallback(callback, new Handler());
477    }
478
479    /**
480     * Adds a callback to this {@code RemoteConnection}.
481     *
482     * @param callback A {@code Callback}.
483     * @param handler A {@code Handler} which command and status changes will be delivered to.
484     */
485    public void registerCallback(Callback callback, Handler handler) {
486        unregisterCallback(callback);
487        if (callback != null && handler != null) {
488            mCallbackRecords.add(new CallbackRecord(callback, handler));
489        }
490    }
491
492    /**
493     * Removes a callback from this {@code RemoteConnection}.
494     *
495     * @param callback A {@code Callback}.
496     */
497    public void unregisterCallback(Callback callback) {
498        if (callback != null) {
499            for (CallbackRecord record : mCallbackRecords) {
500                if (record.getCallback() == callback) {
501                    mCallbackRecords.remove(record);
502                    break;
503                }
504            }
505        }
506    }
507
508    /**
509     * Obtains the state of this {@code RemoteConnection}.
510     *
511     * @return A state value, chosen from the {@code STATE_*} constants.
512     */
513    public int getState() {
514        return mState;
515    }
516
517    /**
518     * Obtains the reason why this {@code RemoteConnection} may have been disconnected.
519     *
520     * @return For a {@link Connection#STATE_DISCONNECTED} {@code RemoteConnection}, the
521     *         disconnect cause expressed as a code chosen from among those declared in
522     *         {@link DisconnectCause}.
523     */
524    public DisconnectCause getDisconnectCause() {
525        return mDisconnectCause;
526    }
527
528    /**
529     * Obtains the capabilities of this {@code RemoteConnection}.
530     *
531     * @return A bitmask of the capabilities of the {@code RemoteConnection}, as defined in
532     *         the {@code CAPABILITY_*} constants in class {@link Connection}.
533     */
534    public int getConnectionCapabilities() {
535        return mConnectionCapabilities;
536    }
537
538    /**
539     * Determines if the audio mode of this {@code RemoteConnection} is VOIP.
540     *
541     * @return {@code true} if the {@code RemoteConnection}'s current audio mode is VOIP.
542     */
543    public boolean isVoipAudioMode() {
544        return mIsVoipAudioMode;
545    }
546
547    /**
548     * Obtains status hints pertaining to this {@code RemoteConnection}.
549     *
550     * @return The current {@link StatusHints} of this {@code RemoteConnection},
551     *         or {@code null} if none have been set.
552     */
553    public StatusHints getStatusHints() {
554        return mStatusHints;
555    }
556
557    /**
558     * Obtains the address of this {@code RemoteConnection}.
559     *
560     * @return The address (e.g., phone number) to which the {@code RemoteConnection}
561     *         is currently connected.
562     */
563    public Uri getAddress() {
564        return mAddress;
565    }
566
567    /**
568     * Obtains the presentation requirements for the address of this {@code RemoteConnection}.
569     *
570     * @return The presentation requirements for the address. See
571     *         {@link TelecomManager} for valid values.
572     */
573    public int getAddressPresentation() {
574        return mAddressPresentation;
575    }
576
577    /**
578     * Obtains the display name for this {@code RemoteConnection}'s caller.
579     *
580     * @return The display name for the caller.
581     */
582    public CharSequence getCallerDisplayName() {
583        return mCallerDisplayName;
584    }
585
586    /**
587     * Obtains the presentation requirements for this {@code RemoteConnection}'s
588     * caller's display name.
589     *
590     * @return The presentation requirements for the caller display name. See
591     *         {@link TelecomManager} for valid values.
592     */
593    public int getCallerDisplayNamePresentation() {
594        return mCallerDisplayNamePresentation;
595    }
596
597    /**
598     * Obtains the video state of this {@code RemoteConnection}.
599     *
600     * @return The video state of the {@code RemoteConnection}. See {@link VideoProfile.VideoState}.
601     * @hide
602     */
603    public int getVideoState() {
604        return mVideoState;
605    }
606
607    /**
608     * Obtains the video provider of this {@code RemoteConnection}.
609     * @return The video provider associated with this {@code RemoteConnection}.
610     * @hide
611     */
612    public final VideoProvider getVideoProvider() {
613        return mVideoProvider;
614    }
615
616    /**
617     * Determines whether this {@code RemoteConnection} is requesting ringback.
618     *
619     * @return Whether the {@code RemoteConnection} is requesting that the framework play a
620     *         ringback tone on its behalf.
621     */
622    public boolean isRingbackRequested() {
623        return false;
624    }
625
626    /**
627     * Instructs this {@code RemoteConnection} to abort.
628     */
629    public void abort() {
630        try {
631            if (mConnected) {
632                mConnectionService.abort(mConnectionId);
633            }
634        } catch (RemoteException ignored) {
635        }
636    }
637
638    /**
639     * Instructs this {@link Connection#STATE_RINGING} {@code RemoteConnection} to answer.
640     */
641    public void answer() {
642        try {
643            if (mConnected) {
644                mConnectionService.answer(mConnectionId);
645            }
646        } catch (RemoteException ignored) {
647        }
648    }
649
650    /**
651     * Instructs this {@link Connection#STATE_RINGING} {@code RemoteConnection} to answer.
652     * @param videoState The video state in which to answer the call.
653     * @hide
654     */
655    public void answer(int videoState) {
656        try {
657            if (mConnected) {
658                mConnectionService.answerVideo(mConnectionId, videoState);
659            }
660        } catch (RemoteException ignored) {
661        }
662    }
663
664    /**
665     * Instructs this {@link Connection#STATE_RINGING} {@code RemoteConnection} to reject.
666     */
667    public void reject() {
668        try {
669            if (mConnected) {
670                mConnectionService.reject(mConnectionId);
671            }
672        } catch (RemoteException ignored) {
673        }
674    }
675
676    /**
677     * Instructs this {@code RemoteConnection} to go on hold.
678     */
679    public void hold() {
680        try {
681            if (mConnected) {
682                mConnectionService.hold(mConnectionId);
683            }
684        } catch (RemoteException ignored) {
685        }
686    }
687
688    /**
689     * Instructs this {@link Connection#STATE_HOLDING} call to release from hold.
690     */
691    public void unhold() {
692        try {
693            if (mConnected) {
694                mConnectionService.unhold(mConnectionId);
695            }
696        } catch (RemoteException ignored) {
697        }
698    }
699
700    /**
701     * Instructs this {@code RemoteConnection} to disconnect.
702     */
703    public void disconnect() {
704        try {
705            if (mConnected) {
706                mConnectionService.disconnect(mConnectionId);
707            }
708        } catch (RemoteException ignored) {
709        }
710    }
711
712    /**
713     * Instructs this {@code RemoteConnection} to play a dual-tone multi-frequency signaling
714     * (DTMF) tone.
715     *
716     * Any other currently playing DTMF tone in the specified call is immediately stopped.
717     *
718     * @param digit A character representing the DTMF digit for which to play the tone. This
719     *         value must be one of {@code '0'} through {@code '9'}, {@code '*'} or {@code '#'}.
720     */
721    public void playDtmfTone(char digit) {
722        try {
723            if (mConnected) {
724                mConnectionService.playDtmfTone(mConnectionId, digit);
725            }
726        } catch (RemoteException ignored) {
727        }
728    }
729
730    /**
731     * Instructs this {@code RemoteConnection} to stop any dual-tone multi-frequency signaling
732     * (DTMF) tone currently playing.
733     *
734     * DTMF tones are played by calling {@link #playDtmfTone(char)}. If no DTMF tone is
735     * currently playing, this method will do nothing.
736     */
737    public void stopDtmfTone() {
738        try {
739            if (mConnected) {
740                mConnectionService.stopDtmfTone(mConnectionId);
741            }
742        } catch (RemoteException ignored) {
743        }
744    }
745
746    /**
747     * Instructs this {@code RemoteConnection} to continue playing a post-dial DTMF string.
748     *
749     * A post-dial DTMF string is a string of digits following the first instance of either
750     * {@link TelecomManager#DTMF_CHARACTER_WAIT} or {@link TelecomManager#DTMF_CHARACTER_PAUSE}.
751     * These digits are immediately sent as DTMF tones to the recipient as soon as the
752     * connection is made.
753     *
754     * If the DTMF string contains a {@link TelecomManager#DTMF_CHARACTER_PAUSE} symbol, this
755     * {@code RemoteConnection} will temporarily pause playing the tones for a pre-defined period
756     * of time.
757     *
758     * If the DTMF string contains a {@link TelecomManager#DTMF_CHARACTER_WAIT} symbol, this
759     * {@code RemoteConnection} will pause playing the tones and notify callbacks via
760     * {@link Callback#onPostDialWait(RemoteConnection, String)}. At this point, the in-call app
761     * should display to the user an indication of this state and an affordance to continue
762     * the postdial sequence. When the user decides to continue the postdial sequence, the in-call
763     * app should invoke the {@link #postDialContinue(boolean)} method.
764     *
765     * @param proceed Whether or not to continue with the post-dial sequence.
766     */
767    public void postDialContinue(boolean proceed) {
768        try {
769            if (mConnected) {
770                mConnectionService.onPostDialContinue(mConnectionId, proceed);
771            }
772        } catch (RemoteException ignored) {
773        }
774    }
775
776    /**
777     * Set the audio state of this {@code RemoteConnection}.
778     *
779     * @param state The audio state of this {@code RemoteConnection}.
780     * @hide
781     * @deprecated Use {@link #setCallAudioState(CallAudioState) instead.
782     */
783    @SystemApi
784    @Deprecated
785    public void setAudioState(AudioState state) {
786        setCallAudioState(new CallAudioState(state));
787    }
788
789    /**
790     * Set the audio state of this {@code RemoteConnection}.
791     *
792     * @param state The audio state of this {@code RemoteConnection}.
793     */
794    public void setCallAudioState(CallAudioState state) {
795        try {
796            if (mConnected) {
797                mConnectionService.onCallAudioStateChanged(mConnectionId, state);
798            }
799        } catch (RemoteException ignored) {
800        }
801    }
802
803    /**
804     * Obtain the {@code RemoteConnection}s with which this {@code RemoteConnection} may be
805     * successfully asked to create a conference with.
806     *
807     * @return The {@code RemoteConnection}s with which this {@code RemoteConnection} may be
808     *         merged into a {@link RemoteConference}.
809     */
810    public List<RemoteConnection> getConferenceableConnections() {
811        return mUnmodifiableconferenceableConnections;
812    }
813
814    /**
815     * Obtain the {@code RemoteConference} that this {@code RemoteConnection} may be a part
816     * of, or {@code null} if there is no such {@code RemoteConference}.
817     *
818     * @return A {@code RemoteConference} or {@code null};
819     */
820    public RemoteConference getConference() {
821        return mConference;
822    }
823
824    /** {@hide} */
825    String getId() {
826        return mConnectionId;
827    }
828
829    /** {@hide} */
830    IConnectionService getConnectionService() {
831        return mConnectionService;
832    }
833
834    /**
835     * @hide
836     */
837    void setState(final int state) {
838        if (mState != state) {
839            mState = state;
840            for (CallbackRecord record: mCallbackRecords) {
841                final RemoteConnection connection = this;
842                final Callback callback = record.getCallback();
843                record.getHandler().post(new Runnable() {
844                    @Override
845                    public void run() {
846                        callback.onStateChanged(connection, state);
847                    }
848                });
849            }
850        }
851    }
852
853    /**
854     * @hide
855     */
856    void setDisconnected(final DisconnectCause disconnectCause) {
857        if (mState != Connection.STATE_DISCONNECTED) {
858            mState = Connection.STATE_DISCONNECTED;
859            mDisconnectCause = disconnectCause;
860
861            for (CallbackRecord record : mCallbackRecords) {
862                final RemoteConnection connection = this;
863                final Callback callback = record.getCallback();
864                record.getHandler().post(new Runnable() {
865                    @Override
866                    public void run() {
867                        callback.onDisconnected(connection, disconnectCause);
868                    }
869                });
870            }
871        }
872    }
873
874    /**
875     * @hide
876     */
877    void setRingbackRequested(final boolean ringback) {
878        if (mRingbackRequested != ringback) {
879            mRingbackRequested = ringback;
880            for (CallbackRecord record : mCallbackRecords) {
881                final RemoteConnection connection = this;
882                final Callback callback = record.getCallback();
883                record.getHandler().post(new Runnable() {
884                    @Override
885                    public void run() {
886                        callback.onRingbackRequested(connection, ringback);
887                    }
888                });
889            }
890        }
891    }
892
893    /**
894     * @hide
895     */
896    void setConnectionCapabilities(final int connectionCapabilities) {
897        mConnectionCapabilities = connectionCapabilities;
898        for (CallbackRecord record : mCallbackRecords) {
899            final RemoteConnection connection = this;
900            final Callback callback = record.getCallback();
901            record.getHandler().post(new Runnable() {
902                @Override
903                public void run() {
904                    callback.onConnectionCapabilitiesChanged(connection, connectionCapabilities);
905                }
906            });
907        }
908    }
909
910    /**
911     * @hide
912     */
913    void setDestroyed() {
914        if (!mCallbackRecords.isEmpty()) {
915            // Make sure that the callbacks are notified that the call is destroyed first.
916            if (mState != Connection.STATE_DISCONNECTED) {
917                setDisconnected(
918                        new DisconnectCause(DisconnectCause.ERROR, "Connection destroyed."));
919            }
920
921            for (CallbackRecord record : mCallbackRecords) {
922                final RemoteConnection connection = this;
923                final Callback callback = record.getCallback();
924                record.getHandler().post(new Runnable() {
925                    @Override
926                    public void run() {
927                        callback.onDestroyed(connection);
928                    }
929                });
930            }
931            mCallbackRecords.clear();
932
933            mConnected = false;
934        }
935    }
936
937    /**
938     * @hide
939     */
940    void setPostDialWait(final String remainingDigits) {
941        for (CallbackRecord record : mCallbackRecords) {
942            final RemoteConnection connection = this;
943            final Callback callback = record.getCallback();
944            record.getHandler().post(new Runnable() {
945                @Override
946                public void run() {
947                    callback.onPostDialWait(connection, remainingDigits);
948                }
949            });
950        }
951    }
952
953    /**
954     * @hide
955     */
956    void onPostDialChar(final char nextChar) {
957        for (CallbackRecord record : mCallbackRecords) {
958            final RemoteConnection connection = this;
959            final Callback callback = record.getCallback();
960            record.getHandler().post(new Runnable() {
961                @Override
962                public void run() {
963                    callback.onPostDialWait(connection, String.valueOf(nextChar));
964                }
965            });
966        }
967    }
968
969    /**
970     * @hide
971     */
972    void setVideoState(final int videoState) {
973        mVideoState = videoState;
974        for (CallbackRecord record : mCallbackRecords) {
975            final RemoteConnection connection = this;
976            final Callback callback = record.getCallback();
977            record.getHandler().post(new Runnable() {
978                @Override
979                public void run() {
980                    callback.onVideoStateChanged(connection, videoState);
981                }
982            });
983        }
984    }
985
986    /**
987     * @hide
988     */
989    void setVideoProvider(final VideoProvider videoProvider) {
990        mVideoProvider = videoProvider;
991        for (CallbackRecord record : mCallbackRecords) {
992            final RemoteConnection connection = this;
993            final Callback callback = record.getCallback();
994            record.getHandler().post(new Runnable() {
995                @Override
996                public void run() {
997                    callback.onVideoProviderChanged(connection, videoProvider);
998                }
999            });
1000        }
1001    }
1002
1003    /** @hide */
1004    void setIsVoipAudioMode(final boolean isVoip) {
1005        mIsVoipAudioMode = isVoip;
1006        for (CallbackRecord record : mCallbackRecords) {
1007            final RemoteConnection connection = this;
1008            final Callback callback = record.getCallback();
1009            record.getHandler().post(new Runnable() {
1010                @Override
1011                public void run() {
1012                    callback.onVoipAudioChanged(connection, isVoip);
1013                }
1014            });
1015        }
1016    }
1017
1018    /** @hide */
1019    void setStatusHints(final StatusHints statusHints) {
1020        mStatusHints = statusHints;
1021        for (CallbackRecord record : mCallbackRecords) {
1022            final RemoteConnection connection = this;
1023            final Callback callback = record.getCallback();
1024            record.getHandler().post(new Runnable() {
1025                @Override
1026                public void run() {
1027                    callback.onStatusHintsChanged(connection, statusHints);
1028                }
1029            });
1030        }
1031    }
1032
1033    /** @hide */
1034    void setAddress(final Uri address, final int presentation) {
1035        mAddress = address;
1036        mAddressPresentation = presentation;
1037        for (CallbackRecord record : mCallbackRecords) {
1038            final RemoteConnection connection = this;
1039            final Callback callback = record.getCallback();
1040            record.getHandler().post(new Runnable() {
1041                @Override
1042                public void run() {
1043                    callback.onAddressChanged(connection, address, presentation);
1044                }
1045            });
1046        }
1047    }
1048
1049    /** @hide */
1050    void setCallerDisplayName(final String callerDisplayName, final int presentation) {
1051        mCallerDisplayName = callerDisplayName;
1052        mCallerDisplayNamePresentation = presentation;
1053        for (CallbackRecord record : mCallbackRecords) {
1054            final RemoteConnection connection = this;
1055            final Callback callback = record.getCallback();
1056            record.getHandler().post(new Runnable() {
1057                @Override
1058                public void run() {
1059                    callback.onCallerDisplayNameChanged(
1060                            connection, callerDisplayName, presentation);
1061                }
1062            });
1063        }
1064    }
1065
1066    /** @hide */
1067    void setConferenceableConnections(final List<RemoteConnection> conferenceableConnections) {
1068        mConferenceableConnections.clear();
1069        mConferenceableConnections.addAll(conferenceableConnections);
1070        for (CallbackRecord record : mCallbackRecords) {
1071            final RemoteConnection connection = this;
1072            final Callback callback = record.getCallback();
1073            record.getHandler().post(new Runnable() {
1074                @Override
1075                public void run() {
1076                    callback.onConferenceableConnectionsChanged(
1077                            connection, mUnmodifiableconferenceableConnections);
1078                }
1079            });
1080        }
1081    }
1082
1083    /** @hide */
1084    void setConference(final RemoteConference conference) {
1085        if (mConference != conference) {
1086            mConference = conference;
1087            for (CallbackRecord record : mCallbackRecords) {
1088                final RemoteConnection connection = this;
1089                final Callback callback = record.getCallback();
1090                record.getHandler().post(new Runnable() {
1091                    @Override
1092                    public void run() {
1093                        callback.onConferenceChanged(connection, conference);
1094                    }
1095                });
1096            }
1097        }
1098    }
1099
1100    /**
1101     * Create a RemoteConnection represents a failure, and which will be in
1102     * {@link Connection#STATE_DISCONNECTED}. Attempting to use it for anything will almost
1103     * certainly result in bad things happening. Do not do this.
1104     *
1105     * @return a failed {@link RemoteConnection}
1106     *
1107     * @hide
1108     */
1109    public static RemoteConnection failure(DisconnectCause disconnectCause) {
1110        return new RemoteConnection(disconnectCause);
1111    }
1112
1113    private static final class CallbackRecord extends Callback {
1114        private final Callback mCallback;
1115        private final Handler mHandler;
1116
1117        public CallbackRecord(Callback callback, Handler handler) {
1118            mCallback = callback;
1119            mHandler = handler;
1120        }
1121
1122        public Callback getCallback() {
1123            return mCallback;
1124        }
1125
1126        public Handler getHandler() {
1127            return mHandler;
1128        }
1129    }
1130}
1131