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