Add CallsManagerListener
Also contains some minor bug fixes:
- add an incoming field to Call
- correcty log failed outgoing calls (previously these were mostly dropped)
- log missed incoming calls
Change-Id: I72dc39efd519302c1f765f4f9c9d04c5095e45a6
diff --git a/Android.mk b/Android.mk
index 76a190e..48eece0 100644
--- a/Android.mk
+++ b/Android.mk
@@ -15,6 +15,9 @@
LOCAL_PROGUARD_ENABLED := optimization
+# Workaround for "local variable type mismatch" error.
+LOCAL_DX_FLAGS += --no-locals
+
include $(BUILD_PACKAGE)
# Build the test package.
diff --git a/src/com/android/telecomm/Call.java b/src/com/android/telecomm/Call.java
index 849c876..82bde9e 100644
--- a/src/com/android/telecomm/Call.java
+++ b/src/com/android/telecomm/Call.java
@@ -41,6 +41,9 @@
/** Additional contact information beyond handle above, optional. */
private final ContactInfo mContactInfo;
+ /** True if this is an incoming call. */
+ private final boolean mIsIncoming;
+
/**
* The time this call was created, typically also the time this call was added to the set
* of pending outgoing calls (mPendingOutgoingCalls) that's maintained by the switchboard.
@@ -77,9 +80,11 @@
/**
* Creates an empty call object with a unique call ID.
+ *
+ * @param isIncoming True if this is an incoming call.
*/
- Call() {
- this(null, null);
+ Call(boolean isIncoming) {
+ this(null, null, isIncoming);
}
/**
@@ -87,19 +92,22 @@
*
* @param handle The handle to dial.
* @param contactInfo Information about the entity being called.
+ * @param isIncoming True if this is an incoming call.
*/
- Call(Uri handle, ContactInfo contactInfo) {
+ Call(Uri handle, ContactInfo contactInfo, boolean isIncoming) {
mId = UUID.randomUUID().toString(); // UUIDs should provide sufficient uniqueness.
mState = CallState.NEW;
mHandle = handle;
mContactInfo = contactInfo;
+ mIsIncoming = isIncoming;
mCreationTime = new Date();
}
/** {@inheritDoc} */
@Override public String toString() {
return String.format(Locale.US, "[%s, %s, %s, %s]", mId, mState,
- mCallService.getComponentName(), Log.pii(mHandle));
+ mCallService == null ? "<null>" : mCallService.getComponentName(),
+ Log.pii(mHandle));
}
String getId() {
@@ -116,9 +124,12 @@
* misbehave and they do this very often. The result is that we do not enforce state transitions
* and instead keep the code resilient to unexpected state changes.
*/
- void setState(CallState state) {
- mState = state;
- clearCallInfo();
+ void setState(CallState newState) {
+ if (mState != newState) {
+ Log.v(this, "setState %s -> %s", mState, newState);
+ mState = newState;
+ clearCallInfo();
+ }
}
Uri getHandle() {
@@ -133,6 +144,10 @@
return mContactInfo;
}
+ boolean isIncoming() {
+ return mIsIncoming;
+ }
+
/**
* @return The "age" of this call object in milliseconds, which typically also represents the
* period since this call was added to the set pending outgoing calls, see mCreationTime.
@@ -222,7 +237,6 @@
}
clearCallService();
clearCallServiceSelector();
- mState = CallState.ABORTED;
}
}
diff --git a/src/com/android/telecomm/CallAudioManager.java b/src/com/android/telecomm/CallAudioManager.java
new file mode 100644
index 0000000..07d5dbe
--- /dev/null
+++ b/src/com/android/telecomm/CallAudioManager.java
@@ -0,0 +1,59 @@
+/*
+ * 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.content.Context;
+import android.media.AudioManager;
+import android.telecomm.CallState;
+
+/**
+ * This class manages audio modes, streams and other properties.
+ */
+final class CallAudioManager extends CallsManagerListenerBase {
+ @Override
+ public void onCallStateChanged(Call call, CallState oldState, CallState newState) {
+ switch (newState) {
+ case ACTIVE:
+ updateAudioFocusForActiveCall(call);
+ break;
+ case DISCONNECTED:
+ updateAudioFocusForNoCall();
+ break;
+ default:
+ break;
+ }
+ }
+
+ private void updateAudioFocusForActiveCall(Call call) {
+ Log.v(this, "onForegroundCallChanged, requesting audio focus");
+ Context context = TelecommApp.getInstance();
+ AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+ audioManager.requestAudioFocusForCall(AudioManager.STREAM_VOICE_CALL,
+ AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
+ audioManager.setMode(AudioManager.MODE_IN_CALL);
+ audioManager.setMicrophoneMute(false);
+ audioManager.setSpeakerphoneOn(false);
+ }
+
+ private void updateAudioFocusForNoCall() {
+ Log.v(this, "updateAudioFocusForNoCall, abandoning audio focus");
+ Context context = TelecommApp.getInstance().getApplicationContext();
+ AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+ audioManager.setMode(AudioManager.MODE_NORMAL);
+ audioManager.abandonAudioFocusForCall();
+ }
+}
diff --git a/src/com/android/telecomm/CallLogManager.java b/src/com/android/telecomm/CallLogManager.java
index 718c685..faaa12d 100644
--- a/src/com/android/telecomm/CallLogManager.java
+++ b/src/com/android/telecomm/CallLogManager.java
@@ -20,6 +20,7 @@
import android.net.Uri;
import android.os.AsyncTask;
import android.provider.CallLog.Calls;
+import android.telecomm.CallState;
import android.telephony.PhoneNumberUtils;
import com.android.internal.telephony.PhoneConstants;
@@ -29,7 +30,7 @@
* caller details to the call log. All logging activity will be performed asynchronously in a
* background thread to avoid blocking on the main thread.
*/
-class CallLogManager {
+final class CallLogManager extends CallsManagerListenerBase {
/**
* Parameter object to hold the arguments to add a call in the call log DB.
*/
@@ -72,19 +73,19 @@
mContext = context;
}
- void logDisconnectedCall(Call call) {
- // TODO: Until we add more state to the Call object to track whether this disconnected
- // call orginated as an incoming or outgoing call, always log it as an incoming call.
- // See b/13420887.
- logCall(call, Calls.INCOMING_TYPE);
- }
-
- void logFailedOutgoingCall(Call call) {
- logCall(call, Calls.OUTGOING_TYPE);
- }
-
- void logMissedCall(Call call) {
- logCall(call, Calls.MISSED_TYPE);
+ @Override
+ public void onCallStateChanged(Call call, CallState oldState, CallState newState) {
+ if (newState == CallState.DISCONNECTED || newState == CallState.ABORTED) {
+ int type;
+ if (!call.isIncoming()) {
+ type = Calls.OUTGOING_TYPE;
+ } else if (oldState == CallState.RINGING) {
+ type = Calls.MISSED_TYPE;
+ } else {
+ type = Calls.INCOMING_TYPE;
+ }
+ logCall(call, type);
+ }
}
/**
diff --git a/src/com/android/telecomm/CallsManager.java b/src/com/android/telecomm/CallsManager.java
index 26f7ac1..452f8e1 100644
--- a/src/com/android/telecomm/CallsManager.java
+++ b/src/com/android/telecomm/CallsManager.java
@@ -16,21 +16,18 @@
package com.android.telecomm;
-import android.Manifest;
import android.content.Context;
-import android.content.Intent;
-import android.media.AudioManager;
import android.net.Uri;
import android.os.Bundle;
-import android.telecomm.CallService;
+import android.telecomm.CallInfo;
import android.telecomm.CallServiceDescriptor;
import android.telecomm.CallState;
-import android.telecomm.TelecommConstants;
-import android.telephony.TelephonyManager;
import com.android.internal.telecomm.ICallService;
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;
@@ -46,15 +43,19 @@
*/
public final class CallsManager {
+ interface CallsManagerListener {
+ void onCallAdded(Call call);
+ void onCallRemoved(Call call);
+ void onCallStateChanged(Call call, CallState oldState, CallState newState);
+ void onIncomingCallAnswered(Call call);
+ void onIncomingCallRejected(Call call);
+ void onForegroundCallChanged(Call oldForegroundCall, Call newForegroundCall);
+ }
+
private static final CallsManager INSTANCE = new CallsManager();
private final Switchboard mSwitchboard;
- /** Used to control the in-call app. */
- private final InCallController mInCallController;
-
- private final Ringer mRinger;
-
/**
* The main call repository. Keeps an instance of all live calls keyed by call ID. New incoming
* and outgoing calls are added to the map and removed when the calls move to the disconnected
@@ -64,24 +65,20 @@
private final Map<String, Call> mCalls = Maps.newHashMap();
/**
- * Used to keep ordering of unanswered incoming calls. The existence of multiple call services
- * means that there can easily exist multiple incoming calls and explicit ordering is useful for
- * maintaining the proper state of the ringer.
- * TODO(santoscordon): May want to add comments about ITelephony.answerCall() method since
- * ordering may also apply to that case.
+ * 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.
*/
- private final List<Call> mUnansweredIncomingCalls = Lists.newLinkedList();
+ private Call mForegroundCall;
- /**
- * May be unnecessary per off-line discussions (between santoscordon and gilad) since the set
- * of CallsManager APIs that need to be exposed to the dialer (or any application firing call
- * intents) may be empty.
- */
- private DialerAdapter mDialerAdapter;
+ private final CallsManagerListener mCallLogManager;
- private InCallAdapter mInCallAdapter;
+ private final CallsManagerListener mPhoneStateBroadcaster;
- private CallLogManager mCallLogManager;
+ private final CallsManagerListener mCallAudioManager;
+
+ private final CallsManagerListener mInCallController;
+
+ private final CallsManagerListener mRinger;
private VoicemailManager mVoicemailManager;
@@ -94,15 +91,25 @@
*/
private CallsManager() {
mSwitchboard = new Switchboard(this);
- mInCallController = new InCallController(this);
- mRinger = new Ringer();
mCallLogManager = new CallLogManager(TelecommApp.getInstance());
+ mPhoneStateBroadcaster = new PhoneStateBroadcaster();
+ mCallAudioManager = new CallAudioManager();
+ mInCallController = new InCallController();
+ mRinger = new Ringer();
}
static CallsManager getInstance() {
return INSTANCE;
}
+ ImmutableCollection<Call> getCalls() {
+ return ImmutableList.copyOf(mCalls.values());
+ }
+
+ Call getForegroundCall() {
+ return mForegroundCall;
+ }
+
/**
* 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
@@ -116,7 +123,7 @@
// Create a call with no handle. Eventually, switchboard will update the call with
// additional information from the call service, but for now we just need one to pass around
// with a unique call ID.
- Call call = new Call();
+ Call call = new Call(true /* isIncoming */);
mSwitchboard.retrieveIncomingCall(call, descriptor, extras);
}
@@ -126,10 +133,11 @@
* 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) {
+ void handleSuccessfulIncomingCall(Call call, CallInfo callInfo) {
Log.d(this, "handleSuccessfulIncomingCall");
- Preconditions.checkState(call.getState() == CallState.RINGING);
+ Preconditions.checkState(callInfo.getState() == CallState.RINGING);
Uri handle = call.getHandle();
ContactInfo contactInfo = call.getContactInfo();
@@ -143,13 +151,18 @@
// 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);
+ }
- mUnansweredIncomingCalls.add(call);
- if (mUnansweredIncomingCalls.size() == 1) {
- // Start the ringer if we are the top-most incoming call (the only one in this case).
- mRinger.startRinging();
- }
+ /**
+ * Called when an incoming call was not connected.
+ *
+ * @param call The incoming call.
+ */
+ void handleUnsuccessfulIncomingCall(Call call) {
+ setCallState(call, CallState.DISCONNECTED);
}
/**
@@ -171,30 +184,36 @@
}
// No objection to issue the call, proceed with trying to put it through.
- Call call = new Call(handle, contactInfo);
+ Call call = new Call(handle, contactInfo, false /* isIncoming */);
+ setCallState(call, CallState.DIALING);
+ addCall(call);
mSwitchboard.placeOutgoingCall(call);
}
/**
- * Adds a new outgoing call to the list of live calls and notifies the in-call app.
+ * Called when a call service acknowledges that it can place a call.
*
* @param call The new outgoing call.
*/
void handleSuccessfulOutgoingCall(Call call) {
- // OutgoingCallProcessor sets the call state to DIALING when it receives confirmation of the
- // placed call from the call service so there is no need to set it here. Instead, check that
- // the state is appropriate.
- Preconditions.checkState(call.getState() == CallState.DIALING);
- addCall(call);
+ Log.v(this, "handleSuccessfulOutgoingCall, %s", call);
}
/**
- * Informs mCallLogManager about the outgoing call that failed, so that it can be logged.
+ * Called when an outgoing call was not placed.
*
- * @param call The failed outgoing call.
+ * @param call The outgoing call.
+ * @param isAborted True if the call was unsuccessful because it was aborted.
*/
- void handleFailedOutgoingCall(Call call) {
- mCallLogManager.logFailedOutgoingCall(call);
+ void handleUnsuccessfulOutgoingCall(Call call, boolean isAborted) {
+ Log.v(this, "handleAbortedOutgoingCall, call: %s, isAborted: %b", call, isAborted);
+ if (isAborted) {
+ call.abort();
+ setCallState(call, CallState.ABORTED);
+ } else {
+ setCallState(call, CallState.DISCONNECTED);
+ }
+ removeCall(call);
}
/**
@@ -209,7 +228,11 @@
if (call == null) {
Log.i(this, "Request to answer a non-existent call %s", callId);
} else {
- stopRinging(call);
+ mCallLogManager.onIncomingCallAnswered(call);
+ mPhoneStateBroadcaster.onIncomingCallAnswered(call);
+ mCallAudioManager.onIncomingCallAnswered(call);
+ mInCallController.onIncomingCallAnswered(call);
+ mRinger.onIncomingCallAnswered(call);
// We do not update the UI until we get confirmation of the answer() through
// {@link #markCallAsActive}. However, if we ever change that to look more responsive,
@@ -231,7 +254,12 @@
if (call == null) {
Log.i(this, "Request to reject a non-existent call %s", callId);
} else {
- stopRinging(call);
+ mCallLogManager.onIncomingCallRejected(call);
+ mPhoneStateBroadcaster.onIncomingCallRejected(call);
+ mCallAudioManager.onIncomingCallRejected(call);
+ mInCallController.onIncomingCallRejected(call);
+ mRinger.onIncomingCallRejected(call);
+
call.reject();
}
}
@@ -250,13 +278,6 @@
} else {
call.disconnect();
}
-
- // TODO(sail): Replace with CallAudioManager.
- Log.v(this, "disconnectCall, abandoning audio focus");
- Context context = TelecommApp.getInstance().getApplicationContext();
- AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
- audioManager.setMode(AudioManager.MODE_NORMAL);
- audioManager.abandonAudioFocusForCall();
}
/**
@@ -303,25 +324,10 @@
void markCallAsActive(String callId) {
setCallState(callId, CallState.ACTIVE);
- removeFromUnansweredCalls(callId);
- mInCallController.markCallAsActive(callId);
-
- // TODO(sail): Replace with CallAudioManager.
- Log.v(this, "markCallAsActive, requesting audio focus");
- Context context = TelecommApp.getInstance().getApplicationContext();
- AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
- audioManager.requestAudioFocusForCall(AudioManager.STREAM_VOICE_CALL,
- AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
- audioManager.setMode(AudioManager.MODE_IN_CALL);
- audioManager.setMicrophoneMute(false);
- audioManager.setSpeakerphoneOn(false);
}
void markCallAsOnHold(String callId) {
setCallState(callId, CallState.ON_HOLD);
-
- // Notify the in-call UI
- mInCallController.markCallAsOnHold(callId);
}
/**
@@ -332,35 +338,7 @@
*/
void markCallAsDisconnected(String callId) {
setCallState(callId, CallState.DISCONNECTED);
- removeFromUnansweredCalls(callId);
-
- Call call = mCalls.remove(callId);
- // At this point the call service has confirmed that the call is disconnected to it is
- // safe to disassociate the call from its call service.
- call.clearCallService();
-
- // Notify the in-call UI
- mInCallController.markCallAsDisconnected(callId);
- if (mCalls.isEmpty()) {
- mInCallController.unbind();
- }
-
- // Log the call in the call log.
- mCallLogManager.logDisconnectedCall(call);
- }
-
- /**
- * Sends all the live calls to the in-call app if any exist. If there are no live calls, then
- * tells the in-call controller to unbind since it is not needed.
- */
- void updateInCall() {
- if (mCalls.isEmpty()) {
- mInCallController.unbind();
- } else {
- for (Call call : mCalls.values()) {
- addInCallEntry(call);
- }
- }
+ removeCall(mCalls.remove(callId));
}
/**
@@ -370,14 +348,24 @@
*/
private void addCall(Call call) {
mCalls.put(call.getId(), call);
- addInCallEntry(call);
+
+ mCallLogManager.onCallAdded(call);
+ mPhoneStateBroadcaster.onCallAdded(call);
+ mCallAudioManager.onCallAdded(call);
+ mInCallController.onCallAdded(call);
+ mRinger.onCallAdded(call);
+ updateForegroundCall();
}
- /**
- * Notifies the in-call app of the specified (new) call.
- */
- private void addInCallEntry(Call call) {
- mInCallController.addCall(call.toCallInfo());
+ private void removeCall(Call call) {
+ call.clearCallService();
+
+ mCallLogManager.onCallRemoved(call);
+ mPhoneStateBroadcaster.onCallRemoved(call);
+ mCallAudioManager.onCallRemoved(call);
+ mInCallController.onCallRemoved(call);
+ mRinger.onCallRemoved(call);
+ updateForegroundCall();
}
/**
@@ -396,102 +384,67 @@
Log.w(this, "Call %s was not found while attempting to update the state to %s.",
callId, newState );
} else {
- if (newState != call.getState()) {
- // Unfortunately, in the telephony world the radio is king. So if the call notifies
- // us that the call is in a particular state, we allow it even if it doesn't make
- // sense (e.g., ACTIVE -> RINGING).
- // TODO(santoscordon): Consider putting a stop to the above and turning CallState
- // into a well-defined state machine.
- // TODO(santoscordon): Define expected state transitions here, and log when an
- // unexpected transition occurs.
- call.setState(newState);
- // TODO(santoscordon): Notify the in-call app whenever a call changes state.
+ setCallState(call, newState);
+ }
+ }
- broadcastState(call);
+ /**
+ * Sets the specified state on the specified call. Updates the ringer if the call is exiting
+ * the RINGING state.
+ *
+ * @param call The call.
+ * @param newState The new state of the call.
+ */
+ private void setCallState(Call call, CallState newState) {
+ CallState oldState = call.getState();
+ if (newState != oldState) {
+ // Unfortunately, in the telephony world the radio is king. So if the call notifies
+ // us that the call is in a particular state, we allow it even if it doesn't make
+ // sense (e.g., ACTIVE -> RINGING).
+ // TODO(santoscordon): Consider putting a stop to the above and turning CallState
+ // into a well-defined state machine.
+ // TODO(santoscordon): Define expected state transitions here, and log when an
+ // unexpected transition occurs.
+ call.setState(newState);
+
+ // Only broadcast state change for calls that are being tracked.
+ if (mCalls.containsKey(call.getId())) {
+ mCallLogManager.onCallStateChanged(call, oldState, newState);
+ mPhoneStateBroadcaster.onCallStateChanged(call, oldState, newState);
+ mCallAudioManager.onCallStateChanged(call, oldState, newState);
+ mInCallController.onCallStateChanged(call, oldState, newState);
+ mRinger.onCallStateChanged(call, oldState, newState);
+ updateForegroundCall();
}
}
}
/**
- * Send a {@link TelephonyManager#ACTION_PHONE_STATE_CHANGED} broadcast when the call state
- * changes. TODO: Split this out into a separate class and do it properly; this is just a first
- * pass over the functionality.
- *
- * @param call The {@link Call} being updated.
+ * Checks which call should be visible to the user and have audio focus.
*/
- private void broadcastState(Call call) {
- final String callState;
- switch (call.getState()) {
- case DIALING:
- case ACTIVE:
- case ON_HOLD:
- callState = TelephonyManager.EXTRA_STATE_OFFHOOK;
+ private void updateForegroundCall() {
+ Call newForegroundCall = null;
+ for (Call call : mCalls.values()) {
+ // Incoming ringing calls have priority.
+ if (call.getState() == CallState.RINGING) {
+ newForegroundCall = call;
break;
-
- case RINGING:
- callState = TelephonyManager.EXTRA_STATE_RINGING;
- break;
-
- case DISCONNECTED:
- case ABORTED:
- callState = TelephonyManager.EXTRA_STATE_IDLE;
- break;
-
- default:
- Log.w(this, "Call is in an unknown state (%s), not broadcasting: %s", call.getState(),
- call.getId());
- return;
- }
-
- Intent updateIntent = new Intent(TelephonyManager.ACTION_PHONE_STATE_CHANGED);
- updateIntent.putExtra(TelephonyManager.EXTRA_STATE, callState);
- // TODO: See if we can add this (the current API doesn't have a callId).
- updateIntent.putExtra(TelecommConstants.EXTRA_CALL_ID, call.getId());
-
- // Populate both, since the original API was needlessly complicated.
- updateIntent.putExtra(TelephonyManager.EXTRA_INCOMING_NUMBER, call.getHandle());
- // TODO: See if we can add this (the current API only sets this on NEW_OUTGOING_CALL).
- updateIntent.putExtra(Intent.EXTRA_PHONE_NUMBER, call.getHandle());
-
- // TODO: Replace these with real constants once this API has been vetted.
- CallServiceWrapper callService = call.getCallService();
- if (callService != null) {
- updateIntent.putExtra(CallService.class.getName(), callService.getComponentName());
- }
- TelecommApp.getInstance().sendBroadcast(updateIntent, Manifest.permission.READ_PHONE_STATE);
- Log.i(this, "Broadcasted state change: %s", callState);
- }
-
- /**
- * Removes the specified call from the list of unanswered incoming calls and updates the ringer
- * based on the new state of {@link #mUnansweredIncomingCalls}. Safe to call with a call ID that
- * is not present in the list of incoming calls.
- *
- * @param callId The ID of the call.
- */
- private void removeFromUnansweredCalls(String callId) {
- Call call = mCalls.get(callId);
- if (call != null && mUnansweredIncomingCalls.remove(call)) {
- if (mUnansweredIncomingCalls.isEmpty()) {
- mRinger.stopRinging();
- } else {
- mRinger.startRinging();
+ }
+ if (call.getState() == CallState.ACTIVE) {
+ newForegroundCall = call;
+ // Don't break in case there's a ringing call that has priority.
}
}
- }
- /**
- * Stops playing the ringer if the specified call is the top-most incoming call. This exists
- * separately from {@link #removeFromUnansweredCalls} to allow stopping the ringer for calls
- * that should remain in {@link #mUnansweredIncomingCalls}, invoked from {@link #answerCall}
- * and {@link #rejectCall}.
- *
- * @param call The call for which we should stop ringing.
- */
- private void stopRinging(Call call) {
- // Only stop the ringer if this call is the top-most incoming call.
- if (!mUnansweredIncomingCalls.isEmpty() && mUnansweredIncomingCalls.get(0) == call) {
- mRinger.stopRinging();
+ if (newForegroundCall != mForegroundCall) {
+ Call oldForegroundCall = mForegroundCall;
+ mForegroundCall = newForegroundCall;
+
+ mCallLogManager.onForegroundCallChanged(oldForegroundCall, mForegroundCall);
+ mPhoneStateBroadcaster.onForegroundCallChanged(oldForegroundCall, mForegroundCall);
+ mCallAudioManager.onForegroundCallChanged(oldForegroundCall, mForegroundCall);
+ mInCallController.onForegroundCallChanged(oldForegroundCall, mForegroundCall);
+ mRinger.onForegroundCallChanged(oldForegroundCall, mForegroundCall);
}
}
}
diff --git a/src/com/android/telecomm/CallsManagerListenerBase.java b/src/com/android/telecomm/CallsManagerListenerBase.java
new file mode 100644
index 0000000..c81067f
--- /dev/null
+++ b/src/com/android/telecomm/CallsManagerListenerBase.java
@@ -0,0 +1,48 @@
+/*
+ * 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.telecomm.CallState;
+
+/**
+ * Provides a default implementation for listeners for CallsManager.
+ */
+class CallsManagerListenerBase implements CallsManager.CallsManagerListener {
+ @Override
+ public void onCallAdded(Call call) {
+ }
+
+ @Override
+ public void onCallRemoved(Call call) {
+ }
+
+ @Override
+ public void onCallStateChanged(Call call, CallState oldState, CallState newState) {
+ }
+
+ @Override
+ public void onIncomingCallAnswered(Call call) {
+ }
+
+ @Override
+ public void onIncomingCallRejected(Call call) {
+ }
+
+ @Override
+ public void onForegroundCallChanged(Call oldForegroundCall, Call newForegroundCall) {
+ }
+}
diff --git a/src/com/android/telecomm/InCallController.java b/src/com/android/telecomm/InCallController.java
index 4f863c5..0bfe30d 100644
--- a/src/com/android/telecomm/InCallController.java
+++ b/src/com/android/telecomm/InCallController.java
@@ -23,8 +23,10 @@
import android.os.IBinder;
import android.os.RemoteException;
import android.telecomm.CallInfo;
+import android.telecomm.CallState;
import com.android.internal.telecomm.IInCallService;
+import com.google.common.collect.ImmutableCollection;
/**
* Binds to {@link IInCallService} and provides the service to {@link CallsManager} through which it
@@ -35,7 +37,7 @@
* CallsManager will invoke {@link #disconnect} to sever the binding until the in-call UI is needed
* again.
*/
-public final class InCallController {
+public final class InCallController extends CallsManagerListenerBase {
/**
* Used to bind to the in-call app and triggers the start of communication between
* CallsManager and in-call app.
@@ -69,93 +71,77 @@
/** Maintains a binding connection to the in-call app. */
private final InCallServiceConnection mConnection = new InCallServiceConnection();
- private final CallsManager mCallsManager;
-
/** The in-call app implementation, see {@link IInCallService}. */
private IInCallService mInCallService;
- /**
- * Persists the specified parameters.
- */
- InCallController(CallsManager callsManager) {
- mCallsManager = callsManager;
- }
-
// TODO(santoscordon): May be better to expose the IInCallService methods directly from this
// class as its own method to make the CallsManager code easier to read.
IInCallService getService() {
return mInCallService;
}
- /**
- * Indicates to the in-call app that a new call has been created and an appropriate
- * user-interface should be built and shown to notify the user. Information about the call
- * including its current state is passed in through the callInfo object.
- *
- * @param callInfo Details about the new call.
- */
- void addCall(CallInfo callInfo) {
- try {
- if (mInCallService == null) {
- bind();
- } else {
- // TODO(santoscordon): Protect against logging phone number.
- Log.i(this, "Adding call: %s", callInfo);
- mInCallService.addCall(callInfo);
+ @Override
+ public void onCallAdded(Call call) {
+ if (mInCallService == null) {
+ bind();
+ } else {
+ Log.i(this, "Adding call: %s", call);
+ try {
+ mInCallService.addCall(call.toCallInfo());
+ } catch (RemoteException e) {
+ Log.e(this, e, "Exception attempting to addCall.");
}
- } catch (RemoteException e) {
- Log.e(this, e, "Exception attempting to addCall.");
}
}
- /**
- * Indicates to the in-call app that a call has moved to the active state.
- *
- * @param callId The identifier of the call that became active.
- */
- void markCallAsActive(String callId) {
- try {
- if (mInCallService != null) {
- Log.i(this, "Mark call as ACTIVE: %s", callId);
- mInCallService.setActive(callId);
- }
- } catch (RemoteException e) {
- Log.e(this, e, "Exception attempting to markCallAsActive.");
+ @Override
+ public void onCallRemoved(Call call) {
+ if (CallsManager.getInstance().getCalls().isEmpty()) {
+ // TODO(sail): Wait for all messages to be delivered to the service before unbinding.
+ unbind();
}
}
- /**
- * Indicates to the in-call app that a call has been disconnected and the user should be
- * notified.
- *
- * @param callId The identifier of the call that was disconnected.
- */
- void markCallAsDisconnected(String callId) {
- try {
- if (mInCallService != null) {
- Log.i(this, "Mark call as DISCONNECTED: %s", callId);
- mInCallService.setDisconnected(callId);
- }
- } catch (RemoteException e) {
- Log.e(this, e, "Exception attempting to markCallAsDisconnected.");
+ @Override
+ public void onCallStateChanged(Call call, CallState oldState, CallState newState) {
+ if (mInCallService == null) {
+ return;
}
- }
- void markCallAsOnHold(String callId) {
- try {
- if (mInCallService != null) {
- Log.i(this, "Mark call as HOLD: %s", callId);
- mInCallService.setOnHold(callId);
- }
- } catch (RemoteException e) {
- Log.e(this, e, "Exception attempting to markCallAsHeld.");
+ switch (newState) {
+ case ACTIVE:
+ Log.i(this, "Mark call as ACTIVE: %s", call.getId());
+ try {
+ mInCallService.setActive(call.getId());
+ } catch (RemoteException e) {
+ Log.e(this, e, "Exception attempting to call setActive.");
+ }
+ break;
+ case ON_HOLD:
+ Log.i(this, "Mark call as HOLD: %s", call.getId());
+ try {
+ mInCallService.setOnHold(call.getId());
+ } catch (RemoteException e) {
+ Log.e(this, e, "Exception attempting to call setOnHold.");
+ }
+ break;
+ case DISCONNECTED:
+ Log.i(this, "Mark call as DISCONNECTED: %s", call.getId());
+ try {
+ mInCallService.setDisconnected(call.getId());
+ } catch (RemoteException e) {
+ Log.e(this, e, "Exception attempting to call setDisconnected.");
+ }
+ break;
+ default:
+ break;
}
}
/**
* Unbinds an existing bound connection to the in-call app.
*/
- void unbind() {
+ private void unbind() {
ThreadUtil.checkOnMainThread();
if (mInCallService != null) {
Log.i(this, "Unbinding from InCallService");
@@ -199,17 +185,22 @@
mInCallService = IInCallService.Stub.asInterface(service);
try {
- mInCallService.setInCallAdapter(new InCallAdapter(mCallsManager));
+ mInCallService.setInCallAdapter(new InCallAdapter(CallsManager.getInstance()));
} catch (RemoteException e) {
Log.e(this, e, "Failed to set the in-call adapter.");
mInCallService = null;
+ return;
}
// Upon successful connection, send the state of the world to the in-call app.
- if (mInCallService != null) {
- mCallsManager.updateInCall();
+ ImmutableCollection<Call> calls = CallsManager.getInstance().getCalls();
+ if (!calls.isEmpty()) {
+ for (Call call : calls) {
+ onCallAdded(call);
+ }
+ } else {
+ unbind();
}
-
}
/**
diff --git a/src/com/android/telecomm/IncomingCallsManager.java b/src/com/android/telecomm/IncomingCallsManager.java
index 0493df8..6edbbbf 100644
--- a/src/com/android/telecomm/IncomingCallsManager.java
+++ b/src/com/android/telecomm/IncomingCallsManager.java
@@ -82,10 +82,7 @@
Call call = mPendingIncomingCalls.remove(callInfo.getId());
if (call != null) {
Log.d(this, "Incoming call %s found.", call.getId());
- call.setHandle(callInfo.getHandle());
- call.setState(callInfo.getState());
-
- mSwitchboard.handleSuccessfulIncomingCall(call);
+ mSwitchboard.handleSuccessfulIncomingCall(call, callInfo);
}
}
diff --git a/src/com/android/telecomm/OutgoingCallProcessor.java b/src/com/android/telecomm/OutgoingCallProcessor.java
index bd49b66..bbcdb17 100644
--- a/src/com/android/telecomm/OutgoingCallProcessor.java
+++ b/src/com/android/telecomm/OutgoingCallProcessor.java
@@ -144,6 +144,7 @@
* Initiates the attempt to place the call. No-op beyond the first invocation.
*/
void process() {
+ Log.v(this, "process, mIsAborted: %b", mIsAborted);
if (!mIsAborted) {
// Only process un-aborted calls.
ThreadUtil.checkOnMainThread();
@@ -165,23 +166,24 @@
* false otherwise.
*/
void setIsCompatibleWith(String callId, boolean isCompatible) {
- if (!callId.equals(mCall.getId())) {
- Log.wtf(this, "setIsCompatibleWith invoked with unexpected call ID: %s - expected call"
+ Log.v(this, "setIsCompatibleWith, callId: %s, isCompatible: %b", callId, isCompatible);
+ if (!callId.equals(mCall.getId())) {
+ Log.wtf(this, "setIsCompatibleWith invoked with unexpected call ID: %s - expected call"
+ " ID: %s", callId, mCall.getId());
- return;
- }
+ return;
+ }
- if (!mIsAborted) {
- CallServiceWrapper callService = mCall.getCallService();
- if (callService != null) {
- if (isCompatible) {
- callService.call(mCall.toCallInfo(), mNextCallServiceCallback);
- return;
- }
- mIncompatibleCallServices.add(callService);
- }
- attemptNextCallService();
- }
+ if (!mIsAborted) {
+ CallServiceWrapper callService = mCall.getCallService();
+ if (callService != null) {
+ if (isCompatible) {
+ callService.call(mCall.toCallInfo(), mNextCallServiceCallback);
+ return;
+ }
+ mIncompatibleCallServices.add(callService);
+ }
+ attemptNextCallService();
+ }
}
/**
@@ -189,10 +191,11 @@
* switchboard through the outgoing-calls manager.
*/
void abort() {
+ Log.v(this, "abort");
ThreadUtil.checkOnMainThread();
if (!mIsAborted) {
- mCall.abort();
mIsAborted = true;
+ mOutgoingCallsManager.handleFailedOutgoingCall(mCall, true /* isAborted */);
}
}
@@ -202,6 +205,7 @@
* placed the call.
*/
void handleSuccessfulCallAttempt() {
+ Log.v(this, "handleSuccessfulCallAttempt");
ThreadUtil.checkOnMainThread();
if (mIsAborted) {
@@ -209,7 +213,6 @@
return;
}
- mCall.setState(CallState.DIALING);
for (CallServiceWrapper callService : mIncompatibleCallServices) {
mCall.addIncompatibleCallService(callService);
}
@@ -223,6 +226,7 @@
* @param reason The call-service supplied reason for the failed call attempt.
*/
void handleFailedCallAttempt(String reason) {
+ Log.v(this, "handleFailedCallAttempt");
if (!mIsAborted) {
ThreadUtil.checkOnMainThread();
@@ -237,6 +241,7 @@
* are available.
*/
private void attemptNextSelector() {
+ Log.v(this, "attemptNextSelector, mIsAborted: %b", mIsAborted);
if (mIsAborted) {
return;
}
@@ -253,7 +258,8 @@
}
} else {
- mOutgoingCallsManager.handleFailedOutgoingCall(mCall);
+ Log.v(this, "attemptNextSelector, no more selectors, failing");
+ mOutgoingCallsManager.handleFailedOutgoingCall(mCall, false /* isAborted */);
}
}
@@ -286,6 +292,7 @@
private void processSelectedCallServiceDescriptors(
List<CallServiceDescriptor> selectedCallServiceDescriptors) {
+ Log.v(this, "processSelectedCallServiceDescriptors");
if (selectedCallServiceDescriptors == null || selectedCallServiceDescriptors.isEmpty()) {
attemptNextSelector();
} else if (mCallServiceDescriptorIterator == null) {
@@ -301,6 +308,7 @@
* {@link #attemptNextSelector}.
*/
private void attemptNextCallService() {
+ Log.v(this, "attemptNextCallService, mIsAborted: %b", mIsAborted);
if (mIsAborted) {
return;
}
diff --git a/src/com/android/telecomm/OutgoingCallsManager.java b/src/com/android/telecomm/OutgoingCallsManager.java
index 7c2a501..37f7eb1 100644
--- a/src/com/android/telecomm/OutgoingCallsManager.java
+++ b/src/com/android/telecomm/OutgoingCallsManager.java
@@ -77,6 +77,7 @@
* false otherwise.
*/
void setIsCompatibleWith(String callId, boolean isCompatible) {
+ Log.v(this, "setIsCompatibleWith, callId %s, isCompatible: %b", callId, isCompatible);
OutgoingCallProcessor processor = mOutgoingCallProcessors.get(callId);
if (processor == null) {
// Shouldn't happen, so log a wtf if it does.
@@ -94,6 +95,7 @@
* @param callId The ID of the call.
*/
void handleSuccessfulCallAttempt(String callId) {
+ Log.v(this, "handleSuccessfulCallAttempt, callId: %s", callId);
OutgoingCallProcessor processor = mOutgoingCallProcessors.remove(callId);
if (processor == null) {
@@ -114,6 +116,7 @@
* @param reason The call-service supplied reason for the failed call attempt.
*/
void handleFailedCallAttempt(String callId, String reason) {
+ Log.v(this, "handleFailedCallAttempt, callId: %s, reason: %s", callId, reason);
OutgoingCallProcessor processor = mOutgoingCallProcessors.get(callId);
// TODO(santoscordon): Consider combining the check here and in handleSuccessfulCallAttempt.
@@ -133,10 +136,12 @@
* should cleanup and notify Switchboard.
*
* @param call The failed outgoing call.
+ * @param isAborted True if the call timedout and is aborted.
*/
- void handleFailedOutgoingCall(Call call) {
+ void handleFailedOutgoingCall(Call call, boolean isAborted) {
+ Log.v(this, "handleFailedOutgoingCall, call: %s", call);
mOutgoingCallProcessors.remove(call.getId());
- mSwitchboard.handleFailedOutgoingCall(call);
+ mSwitchboard.handleFailedOutgoingCall(call, isAborted);
}
/**
@@ -145,6 +150,7 @@
* @param call The call to be aborted.
*/
void abort(Call call) {
+ Log.v(this, "abort, call: %s", call);
OutgoingCallProcessor processor = mOutgoingCallProcessors.remove(call.getId());
if (processor != null) {
processor.abort();
diff --git a/src/com/android/telecomm/PhoneStateBroadcaster.java b/src/com/android/telecomm/PhoneStateBroadcaster.java
new file mode 100644
index 0000000..4bb77d3
--- /dev/null
+++ b/src/com/android/telecomm/PhoneStateBroadcaster.java
@@ -0,0 +1,76 @@
+/*
+ * 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.Manifest;
+import android.content.Intent;
+import android.telecomm.CallService;
+import android.telecomm.CallState;
+import android.telecomm.TelecommConstants;
+import android.telephony.TelephonyManager;
+
+/**
+ * Send a {@link TelephonyManager#ACTION_PHONE_STATE_CHANGED} broadcast when the call state
+ * changes.
+ */
+final class PhoneStateBroadcaster extends CallsManagerListenerBase {
+ @Override
+ public void onCallStateChanged(Call call, CallState oldState, CallState newState) {
+ final String phoneState;
+ switch (newState) {
+ case DIALING:
+ case ACTIVE:
+ case ON_HOLD:
+ phoneState = TelephonyManager.EXTRA_STATE_OFFHOOK;
+ break;
+ case RINGING:
+ phoneState = TelephonyManager.EXTRA_STATE_RINGING;
+ break;
+ case ABORTED:
+ case DISCONNECTED:
+ phoneState = TelephonyManager.EXTRA_STATE_IDLE;
+ break;
+ default:
+ Log.w(this, "Call is in an unknown state (%s), not broadcasting: %s",
+ newState, call.getId());
+ return;
+ }
+ sendPhoneStateChangedBroadcast(call, phoneState);
+ }
+
+ private void sendPhoneStateChangedBroadcast(Call call, String phoneState) {
+ Log.v(this, "sendPhoneStateChangedBroadcast, call %s, phoneState: %s", call, phoneState);
+
+ Intent intent = new Intent(TelephonyManager.ACTION_PHONE_STATE_CHANGED);
+ intent.putExtra(TelephonyManager.EXTRA_STATE, phoneState);
+ // TODO: See if we can add this (the current API doesn't have a callId).
+ intent.putExtra(TelecommConstants.EXTRA_CALL_ID, call.getId());
+
+ // Populate both, since the original API was needlessly complicated.
+ intent.putExtra(TelephonyManager.EXTRA_INCOMING_NUMBER, call.getHandle());
+ // TODO: See if we can add this (the current API only sets this on NEW_OUTGOING_CALL).
+ intent.putExtra(Intent.EXTRA_PHONE_NUMBER, call.getHandle());
+
+ // TODO: Replace these with real constants once this API has been vetted.
+ CallServiceWrapper callService = call.getCallService();
+ if (callService != null) {
+ intent.putExtra(CallService.class.getName(), callService.getComponentName());
+ }
+ TelecommApp.getInstance().sendBroadcast(intent, Manifest.permission.READ_PHONE_STATE);
+ Log.i(this, "Broadcasted state change: %s", phoneState);
+ }
+}
diff --git a/src/com/android/telecomm/Ringer.java b/src/com/android/telecomm/Ringer.java
index bca0d15..bc24560 100644
--- a/src/com/android/telecomm/Ringer.java
+++ b/src/com/android/telecomm/Ringer.java
@@ -23,18 +23,18 @@
import android.media.MediaPlayer.OnPreparedListener;
import android.media.RingtoneManager;
import android.net.Uri;
+import android.telecomm.CallState;
import com.google.common.base.Preconditions;
+import com.google.common.collect.Lists;
import java.io.IOException;
+import java.util.List;
/**
* Controls ringing and vibration for incoming calls.
- *
- * TODO(santoscordon): Consider moving all ringing responsibility to InCall app as an implementation
- * within InCallServiceBase.
*/
-final class Ringer implements OnErrorListener, OnPreparedListener {
+final class Ringer extends CallsManagerListenerBase implements OnErrorListener, OnPreparedListener {
// States for the Ringer.
/** Actively playing the ringer. */
private static final int RINGING = 1;
@@ -56,11 +56,79 @@
/** The active media player for the ringer. */
private MediaPlayer mMediaPlayer;
+ /*
+ * Used to keep ordering of unanswered incoming calls. The existence of multiple call services
+ * means that there can easily exist multiple incoming calls and explicit ordering is useful for
+ * maintaining the proper state of the ringer.
+ */
+ private final List<String> mUnansweredCallIds = Lists.newLinkedList();
+
+ @Override
+ public void onCallAdded(Call call) {
+ if (call.isIncoming() && call.getState() == CallState.RINGING) {
+ if (mUnansweredCallIds.contains(call.getId())) {
+ Log.wtf(this, "New ringing call is already in list of unanswered calls");
+ }
+ mUnansweredCallIds.add(call.getId());
+ if (mUnansweredCallIds.size() == 1) {
+ // Start the ringer if we are the top-most incoming call (the only one in this
+ // case).
+ startRinging();
+ }
+ }
+ }
+
+ @Override
+ public void onCallRemoved(Call call) {
+ removeFromUnansweredCallIds(call.getId());
+ }
+
+ @Override
+ public void onCallStateChanged(Call call, CallState oldState, CallState newState) {
+ if (newState != CallState.RINGING) {
+ removeFromUnansweredCallIds(call.getId());
+ }
+ }
+
+ @Override
+ public void onIncomingCallAnswered(Call call) {
+ onRespondedToIncomingCall(call);
+ }
+
+ @Override
+ public void onIncomingCallRejected(Call call) {
+ onRespondedToIncomingCall(call);
+ }
+
+ private void onRespondedToIncomingCall(Call call) {
+ // Only stop the ringer if this call is the top-most incoming call.
+ if (!mUnansweredCallIds.isEmpty() && mUnansweredCallIds.get(0).equals(call.getId())) {
+ stopRinging();
+ }
+ }
+
+ /**
+ * Removes the specified call from the list of unanswered incoming calls and updates the ringer
+ * based on the new state of {@link #mUnansweredCallIds}. Safe to call with a call ID that
+ * is not present in the list of incoming calls.
+ *
+ * @param callId The ID of the call.
+ */
+ private void removeFromUnansweredCallIds(String callId) {
+ if (mUnansweredCallIds.remove(callId)) {
+ if (mUnansweredCallIds.isEmpty()) {
+ stopRinging();
+ } else {
+ startRinging();
+ }
+ }
+ }
+
/**
* Starts the vibration, ringer, and/or call-waiting tone.
* TODO(santoscordon): vibration and call-waiting tone.
*/
- void startRinging() {
+ private void startRinging() {
// Check if we are muted before playing the ringer.
if (getAudioManager().getStreamVolume(AudioManager.STREAM_RING) > 0) {
moveToState(RINGING);
@@ -72,7 +140,7 @@
/**
* Stops the vibration, ringer, and/or call-waiting tone.
*/
- void stopRinging() {
+ private void stopRinging() {
moveToState(STOPPED);
}
diff --git a/src/com/android/telecomm/Switchboard.java b/src/com/android/telecomm/Switchboard.java
index af8d380..8a3fc81 100644
--- a/src/com/android/telecomm/Switchboard.java
+++ b/src/com/android/telecomm/Switchboard.java
@@ -22,6 +22,7 @@
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
+import android.telecomm.CallInfo;
import android.telecomm.CallServiceDescriptor;
import android.telecomm.TelecommConstants;
@@ -208,9 +209,10 @@
* Handles the case where an outgoing call could not be proceed by any of the
* selector/call-service implementations, see {@link OutgoingCallProcessor}.
*/
- void handleFailedOutgoingCall(Call call) {
+ void handleFailedOutgoingCall(Call call, boolean isAborted) {
Log.d(this, "handleFailedOutgoingCall");
+ mCallsManager.handleUnsuccessfulOutgoingCall(call, isAborted);
finalizeOutgoingCall(call);
}
@@ -219,10 +221,10 @@
* resulting call to {@link CallsManager} as the final step in the incoming sequence. At that
* point, {@link CallsManager} should bring up the incoming-call UI.
*/
- void handleSuccessfulIncomingCall(Call call) {
+ void handleSuccessfulIncomingCall(Call call, CallInfo callInfo) {
Log.d(this, "handleSuccessfulIncomingCall");
- mCallsManager.handleSuccessfulIncomingCall(call);
+ mCallsManager.handleSuccessfulIncomingCall(call, callInfo);
mBinderDeallocator.releaseUsePermit();
}
@@ -241,6 +243,8 @@
mBinderDeallocator.releaseUsePermit();
+ mCallsManager.handleUnsuccessfulIncomingCall(call);
+
// 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
// and this method could tell {@link CallsManager} to unbind from the in-call app if the