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