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