/* * Copyright (C) 2014 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.telecom; import com.android.internal.telecom.IConnectionService; import com.android.internal.telecom.IVideoCallback; import com.android.internal.telecom.IVideoProvider; import android.annotation.Nullable; import android.annotation.SystemApi; import android.hardware.camera2.CameraManager; import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.RemoteException; import android.view.Surface; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; /** * A connection provided to a {@link ConnectionService} by another {@code ConnectionService} * running in a different process. * * @see ConnectionService#createRemoteOutgoingConnection(PhoneAccountHandle, ConnectionRequest) * @see ConnectionService#createRemoteIncomingConnection(PhoneAccountHandle, ConnectionRequest) */ public final class RemoteConnection { /** * Callback base class for {@link RemoteConnection}. */ public static abstract class Callback { /** * Invoked when the state of this {@code RemoteConnection} has changed. See * {@link #getState()}. * * @param connection The {@code RemoteConnection} invoking this method. * @param state The new state of the {@code RemoteConnection}. */ public void onStateChanged(RemoteConnection connection, int state) {} /** * Invoked when this {@code RemoteConnection} is disconnected. * * @param connection The {@code RemoteConnection} invoking this method. * @param disconnectCause The ({@see DisconnectCause}) associated with this failed * connection. */ public void onDisconnected( RemoteConnection connection, DisconnectCause disconnectCause) {} /** * Invoked when this {@code RemoteConnection} is requesting ringback. See * {@link #isRingbackRequested()}. * * @param connection The {@code RemoteConnection} invoking this method. * @param ringback Whether the {@code RemoteConnection} is requesting ringback. */ public void onRingbackRequested(RemoteConnection connection, boolean ringback) {} /** * Indicates that the call capabilities of this {@code RemoteConnection} have changed. * See {@link #getConnectionCapabilities()}. * * @param connection The {@code RemoteConnection} invoking this method. * @param connectionCapabilities The new capabilities of the {@code RemoteConnection}. */ public void onConnectionCapabilitiesChanged( RemoteConnection connection, int connectionCapabilities) {} /** * Indicates that the call properties of this {@code RemoteConnection} have changed. * See {@link #getConnectionProperties()}. * * @param connection The {@code RemoteConnection} invoking this method. * @param connectionProperties The new properties of the {@code RemoteConnection}. * @hide */ public void onConnectionPropertiesChanged( RemoteConnection connection, int connectionProperties) {} /** * Invoked when the post-dial sequence in the outgoing {@code Connection} has reached a * pause character. This causes the post-dial signals to stop pending user confirmation. An * implementation should present this choice to the user and invoke * {@link RemoteConnection#postDialContinue(boolean)} when the user makes the choice. * * @param connection The {@code RemoteConnection} invoking this method. * @param remainingPostDialSequence The post-dial characters that remain to be sent. */ public void onPostDialWait(RemoteConnection connection, String remainingPostDialSequence) {} /** * Invoked when the post-dial sequence in the outgoing {@code Connection} has processed * a character. * * @param connection The {@code RemoteConnection} invoking this method. * @param nextChar The character being processed. */ public void onPostDialChar(RemoteConnection connection, char nextChar) {} /** * Indicates that the VOIP audio status of this {@code RemoteConnection} has changed. * See {@link #isVoipAudioMode()}. * * @param connection The {@code RemoteConnection} invoking this method. * @param isVoip Whether the new audio state of the {@code RemoteConnection} is VOIP. */ public void onVoipAudioChanged(RemoteConnection connection, boolean isVoip) {} /** * Indicates that the status hints of this {@code RemoteConnection} have changed. See * {@link #getStatusHints()} ()}. * * @param connection The {@code RemoteConnection} invoking this method. * @param statusHints The new status hints of the {@code RemoteConnection}. */ public void onStatusHintsChanged(RemoteConnection connection, StatusHints statusHints) {} /** * Indicates that the address (e.g., phone number) of this {@code RemoteConnection} has * changed. See {@link #getAddress()} and {@link #getAddressPresentation()}. * * @param connection The {@code RemoteConnection} invoking this method. * @param address The new address of the {@code RemoteConnection}. * @param presentation The presentation requirements for the address. * See {@link TelecomManager} for valid values. */ public void onAddressChanged(RemoteConnection connection, Uri address, int presentation) {} /** * Indicates that the caller display name of this {@code RemoteConnection} has changed. * See {@link #getCallerDisplayName()} and {@link #getCallerDisplayNamePresentation()}. * * @param connection The {@code RemoteConnection} invoking this method. * @param callerDisplayName The new caller display name of the {@code RemoteConnection}. * @param presentation The presentation requirements for the handle. * See {@link TelecomManager} for valid values. */ public void onCallerDisplayNameChanged( RemoteConnection connection, String callerDisplayName, int presentation) {} /** * Indicates that the video state of this {@code RemoteConnection} has changed. * See {@link #getVideoState()}. * * @param connection The {@code RemoteConnection} invoking this method. * @param videoState The new video state of the {@code RemoteConnection}. */ public void onVideoStateChanged(RemoteConnection connection, int videoState) {} /** * Indicates that this {@code RemoteConnection} has been destroyed. No further requests * should be made to the {@code RemoteConnection}, and references to it should be cleared. * * @param connection The {@code RemoteConnection} invoking this method. */ public void onDestroyed(RemoteConnection connection) {} /** * Indicates that the {@code RemoteConnection}s with which this {@code RemoteConnection} * may be asked to create a conference has changed. * * @param connection The {@code RemoteConnection} invoking this method. * @param conferenceableConnections The {@code RemoteConnection}s with which this * {@code RemoteConnection} may be asked to create a conference. */ public void onConferenceableConnectionsChanged( RemoteConnection connection, List conferenceableConnections) {} /** * Indicates that the {@code VideoProvider} associated with this {@code RemoteConnection} * has changed. * * @param connection The {@code RemoteConnection} invoking this method. * @param videoProvider The new {@code VideoProvider} associated with this * {@code RemoteConnection}. */ public void onVideoProviderChanged( RemoteConnection connection, VideoProvider videoProvider) {} /** * Indicates that the {@code RemoteConference} that this {@code RemoteConnection} is a part * of has changed. * * @param connection The {@code RemoteConnection} invoking this method. * @param conference The {@code RemoteConference} of which this {@code RemoteConnection} is * a part, which may be {@code null}. */ public void onConferenceChanged( RemoteConnection connection, RemoteConference conference) {} /** * Handles changes to the {@code RemoteConnection} extras. * * @param connection The {@code RemoteConnection} invoking this method. * @param extras The extras containing other information associated with the connection. */ public void onExtrasChanged(RemoteConnection connection, @Nullable Bundle extras) {} /** * Handles a connection event propagated to this {@link RemoteConnection}. *

* Connection events originate from {@link Connection#sendConnectionEvent(String, Bundle)}. * * @param connection The {@code RemoteConnection} invoking this method. * @param event The connection event. * @param extras Extras associated with the event. * @hide */ public void onConnectionEvent(RemoteConnection connection, String event, Bundle extras) {} } /** * {@link RemoteConnection.VideoProvider} associated with a {@link RemoteConnection}. Used to * receive video related events and control the video associated with a * {@link RemoteConnection}. * * @see Connection.VideoProvider */ public static class VideoProvider { /** * Callback class used by the {@link RemoteConnection.VideoProvider} to relay events from * the {@link Connection.VideoProvider}. */ public abstract static class Callback { /** * Reports a session modification request received from the * {@link Connection.VideoProvider} associated with a {@link RemoteConnection}. * * @param videoProvider The {@link RemoteConnection.VideoProvider} invoking this method. * @param videoProfile The requested video call profile. * @see InCallService.VideoCall.Callback#onSessionModifyRequestReceived(VideoProfile) * @see Connection.VideoProvider#receiveSessionModifyRequest(VideoProfile) */ public void onSessionModifyRequestReceived( VideoProvider videoProvider, VideoProfile videoProfile) {} /** * Reports a session modification response received from the * {@link Connection.VideoProvider} associated with a {@link RemoteConnection}. * * @param videoProvider The {@link RemoteConnection.VideoProvider} invoking this method. * @param status Status of the session modify request. * @param requestedProfile The original request which was sent to the peer device. * @param responseProfile The actual profile changes made by the peer device. * @see InCallService.VideoCall.Callback#onSessionModifyResponseReceived(int, * VideoProfile, VideoProfile) * @see Connection.VideoProvider#receiveSessionModifyResponse(int, VideoProfile, * VideoProfile) */ public void onSessionModifyResponseReceived( VideoProvider videoProvider, int status, VideoProfile requestedProfile, VideoProfile responseProfile) {} /** * Reports a call session event received from the {@link Connection.VideoProvider} * associated with a {@link RemoteConnection}. * * @param videoProvider The {@link RemoteConnection.VideoProvider} invoking this method. * @param event The event. * @see InCallService.VideoCall.Callback#onCallSessionEvent(int) * @see Connection.VideoProvider#handleCallSessionEvent(int) */ public void onCallSessionEvent(VideoProvider videoProvider, int event) {} /** * Reports a change in the peer video dimensions received from the * {@link Connection.VideoProvider} associated with a {@link RemoteConnection}. * * @param videoProvider The {@link RemoteConnection.VideoProvider} invoking this method. * @param width The updated peer video width. * @param height The updated peer video height. * @see InCallService.VideoCall.Callback#onPeerDimensionsChanged(int, int) * @see Connection.VideoProvider#changePeerDimensions(int, int) */ public void onPeerDimensionsChanged(VideoProvider videoProvider, int width, int height) {} /** * Reports a change in the data usage (in bytes) received from the * {@link Connection.VideoProvider} associated with a {@link RemoteConnection}. * * @param videoProvider The {@link RemoteConnection.VideoProvider} invoking this method. * @param dataUsage The updated data usage (in bytes). * @see InCallService.VideoCall.Callback#onCallDataUsageChanged(long) * @see Connection.VideoProvider#setCallDataUsage(long) */ public void onCallDataUsageChanged(VideoProvider videoProvider, long dataUsage) {} /** * Reports a change in the capabilities of the current camera, received from the * {@link Connection.VideoProvider} associated with a {@link RemoteConnection}. * * @param videoProvider The {@link RemoteConnection.VideoProvider} invoking this method. * @param cameraCapabilities The changed camera capabilities. * @see InCallService.VideoCall.Callback#onCameraCapabilitiesChanged( * VideoProfile.CameraCapabilities) * @see Connection.VideoProvider#changeCameraCapabilities( * VideoProfile.CameraCapabilities) */ public void onCameraCapabilitiesChanged( VideoProvider videoProvider, VideoProfile.CameraCapabilities cameraCapabilities) {} /** * Reports a change in the video quality received from the * {@link Connection.VideoProvider} associated with a {@link RemoteConnection}. * * @param videoProvider The {@link RemoteConnection.VideoProvider} invoking this method. * @param videoQuality The updated peer video quality. * @see InCallService.VideoCall.Callback#onVideoQualityChanged(int) * @see Connection.VideoProvider#changeVideoQuality(int) */ public void onVideoQualityChanged(VideoProvider videoProvider, int videoQuality) {} } private final IVideoCallback mVideoCallbackDelegate = new IVideoCallback() { @Override public void receiveSessionModifyRequest(VideoProfile videoProfile) { for (Callback l : mCallbacks) { l.onSessionModifyRequestReceived(VideoProvider.this, videoProfile); } } @Override public void receiveSessionModifyResponse(int status, VideoProfile requestedProfile, VideoProfile responseProfile) { for (Callback l : mCallbacks) { l.onSessionModifyResponseReceived( VideoProvider.this, status, requestedProfile, responseProfile); } } @Override public void handleCallSessionEvent(int event) { for (Callback l : mCallbacks) { l.onCallSessionEvent(VideoProvider.this, event); } } @Override public void changePeerDimensions(int width, int height) { for (Callback l : mCallbacks) { l.onPeerDimensionsChanged(VideoProvider.this, width, height); } } @Override public void changeCallDataUsage(long dataUsage) { for (Callback l : mCallbacks) { l.onCallDataUsageChanged(VideoProvider.this, dataUsage); } } @Override public void changeCameraCapabilities( VideoProfile.CameraCapabilities cameraCapabilities) { for (Callback l : mCallbacks) { l.onCameraCapabilitiesChanged(VideoProvider.this, cameraCapabilities); } } @Override public void changeVideoQuality(int videoQuality) { for (Callback l : mCallbacks) { l.onVideoQualityChanged(VideoProvider.this, videoQuality); } } @Override public IBinder asBinder() { return null; } }; private final VideoCallbackServant mVideoCallbackServant = new VideoCallbackServant(mVideoCallbackDelegate); private final IVideoProvider mVideoProviderBinder; /** * ConcurrentHashMap constructor params: 8 is initial table size, 0.9f is * load factor before resizing, 1 means we only expect a single thread to * access the map so make only a single shard */ private final Set mCallbacks = Collections.newSetFromMap( new ConcurrentHashMap(8, 0.9f, 1)); VideoProvider(IVideoProvider videoProviderBinder) { mVideoProviderBinder = videoProviderBinder; try { mVideoProviderBinder.addVideoCallback(mVideoCallbackServant.getStub().asBinder()); } catch (RemoteException e) { } } /** * Registers a callback to receive commands and state changes for video calls. * * @param l The video call callback. */ public void registerCallback(Callback l) { mCallbacks.add(l); } /** * Clears the video call callback set via {@link #registerCallback}. * * @param l The video call callback to clear. */ public void unregisterCallback(Callback l) { mCallbacks.remove(l); } /** * Sets the camera to be used for the outgoing video for the * {@link RemoteConnection.VideoProvider}. * * @param cameraId The id of the camera (use ids as reported by * {@link CameraManager#getCameraIdList()}). * @see Connection.VideoProvider#onSetCamera(String) */ public void setCamera(String cameraId) { try { mVideoProviderBinder.setCamera(cameraId); } catch (RemoteException e) { } } /** * Sets the surface to be used for displaying a preview of what the user's camera is * currently capturing for the {@link RemoteConnection.VideoProvider}. * * @param surface The {@link Surface}. * @see Connection.VideoProvider#onSetPreviewSurface(Surface) */ public void setPreviewSurface(Surface surface) { try { mVideoProviderBinder.setPreviewSurface(surface); } catch (RemoteException e) { } } /** * Sets the surface to be used for displaying the video received from the remote device for * the {@link RemoteConnection.VideoProvider}. * * @param surface The {@link Surface}. * @see Connection.VideoProvider#onSetDisplaySurface(Surface) */ public void setDisplaySurface(Surface surface) { try { mVideoProviderBinder.setDisplaySurface(surface); } catch (RemoteException e) { } } /** * Sets the device orientation, in degrees, for the {@link RemoteConnection.VideoProvider}. * Assumes that a standard portrait orientation of the device is 0 degrees. * * @param rotation The device orientation, in degrees. * @see Connection.VideoProvider#onSetDeviceOrientation(int) */ public void setDeviceOrientation(int rotation) { try { mVideoProviderBinder.setDeviceOrientation(rotation); } catch (RemoteException e) { } } /** * Sets camera zoom ratio for the {@link RemoteConnection.VideoProvider}. * * @param value The camera zoom ratio. * @see Connection.VideoProvider#onSetZoom(float) */ public void setZoom(float value) { try { mVideoProviderBinder.setZoom(value); } catch (RemoteException e) { } } /** * Issues a request to modify the properties of the current video session for the * {@link RemoteConnection.VideoProvider}. * * @param fromProfile The video profile prior to the request. * @param toProfile The video profile with the requested changes made. * @see Connection.VideoProvider#onSendSessionModifyRequest(VideoProfile, VideoProfile) */ public void sendSessionModifyRequest(VideoProfile fromProfile, VideoProfile toProfile) { try { mVideoProviderBinder.sendSessionModifyRequest(fromProfile, toProfile); } catch (RemoteException e) { } } /** * Provides a response to a request to change the current call video session * properties for the {@link RemoteConnection.VideoProvider}. * * @param responseProfile The response call video properties. * @see Connection.VideoProvider#onSendSessionModifyResponse(VideoProfile) */ public void sendSessionModifyResponse(VideoProfile responseProfile) { try { mVideoProviderBinder.sendSessionModifyResponse(responseProfile); } catch (RemoteException e) { } } /** * Issues a request to retrieve the capabilities of the current camera for the * {@link RemoteConnection.VideoProvider}. * * @see Connection.VideoProvider#onRequestCameraCapabilities() */ public void requestCameraCapabilities() { try { mVideoProviderBinder.requestCameraCapabilities(); } catch (RemoteException e) { } } /** * Issues a request to retrieve the data usage (in bytes) of the video portion of the * {@link RemoteConnection} for the {@link RemoteConnection.VideoProvider}. * * @see Connection.VideoProvider#onRequestConnectionDataUsage() */ public void requestCallDataUsage() { try { mVideoProviderBinder.requestCallDataUsage(); } catch (RemoteException e) { } } /** * Sets the {@link Uri} of an image to be displayed to the peer device when the video signal * is paused, for the {@link RemoteConnection.VideoProvider}. * * @see Connection.VideoProvider#onSetPauseImage(Uri) */ public void setPauseImage(Uri uri) { try { mVideoProviderBinder.setPauseImage(uri); } catch (RemoteException e) { } } } private IConnectionService mConnectionService; private final String mConnectionId; /** * ConcurrentHashMap constructor params: 8 is initial table size, 0.9f is * load factor before resizing, 1 means we only expect a single thread to * access the map so make only a single shard */ private final Set mCallbackRecords = Collections.newSetFromMap( new ConcurrentHashMap(8, 0.9f, 1)); private final List mConferenceableConnections = new ArrayList<>(); private final List mUnmodifiableconferenceableConnections = Collections.unmodifiableList(mConferenceableConnections); private int mState = Connection.STATE_NEW; private DisconnectCause mDisconnectCause; private boolean mRingbackRequested; private boolean mConnected; private int mConnectionCapabilities; private int mConnectionProperties; private int mVideoState; private VideoProvider mVideoProvider; private boolean mIsVoipAudioMode; private StatusHints mStatusHints; private Uri mAddress; private int mAddressPresentation; private String mCallerDisplayName; private int mCallerDisplayNamePresentation; private RemoteConference mConference; private Bundle mExtras; /** * @hide */ RemoteConnection( String id, IConnectionService connectionService, ConnectionRequest request) { mConnectionId = id; mConnectionService = connectionService; mConnected = true; mState = Connection.STATE_INITIALIZING; } /** * @hide */ RemoteConnection(String callId, IConnectionService connectionService, ParcelableConnection connection) { mConnectionId = callId; mConnectionService = connectionService; mConnected = true; mState = connection.getState(); mDisconnectCause = connection.getDisconnectCause(); mRingbackRequested = connection.isRingbackRequested(); mConnectionCapabilities = connection.getConnectionCapabilities(); mConnectionProperties = connection.getConnectionProperties(); mVideoState = connection.getVideoState(); mVideoProvider = new RemoteConnection.VideoProvider(connection.getVideoProvider()); mIsVoipAudioMode = connection.getIsVoipAudioMode(); mStatusHints = connection.getStatusHints(); mAddress = connection.getHandle(); mAddressPresentation = connection.getHandlePresentation(); mCallerDisplayName = connection.getCallerDisplayName(); mCallerDisplayNamePresentation = connection.getCallerDisplayNamePresentation(); mConference = null; } /** * Create a RemoteConnection which is used for failed connections. Note that using it for any * "real" purpose will almost certainly fail. Callers should note the failure and act * accordingly (moving on to another RemoteConnection, for example) * * @param disconnectCause The reason for the failed connection. * @hide */ RemoteConnection(DisconnectCause disconnectCause) { mConnectionId = "NULL"; mConnected = false; mState = Connection.STATE_DISCONNECTED; mDisconnectCause = disconnectCause; } /** * Adds a callback to this {@code RemoteConnection}. * * @param callback A {@code Callback}. */ public void registerCallback(Callback callback) { registerCallback(callback, new Handler()); } /** * Adds a callback to this {@code RemoteConnection}. * * @param callback A {@code Callback}. * @param handler A {@code Handler} which command and status changes will be delivered to. */ public void registerCallback(Callback callback, Handler handler) { unregisterCallback(callback); if (callback != null && handler != null) { mCallbackRecords.add(new CallbackRecord(callback, handler)); } } /** * Removes a callback from this {@code RemoteConnection}. * * @param callback A {@code Callback}. */ public void unregisterCallback(Callback callback) { if (callback != null) { for (CallbackRecord record : mCallbackRecords) { if (record.getCallback() == callback) { mCallbackRecords.remove(record); break; } } } } /** * Obtains the state of this {@code RemoteConnection}. * * @return A state value, chosen from the {@code STATE_*} constants. */ public int getState() { return mState; } /** * Obtains the reason why this {@code RemoteConnection} may have been disconnected. * * @return For a {@link Connection#STATE_DISCONNECTED} {@code RemoteConnection}, the * disconnect cause expressed as a code chosen from among those declared in * {@link DisconnectCause}. */ public DisconnectCause getDisconnectCause() { return mDisconnectCause; } /** * Obtains the capabilities of this {@code RemoteConnection}. * * @return A bitmask of the capabilities of the {@code RemoteConnection}, as defined in * the {@code CAPABILITY_*} constants in class {@link Connection}. */ public int getConnectionCapabilities() { return mConnectionCapabilities; } /** * Obtains the properties of this {@code RemoteConnection}. * * @return A bitmask of the properties of the {@code RemoteConnection}, as defined in the * {@code PROPERTY_*} constants in class {@link Connection}. * @hide */ public int getConnectionProperties() { return mConnectionProperties; } /** * Determines if the audio mode of this {@code RemoteConnection} is VOIP. * * @return {@code true} if the {@code RemoteConnection}'s current audio mode is VOIP. */ public boolean isVoipAudioMode() { return mIsVoipAudioMode; } /** * Obtains status hints pertaining to this {@code RemoteConnection}. * * @return The current {@link StatusHints} of this {@code RemoteConnection}, * or {@code null} if none have been set. */ public StatusHints getStatusHints() { return mStatusHints; } /** * Obtains the address of this {@code RemoteConnection}. * * @return The address (e.g., phone number) to which the {@code RemoteConnection} * is currently connected. */ public Uri getAddress() { return mAddress; } /** * Obtains the presentation requirements for the address of this {@code RemoteConnection}. * * @return The presentation requirements for the address. See * {@link TelecomManager} for valid values. */ public int getAddressPresentation() { return mAddressPresentation; } /** * Obtains the display name for this {@code RemoteConnection}'s caller. * * @return The display name for the caller. */ public CharSequence getCallerDisplayName() { return mCallerDisplayName; } /** * Obtains the presentation requirements for this {@code RemoteConnection}'s * caller's display name. * * @return The presentation requirements for the caller display name. See * {@link TelecomManager} for valid values. */ public int getCallerDisplayNamePresentation() { return mCallerDisplayNamePresentation; } /** * Obtains the video state of this {@code RemoteConnection}. * * @return The video state of the {@code RemoteConnection}. See {@link VideoProfile}. */ public int getVideoState() { return mVideoState; } /** * Obtains the video provider of this {@code RemoteConnection}. * @return The video provider associated with this {@code RemoteConnection}. */ public final VideoProvider getVideoProvider() { return mVideoProvider; } /** * Obtain the extras associated with this {@code RemoteConnection}. * * @return The extras for this connection. */ public final Bundle getExtras() { return mExtras; } /** * Determines whether this {@code RemoteConnection} is requesting ringback. * * @return Whether the {@code RemoteConnection} is requesting that the framework play a * ringback tone on its behalf. */ public boolean isRingbackRequested() { return mRingbackRequested; } /** * Instructs this {@code RemoteConnection} to abort. */ public void abort() { try { if (mConnected) { mConnectionService.abort(mConnectionId); } } catch (RemoteException ignored) { } } /** * Instructs this {@link Connection#STATE_RINGING} {@code RemoteConnection} to answer. */ public void answer() { try { if (mConnected) { mConnectionService.answer(mConnectionId); } } catch (RemoteException ignored) { } } /** * Instructs this {@link Connection#STATE_RINGING} {@code RemoteConnection} to answer. * @param videoState The video state in which to answer the call. * @hide */ public void answer(int videoState) { try { if (mConnected) { mConnectionService.answerVideo(mConnectionId, videoState); } } catch (RemoteException ignored) { } } /** * Instructs this {@link Connection#STATE_RINGING} {@code RemoteConnection} to reject. */ public void reject() { try { if (mConnected) { mConnectionService.reject(mConnectionId); } } catch (RemoteException ignored) { } } /** * Instructs this {@code RemoteConnection} to go on hold. */ public void hold() { try { if (mConnected) { mConnectionService.hold(mConnectionId); } } catch (RemoteException ignored) { } } /** * Instructs this {@link Connection#STATE_HOLDING} call to release from hold. */ public void unhold() { try { if (mConnected) { mConnectionService.unhold(mConnectionId); } } catch (RemoteException ignored) { } } /** * Instructs this {@code RemoteConnection} to disconnect. */ public void disconnect() { try { if (mConnected) { mConnectionService.disconnect(mConnectionId); } } catch (RemoteException ignored) { } } /** * Instructs this {@code RemoteConnection} to play a dual-tone multi-frequency signaling * (DTMF) tone. * * Any other currently playing DTMF tone in the specified call is immediately stopped. * * @param digit A character representing the DTMF digit for which to play the tone. This * value must be one of {@code '0'} through {@code '9'}, {@code '*'} or {@code '#'}. */ public void playDtmfTone(char digit) { try { if (mConnected) { mConnectionService.playDtmfTone(mConnectionId, digit); } } catch (RemoteException ignored) { } } /** * Instructs this {@code RemoteConnection} to stop any dual-tone multi-frequency signaling * (DTMF) tone currently playing. * * DTMF tones are played by calling {@link #playDtmfTone(char)}. If no DTMF tone is * currently playing, this method will do nothing. */ public void stopDtmfTone() { try { if (mConnected) { mConnectionService.stopDtmfTone(mConnectionId); } } catch (RemoteException ignored) { } } /** * Instructs this {@code RemoteConnection} to continue playing a post-dial DTMF string. * * A post-dial DTMF string is a string of digits following the first instance of either * {@link TelecomManager#DTMF_CHARACTER_WAIT} or {@link TelecomManager#DTMF_CHARACTER_PAUSE}. * These digits are immediately sent as DTMF tones to the recipient as soon as the * connection is made. * * If the DTMF string contains a {@link TelecomManager#DTMF_CHARACTER_PAUSE} symbol, this * {@code RemoteConnection} will temporarily pause playing the tones for a pre-defined period * of time. * * If the DTMF string contains a {@link TelecomManager#DTMF_CHARACTER_WAIT} symbol, this * {@code RemoteConnection} will pause playing the tones and notify callbacks via * {@link Callback#onPostDialWait(RemoteConnection, String)}. At this point, the in-call app * should display to the user an indication of this state and an affordance to continue * the postdial sequence. When the user decides to continue the postdial sequence, the in-call * app should invoke the {@link #postDialContinue(boolean)} method. * * @param proceed Whether or not to continue with the post-dial sequence. */ public void postDialContinue(boolean proceed) { try { if (mConnected) { mConnectionService.onPostDialContinue(mConnectionId, proceed); } } catch (RemoteException ignored) { } } /** * Instructs this {@link RemoteConnection} to pull itself to the local device. *

* See {@link Call#pullExternalCall()} for more information. * @hide */ public void pullExternalCall() { try { if (mConnected) { mConnectionService.pullExternalCall(mConnectionId); } } catch (RemoteException ignored) { } } /** * Set the audio state of this {@code RemoteConnection}. * * @param state The audio state of this {@code RemoteConnection}. * @hide * @deprecated Use {@link #setCallAudioState(CallAudioState) instead. */ @SystemApi @Deprecated public void setAudioState(AudioState state) { setCallAudioState(new CallAudioState(state)); } /** * Set the audio state of this {@code RemoteConnection}. * * @param state The audio state of this {@code RemoteConnection}. */ public void setCallAudioState(CallAudioState state) { try { if (mConnected) { mConnectionService.onCallAudioStateChanged(mConnectionId, state); } } catch (RemoteException ignored) { } } /** * Obtain the {@code RemoteConnection}s with which this {@code RemoteConnection} may be * successfully asked to create a conference with. * * @return The {@code RemoteConnection}s with which this {@code RemoteConnection} may be * merged into a {@link RemoteConference}. */ public List getConferenceableConnections() { return mUnmodifiableconferenceableConnections; } /** * Obtain the {@code RemoteConference} that this {@code RemoteConnection} may be a part * of, or {@code null} if there is no such {@code RemoteConference}. * * @return A {@code RemoteConference} or {@code null}; */ public RemoteConference getConference() { return mConference; } /** {@hide} */ String getId() { return mConnectionId; } /** {@hide} */ IConnectionService getConnectionService() { return mConnectionService; } /** * @hide */ void setState(final int state) { if (mState != state) { mState = state; for (CallbackRecord record: mCallbackRecords) { final RemoteConnection connection = this; final Callback callback = record.getCallback(); record.getHandler().post(new Runnable() { @Override public void run() { callback.onStateChanged(connection, state); } }); } } } /** * @hide */ void setDisconnected(final DisconnectCause disconnectCause) { if (mState != Connection.STATE_DISCONNECTED) { mState = Connection.STATE_DISCONNECTED; mDisconnectCause = disconnectCause; for (CallbackRecord record : mCallbackRecords) { final RemoteConnection connection = this; final Callback callback = record.getCallback(); record.getHandler().post(new Runnable() { @Override public void run() { callback.onDisconnected(connection, disconnectCause); } }); } } } /** * @hide */ void setRingbackRequested(final boolean ringback) { if (mRingbackRequested != ringback) { mRingbackRequested = ringback; for (CallbackRecord record : mCallbackRecords) { final RemoteConnection connection = this; final Callback callback = record.getCallback(); record.getHandler().post(new Runnable() { @Override public void run() { callback.onRingbackRequested(connection, ringback); } }); } } } /** * @hide */ void setConnectionCapabilities(final int connectionCapabilities) { mConnectionCapabilities = connectionCapabilities; for (CallbackRecord record : mCallbackRecords) { final RemoteConnection connection = this; final Callback callback = record.getCallback(); record.getHandler().post(new Runnable() { @Override public void run() { callback.onConnectionCapabilitiesChanged(connection, connectionCapabilities); } }); } } /** * @hide */ void setConnectionProperties(final int connectionProperties) { mConnectionProperties = connectionProperties; for (CallbackRecord record : mCallbackRecords) { final RemoteConnection connection = this; final Callback callback = record.getCallback(); record.getHandler().post(new Runnable() { @Override public void run() { callback.onConnectionPropertiesChanged(connection, connectionProperties); } }); } } /** * @hide */ void setDestroyed() { if (!mCallbackRecords.isEmpty()) { // Make sure that the callbacks are notified that the call is destroyed first. if (mState != Connection.STATE_DISCONNECTED) { setDisconnected( new DisconnectCause(DisconnectCause.ERROR, "Connection destroyed.")); } for (CallbackRecord record : mCallbackRecords) { final RemoteConnection connection = this; final Callback callback = record.getCallback(); record.getHandler().post(new Runnable() { @Override public void run() { callback.onDestroyed(connection); } }); } mCallbackRecords.clear(); mConnected = false; } } /** * @hide */ void setPostDialWait(final String remainingDigits) { for (CallbackRecord record : mCallbackRecords) { final RemoteConnection connection = this; final Callback callback = record.getCallback(); record.getHandler().post(new Runnable() { @Override public void run() { callback.onPostDialWait(connection, remainingDigits); } }); } } /** * @hide */ void onPostDialChar(final char nextChar) { for (CallbackRecord record : mCallbackRecords) { final RemoteConnection connection = this; final Callback callback = record.getCallback(); record.getHandler().post(new Runnable() { @Override public void run() { callback.onPostDialChar(connection, nextChar); } }); } } /** * @hide */ void setVideoState(final int videoState) { mVideoState = videoState; for (CallbackRecord record : mCallbackRecords) { final RemoteConnection connection = this; final Callback callback = record.getCallback(); record.getHandler().post(new Runnable() { @Override public void run() { callback.onVideoStateChanged(connection, videoState); } }); } } /** * @hide */ void setVideoProvider(final VideoProvider videoProvider) { mVideoProvider = videoProvider; for (CallbackRecord record : mCallbackRecords) { final RemoteConnection connection = this; final Callback callback = record.getCallback(); record.getHandler().post(new Runnable() { @Override public void run() { callback.onVideoProviderChanged(connection, videoProvider); } }); } } /** @hide */ void setIsVoipAudioMode(final boolean isVoip) { mIsVoipAudioMode = isVoip; for (CallbackRecord record : mCallbackRecords) { final RemoteConnection connection = this; final Callback callback = record.getCallback(); record.getHandler().post(new Runnable() { @Override public void run() { callback.onVoipAudioChanged(connection, isVoip); } }); } } /** @hide */ void setStatusHints(final StatusHints statusHints) { mStatusHints = statusHints; for (CallbackRecord record : mCallbackRecords) { final RemoteConnection connection = this; final Callback callback = record.getCallback(); record.getHandler().post(new Runnable() { @Override public void run() { callback.onStatusHintsChanged(connection, statusHints); } }); } } /** @hide */ void setAddress(final Uri address, final int presentation) { mAddress = address; mAddressPresentation = presentation; for (CallbackRecord record : mCallbackRecords) { final RemoteConnection connection = this; final Callback callback = record.getCallback(); record.getHandler().post(new Runnable() { @Override public void run() { callback.onAddressChanged(connection, address, presentation); } }); } } /** @hide */ void setCallerDisplayName(final String callerDisplayName, final int presentation) { mCallerDisplayName = callerDisplayName; mCallerDisplayNamePresentation = presentation; for (CallbackRecord record : mCallbackRecords) { final RemoteConnection connection = this; final Callback callback = record.getCallback(); record.getHandler().post(new Runnable() { @Override public void run() { callback.onCallerDisplayNameChanged( connection, callerDisplayName, presentation); } }); } } /** @hide */ void setConferenceableConnections(final List conferenceableConnections) { mConferenceableConnections.clear(); mConferenceableConnections.addAll(conferenceableConnections); for (CallbackRecord record : mCallbackRecords) { final RemoteConnection connection = this; final Callback callback = record.getCallback(); record.getHandler().post(new Runnable() { @Override public void run() { callback.onConferenceableConnectionsChanged( connection, mUnmodifiableconferenceableConnections); } }); } } /** @hide */ void setConference(final RemoteConference conference) { if (mConference != conference) { mConference = conference; for (CallbackRecord record : mCallbackRecords) { final RemoteConnection connection = this; final Callback callback = record.getCallback(); record.getHandler().post(new Runnable() { @Override public void run() { callback.onConferenceChanged(connection, conference); } }); } } } /** @hide */ void putExtras(final Bundle extras) { if (mExtras == null) { mExtras = new Bundle(); } mExtras.putAll(extras); notifyExtrasChanged(); } /** @hide */ void removeExtras(List keys) { if (mExtras == null || keys == null || keys.isEmpty()) { return; } for (String key : keys) { mExtras.remove(key); } notifyExtrasChanged(); } private void notifyExtrasChanged() { for (CallbackRecord record : mCallbackRecords) { final RemoteConnection connection = this; final Callback callback = record.getCallback(); record.getHandler().post(new Runnable() { @Override public void run() { callback.onExtrasChanged(connection, mExtras); } }); } } /** @hide */ void onConnectionEvent(final String event, final Bundle extras) { for (CallbackRecord record : mCallbackRecords) { final RemoteConnection connection = this; final Callback callback = record.getCallback(); record.getHandler().post(new Runnable() { @Override public void run() { callback.onConnectionEvent(connection, event, extras); } }); } } /** * Create a RemoteConnection represents a failure, and which will be in * {@link Connection#STATE_DISCONNECTED}. Attempting to use it for anything will almost * certainly result in bad things happening. Do not do this. * * @return a failed {@link RemoteConnection} * * @hide */ public static RemoteConnection failure(DisconnectCause disconnectCause) { return new RemoteConnection(disconnectCause); } private static final class CallbackRecord extends Callback { private final Callback mCallback; private final Handler mHandler; public CallbackRecord(Callback callback, Handler handler) { mCallback = callback; mHandler = handler; } public Callback getCallback() { return mCallback; } public Handler getHandler() { return mHandler; } } }