Making CallsManager interact with only Calls.
Eventually, I would like CallsManager to interact only with Call
objects. This is one move in that direction.
OLD:
CallsManager->Switchboard-+->Call-+->CallService
+-------+
NEW:
CallsManager->Call-+->Switchboard-+->CallService
+--------------+
This change also includes new direct listening to Call events by
CallsManager. At the moment includes only success/failure of incoming
and outgoing calls but should eventually be more generic as we implement
more of the Telecomm design.
Change-Id: I7a764706acd0872960642a7b16c71a2bc514f3b3
diff --git a/src/com/android/telecomm/Call.java b/src/com/android/telecomm/Call.java
index e21f3de..fe984cd 100644
--- a/src/com/android/telecomm/Call.java
+++ b/src/com/android/telecomm/Call.java
@@ -22,6 +22,7 @@
import android.telecomm.CallServiceDescriptor;
import android.telecomm.CallState;
import android.telecomm.GatewayInfo;
+import android.telecomm.TelecommConstants;
import android.telephony.DisconnectCause;
import android.telephony.PhoneNumberUtils;
@@ -37,6 +38,17 @@
* connected etc).
*/
final class Call {
+
+ /**
+ * Listener for events on the call.
+ */
+ interface Listener {
+ void onSuccessfulOutgoingCall(Call call);
+ void onFailedOutgoingCall(Call call, boolean isAborted);
+ void onSuccessfulIncomingCall(Call call, CallInfo callInfo);
+ void onFailedIncomingCall(Call call);
+ }
+
/** Additional contact information beyond handle above, optional. */
private final ContactInfo mContactInfo;
@@ -112,6 +124,9 @@
*/
private CallServiceDescriptor mHandoffCallServiceDescriptor;
+ /** Set of listeners on this call. */
+ private Set<Listener> mListeners = Sets.newHashSet();
+
/**
* Creates an empty call object.
*
@@ -137,6 +152,14 @@
mIsIncoming = isIncoming;
}
+ void addListener(Listener listener) {
+ mListeners.add(listener);
+ }
+
+ void removeListener(Listener listener) {
+ mListeners.remove(listener);
+ }
+
/** {@inheritDoc} */
@Override public String toString() {
String component = null;
@@ -318,6 +341,66 @@
}
/**
+ * Starts the incoming call flow through the switchboard. When switchboard completes, it will
+ * invoke handle[Un]SuccessfulIncomingCall.
+ *
+ * @param descriptor The relevant call-service descriptor.
+ * @param extras The optional extras passed via
+ * {@link TelecommConstants#EXTRA_INCOMING_CALL_EXTRAS}.
+ */
+ void startIncoming(CallServiceDescriptor descriptor, Bundle extras) {
+ Switchboard.getInstance().retrieveIncomingCall(this, descriptor, extras);
+ }
+
+ void handleSuccessfulIncoming(CallInfo callInfo) {
+ Preconditions.checkState(callInfo.getState() == CallState.RINGING);
+ setHandle(callInfo.getHandle());
+
+ // TODO(santoscordon): Make this class (not CallsManager) responsible for changing the call
+ // state to RINGING.
+
+ // TODO(santoscordon): Replace this with state transitions related to "connecting".
+ for (Listener l : mListeners) {
+ l.onSuccessfulIncomingCall(this, callInfo);
+ }
+ }
+
+ void handleFailedIncoming() {
+ clearCallService();
+
+ // TODO: Needs more specific disconnect error for this case.
+ setDisconnectCause(DisconnectCause.ERROR_UNSPECIFIED, null);
+ setState(CallState.DISCONNECTED);
+
+ // TODO(santoscordon): Replace this with state transitions related to "connecting".
+ for (Listener l : mListeners) {
+ l.onFailedIncomingCall(this);
+ }
+ }
+
+ /**
+ * Starts the outgoing call flow through the switchboard. When switchboard completes, it will
+ * invoke handleSuccessful/FailedOutgoingCall.
+ */
+ void startOutgoing() {
+ Switchboard.getInstance().placeOutgoingCall(this);
+ }
+
+ void handleSuccessfulOutgoing() {
+ // TODO(santoscordon): Replace this with state transitions related to "connecting".
+ for (Listener l : mListeners) {
+ l.onSuccessfulOutgoingCall(this);
+ }
+ }
+
+ void handleFailedOutgoing(boolean isAborted) {
+ // TODO(santoscordon): Replace this with state transitions related to "connecting".
+ for (Listener l : mListeners) {
+ l.onFailedOutgoingCall(this, isAborted);
+ }
+ }
+
+ /**
* Adds the specified call service to the list of incompatible services. The set is used when
* attempting to switch a phone call between call services such that incompatible services can
* be avoided.
@@ -344,17 +427,17 @@
}
/**
- * Aborts ongoing attempts to connect this call. Only applicable to {@link CallState#NEW}
- * outgoing calls. See {@link #disconnect} for already-connected calls.
+ * Issues an abort signal to the associated call service and clears the current call service
+ * and call-service selector.
*/
- void abort() {
- if (mState == CallState.NEW) {
- if (mCallService != null) {
- mCallService.abort(this);
- }
- clearCallService();
- clearCallServiceSelector();
+ void finalizeAbort() {
+ Preconditions.checkState(mState == CallState.NEW);
+
+ if (mCallService != null) {
+ mCallService.abort(this);
}
+ clearCallService();
+ clearCallServiceSelector();
}
/**
@@ -385,11 +468,20 @@
* Attempts to disconnect the call through the call service.
*/
void disconnect() {
- if (mCallService == null) {
- Log.d(this, "disconnect() request on a call without a call service.");
- // In case this call is ringing, ensure we abort and clean up right away.
- CallsManager.getInstance().abortCall(this);
+ if (mState == CallState.NEW) {
+ // There is a very strange indirection here. When we are told to disconnect, we issue
+ // an 'abort' to the switchboard if we are still in the NEW (or "connecting") state.
+ // The switchboard will then cancel the outgoing call process and ask this class to
+ // finalize the abort procedure via {@link #finalizeAbort}. The issue is that
+ // Switchboard edits the state of the call as part of the process and then this class
+ // is responsible for undoing it, and that is all kinds of backwards.
+ // TODO(santoscordon): Remove Switchboard's requirement to edit the state of the Call
+ // objects and remove any multi-class shared state of incoming and outgoing call
+ // processing.
+ Switchboard.getInstance().abortCall(this);
} else {
+ Preconditions.checkNotNull(mCallService);
+
Log.i(this, "Send disconnect to call service for call: %s", this);
// The call isn't officially disconnected until the call service confirms that the call
// was actually disconnected. Only then is the association between call and call service
diff --git a/src/com/android/telecomm/CallsManager.java b/src/com/android/telecomm/CallsManager.java
index e019349..bef1dfa 100644
--- a/src/com/android/telecomm/CallsManager.java
+++ b/src/com/android/telecomm/CallsManager.java
@@ -23,19 +23,13 @@
import android.telecomm.CallServiceDescriptor;
import android.telecomm.CallState;
import android.telecomm.GatewayInfo;
-import android.telecomm.InCallCall;
import android.telephony.DisconnectCause;
import com.google.common.base.Preconditions;
-import com.google.common.base.Strings;
import com.google.common.collect.ImmutableCollection;
import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
-import java.util.List;
-import java.util.Map;
import java.util.Set;
/**
@@ -45,9 +39,9 @@
* access from other packages specifically refraining from passing the CallsManager instance
* beyond the com.android.telecomm package boundary.
*/
-public final class CallsManager {
+public final class CallsManager implements Call.Listener {
- // TODO(santoscordon): Consider renaming this CallsManagerPlugin.
+ // TODO(santoscordon): Consider renaming this CallsManagerPlugin.
interface CallsManagerListener {
void onCallAdded(Call call);
void onCallRemoved(Call call);
@@ -69,8 +63,6 @@
private static final CallsManager INSTANCE = new CallsManager();
- private final Switchboard mSwitchboard;
-
/**
* The main call repository. Keeps an instance of all live calls. New incoming and outgoing
* calls are added to the map and removed when the calls move to the disconnected state.
@@ -95,9 +87,10 @@
private final Set<CallsManagerListener> mListeners = Sets.newHashSet();
- private final List<OutgoingCallValidator> mOutgoingCallValidators = Lists.newArrayList();
-
- private final List<IncomingCallValidator> mIncomingCallValidators = Lists.newArrayList();
+ /** Singleton accessor. */
+ static CallsManager getInstance() {
+ return INSTANCE;
+ }
/**
* Initializes the required Telecomm components.
@@ -105,7 +98,6 @@
private CallsManager() {
TelecommApp app = TelecommApp.getInstance();
- mSwitchboard = new Switchboard(this);
mCallAudioManager = new CallAudioManager();
InCallTonePlayer.Factory playerFactory = new InCallTonePlayer.Factory(mCallAudioManager);
@@ -120,8 +112,42 @@
mListeners.add(mDtmfLocalTonePlayer);
}
- static CallsManager getInstance() {
- return INSTANCE;
+ @Override
+ public void onSuccessfulOutgoingCall(Call call) {
+ Log.v(this, "onSuccessfulOutgoingCall, %s", call);
+ if (mCalls.contains(call)) {
+ // The call's CallService has been updated.
+ for (CallsManagerListener listener : mListeners) {
+ listener.onCallServiceChanged(call, null, call.getCallService());
+ }
+ } else if (mPendingHandoffCalls.contains(call)) {
+ updateHandoffCallServiceDescriptor(call.getOriginalCall(),
+ call.getCallService().getDescriptor());
+ }
+ }
+
+ @Override
+ public void onFailedOutgoingCall(Call call, boolean isAborted) {
+ Log.v(this, "onFailedOutgoingCall, call: %s, isAborted: %b", call, isAborted);
+ if (isAborted) {
+ setCallState(call, CallState.ABORTED);
+ removeCall(call);
+ } else {
+ // TODO: Replace disconnect cause with more specific disconnect causes.
+ markCallAsDisconnected(call, DisconnectCause.ERROR_UNSPECIFIED, null);
+ }
+ }
+
+ @Override
+ public void onSuccessfulIncomingCall(Call call, CallInfo callInfo) {
+ Log.d(this, "onSuccessfulIncomingCall");
+ setCallState(call, callInfo.getState());
+ addCall(call);
+ }
+
+ @Override
+ public void onFailedIncomingCall(Call call) {
+ call.removeListener(this);
}
ImmutableCollection<Call> getCalls() {
@@ -148,7 +174,7 @@
/**
* Starts the incoming call sequence by having switchboard gather more information about the
* specified call; using the specified call service descriptor. Upon success, execution returns
- * to {@link #handleSuccessfulIncomingCall} to start the in-call UI.
+ * to {@link #onSuccessfulIncomingCall} to start the in-call UI.
*
* @param descriptor The descriptor of the call service to use for this incoming call.
* @param extras The optional extras Bundle passed with the intent used for the incoming call.
@@ -159,50 +185,10 @@
// additional information from the call service, but for now we just need one to pass
// around.
Call call = new Call(true /* isIncoming */);
+ // TODO(santoscordon): Move this to be a part of addCall()
+ call.addListener(this);
- mSwitchboard.retrieveIncomingCall(call, descriptor, extras);
- }
-
- /**
- * Validates the specified call and, upon no objection to connect it, adds the new call to the
- * list of live calls. Also notifies the in-call app so the user can answer or reject the call.
- *
- * @param call The new incoming call.
- * @param callInfo The details of the call.
- */
- void handleSuccessfulIncomingCall(Call call, CallInfo callInfo) {
- Log.d(this, "handleSuccessfulIncomingCall");
- Preconditions.checkState(callInfo.getState() == CallState.RINGING);
-
- Uri handle = call.getHandle();
- ContactInfo contactInfo = call.getContactInfo();
- for (IncomingCallValidator validator : mIncomingCallValidators) {
- if (!validator.isValid(handle, contactInfo)) {
- // TODO(gilad): Consider displaying an error message.
- Log.i(this, "Dropping restricted incoming call");
- return;
- }
- }
-
- // No objection to accept the incoming call, proceed with potentially connecting it (based
- // on the user's action, or lack thereof).
- call.setHandle(callInfo.getHandle());
- setCallState(call, callInfo.getState());
- addCall(call);
- }
-
- /**
- * Called when an incoming call was not connected.
- *
- * @param call The incoming call.
- */
- void handleUnsuccessfulIncomingCall(Call call) {
- // Incoming calls are not added unless they are successful. We set the state and disconnect
- // cause just as a matter of good bookkeeping. We do not use the specific methods for
- // setting those values so that we do not trigger CallsManagerListener events.
- // TODO: Needs more specific disconnect error for this case.
- call.setDisconnectCause(DisconnectCause.ERROR_UNSPECIFIED, null);
- call.setState(CallState.DISCONNECTED);
+ call.startIncoming(descriptor, extras);
}
/**
@@ -214,14 +200,6 @@
* actual dialed handle via a gateway provider. May be null.
*/
void placeOutgoingCall(Uri handle, ContactInfo contactInfo, GatewayInfo gatewayInfo) {
- for (OutgoingCallValidator validator : mOutgoingCallValidators) {
- if (!validator.isValid(handle, contactInfo)) {
- // TODO(gilad): Display an error message.
- Log.i(this, "Dropping restricted outgoing call.");
- return;
- }
- }
-
final Uri uriHandle = (gatewayInfo == null) ? handle : gatewayInfo.getGatewayHandle();
if (gatewayInfo == null) {
@@ -230,45 +208,14 @@
Log.i(this, "Creating a new outgoing call with gateway handle: %s, original handle: %s",
Log.pii(uriHandle), Log.pii(handle));
}
+
Call call = new Call(uriHandle, contactInfo, gatewayInfo, false /* isIncoming */);
+
+ // TODO(santoscordon): Move this to be a part of addCall()
+ call.addListener(this);
addCall(call);
- mSwitchboard.placeOutgoingCall(call);
- }
- /**
- * Called when a call service acknowledges that it can place a call.
- *
- * @param call The new outgoing call.
- */
- void handleSuccessfulOutgoingCall(Call call) {
- Log.v(this, "handleSuccessfulOutgoingCall, %s", call);
- if (mCalls.contains(call)) {
- // The call's CallService has been updated.
- for (CallsManagerListener listener : mListeners) {
- listener.onCallServiceChanged(call, null, call.getCallService());
- }
- } else if (mPendingHandoffCalls.contains(call)) {
- updateHandoffCallServiceDescriptor(call.getOriginalCall(),
- call.getCallService().getDescriptor());
- }
- }
-
- /**
- * Called when an outgoing call was not placed.
- *
- * @param call The outgoing call.
- * @param isAborted True if the call was unsuccessful because it was aborted.
- */
- void handleUnsuccessfulOutgoingCall(Call call, boolean isAborted) {
- Log.v(this, "handleAbortedOutgoingCall, call: %s, isAborted: %b", call, isAborted);
- if (isAborted) {
- call.abort();
- setCallState(call, CallState.ABORTED);
- removeCall(call);
- } else {
- // TODO: Replace disconnect cause with more specific disconnect causes.
- markCallAsDisconnected(call, DisconnectCause.ERROR_UNSPECIFIED, null);
- }
+ call.startOutgoing();
}
/**
@@ -399,18 +346,6 @@
}
}
- /**
- * Instructs Telecomm to abort any outgoing state of the specified call.
- */
- void abortCall(Call call) {
- if (!mCalls.contains(call)) {
- Log.w(this, "Unknown call (%s) asked to be aborted", call);
- } else {
- Log.d(this, "Aborting call: (%s)", call);
- mSwitchboard.abortCall(call);
- }
- }
-
/** Called by the in-call UI to change the mute state. */
void mute(boolean shouldMute) {
mCallAudioManager.mute(shouldMute);
@@ -448,7 +383,7 @@
tempCall.setCallServiceSelector(originalCall.getCallServiceSelector());
mPendingHandoffCalls.add(tempCall);
Log.d(this, "Placing handoff call");
- mSwitchboard.placeOutgoingCall(tempCall);
+ tempCall.startOutgoing();
}
/** Called when the audio state changes. */
@@ -557,6 +492,7 @@
Preconditions.checkState(call.getHandoffCallServiceDescriptor() == null);
Log.v(this, "removeCall(%s)", call);
+ call.removeListener(this);
call.clearCallService();
call.clearCallServiceSelector();
diff --git a/src/com/android/telecomm/IncomingCallValidator.java b/src/com/android/telecomm/IncomingCallValidator.java
deleted file mode 100644
index 769eae7..0000000
--- a/src/com/android/telecomm/IncomingCallValidator.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * 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 com.android.telecomm;
-
-import android.net.Uri;
-
-/**
- * Implementations can be used to reject incoming calls based on arbitrary restrictions across call
- * services (e.g. suppressing calls from certain phone numbers regardless if they are received over
- * PSTN or WiFi). See OutgoingCallValidator regarding outgoing calls.
- */
-public interface IncomingCallValidator {
-
- boolean isValid(Uri handle, ContactInfo contactInfo);
-}
diff --git a/src/com/android/telecomm/OutgoingCallProcessor.java b/src/com/android/telecomm/OutgoingCallProcessor.java
index 315a9bd..5c8d5f5 100644
--- a/src/com/android/telecomm/OutgoingCallProcessor.java
+++ b/src/com/android/telecomm/OutgoingCallProcessor.java
@@ -16,15 +16,11 @@
package com.android.telecomm;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.RemoteException;
-import android.telecomm.CallState;
import android.telecomm.CallServiceDescriptor;
import com.google.android.collect.Sets;
-import com.google.common.collect.Maps;
import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
import java.util.Collection;
import java.util.Iterator;
diff --git a/src/com/android/telecomm/OutgoingCallValidator.java b/src/com/android/telecomm/OutgoingCallValidator.java
deleted file mode 100644
index 288d37e..0000000
--- a/src/com/android/telecomm/OutgoingCallValidator.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * 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 com.android.telecomm;
-
-import android.net.Uri;
-
-/**
- * Implementations can be used to suppress certain classes of outgoing calls based on arbitrary
- * restrictions across call services (e.g. black-listing some phone numbers regardless if these
- * are attempted over PSTN or WiFi). That being the case, FDN which is specific to GSM may need
- * to be implemented separately since classes implementing this interface are generally invoked
- * before any given call service is selected.
- * See http://en.wikipedia.org/wiki/Fixed_Dialing_Number and/or IncomingCallValidator regarding
- * incoming calls.
- */
-public interface OutgoingCallValidator {
-
- boolean isValid(Uri handle, ContactInfo contactInfo);
-}
diff --git a/src/com/android/telecomm/Switchboard.java b/src/com/android/telecomm/Switchboard.java
index 82f94fa..81ab172 100644
--- a/src/com/android/telecomm/Switchboard.java
+++ b/src/com/android/telecomm/Switchboard.java
@@ -19,8 +19,6 @@
import android.content.ComponentName;
import android.os.Bundle;
import android.os.Handler;
-import android.os.Looper;
-
import android.os.Message;
import android.telecomm.CallInfo;
import android.telecomm.CallServiceDescriptor;
@@ -32,7 +30,6 @@
import com.google.common.collect.Sets;
import java.util.Collection;
-import java.util.Iterator;
import java.util.Set;
/**
@@ -45,7 +42,7 @@
final class Switchboard {
private final static int MSG_EXPIRE_STALE_CALL = 1;
- private final CallsManager mCallsManager;
+ private final static Switchboard sInstance = new Switchboard();
/** Used to place outgoing calls. */
private final OutgoingCallsManager mOutgoingCallsManager;
@@ -95,13 +92,17 @@
*/
private int mLookupId = 0;
+ /** Singleton accessor. */
+ static Switchboard getInstance() {
+ return sInstance;
+ }
+
/**
* Persists the specified parameters and initializes Switchboard.
*/
- Switchboard(CallsManager callsManager) {
+ private Switchboard() {
ThreadUtil.checkOnMainThread();
- mCallsManager = callsManager;
mOutgoingCallsManager = new OutgoingCallsManager(this);
mIncomingCallsManager = new IncomingCallsManager(this);
mSelectorRepository = new CallServiceSelectorRepository(this, mOutgoingCallsManager);
@@ -194,8 +195,8 @@
void handleSuccessfulOutgoingCall(Call call) {
Log.d(this, "handleSuccessfulOutgoingCall");
- mCallsManager.handleSuccessfulOutgoingCall(call);
finalizeOutgoingCall(call);
+ call.handleSuccessfulOutgoing();
}
/**
@@ -205,8 +206,8 @@
void handleFailedOutgoingCall(Call call, boolean isAborted) {
Log.d(this, "handleFailedOutgoingCall");
- mCallsManager.handleUnsuccessfulOutgoingCall(call, isAborted);
finalizeOutgoingCall(call);
+ call.handleFailedOutgoing(isAborted);
}
/**
@@ -216,8 +217,7 @@
*/
void handleSuccessfulIncomingCall(Call call, CallInfo callInfo) {
Log.d(this, "handleSuccessfulIncomingCall");
-
- mCallsManager.handleSuccessfulIncomingCall(call, callInfo);
+ call.handleSuccessfulIncoming(callInfo);
}
/**
@@ -231,8 +231,7 @@
// Since we set the call service before calling into incoming-calls manager, we clear it for
// good measure if an error is reported.
- call.clearCallService();
- mCallsManager.handleUnsuccessfulIncomingCall(call);
+ call.handleFailedIncoming();
// At the moment there is nothing more to do if an incoming call is not retrieved. We may at
// a future date bind to the in-call app optimistically during the incoming-call sequence
@@ -301,7 +300,7 @@
new CallServiceSelectorWrapper(
componentName.flattenToShortString(),
componentName,
- mCallsManager,
+ CallsManager.getInstance(),
mOutgoingCallsManager);
selectorsBuilder.add(emergencySelector);