Merge "Add tones for call disconnection." into master-nova
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 780906c..d58c898 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -161,5 +161,8 @@
</intent-filter>
</activity-alias>
+ <!-- Selects call services to place emergency calls. -->
+ <service android:name="EmergencyCallServiceSelector" android:exported="false" />
+
</application>
</manifest>
diff --git a/src/com/android/telecomm/Call.java b/src/com/android/telecomm/Call.java
index b57ec80..f79a362 100644
--- a/src/com/android/telecomm/Call.java
+++ b/src/com/android/telecomm/Call.java
@@ -17,7 +17,9 @@
package com.android.telecomm;
import android.net.Uri;
+import android.os.Bundle;
import android.telecomm.CallInfo;
+import android.telecomm.CallServiceDescriptor;
import android.telecomm.CallState;
import android.telecomm.GatewayInfo;
import android.telephony.DisconnectCause;
@@ -91,6 +93,18 @@
*/
private String mDisconnectMessage;
+ /** Info used by the call services. */
+ private Bundle mExtras;
+
+ /** The Uri to dial to perform the handoff. If this is null then handoff is not supported. */
+ private Uri mHandoffHandle;
+
+ /**
+ * References the call that is being handed off. This value is non-null for untracked calls
+ * that are being used to perform a handoff.
+ */
+ private Call mOriginalCall;
+
/**
* Creates an empty call object.
*
@@ -116,6 +130,7 @@
mIsIncoming = isIncoming;
mCreationTime = new Date();
mDisconnectCause = DisconnectCause.NOT_VALID;
+ mExtras = Bundle.EMPTY;
}
/** {@inheritDoc} */
@@ -242,6 +257,10 @@
}
}
+ CallServiceSelectorWrapper getCallServiceSelector() {
+ return mCallServiceSelector;
+ }
+
void setCallServiceSelector(CallServiceSelectorWrapper selector) {
Preconditions.checkNotNull(selector);
@@ -395,7 +414,13 @@
* @return An object containing read-only information about this call.
*/
CallInfo toCallInfo(String callId) {
- return new CallInfo(callId, mState, mHandle, mGatewayInfo);
+ CallServiceDescriptor descriptor = null;
+ if (mCallService != null) {
+ descriptor = mCallService.getDescriptor();
+ } else if (mOriginalCall != null && mOriginalCall.mCallService != null) {
+ descriptor = mOriginalCall.mCallService.getDescriptor();
+ }
+ return new CallInfo(callId, mState, mHandle, mGatewayInfo, mExtras, descriptor);
}
/** Checks if this is a live call or not. */
@@ -411,6 +436,30 @@
}
}
+ Bundle getExtras() {
+ return mExtras;
+ }
+
+ void setExtras(Bundle extras) {
+ mExtras = extras;
+ }
+
+ Uri getHandoffHandle() {
+ return mHandoffHandle;
+ }
+
+ void setHandoffHandle(Uri handoffHandle) {
+ mHandoffHandle = handoffHandle;
+ }
+
+ Call getOriginalCall() {
+ return mOriginalCall;
+ }
+
+ void setOriginalCall(Call originalCall) {
+ mOriginalCall = originalCall;
+ }
+
/**
* @return True if the call is ringing, else logs the action name.
*/
diff --git a/src/com/android/telecomm/CallActivity.java b/src/com/android/telecomm/CallActivity.java
index 43c4cf3..77e64bc 100644
--- a/src/com/android/telecomm/CallActivity.java
+++ b/src/com/android/telecomm/CallActivity.java
@@ -22,7 +22,6 @@
import android.os.Bundle;
import android.telecomm.CallServiceDescriptor;
import android.telecomm.TelecommConstants;
-import android.widget.Toast;
/**
* Activity that handles system CALL actions and forwards them to {@link CallsManager}.
diff --git a/src/com/android/telecomm/CallServiceAdapter.java b/src/com/android/telecomm/CallServiceAdapter.java
index 2db6255..39de235 100644
--- a/src/com/android/telecomm/CallServiceAdapter.java
+++ b/src/com/android/telecomm/CallServiceAdapter.java
@@ -55,7 +55,7 @@
mOutgoingCallsManager.setIsCompatibleWith(call,
msg.arg1 == 1 ? true : false);
} else {
- Log.w(this, "Unknown call: %s, id: %s", call, msg.obj);
+ Log.w(this, "setIsCompatibleWith, unknown call: %s, id: %s", call, msg.obj);
}
break;
case MSG_NOTIFY_INCOMING_CALL:
@@ -66,7 +66,7 @@
clientCallInfo.getHandle());
mIncomingCallsManager.handleSuccessfulIncomingCall(call, callInfo);
} else {
- Log.w(this, "Unknown incoming call: %s, id: %s", call,
+ Log.w(this, "notifyIncomingCall, unknown incoming call: %s, id: %s", call,
clientCallInfo.getId());
}
break;
@@ -76,7 +76,9 @@
mOutgoingCallsManager.handleSuccessfulCallAttempt(call);
} else {
// TODO(gilad): Figure out how to wire up the callService.abort() call.
- Log.w(this, "Unknown outgoing call: %s, id: %s", call, msg.obj);
+ Log.w(this,
+ "handleSuccessfulOutgoingCall, unknown outgoing call: %s, id: %s",
+ call, msg.obj);
}
break;
case MSG_HANDLE_FAILED_OUTGOING_CALL: {
@@ -87,7 +89,9 @@
if (call != null && mPendingCalls.remove(call) && !call.isIncoming()) {
mOutgoingCallsManager.handleFailedCallAttempt(call, reason);
} else {
- Log.w(this, "Unknown outgoing call: %s, id: %s", call, args.arg1);
+ Log.w(this,
+ "handleFailedOutgoingCall, unknown outgoing call: %s, id: %s",
+ call, args.arg1);
}
} finally {
args.recycle();
@@ -99,7 +103,7 @@
if (call != null) {
mCallsManager.markCallAsActive(call);
} else {
- Log.w(this, "Unknown call id: %s", msg.obj);
+ Log.w(this, "setActive, unknown call id: %s", msg.obj);
}
break;
case MSG_SET_RINGING:
@@ -107,7 +111,7 @@
if (call != null) {
mCallsManager.markCallAsRinging(call);
} else {
- Log.w(this, "Unknown call id: %s", msg.obj);
+ Log.w(this, "setRinging, unknown call id: %s", msg.obj);
}
break;
case MSG_SET_DIALING:
@@ -115,7 +119,7 @@
if (call != null) {
mCallsManager.markCallAsDialing(call);
} else {
- Log.w(this, "Unknown call id: %s", msg.obj);
+ Log.w(this, "setDialing, unknown call id: %s", msg.obj);
}
break;
case MSG_SET_DISCONNECTED: {
@@ -128,7 +132,7 @@
mCallsManager.markCallAsDisconnected(call, disconnectCause,
disconnectMessage);
} else {
- Log.w(this, "Unknown call id: %s", args.arg1);
+ Log.w(this, "setDisconnected, unknown call id: %s", args.arg1);
}
} finally {
args.recycle();
@@ -140,7 +144,7 @@
if (call != null) {
mCallsManager.markCallAsOnHold(call);
} else {
- Log.w(this, "Unknown call id: %s", msg.obj);
+ Log.w(this, "setOnHold, unknown call id: %s", msg.obj);
}
break;
}
@@ -174,7 +178,7 @@
/** {@inheritDoc} */
@Override
public void setIsCompatibleWith(String callId, boolean isCompatible) {
- Log.v(this, "setIsCompatibleWith id: %d, isCompatible: %b", callId, isCompatible);
+ Log.v(this, "setIsCompatibleWith id: %s, isCompatible: %b", callId, isCompatible);
mCallIdMapper.checkValidCallId(callId);
mHandler.obtainMessage(MSG_SET_IS_COMPATIBLE_WITH, isCompatible ? 1 : 0, 0, callId).
sendToTarget();
diff --git a/src/com/android/telecomm/CallServiceSelectorRepository.java b/src/com/android/telecomm/CallServiceSelectorRepository.java
index 50d26e8..709d440 100644
--- a/src/com/android/telecomm/CallServiceSelectorRepository.java
+++ b/src/com/android/telecomm/CallServiceSelectorRepository.java
@@ -93,8 +93,9 @@
ServiceInfo serviceInfo = entry.serviceInfo;
if (serviceInfo != null) {
// The entry resolves to a proper service, add it to the list of selector names.
- selectorNames.add(
- new ComponentName(serviceInfo.packageName, serviceInfo.name));
+ ComponentName componentName =
+ new ComponentName(serviceInfo.packageName, serviceInfo.name);
+ selectorNames.add(componentName);
}
}
diff --git a/src/com/android/telecomm/CallServiceSelectorWrapper.java b/src/com/android/telecomm/CallServiceSelectorWrapper.java
index f4ddf19..b5adda0 100644
--- a/src/com/android/telecomm/CallServiceSelectorWrapper.java
+++ b/src/com/android/telecomm/CallServiceSelectorWrapper.java
@@ -17,7 +17,6 @@
package com.android.telecomm;
import android.content.ComponentName;
-import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
import android.telecomm.CallInfo;
@@ -25,7 +24,6 @@
import android.telecomm.CallServiceSelector;
import android.telecomm.TelecommConstants;
-import com.google.common.base.Preconditions;
import com.android.internal.telecomm.ICallServiceSelector;
import com.android.internal.telecomm.ICallServiceSelectorAdapter;
@@ -38,20 +36,46 @@
final class CallServiceSelectorWrapper extends ServiceBinder<ICallServiceSelector> {
private ICallServiceSelector mSelectorInterface;
private final Binder mBinder = new Binder();
- private final Handler mHandler = new Handler();
private final CallIdMapper mCallIdMapper = new CallIdMapper("CallServiceSelector");
private final CallServiceSelectorAdapter mAdapter;
/**
- * Creates a call-service selector for the specified component.
+ * Creates a call-service selector for the specified component using the specified action to
+ * bind to it.
+ *
+ * @param action The action used to bind to the selector.
+ * @param componentName The component name of the service.
+ * @param callsManager The calls manager.
+ * @param outgoingCallsManager The outgoing calls manager.
+ */
+ CallServiceSelectorWrapper(
+ String action,
+ ComponentName componentName,
+ CallsManager callsManager,
+ OutgoingCallsManager outgoingCallsManager) {
+
+ super(action, componentName);
+ mAdapter =
+ new CallServiceSelectorAdapter(callsManager, outgoingCallsManager, mCallIdMapper);
+ }
+
+ /**
+ * Creates a call-service selector for specified component and uses
+ * {@link TelecommConstants#ACTION_CALL_SERVICE_SELECTOR} as the action to bind.
*
* @param componentName The component name of the service.
+ * @param callsManager The calls manager.
+ * @param outgoingCallsManager The outgoing calls manager.
*/
- CallServiceSelectorWrapper(ComponentName componentName, CallsManager callsManager,
+ CallServiceSelectorWrapper(
+ ComponentName componentName,
+ CallsManager callsManager,
OutgoingCallsManager outgoingCallsManager) {
- super(TelecommConstants.ACTION_CALL_SERVICE_SELECTOR, componentName);
- mAdapter = new CallServiceSelectorAdapter(callsManager, outgoingCallsManager,
- mCallIdMapper);
+
+ this(TelecommConstants.ACTION_CALL_SERVICE_SELECTOR,
+ componentName,
+ callsManager,
+ outgoingCallsManager);
}
/** See {@link CallServiceSelector#setCallServiceSelectorAdapter}. */
@@ -96,12 +120,51 @@
mBinder.bind(callback);
}
+ private void onCallUpdated(final CallInfo callInfo) {
+ BindCallback callback = new BindCallback() {
+ @Override
+ public void onSuccess() {
+ if (isServiceValid("onCallUpdated")) {
+ try {
+ mSelectorInterface.onCallUpdated(callInfo);
+ } catch (RemoteException e) {
+ }
+ }
+ }
+ @Override
+ public void onFailure() {
+ }
+ };
+ mBinder.bind(callback);
+ }
+
+ private void onCallRemoved(final String callId) {
+ BindCallback callback = new BindCallback() {
+ @Override
+ public void onSuccess() {
+ if (isServiceValid("onCallRemoved")) {
+ try {
+ mSelectorInterface.onCallRemoved(callId);
+ } catch (RemoteException e) {
+ }
+ }
+ }
+ @Override
+ public void onFailure() {
+ }
+ };
+ mBinder.bind(callback);
+ }
+
void addCall(Call call) {
mCallIdMapper.addCall(call);
+ onCallUpdated(call.toCallInfo(mCallIdMapper.getCallId(call)));
}
void removeCall(Call call) {
+ String callId = mCallIdMapper.getCallId(call);
mCallIdMapper.removeCall(call);
+ onCallRemoved(callId);
}
/** {@inheritDoc} */
diff --git a/src/com/android/telecomm/CallsManager.java b/src/com/android/telecomm/CallsManager.java
index bb130d1..b7f7aec 100644
--- a/src/com/android/telecomm/CallsManager.java
+++ b/src/com/android/telecomm/CallsManager.java
@@ -53,6 +53,7 @@
void onCallStateChanged(Call call, CallState oldState, CallState newState);
void onIncomingCallAnswered(Call call);
void onIncomingCallRejected(Call call);
+ void onCallHandoffHandleChanged(Call call, Uri oldHandle, Uri newHandle);
void onForegroundCallChanged(Call oldForegroundCall, Call newForegroundCall);
void onAudioStateChanged(CallAudioState oldAudioState, CallAudioState newAudioState);
}
@@ -68,6 +69,12 @@
private final Set<Call> mCalls = Sets.newLinkedHashSet();
/**
+ * Set of new calls created to perform a handoff. The calls are added when handoff is initiated
+ * and removed when hadnoff is complete.
+ */
+ private final Set<Call> mPendingHandoffCalls = Sets.newLinkedHashSet();
+
+ /**
* The call the user is currently interacting with. This is the call that should have audio
* focus and be visible in the in-call UI.
*/
@@ -191,7 +198,7 @@
* @param handle Handle to connect the call with.
* @param contactInfo Information about the entity being called.
* @param gatewayInfo Optional gateway information that can be used to route the call to the
- * actual dialed handle via a gateway provider. May be null.
+ * actual dialed handle via a gateway provider. May be null.
*/
void placeOutgoingCall(Uri handle, ContactInfo contactInfo, GatewayInfo gatewayInfo) {
for (OutgoingCallValidator validator : mOutgoingCallValidators) {
@@ -210,7 +217,7 @@
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*/);
+ Call call = new Call(uriHandle, contactInfo, gatewayInfo, false /* isIncoming */);
setCallState(call, CallState.DIALING);
addCall(call);
mSwitchboard.placeOutgoingCall(call);
@@ -369,6 +376,33 @@
mCallAudioManager.setAudioRoute(route);
}
+ void startHandoffForCall(Call originalCall) {
+ if (!mCalls.contains(originalCall)) {
+ Log.w(this, "Unknown call %s asked to be handed off", originalCall);
+ return;
+ }
+
+ for (Call handoffCall : mPendingHandoffCalls) {
+ if (handoffCall.getOriginalCall() == originalCall) {
+ Log.w(this, "Call %s is already being handed off, skipping", originalCall);
+ return;
+ }
+ }
+
+ // Create a new call to be placed in the background. If handoff is successful then the
+ // original call will live on but its state will be updated to the new call's state. In
+ // particular the original call's call service will be updated to the new call's call
+ // service.
+ Call tempCall = new Call(originalCall.getHandoffHandle(), originalCall.getContactInfo(),
+ originalCall.getGatewayInfo(), false);
+ tempCall.setOriginalCall(originalCall);
+ tempCall.setExtras(originalCall.getExtras());
+ tempCall.setCallServiceSelector(originalCall.getCallServiceSelector());
+ mPendingHandoffCalls.add(tempCall);
+ Log.d(this, "Placing handoff call");
+ mSwitchboard.placeOutgoingCall(tempCall);
+ }
+
/** Called when the audio state changes. */
void onAudioStateChanged(CallAudioState oldAudioState, CallAudioState newAudioState) {
Log.v(this, "onAudioStateChanged, audioState: %s -> %s", oldAudioState, newAudioState);
@@ -387,6 +421,10 @@
void markCallAsActive(Call call) {
setCallState(call, CallState.ACTIVE);
+
+ if (mPendingHandoffCalls.contains(call)) {
+ completeHandoff(call);
+ }
}
void markCallAsOnHold(Call call) {
@@ -407,7 +445,25 @@
}
void setHandoffInfo(Call call, Uri handle, Bundle extras) {
- // TODO(sail): Implement this.
+ if (!mCalls.contains(call)) {
+ Log.w(this, "Unknown call (%s) asked to set handoff info", call);
+ return;
+ }
+
+ if (extras == null) {
+ call.setExtras(Bundle.EMPTY);
+ } else {
+ call.setExtras(extras);
+ }
+
+ Uri oldHandle = call.getHandoffHandle();
+ Log.v(this, "set handoff handle %s -> %s, for call: %s", oldHandle, handle, call);
+ if (!areUriEqual(oldHandle, handle)) {
+ call.setHandoffHandle(handle);
+ for (CallsManagerListener listener : mListeners) {
+ listener.onCallHandoffHandleChanged(call, oldHandle, handle);
+ }
+ }
}
/**
@@ -446,6 +502,10 @@
if (mCalls.contains(call)) {
mCalls.remove(call);
shouldNotify = true;
+ cleanUpHandoffCallsForOriginalCall(call);
+ } else if (mPendingHandoffCalls.contains(call)) {
+ Log.v(this, "silently removing handoff call %s", call);
+ mPendingHandoffCalls.remove(call);
}
// Only broadcast changes for calls that are being tracked.
@@ -512,4 +572,50 @@
}
}
}
+
+ private void completeHandoff(Call handoffCall) {
+ Call originalCall = handoffCall.getOriginalCall();
+ Log.v(this, "complete handoff, %s -> %s", handoffCall, originalCall);
+
+ // Disconnect.
+ originalCall.disconnect();
+
+ // Synchronize.
+ originalCall.setCallService(handoffCall.getCallService());
+ setCallState(originalCall, handoffCall.getState());
+
+ // Remove the transient handoff call object (don't disconnect because the call is still
+ // live).
+ removeCall(handoffCall);
+
+ // Force the foreground call changed notification to be sent.
+ for (CallsManagerListener listener : mListeners) {
+ listener.onForegroundCallChanged(mForegroundCall, mForegroundCall);
+ }
+ }
+
+ /** Makes sure there are no dangling handoff calls. */
+ private void cleanUpHandoffCallsForOriginalCall(Call originalCall) {
+ for (Call handoffCall : ImmutableList.copyOf((mPendingHandoffCalls))) {
+ if (handoffCall.getOriginalCall() == originalCall) {
+ Log.d(this, "cancelling handoff call %s for originalCall: %s", handoffCall,
+ originalCall);
+ if (handoffCall.getState() == CallState.NEW) {
+ handoffCall.abort();
+ handoffCall.setState(CallState.ABORTED);
+ } else {
+ handoffCall.disconnect();
+ handoffCall.setState(CallState.DISCONNECTED);
+ }
+ removeCall(handoffCall);
+ }
+ }
+ }
+
+ private static boolean areUriEqual(Uri handle1, Uri handle2) {
+ if (handle1 == null) {
+ return handle2 == null;
+ }
+ return handle1.equals(handle2);
+ }
}
diff --git a/src/com/android/telecomm/CallsManagerListenerBase.java b/src/com/android/telecomm/CallsManagerListenerBase.java
index 04b78fe..6144651 100644
--- a/src/com/android/telecomm/CallsManagerListenerBase.java
+++ b/src/com/android/telecomm/CallsManagerListenerBase.java
@@ -16,6 +16,7 @@
package com.android.telecomm;
+import android.net.Uri;
import android.telecomm.CallAudioState;
import android.telecomm.CallState;
@@ -44,6 +45,10 @@
}
@Override
+ public void onCallHandoffHandleChanged(Call call, Uri oldHandle, Uri newHandle) {
+ }
+
+ @Override
public void onForegroundCallChanged(Call oldForegroundCall, Call newForegroundCall) {
}
diff --git a/src/com/android/telecomm/EmergencyCallServiceSelector.java b/src/com/android/telecomm/EmergencyCallServiceSelector.java
new file mode 100644
index 0000000..18a5b15
--- /dev/null
+++ b/src/com/android/telecomm/EmergencyCallServiceSelector.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 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;
+import android.telecomm.CallInfo;
+import android.telecomm.CallServiceDescriptor;
+import android.telecomm.CallServiceSelector;
+import android.telecomm.CallServiceSelectorAdapter;
+import android.telephony.PhoneNumberUtils;
+
+import com.google.common.collect.Lists;
+
+import java.util.List;
+
+/**
+ * Selects call-services which can place emergency calls. For emergency handles, this selector is
+ * always the first selector attempted when placing a call, see
+ * {@link Switchboard#processNewOutgoingCall}. If this is ever invoked for a non-emergency handle,
+ * this selector returns zero call services so that call-service selection continues to the next
+ * selector. For emergency calls, it selects telephony's PSTN call services so that they are the
+ * first ones to try to place the call.
+ */
+public class EmergencyCallServiceSelector extends CallServiceSelector {
+
+ /**
+ * Returns true if the handle passed in is to a potential emergency number.
+ */
+ static boolean shouldUseSelector(Uri handle) {
+ return PhoneNumberUtils.isPotentialLocalEmergencyNumber(
+ handle.getSchemeSpecificPart(), TelecommApp.getInstance());
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void select(CallInfo callInfo, List<CallServiceDescriptor> descriptors) {
+ List<CallServiceDescriptor> selectedDescriptors = Lists.newArrayList();
+
+ // We check to see if the handle is potentially for an emergency call. *Potentially* means
+ // that we match both the full number and prefix (e.g., "911444" matches 911). After the
+ // call is made, the telephony call services will inform us of the actual number that was
+ // connected, which would be 911. This is why we check *potential* APIs before, but use the
+ // exact {@link PhoneNumberUtils#isLocalEmergencyNumber} once the call is connected.
+ if (shouldUseSelector(callInfo.getHandle())) {
+ // Search for and return the pstn call services if found.
+ for (CallServiceDescriptor descriptor : descriptors) {
+ // TODO(santoscordon): Consider adding some type of CAN_MAKE_EMERGENCY_CALLS
+ // capability for call services and relying on that. Also consider combining this
+ // with a permission so that we can check that instead of relying on hardcoded
+ // paths.
+ if (TelephonyUtil.isPstnCallService(descriptor)) {
+ selectedDescriptors.add(descriptor);
+ }
+ }
+ }
+
+ getAdapter().setSelectedCallServices(callInfo.getId(), selectedDescriptors);
+ }
+}
diff --git a/src/com/android/telecomm/InCallAdapter.java b/src/com/android/telecomm/InCallAdapter.java
index 14669c6..da7cf18 100644
--- a/src/com/android/telecomm/InCallAdapter.java
+++ b/src/com/android/telecomm/InCallAdapter.java
@@ -35,8 +35,9 @@
private static final int MSG_DISCONNECT_CALL = 5;
private static final int MSG_HOLD_CALL = 6;
private static final int MSG_UNHOLD_CALL = 7;
- private static final int MSG_MUTE = 8;
- private static final int MSG_SET_AUDIO_ROUTE = 9;
+ private static final int MSG_HANDOFF_CALL = 8;
+ private static final int MSG_MUTE = 9;
+ private static final int MSG_SET_AUDIO_ROUTE = 10;
private final class InCallAdapterHandler extends Handler {
@Override
@@ -75,6 +76,9 @@
case MSG_UNHOLD_CALL:
mCallsManager.unholdCall(call);
break;
+ case MSG_HANDOFF_CALL:
+ mCallsManager.startHandoffForCall(call);
+ break;
case MSG_MUTE:
mCallsManager.mute(msg.arg1 == 1 ? true : false);
break;
@@ -160,6 +164,13 @@
/** {@inheritDoc} */
@Override
+ public void handoffCall(String callId) {
+ mCallIdMapper.checkValidCallId(callId);
+ mHandler.obtainMessage(MSG_HANDOFF_CALL, callId).sendToTarget();
+ }
+
+ /** {@inheritDoc} */
+ @Override
public void mute(boolean shouldMute) {
mHandler.obtainMessage(MSG_MUTE, shouldMute ? 1 : 0, 0).sendToTarget();
}
diff --git a/src/com/android/telecomm/InCallController.java b/src/com/android/telecomm/InCallController.java
index d975b0b..beac0d5 100644
--- a/src/com/android/telecomm/InCallController.java
+++ b/src/com/android/telecomm/InCallController.java
@@ -20,6 +20,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
+import android.net.Uri;
import android.os.IBinder;
import android.os.RemoteException;
import android.telecomm.CallAudioState;
@@ -136,6 +137,17 @@
}
@Override
+ public void onCallHandoffHandleChanged(Call call, Uri oldHandle, Uri newHandle) {
+ if (mInCallService != null) {
+ try {
+ mInCallService.setHandoffEnabled(mCallIdMapper.getCallId(call), newHandle != null);
+ } catch (RemoteException e) {
+ Log.e(this, e, "Exception attempting to call setHandoffEnabled.");
+ }
+ }
+ }
+
+ @Override
public void onAudioStateChanged(CallAudioState oldAudioState, CallAudioState newAudioState) {
if (mInCallService != null) {
Log.i(this, "Calling onAudioStateChanged, audioState: %s -> %s", oldAudioState,
@@ -207,6 +219,7 @@
if (!calls.isEmpty()) {
for (Call call : calls) {
onCallAdded(call);
+ onCallHandoffHandleChanged(call, null, call.getHandoffHandle());
}
onAudioStateChanged(null, CallsManager.getInstance().getAudioState());
} else {
diff --git a/src/com/android/telecomm/Switchboard.java b/src/com/android/telecomm/Switchboard.java
index 77c3c16..c2df63e 100644
--- a/src/com/android/telecomm/Switchboard.java
+++ b/src/com/android/telecomm/Switchboard.java
@@ -16,12 +16,7 @@
package com.android.telecomm;
-import com.google.common.base.Preconditions;
-import com.google.common.collect.ImmutableCollection;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Sets;
-
+import android.content.ComponentName;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
@@ -29,18 +24,21 @@
import android.telecomm.CallServiceDescriptor;
import android.telecomm.TelecommConstants;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableCollection;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Sets;
+
+import java.util.Collection;
import java.util.Iterator;
-import java.util.List;
import java.util.Set;
/**
* Switchboard is responsible for:
- * <ul>
- * <li> gathering the {@link CallServiceWrapper}s and {@link CallServiceSelectorWrapper}s through which to place
- * outgoing calls
- * <li> starting outgoing calls (via {@link OutgoingCallsManager}
- * <li> switching active calls between call services.
- * </ul>
+ * - gathering the {@link CallServiceWrapper}s and {@link CallServiceSelectorWrapper}s through
+ * which to place outgoing calls
+ * - starting outgoing calls (via {@link OutgoingCallsManager}
+ * - switching active calls between call services.
*/
final class Switchboard {
@@ -149,7 +147,7 @@
* @param call The call object.
* @param descriptor The relevant call-service descriptor.
* @param extras The optional extras passed via
- * {@link TelecommConstants#EXTRA_INCOMING_CALL_EXTRAS}
+ * {@link TelecommConstants#EXTRA_INCOMING_CALL_EXTRAS}
*/
void retrieveIncomingCall(Call call, CallServiceDescriptor descriptor, Bundle extras) {
Log.d(this, "retrieveIncomingCall");
@@ -295,7 +293,40 @@
* @param call The call to place.
*/
private void processNewOutgoingCall(Call call) {
- mOutgoingCallsManager.placeCall(call, mCallServices, mSelectors);
+ Collection<CallServiceSelectorWrapper> selectors;
+
+ // Use the call's selector if it's already tied to one. This is the case for handoff calls.
+ if (call.getCallServiceSelector() != null) {
+ selectors = ImmutableList.of(call.getCallServiceSelector());
+ } else {
+ selectors = mSelectors;
+ }
+
+ boolean useEmergencySelector =
+ EmergencyCallServiceSelector.shouldUseSelector(call.getHandle());
+ Log.d(this, "processNewOutgoingCall, isEmergency=%b", useEmergencySelector);
+
+ if (useEmergencySelector) {
+ // This is potentially an emergency call so add the emergency selector before the
+ // other selectors.
+ ImmutableList.Builder<CallServiceSelectorWrapper> selectorsBuilder =
+ ImmutableList.builder();
+
+ ComponentName componentName = new ComponentName(
+ TelecommApp.getInstance(), EmergencyCallServiceSelector.class);
+ CallServiceSelectorWrapper emergencySelector =
+ new CallServiceSelectorWrapper(
+ componentName.flattenToShortString(),
+ componentName,
+ mCallsManager,
+ mOutgoingCallsManager);
+
+ selectorsBuilder.add(emergencySelector);
+ selectorsBuilder.addAll(selectors);
+ selectors = selectorsBuilder.build();
+ }
+
+ mOutgoingCallsManager.placeCall(call, mCallServices, selectors);
}
/**
diff --git a/src/com/android/telecomm/TelephonyUtil.java b/src/com/android/telecomm/TelephonyUtil.java
index 7a9ac22..be501f0 100644
--- a/src/com/android/telecomm/TelephonyUtil.java
+++ b/src/com/android/telecomm/TelephonyUtil.java
@@ -43,6 +43,17 @@
return selector.getComponentName().getPackageName().equals(TELEPHONY_PACKAGE_NAME);
}
+ static boolean isPstnCallService(CallServiceDescriptor descriptor) {
+ ComponentName componentName = descriptor.getServiceComponent();
+ if (TELEPHONY_PACKAGE_NAME.equals(componentName.getPackageName())) {
+ String className = componentName.getClassName();
+ return GSM_CALL_SERVICE_CLASS_NAME.equals(className) ||
+ CDMA_CALL_SERVICE_CLASS_NAME.equals(className);
+ }
+
+ return false;
+ }
+
/**
* Returns whether or not the call is currently connected as a cellular call (through the
* device's cellular radio).
@@ -57,10 +68,7 @@
if (callService == null) {
return false;
}
- CallServiceDescriptor descriptor = callService.getDescriptor();
- String className = descriptor.getServiceComponent().getClassName();
- return className.equals(GSM_CALL_SERVICE_CLASS_NAME) ||
- className.equals(CDMA_CALL_SERVICE_CLASS_NAME);
+ return isPstnCallService(callService.getDescriptor());
}
private static void verifyCallServiceExists(String serviceName) {
diff --git a/tests/src/com/android/telecomm/testcallservice/DummyCallServiceSelector.java b/tests/src/com/android/telecomm/testcallservice/DummyCallServiceSelector.java
index f759291..c267867 100644
--- a/tests/src/com/android/telecomm/testcallservice/DummyCallServiceSelector.java
+++ b/tests/src/com/android/telecomm/testcallservice/DummyCallServiceSelector.java
@@ -30,12 +30,6 @@
* was given. Also returns false for every request on switchability.
*/
public class DummyCallServiceSelector extends CallServiceSelector {
- private CallServiceSelectorAdapter mAdapter;
-
- @Override
- protected void setCallServiceSelectorAdapter(CallServiceSelectorAdapter adapter) {
- mAdapter = adapter;
- }
@Override
protected void select(CallInfo callInfo, List<CallServiceDescriptor> descriptors) {
@@ -49,6 +43,6 @@
}
}
- mAdapter.setSelectedCallServices(callInfo.getId(), orderedList);
+ getAdapter().setSelectedCallServices(callInfo.getId(), orderedList);
}
}
diff --git a/tests/src/com/android/telecomm/testcallservice/TestCallService.java b/tests/src/com/android/telecomm/testcallservice/TestCallService.java
index 4e459a3..f6e2da7 100644
--- a/tests/src/com/android/telecomm/testcallservice/TestCallService.java
+++ b/tests/src/com/android/telecomm/testcallservice/TestCallService.java
@@ -49,21 +49,15 @@
private Set<String> mLiveCallIds;
/**
- * Adapter to call back into CallsManager.
- */
- private CallServiceAdapter mTelecommAdapter;
-
- /**
* Used to play an audio tone during a call.
*/
private MediaPlayer mMediaPlayer;
/** {@inheritDoc} */
@Override
- public void setCallServiceAdapter(CallServiceAdapter callServiceAdapter) {
+ public void onAdapterAttached(CallServiceAdapter callServiceAdapter) {
Log.i(TAG, "setCallServiceAdapter()");
- mTelecommAdapter = callServiceAdapter;
mLiveCallIds = Sets.newHashSet();
mMediaPlayer = createMediaPlayer();
@@ -87,7 +81,7 @@
// Returning positively on setCompatibleWith() doesn't guarantee that we will be chosen
// to place the call. If we *are* chosen then CallsManager will execute the call()
// method below.
- mTelecommAdapter.setIsCompatibleWith(callInfo.getId(), isCompatible);
+ getAdapter().setIsCompatibleWith(callInfo.getId(), isCompatible);
}
/**
@@ -107,7 +101,7 @@
}
createCall(callInfo.getId());
- mTelecommAdapter.handleSuccessfulOutgoingCall(callInfo.getId());
+ getAdapter().handleSuccessfulOutgoingCall(callInfo.getId());
}
/** {@inheritDoc} */
@@ -126,20 +120,20 @@
Uri handle = Uri.fromParts("tel", "5551234", null);
CallInfo callInfo = new CallInfo(callId, CallState.RINGING, handle);
- mTelecommAdapter.notifyIncomingCall(callInfo);
+ getAdapter().notifyIncomingCall(callInfo);
}
/** {@inheritDoc} */
@Override
public void answer(String callId) {
- mTelecommAdapter.setActive(callId);
+ getAdapter().setActive(callId);
createCall(callId);
}
/** {@inheritDoc} */
@Override
public void reject(String callId) {
- mTelecommAdapter.setDisconnected(callId, DisconnectCause.INCOMING_REJECTED, null);
+ getAdapter().setDisconnected(callId, DisconnectCause.INCOMING_REJECTED, null);
}
/** {@inheritDoc} */
@@ -148,21 +142,21 @@
Log.i(TAG, "disconnect(" + callId + ")");
destroyCall(callId);
- mTelecommAdapter.setDisconnected(callId, DisconnectCause.LOCAL, null);
+ getAdapter().setDisconnected(callId, DisconnectCause.LOCAL, null);
}
/** {@inheritDoc} */
@Override
public void hold(String callId) {
Log.i(TAG, "hold(" + callId + ")");
- mTelecommAdapter.setOnHold(callId);
+ getAdapter().setOnHold(callId);
}
/** {@inheritDoc} */
@Override
public void unhold(String callId) {
Log.i(TAG, "unhold(" + callId + ")");
- mTelecommAdapter.setActive(callId);
+ getAdapter().setActive(callId);
}
/** {@inheritDoc} */