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