blob: b55f62a296adee231bc20105590afe67527a671e [file] [log] [blame]
/*
* 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.telecomm;
import android.app.PendingIntent;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import com.android.internal.os.SomeArgs;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* Represents a connection to a remote endpoint that carries voice traffic.
*/
public abstract class Connection {
private static final int MSG_ADD_CONNECTION_LISTENER = 1;
private static final int MSG_REMOVE_CONNECTION_LISTENER = 2;
private static final int MSG_SET_AUDIO_STATE = 3;
private static final int MSG_SET_PARENT_CONNECTION = 4;
private static final int MSG_SET_HANDLE = 5;
private static final int MSG_SET_CALLER_DISPLAY_NAME = 6;
private static final int MSG_SET_CANCELED = 7;
private static final int MSG_SET_FAILED = 8;
private static final int MSG_SET_VIDEO_STATE = 9;
private static final int MSG_SET_ACTIVE = 10;
private static final int MSG_SET_RINGING = 11;
private static final int MSG_SET_INITIALIZING = 12;
private static final int MSG_SET_INITIALIZED = 13;
private static final int MSG_SET_DIALING = 14;
private static final int MSG_SET_ON_HOLD = 15;
private static final int MSG_SET_VIDEO_CALL_PROVIDER = 16;
private static final int MSG_SET_DISCONNECTED = 17;
private static final int MSG_SET_POST_DIAL_WAIT = 18;
private static final int MSG_SET_REQUESTING_RINGBACK = 19;
private static final int MSG_SET_CALL_CAPABILITIES = 20;
private static final int MSG_DESTROY = 21;
private static final int MSG_SET_SIGNAL = 22;
private static final int MSG_SET_AUDIO_MODE_IS_VOIP = 23;
private static final int MSG_SET_STATUS_HINTS = 24;
private static final int MSG_START_ACTIVITY_FROM_IN_CALL = 25;
/** @hide */
public abstract static class Listener {
public void onStateChanged(Connection c, int state) {}
public void onHandleChanged(Connection c, Uri newHandle, int presentation) {}
public void onCallerDisplayNameChanged(
Connection c, String callerDisplayName, int presentation) {}
public void onVideoStateChanged(Connection c, int videoState) {}
public void onSignalChanged(Connection c, Bundle details) {}
public void onDisconnected(Connection c, int cause, String message) {}
public void onPostDialWait(Connection c, String remaining) {}
public void onRequestingRingback(Connection c, boolean ringback) {}
public void onDestroyed(Connection c) {}
public void onCallCapabilitiesChanged(Connection c, int callCapabilities) {}
public void onParentConnectionChanged(Connection c, Connection parent) {}
public void onVideoCallProviderChanged(
Connection c, ConnectionService.VideoCallProvider videoCallProvider) {}
public void onAudioModeIsVoipChanged(Connection c, boolean isVoip) {}
public void onStatusHintsChanged(Connection c, StatusHints statusHints) {}
public void onStartActivityFromInCall(Connection c, PendingIntent intent) {}
}
public final class State {
private State() {}
public static final int INITIALIZING = 0;
public static final int NEW = 1;
public static final int RINGING = 2;
public static final int DIALING = 3;
public static final int ACTIVE = 4;
public static final int HOLDING = 5;
public static final int DISCONNECTED = 6;
public static final int FAILED = 7;
public static final int CANCELED = 8;
}
private final Set<Listener> mListeners = new HashSet<>();
private final List<Connection> mChildConnections = new ArrayList<>();
private int mState = State.NEW;
private CallAudioState mCallAudioState;
private Uri mHandle;
private int mHandlePresentation;
private String mCallerDisplayName;
private int mCallerDisplayNamePresentation;
private boolean mRequestingRingback = false;
private int mCallCapabilities;
private Connection mParentConnection;
private ConnectionService.VideoCallProvider mVideoCallProvider;
private boolean mAudioModeIsVoip;
private StatusHints mStatusHints;
private int mVideoState;
private int mFailureCode;
private String mFailureMessage;
private boolean mIsCanceled;
private final Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_ADD_CONNECTION_LISTENER: {
Listener listener = (Listener) msg.obj;
mListeners.add(listener);
}
break;
case MSG_REMOVE_CONNECTION_LISTENER: {
Listener listener = (Listener) msg.obj;
mListeners.remove(listener);
}
break;
case MSG_SET_AUDIO_STATE: {
CallAudioState state = (CallAudioState) msg.obj;
mCallAudioState = state;
onSetAudioState(state);
}
break;
case MSG_SET_PARENT_CONNECTION: {
Connection parentConnection = (Connection) msg.obj;
if (mParentConnection != parentConnection) {
if (mParentConnection != null) {
mParentConnection.removeChild(Connection.this);
}
mParentConnection = parentConnection;
if (mParentConnection != null) {
mParentConnection.addChild(Connection.this);
// do something if the child connections goes down to ZERO.
}
for (Listener l : mListeners) {
l.onParentConnectionChanged(Connection.this, mParentConnection);
}
}
}
break;
case MSG_SET_HANDLE: {
SomeArgs args = (SomeArgs) msg.obj;
try {
Uri handle = (Uri) args.arg1;
int presentation = args.argi1;
mHandle = handle;
mHandlePresentation = presentation;
for (Listener l : mListeners) {
l.onHandleChanged(Connection.this, handle, presentation);
}
} finally {
args.recycle();
}
}
break;
case MSG_SET_CALLER_DISPLAY_NAME: {
SomeArgs args = (SomeArgs) msg.obj;
try {
String callerDisplayName = (String) args.arg1;
int presentation = args.argi1;
mCallerDisplayName = callerDisplayName;
mCallerDisplayNamePresentation = presentation;
for (Listener l : mListeners) {
l.onCallerDisplayNameChanged(Connection.this, callerDisplayName,
presentation);
}
} finally {
args.recycle();
}
}
break;
case MSG_SET_CANCELED: {
setState(State.CANCELED);
}
break;
case MSG_SET_FAILED: {
SomeArgs args = (SomeArgs) msg.obj;
try {
int code = args.argi1;
String message = (String) args.arg1;
mFailureCode = code;
mFailureMessage = message;
setState(State.FAILED);
} finally {
args.recycle();
}
}
break;
case MSG_SET_VIDEO_STATE: {
int videoState = ((Integer) msg.obj).intValue();
mVideoState = videoState;
for (Listener l : mListeners) {
l.onVideoStateChanged(Connection.this, mVideoState);
}
}
break;
case MSG_SET_ACTIVE: {
setRequestingRingback(false);
setState(State.ACTIVE);
}
break;
case MSG_SET_RINGING: {
setState(State.RINGING);
}
break;
case MSG_SET_INITIALIZING: {
setState(State.INITIALIZING);
}
break;
case MSG_SET_INITIALIZED: {
setState(State.NEW);
}
break;
case MSG_SET_DIALING: {
setState(State.DIALING);
}
break;
case MSG_SET_ON_HOLD: {
setState(State.HOLDING);
}
break;
case MSG_SET_VIDEO_CALL_PROVIDER: {
ConnectionService.VideoCallProvider videoCallProvider =
(ConnectionService.VideoCallProvider) msg.obj;
mVideoCallProvider = videoCallProvider;
for (Listener l : mListeners) {
l.onVideoCallProviderChanged(Connection.this, videoCallProvider);
}
}
break;
case MSG_SET_DISCONNECTED: {
SomeArgs args = (SomeArgs) msg.obj;
try {
int cause = args.argi1;
String message = (String) args.arg1;
setState(State.DISCONNECTED);
Log.d(this, "Disconnected with cause %d message %s", cause, message);
for (Listener l : mListeners) {
l.onDisconnected(Connection.this, cause, message);
}
} finally {
args.recycle();
}
}
break;
case MSG_SET_POST_DIAL_WAIT: {
String remaining = (String) msg.obj;
for (Listener l : mListeners) {
l.onPostDialWait(Connection.this, remaining);
}
}
break;
case MSG_SET_REQUESTING_RINGBACK: {
boolean ringback = ((Boolean) msg.obj).booleanValue();
if (mRequestingRingback != ringback) {
mRequestingRingback = ringback;
for (Listener l : mListeners) {
l.onRequestingRingback(Connection.this, ringback);
}
}
} break;
case MSG_SET_CALL_CAPABILITIES: {
int callCapabilities = ((Integer) msg.obj).intValue();
if (mCallCapabilities != callCapabilities) {
mCallCapabilities = callCapabilities;
for (Listener l : mListeners) {
l.onCallCapabilitiesChanged(Connection.this, mCallCapabilities);
}
}
}
break;
case MSG_DESTROY: {
// TODO: Is this still relevant because everything is on the main thread now.
// It is possible that onDestroy() will trigger the listener to remove itself
// which will result in a concurrent modification exception. To counteract
// this we make a copy of the listeners and iterate on that.
for (Listener l : new ArrayList<>(mListeners)) {
if (mListeners.contains(l)) {
l.onDestroyed(Connection.this);
}
}
}
break;
case MSG_SET_SIGNAL: {
Bundle details = (Bundle) msg.obj;
for (Listener l : mListeners) {
l.onSignalChanged(Connection.this, details);
}
}
break;
case MSG_SET_AUDIO_MODE_IS_VOIP: {
boolean isVoip = ((Boolean) msg.obj).booleanValue();
mAudioModeIsVoip = isVoip;
for (Listener l : mListeners) {
l.onAudioModeIsVoipChanged(Connection.this, isVoip);
}
}
break;
case MSG_SET_STATUS_HINTS: {
StatusHints statusHints = (StatusHints) msg.obj;
mStatusHints = statusHints;
for (Listener l : mListeners) {
l.onStatusHintsChanged(Connection.this, statusHints);
}
}
break;
case MSG_START_ACTIVITY_FROM_IN_CALL: {
PendingIntent intent = (PendingIntent) msg.obj;
for (Listener l : mListeners) {
l.onStartActivityFromInCall(Connection.this, intent);
}
}
break;
}
}
};
/**
* Create a new Connection.
*/
public Connection() {}
/**
* @return The handle (e.g., phone number) to which this Connection is currently communicating.
*/
public final Uri getHandle() {
return mHandle;
}
/**
* @return The {@link CallPropertyPresentation} which controls how the handle is shown.
*/
public final int getHandlePresentation() {
return mHandlePresentation;
}
/**
* @return The caller display name (CNAP).
*/
public final String getCallerDisplayName() {
return mCallerDisplayName;
}
/**
* @return The {@link CallPropertyPresentation} which controls how the caller display name is
* shown.
*/
public final int getCallerDisplayNamePresentation() {
return mCallerDisplayNamePresentation;
}
/**
* @return The state of this Connection.
*/
public final int getState() {
return mState;
}
/**
* Returns the video state of the call.
* Valid values: {@link android.telecomm.VideoCallProfile.VideoState#AUDIO_ONLY},
* {@link android.telecomm.VideoCallProfile.VideoState#BIDIRECTIONAL},
* {@link android.telecomm.VideoCallProfile.VideoState#TX_ENABLED},
* {@link android.telecomm.VideoCallProfile.VideoState#RX_ENABLED}.
*
* @return The video state of the call.
*/
public final int getVideoState() {
return mVideoState;
}
/**
* @return The audio state of the call, describing how its audio is currently
* being routed by the system. This is {@code null} if this Connection
* does not directly know about its audio state.
*/
public final CallAudioState getCallAudioState() {
return mCallAudioState;
}
/**
* Returns whether this connection is requesting that the system play a ringback tone
* on its behalf.
*/
public final boolean isRequestingRingback() {
return mRequestingRingback;
}
/**
* Returns whether this connection is a conference connection (has child connections).
*/
public final boolean isConferenceConnection() {
return !mChildConnections.isEmpty();
}
/**
* @return True if the connection's audio mode is VOIP.
*/
public final boolean getAudioModeIsVoip() {
return mAudioModeIsVoip;
}
/**
* @return The status hints for this connection.
*/
public final StatusHints getStatusHints() {
return mStatusHints;
}
/**
* Assign a listener to be notified of state changes.
*
* @param l A listener.
* @return This Connection.
*
* @hide
*/
public final Connection addConnectionListener(Listener l) {
mHandler.obtainMessage(MSG_ADD_CONNECTION_LISTENER, l).sendToTarget();
return this;
}
/**
* Remove a previously assigned listener that was being notified of state changes.
*
* @param l A Listener.
* @return This Connection.
*
* @hide
*/
public final Connection removeConnectionListener(Listener l) {
mHandler.obtainMessage(MSG_REMOVE_CONNECTION_LISTENER, l).sendToTarget();
return this;
}
/**
* @return The failure code ({@see DisconnectCause}) associated with this failed connection.
*/
public final int getFailureCode() {
return mFailureCode;
}
/**
* @return The reason for the connection failure. This will not be displayed to the user.
*/
public final String getFailureMessage() {
return mFailureMessage;
}
/**
* Inform this Connection that the state of its audio output has been changed externally.
*
* @param state The new audio state.
* @hide
*/
final void setAudioState(CallAudioState state) {
Log.d(this, "setAudioState %s", state);
mHandler.obtainMessage(MSG_SET_AUDIO_STATE, state).sendToTarget();
}
/**
* @param state An integer value from {@link State}.
* @return A string representation of the value.
*/
public static String stateToString(int state) {
switch (state) {
case State.INITIALIZING:
return "INITIALIZING";
case State.NEW:
return "NEW";
case State.RINGING:
return "RINGING";
case State.DIALING:
return "DIALING";
case State.ACTIVE:
return "ACTIVE";
case State.HOLDING:
return "HOLDING";
case State.DISCONNECTED:
return "DISCONNECTED";
case State.FAILED:
return "FAILED";
case State.CANCELED:
return "CANCELED";
default:
Log.wtf(Connection.class, "Unknown state %d", state);
return "UNKNOWN";
}
}
/**
* TODO(santoscordon): Needs documentation.
*/
public final void setParentConnection(Connection parentConnection) {
Log.d(this, "parenting %s to %s", this, parentConnection);
mHandler.obtainMessage(MSG_SET_PARENT_CONNECTION, parentConnection).sendToTarget();
}
public final Connection getParentConnection() {
return mParentConnection;
}
public final List<Connection> getChildConnections() {
return mChildConnections;
}
/**
* Returns the connection's {@link CallCapabilities}
*/
public final int getCallCapabilities() {
return mCallCapabilities;
}
/**
* Sets the value of the {@link #getHandle()} property.
*
* @param handle The new handle.
* @param presentation The {@link CallPropertyPresentation} which controls how the handle is
* shown.
*/
public final void setHandle(Uri handle, int presentation) {
Log.d(this, "setHandle %s", handle);
SomeArgs args = SomeArgs.obtain();
args.arg1 = handle;
args.argi1 = presentation;
mHandler.obtainMessage(MSG_SET_HANDLE, args).sendToTarget();
}
/**
* Sets the caller display name (CNAP).
*
* @param callerDisplayName The new display name.
* @param presentation The {@link CallPropertyPresentation} which controls how the name is
* shown.
*/
public final void setCallerDisplayName(String callerDisplayName, int presentation) {
Log.d(this, "setCallerDisplayName %s", callerDisplayName);
SomeArgs args = SomeArgs.obtain();
args.arg1 = callerDisplayName;
args.argi1 = presentation;
mHandler.obtainMessage(MSG_SET_CALLER_DISPLAY_NAME, args).sendToTarget();
}
/**
* Cancel the {@link Connection}. Once this is called, the {@link Connection} will not be used,
* and no subsequent {@link Connection}s will be attempted.
*/
public final void setCanceled() {
Log.d(this, "setCanceled");
mHandler.obtainMessage(MSG_SET_CANCELED).sendToTarget();
}
/**
* Move the {@link Connection} to the {@link State#FAILED} state, with the given code
* ({@see DisconnectCause}) and message. This message is not shown to the user, but is useful
* for logging and debugging purposes.
* <p>
* After calling this, the {@link Connection} will not be used.
*
* @param code The {@link android.telephony.DisconnectCause} indicating why the connection
* failed.
* @param message A message explaining why the {@link Connection} failed.
*/
public final void setFailed(int code, String message) {
Log.d(this, "setFailed (%d: %s)", code, message);
SomeArgs args = SomeArgs.obtain();
args.argi1 = code;
args.arg1 = message;
mHandler.obtainMessage(MSG_SET_FAILED, args).sendToTarget();
}
/**
* Set the video state for the connection.
* Valid values: {@link android.telecomm.VideoCallProfile.VideoState#AUDIO_ONLY},
* {@link android.telecomm.VideoCallProfile.VideoState#BIDIRECTIONAL},
* {@link android.telecomm.VideoCallProfile.VideoState#TX_ENABLED},
* {@link android.telecomm.VideoCallProfile.VideoState#RX_ENABLED}.
*
* @param videoState The new video state.
*/
public final void setVideoState(int videoState) {
Log.d(this, "setVideoState %d", videoState);
mHandler.obtainMessage(MSG_SET_VIDEO_STATE, Integer.valueOf(videoState)).sendToTarget();
}
/**
* Sets state to active (e.g., an ongoing call where two or more parties can actively
* communicate).
*/
public final void setActive() {
mHandler.obtainMessage(MSG_SET_ACTIVE).sendToTarget();
}
/**
* Sets state to ringing (e.g., an inbound ringing call).
*/
public final void setRinging() {
mHandler.obtainMessage(MSG_SET_RINGING).sendToTarget();
}
/**
* Sets state to initializing (this Connection is not yet ready to be used).
*/
public final void setInitializing() {
mHandler.obtainMessage(MSG_SET_INITIALIZING).sendToTarget();
}
/**
* Sets state to initialized (the Connection has been set up and is now ready to be used).
*/
public final void setInitialized() {
mHandler.obtainMessage(MSG_SET_INITIALIZED).sendToTarget();
setState(State.NEW);
}
/**
* Sets state to dialing (e.g., dialing an outbound call).
*/
public final void setDialing() {
mHandler.obtainMessage(MSG_SET_DIALING).sendToTarget();
}
/**
* Sets state to be on hold.
*/
public final void setOnHold() {
mHandler.obtainMessage(MSG_SET_ON_HOLD).sendToTarget();
}
/**
* Sets the video call provider.
* @param videoCallProvider The video call provider.
*/
public final void setVideoCallProvider(ConnectionService.VideoCallProvider videoCallProvider) {
mHandler.obtainMessage(MSG_SET_VIDEO_CALL_PROVIDER, videoCallProvider).sendToTarget();
}
public final ConnectionService.VideoCallProvider getVideoCallProvider() {
return mVideoCallProvider;
}
/**
* Sets state to disconnected.
*
* @param cause The reason for the disconnection, any of
* {@link android.telephony.DisconnectCause}.
* @param message Optional call-service-provided message about the disconnect.
*/
public final void setDisconnected(int cause, String message) {
SomeArgs args = SomeArgs.obtain();
args.argi1 = cause;
args.arg1 = message;
mHandler.obtainMessage(MSG_SET_DISCONNECTED, args).sendToTarget();
}
/**
* TODO(santoscordon): Needs documentation.
*/
public final void setPostDialWait(String remaining) {
mHandler.obtainMessage(MSG_SET_POST_DIAL_WAIT, remaining).sendToTarget();
}
/**
* Requests that the framework play a ringback tone. This is to be invoked by implementations
* that do not play a ringback tone themselves in the call's audio stream.
*
* @param ringback Whether the ringback tone is to be played.
*/
public final void setRequestingRingback(boolean ringback) {
mHandler.obtainMessage(MSG_SET_REQUESTING_RINGBACK, Boolean.valueOf(ringback))
.sendToTarget();
}
/**
* Sets the connection's {@link CallCapabilities}.
*
* @param callCapabilities The new call capabilities.
*/
public final void setCallCapabilities(int callCapabilities) {
mHandler.obtainMessage(MSG_SET_CALL_CAPABILITIES, Integer.valueOf(callCapabilities))
.sendToTarget();
}
/**
* TODO(santoscordon): Needs documentation.
*/
public final void destroy() {
mHandler.obtainMessage(MSG_DESTROY).sendToTarget();
}
/**
* Sets the current signal levels for the underlying data transport.
*
* @param details A {@link android.os.Bundle} containing details of the current level.
*/
public final void setSignal(Bundle details) {
mHandler.obtainMessage(MSG_SET_SIGNAL, details).sendToTarget();
}
/**
* Requests that the framework use VOIP audio mode for this connection.
*
* @param isVoip True if the audio mode is VOIP.
*/
public final void setAudioModeIsVoip(boolean isVoip) {
mHandler.obtainMessage(MSG_SET_AUDIO_MODE_IS_VOIP, Boolean.valueOf(isVoip)).sendToTarget();
}
/**
* Sets the label and icon status to display in the in-call UI.
*
* @param statusHints The status label and icon to set.
*/
public final void setStatusHints(StatusHints statusHints) {
mHandler.obtainMessage(MSG_SET_STATUS_HINTS, statusHints).sendToTarget();
}
/**
* Launches an activity for this connection on top of the in-call UI.
*
* @param intent The intent to use to start the activity.
*/
public final void startActivityFromInCall(PendingIntent intent) {
if (!intent.isActivity()) {
throw new IllegalArgumentException("Activity intent required.");
}
mHandler.obtainMessage(MSG_START_ACTIVITY_FROM_IN_CALL, intent).sendToTarget();
}
/**
* Notifies this Connection that the {@link #getCallAudioState()} property has a new value.
* <p>
* This callback will happen on the main thread.
*
* @param state The new call audio state.
*/
public void onSetAudioState(CallAudioState state) {}
/**
* Notifies this Connection of an internal state change. This method is called after the
* state is changed.
* <p>
* This callback will happen on the main thread.
*
* @param state The new state, a {@link Connection.State} member.
*/
public void onSetState(int state) {}
/**
* Notifies this Connection of a request to play a DTMF tone.
* <p>
* This callback will happen on the main thread.
*
* @param c A DTMF character.
*/
public void onPlayDtmfTone(char c) {}
/**
* Notifies this Connection of a request to stop any currently playing DTMF tones.
* <p>
* This callback will happen on the main thread.
*/
public void onStopDtmfTone() {}
/**
* Notifies this Connection of a request to disconnect.
* <p>
* This callback will happen on the main thread.
*/
public void onDisconnect() {}
/**
* Notifies this Connection of a request to disconnect.
* <p>
* This callback will happen on the main thread.
*/
public void onSeparate() {}
/**
* Notifies this Connection of a request to abort.
* <p>
* This callback will happen on the main thread.
*/
public void onAbort() {}
/**
* Notifies this Connection of a request to hold.
* <p>
* This callback will happen on the main thread.
*/
public void onHold() {}
/**
* Notifies this Connection of a request to exit a hold state.
* <p>
* This callback will happen on the main thread.
*/
public void onUnhold() {}
/**
* Notifies this Connection, which is in {@link State#RINGING}, of a request to accept.
* <p>
* This callback will happen on the main thread.
*
* @param videoState The video state in which to answer the call.
*/
public void onAnswer(int videoState) {}
/**
* Notifies this Connection, which is in {@link State#RINGING}, of a request to reject.
* <p>
* This callback will happen on the main thread.
*/
public void onReject() {}
/**
* Notifies this Connection whether the user wishes to proceed with the post-dial DTMF codes.
* <p>
* This callback will happen on the main thread.
*/
public void onPostDialContinue(boolean proceed) {}
/**
* Swap this call with a background call. This is used for calls that don't support hold,
* e.g. CDMA.
* <p>
* This callback will happen on the main thread.
*/
public void onSwapWithBackgroundCall() {}
/**
* TODO(santoscordon): Needs documentation.
* <p>
* This callback will happen on the main thread.
*/
public void onChildrenChanged(List<Connection> children) {}
/**
* Called when the phone account UI was clicked.
*/
public void onPhoneAccountClicked() {}
/** This must be called from the main thread. */
private void addChild(Connection connection) {
Log.d(this, "adding child %s", connection);
mChildConnections.add(connection);
onChildrenChanged(mChildConnections);
}
/** This must be called from the main thread. */
private void removeChild(Connection connection) {
Log.d(this, "removing child %s", connection);
mChildConnections.remove(connection);
onChildrenChanged(mChildConnections);
}
private void setState(int state) {
if (mState == State.FAILED || mState == State.CANCELED) {
Log.d(this, "Connection already %s; cannot transition out of this state.",
stateToString(mState));
return;
}
if (mState != state) {
Log.d(this, "setState: %s", stateToString(state));
mState = state;
for (Listener l : mListeners) {
l.onStateChanged(this, state);
}
onSetState(state);
}
}
/**
* Return a {@link Connection} which represents a failed connection attempt. The returned
* {@link Connection} will have {@link #getFailureCode()}, {@link #getFailureMessage()}, and
* {@link #getState()} set appropriately, but the {@link Connection} itself should not be used
* for anything.
*
* @param code The failure code ({@see DisconnectCause}).
* @param message A reason for why the connection failed (not intended to be shown to the user).
* @return A {@link Connection} which indicates failure.
*/
public static Connection getFailedConnection(final int code, final String message) {
return new Connection() {{
setFailed(code, message);
}};
}
private static final Connection CANCELED_CONNECTION = new Connection() {{
setCanceled();
}};
/**
* Return a {@link Connection} which represents a canceled a connection attempt. The returned
* {@link Connection} will have state {@link State#CANCELED}, and cannot be moved out of that
* state. This connection should not be used for anything, and no other {@link Connection}s
* should be attempted.
*
* @return A {@link Connection} which indicates that the underlying call should be canceled.
*/
public static Connection getCanceledConnection() {
return CANCELED_CONNECTION;
}
}