Merge "Add Call Id to state change broadcast" into master-nova
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 295e3dc..780906c 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -23,6 +23,8 @@
the user presses "home". -->
<!-- TODO(gilad): Better understand/document this use case. -->
<uses-permission android:name="android.permission.STOP_APP_SWITCHES" />
+ <uses-permission android:name="android.permission.READ_CALL_LOG" />
+ <uses-permission android:name="android.permission.WRITE_CALL_LOG" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"></uses-permission>
diff --git a/res/values/config.xml b/res/values/config.xml
new file mode 100644
index 0000000..6905860
--- /dev/null
+++ b/res/values/config.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<!-- Telecomm resources that may need to be customized for different hardware or product
+ builds. -->
+<resources>
+ <!-- Determines if the current device should allow emergency numbers to be logged in the
+ call log. Some carriers require that emergency calls *not* be logged, presumably to
+ avoid the risk of accidental redialing from the call log UI.
+ The default is false. -->
+ <bool name="allow_emergency_numbers_in_call_log">false</bool>
+</resources>
diff --git a/src/com/android/telecomm/Call.java b/src/com/android/telecomm/Call.java
index 1463c51..6660dc3 100644
--- a/src/com/android/telecomm/Call.java
+++ b/src/com/android/telecomm/Call.java
@@ -20,10 +20,12 @@
import android.telecomm.CallState;
import com.android.internal.telecomm.ICallServiceSelector;
+import com.google.android.collect.Sets;
import com.google.common.base.Preconditions;
import java.util.Date;
import java.util.Locale;
+import java.util.Set;
import java.util.UUID;
/**
@@ -67,6 +69,12 @@
private CallInfo mCallInfo;
/**
+ * The set of call services that were attempted in the process of placing/switching this call
+ * but turned out unsuitable. Only used in the context of call switching.
+ */
+ private Set<CallServiceWrapper> mIncompatibleCallServices;
+
+ /**
* Creates an empty call object with a unique call ID.
*/
Call() {
@@ -132,6 +140,14 @@
return new Date().getTime() - mCreationTime.getTime();
}
+ /**
+ * @return The time when this call object was created and added to the set of pending outgoing
+ * calls.
+ */
+ long getCreationTimeInMilliseconds() {
+ return mCreationTime.getTime();
+ }
+
CallServiceWrapper getCallService() {
return mCallService;
}
@@ -169,6 +185,32 @@
}
/**
+ * 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.
+ *
+ * @param callService The incompatible call service.
+ */
+ void addIncompatibleCallService(CallServiceWrapper callService) {
+ if (mIncompatibleCallServices == null) {
+ mIncompatibleCallServices = Sets.newHashSet();
+ }
+ mIncompatibleCallServices.add(callService);
+ }
+
+ /**
+ * Checks whether or not the specified callService was identified as incompatible in the
+ * context of this call.
+ *
+ * @param callService The call service to evaluate.
+ * @return True upon incompatible call services and false otherwise.
+ */
+ boolean isIncompatibleCallService(CallServiceWrapper callService) {
+ return mIncompatibleCallServices != null &&
+ mIncompatibleCallServices.contains(callService);
+ }
+
+ /**
* Aborts ongoing attempts to connect this call. Only applicable to {@link CallState#NEW}
* outgoing calls. See {@link #disconnect} for already-connected calls.
*/
diff --git a/src/com/android/telecomm/CallLogManager.java b/src/com/android/telecomm/CallLogManager.java
index eb3b0f5..de9b301 100644
--- a/src/com/android/telecomm/CallLogManager.java
+++ b/src/com/android/telecomm/CallLogManager.java
@@ -1,5 +1,248 @@
+/*
+ * 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;
-/** Package private */
+import android.content.Context;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.provider.CallLog.Calls;
+import android.telephony.PhoneNumberUtils;
+
+import com.android.internal.telephony.PhoneConstants;
+
+/**
+ * Helper class that provides functionality to write information about calls and their associated
+ * 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 {
+ /**
+ * Parameter object to hold the arguments to add a call in the call log DB.
+ */
+ private static class AddCallArgs {
+ /**
+ * @param contactInfo Caller details.
+ * @param number The phone number to be logged.
+ * @param presentation Number presentation of the phone number to be logged.
+ * @param callType The type of call (e.g INCOMING_TYPE). @see
+ * {@link android.provider.CallLog} for the list of values.
+ * @param creationDate Time when the call was created (milliseconds since epoch).
+ * @param durationInMillis Duration of the call (milliseconds).
+ */
+ public AddCallArgs(Context context, ContactInfo contactInfo, String number,
+ int presentation, int callType, long creationDate, long durationInMillis) {
+ this.context = context;
+ this.contactInfo = contactInfo;
+ this.number = number;
+ this.presentation = presentation;
+ this.callType = callType;
+ this.timestamp = creationDate;
+ this.durationInSec = (int)(durationInMillis / 1000);
+ }
+ // Since the members are accessed directly, we don't use the
+ // mXxxx notation.
+ public final Context context;
+ public final ContactInfo contactInfo;
+ public final String number;
+ public final int presentation;
+ public final int callType;
+ public final long timestamp;
+ public final int durationInSec;
+ }
+
+ private static final String TAG = CallLogManager.class.getSimpleName();
+
+ private final Context mContext;
+
+ public CallLogManager(Context context) {
+ 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);
+ }
+
+ /**
+ * Logs a call to the call log based on the {@link Call} object passed in.
+ *
+ * @param call The call object being logged
+ * @param callLogType The type of call log entry to log this call as. See:
+ * {@link android.provider.CallLog.Calls#INCOMING_TYPE}
+ * {@link android.provider.CallLog.Calls#OUTGOING_TYPE}
+ * {@link android.provider.CallLog.Calls#MISSED_TYPE}
+ */
+ private void logCall(Call call, int callLogType) {
+ String number = call.getHandle();
+ final long creationTime = call.getCreationTimeInMilliseconds();
+ final long age = call.getAgeInMilliseconds();
+
+ final ContactInfo contactInfo = call.getContactInfo(); // May be null.
+ final String logNumber = getLogNumber(call);
+
+ Log.d(TAG, "logNumber set to:" + Log.pii(logNumber) + ", number set to: "
+ + Log.pii(number));
+
+ final int presentation = getPresentation(call, contactInfo);
+
+ logCall(contactInfo, logNumber, presentation, callLogType, creationTime, age);
+ }
+
+ /**
+ * Inserts a call into the call log, based on the parameters passed in.
+ *
+ * @param contactInfo Caller details.
+ * @param number The number the call was made to or from.
+ * @param presentation
+ * @param callType The type of call.
+ * @param start The start time of the call, in milliseconds.
+ * @param duration The duration of the call, in milliseconds.
+ */
+ private void logCall(
+ ContactInfo contactInfo,
+ String number,
+ int presentation,
+ int callType,
+ long start,
+ long duration) {
+ boolean isEmergencyNumber = PhoneNumberUtils.isLocalEmergencyNumber(number, mContext);
+
+ // On some devices, to avoid accidental redialing of emergency numbers, we *never* log
+ // emergency calls to the Call Log. (This behavior is set on a per-product basis, based
+ // on carrier requirements.)
+ final boolean okToLogEmergencyNumber =
+ mContext.getResources().getBoolean(R.bool.allow_emergency_numbers_in_call_log);
+
+ // Don't log emergency numbers if the device doesn't allow it.
+ final boolean isOkToLogThisCall = !isEmergencyNumber || okToLogEmergencyNumber;
+
+ if (isOkToLogThisCall) {
+ Log.d(TAG, "Logging Calllog entry: " + contactInfo + ", "
+ + Log.pii(number) + "," + presentation + ", " + callType
+ + ", " + start + ", " + duration);
+ AddCallArgs args = new AddCallArgs(mContext, contactInfo, number, presentation,
+ callType, start, duration);
+ logCallAsync(args);
+ } else {
+ Log.d(TAG, "Not adding emergency call to call log.");
+ }
+ }
+
+ /**
+ * Retrieve the phone number from the call, and then process it before returning the
+ * actual number that is to be logged.
+ *
+ * @param call The phone connection.
+ * @return the phone number to be logged.
+ */
+ private String getLogNumber(Call call) {
+ String handle = call.getHandle();
+
+ if (handle == null) {
+ return null;
+ }
+
+ if (!PhoneNumberUtils.isUriNumber(handle)) {
+ handle = PhoneNumberUtils.stripSeparators(handle);
+ }
+ return handle;
+ }
+
+ /**
+ * Gets the presentation from the {@link ContactInfo} if not null. Otherwise, gets it from the
+ * {@link Call}.
+ *
+ * TODO: There needs to be a way to pass information from
+ * Connection.getNumberPresentation() into a {@link Call} object. Until then, always return
+ * PhoneConstants.PRESENTATION_ALLOWED. On top of that, we might need to introduce
+ * getNumberPresentation to the ContactInfo object as well.
+ *
+ * @param call The call object to retrieve caller details from.
+ * @param contactInfo The CallerInfo. May be null.
+ * @return The number presentation constant to insert into the call logs.
+ */
+ private int getPresentation(Call call, ContactInfo contactInfo) {
+ return PhoneConstants.PRESENTATION_ALLOWED;
+ }
+
+ /**
+ * Adds the call defined by the parameters in the provided AddCallArgs to the CallLogProvider
+ * using an AsyncTask to avoid blocking the main thread.
+ *
+ * @param args Prepopulated call details.
+ * @return A handle to the AsyncTask that will add the call to the call log asynchronously.
+ */
+ public AsyncTask<AddCallArgs, Void, Uri[]> logCallAsync(AddCallArgs args) {
+ return new LogCallAsyncTask().execute(args);
+ }
+
+ /**
+ * Helper AsyncTask to access the call logs database asynchronously since database operations
+ * can take a long time depending on the system's load. Since it extends AsyncTask, it uses
+ * its own thread pool.
+ */
+ private class LogCallAsyncTask extends AsyncTask<AddCallArgs, Void, Uri[]> {
+ @Override
+ protected Uri[] doInBackground(AddCallArgs... callList) {
+ int count = callList.length;
+ Uri[] result = new Uri[count];
+ for (int i = 0; i < count; i++) {
+ AddCallArgs c = callList[i];
+
+ try {
+ // May block.
+ result[i] = Calls.addCall(null, c.context, c.number, c.presentation,
+ c.callType, c.timestamp, c.durationInSec);
+ } catch (Exception e) {
+ // This is very rare but may happen in legitimate cases.
+ // E.g. If the phone is encrypted and thus write request fails, it may cause
+ // some kind of Exception (right now it is IllegalArgumentException, but this
+ // might change).
+ //
+ // We don't want to crash the whole process just because of that, so just log
+ // it instead.
+ Log.e(TAG, e, "Exception raised during adding CallLog entry.");
+ result[i] = null;
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Performs a simple sanity check to make sure the call was written in the database.
+ * Typically there is only one result per call so it is easy to identify which one failed.
+ */
+ @Override
+ protected void onPostExecute(Uri[] result) {
+ for (Uri uri : result) {
+ if (uri == null) {
+ Log.w(TAG, "Failed to write call to the log.");
+ }
+ }
+ }
+ }
}
diff --git a/src/com/android/telecomm/CallServiceAdapter.java b/src/com/android/telecomm/CallServiceAdapter.java
index 3e63f39..1e830d5 100644
--- a/src/com/android/telecomm/CallServiceAdapter.java
+++ b/src/com/android/telecomm/CallServiceAdapter.java
@@ -74,8 +74,17 @@
}
/** {@inheritDoc} */
- @Override public void setIsCompatibleWith(String callId, boolean isCompatible) {
- // TODO(santoscordon): fill in.
+ @Override public void setIsCompatibleWith(final String callId, final boolean isCompatible) {
+ checkValidCallId(callId);
+ mHandler.post(new Runnable() {
+ @Override public void run() {
+ if (mPendingOutgoingCallIds.contains(callId)) {
+ mOutgoingCallsManager.setIsCompatibleWith(callId, isCompatible);
+ } else {
+ Log.wtf(CallServiceAdapter.this, "Unknown outgoing call: %s", callId);
+ }
+ }
+ });
}
/** {@inheritDoc} */
@@ -86,8 +95,7 @@
if (mPendingIncomingCallIds.remove(callInfo.getId())) {
mIncomingCallsManager.handleSuccessfulIncomingCall(callInfo);
} else {
- Log.wtf(CallServiceAdapter.this,
- "Unknown incoming call: %s", callInfo);
+ Log.wtf(CallServiceAdapter.this, "Unknown incoming call: %s", callInfo);
}
}
});
@@ -102,8 +110,7 @@
mOutgoingCallsManager.handleSuccessfulCallAttempt(callId);
} else {
// TODO(gilad): Figure out how to wire up the callService.abort() call.
- Log.wtf(CallServiceAdapter.this,
- "Unknown outgoing call: %s", callId);
+ Log.wtf(CallServiceAdapter.this, "Unknown outgoing call: %s", callId);
}
}
});
@@ -117,8 +124,7 @@
if (mPendingOutgoingCallIds.remove(callId)) {
mOutgoingCallsManager.handleFailedCallAttempt(callId, reason);
} else {
- Log.wtf(CallServiceAdapter.this,
- "Unknown outgoing call: %s", callId);
+ Log.wtf(CallServiceAdapter.this, "Unknown outgoing call: %s", callId);
}
}
});
diff --git a/src/com/android/telecomm/CallServiceProviderWrapper.java b/src/com/android/telecomm/CallServiceProviderWrapper.java
index 97303cd..1a3b3e1 100644
--- a/src/com/android/telecomm/CallServiceProviderWrapper.java
+++ b/src/com/android/telecomm/CallServiceProviderWrapper.java
@@ -31,35 +31,56 @@
* {@link ICallServiceProvider}.
* TODO(santoscordon): Keep track of when the service can be safely unbound.
*/
-public class CallServiceProviderWrapper extends ServiceBinder<ICallServiceProvider> {
+final class CallServiceProviderWrapper extends ServiceBinder<ICallServiceProvider> {
+ /**
+ * The service action used to bind to ICallServiceProvider implementations.
+ * TODO(santoscordon): Move this to TelecommConstants.
+ */
+ static final String CALL_SERVICE_PROVIDER_ACTION = ICallServiceProvider.class.getName();
+
/** The actual service implementation. */
private ICallServiceProvider mServiceInterface;
+ private Binder mBinder = new Binder();
+
/**
* Creates a call-service provider for the specified component.
*
* @param componentName The component name of the service to bind to.
- * @param repository The call-service repository.
*/
- public CallServiceProviderWrapper(
- ComponentName componentName, CallServiceRepository repository) {
-
+ CallServiceProviderWrapper(ComponentName componentName) {
super(TelecommConstants.ACTION_CALL_SERVICE_PROVIDER, componentName);
}
/**
- * See {@link ICallServiceProvider#lookupCallServices}.
+ * initiates a call-service lookup cycle, see {@link ICallServiceProvider#lookupCallServices}.
+ * Upon failure, the specified error callback is invoked. Can be invoked even when the call
+ * service is unbound.
+ *
+ * @param response The response object via which to return the relevant call-service
+ * implementations, if any.
+ * @param errorCallback The callback to invoke upon failure.
*/
- public void lookupCallServices(ICallServiceLookupResponse response) {
- try {
- if (mServiceInterface == null) {
- Log.wtf(this, "lookupCallServices() invoked while the service is unbound.");
- } else {
- mServiceInterface.lookupCallServices(response);
+ void lookupCallServices(
+ final ICallServiceLookupResponse response,
+ final Runnable errorCallback) {
+
+ BindCallback callback = new BindCallback() {
+ @Override public void onSuccess() {
+ if (isServiceValid("lookupCallServices")) {
+ try {
+ mServiceInterface.lookupCallServices(response);
+ } catch (RemoteException e) {
+ Log.e(CallServiceProviderWrapper.this, e, "Failed to lookupCallServices.");
+ }
+ }
}
- } catch (RemoteException e) {
- Log.e(this, e, "Failed to lookupCallServices.");
- }
+ @Override public void onFailure() {
+ errorCallback.run();
+ }
+ };
+
+ mBinder.bind(callback);
}
/** {@inheritDoc} */
diff --git a/src/com/android/telecomm/CallServiceRepository.java b/src/com/android/telecomm/CallServiceRepository.java
index 6a341cd..2abc8fe 100644
--- a/src/com/android/telecomm/CallServiceRepository.java
+++ b/src/com/android/telecomm/CallServiceRepository.java
@@ -28,7 +28,6 @@
import com.android.internal.telecomm.ICallServiceLookupResponse;
import com.android.internal.telecomm.ICallServiceProvider;
-import com.android.telecomm.ServiceBinder.BindCallback;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
@@ -143,7 +142,7 @@
mOutstandingProviders.clear();
for (ComponentName name : providerNames) {
mOutstandingProviders.add(name);
- bindProvider(name);
+ lookupCallServices(name);
}
Log.i(this, "Found %d implementations of ICallServiceProvider.",
@@ -213,41 +212,16 @@
}
/**
- * Attempts to bind to the specified provider.
+ * Attempts to obtain call-service descriptors from the specified provider (asynchronously) and
+ * passes the list through to {@link #processCallServices}, which then relinquishes control back
+ * to the switchboard.
*
* @param providerName The component name of the relevant provider.
*/
- private void bindProvider(final ComponentName providerName) {
- final CallServiceProviderWrapper provider =
- new CallServiceProviderWrapper(providerName, this);
+ private void lookupCallServices(final ComponentName providerName) {
+ final CallServiceProviderWrapper provider = new CallServiceProviderWrapper(providerName);
- BindCallback callback = new BindCallback() {
- @Override public void onSuccess() {
- processProvider(provider);
- }
- @Override public void onFailure() {
- removeOutstandingProvider(providerName);
- }
- };
-
- provider.bind(callback);
- }
-
- /**
- * Queries the supplied provider asynchronously for its CallServices and passes the list through
- * to {@link #processCallServices} which will relinquish control back to switchboard.
- *
- * @param provider The provider object to process.
- */
- private void processProvider(final CallServiceProviderWrapper provider) {
- if (!mIsLookupInProgress) {
- return;
- }
-
- Preconditions.checkNotNull(provider);
-
- // Query the provider for {@link ICallService} implementations.
- provider.lookupCallServices(new ICallServiceLookupResponse.Stub() {
+ ICallServiceLookupResponse response = new ICallServiceLookupResponse.Stub() {
@Override
public void setCallServiceDescriptors(
final List<CallServiceDescriptor> callServiceDescriptors) {
@@ -255,11 +229,21 @@
// TODO(santoscordon): Do we need Binder.clear/restoreCallingIdentity()?
mHandler.post(new Runnable() {
@Override public void run() {
- processCallServices(provider, Sets.newHashSet(callServiceDescriptors));
+ if (mIsLookupInProgress) {
+ processCallServices(provider, Sets.newHashSet(callServiceDescriptors));
+ }
}
});
}
- });
+ };
+
+ Runnable errorCallback = new Runnable() {
+ @Override public void run() {
+ removeOutstandingProvider(providerName);
+ }
+ };
+
+ provider.lookupCallServices(response, errorCallback);
}
/**
@@ -291,8 +275,8 @@
removeOutstandingProvider(providerName);
} else {
- Log.i(this, "Unexpected list of call services in lookup %s from %s ", mLookupId,
- providerName);
+ Log.i(this,
+ "Unexpected call services from %s in lookup %s", providerName, mLookupId);
}
}
diff --git a/src/com/android/telecomm/CallServiceWrapper.java b/src/com/android/telecomm/CallServiceWrapper.java
index 5c9905e..c4d033c 100644
--- a/src/com/android/telecomm/CallServiceWrapper.java
+++ b/src/com/android/telecomm/CallServiceWrapper.java
@@ -35,7 +35,7 @@
* TODO(santoscordon): Keep track of when the service can be safely unbound.
* TODO(santoscordon): Look into combining with android.telecomm.CallService.
*/
-public class CallServiceWrapper extends ServiceBinder<ICallService> {
+final class CallServiceWrapper extends ServiceBinder<ICallService> {
/** The descriptor of this call service as supplied by the call-service provider. */
private final CallServiceDescriptor mDescriptor;
@@ -48,6 +48,8 @@
/** The actual service implementation. */
private ICallService mServiceInterface;
+ private Binder mBinder = new Binder();
+
/**
* Creates a call-service provider for the specified component.
*
@@ -55,18 +57,18 @@
* {@link ICallServiceProvider#lookupCallServices}.
* @param adapter The call-service adapter.
*/
- public CallServiceWrapper(CallServiceDescriptor descriptor, CallServiceAdapter adapter) {
+ CallServiceWrapper(CallServiceDescriptor descriptor, CallServiceAdapter adapter) {
super(TelecommConstants.ACTION_CALL_SERVICE, descriptor.getServiceComponent());
mDescriptor = descriptor;
mAdapter = adapter;
}
- public CallServiceDescriptor getDescriptor() {
+ CallServiceDescriptor getDescriptor() {
return mDescriptor;
}
/** See {@link ICallService#setCallServiceAdapter}. */
- public void setCallServiceAdapter(ICallServiceAdapter callServiceAdapter) {
+ void setCallServiceAdapter(ICallServiceAdapter callServiceAdapter) {
if (isServiceValid("setCallServiceAdapter")) {
try {
mServiceInterface.setCallServiceAdapter(callServiceAdapter);
@@ -76,32 +78,63 @@
}
}
- /** See {@link ICallService#isCompatibleWith}. */
- public void isCompatibleWith(CallInfo callInfo) {
- if (isServiceValid("isCompatibleWith")) {
- try {
- mServiceInterface.isCompatibleWith(callInfo);
- } catch (RemoteException e) {
- Log.e(this, e, "Failed checking isCompatibleWith.");
+ /**
+ * Checks whether or not the specified call is compatible with this call-service implementation,
+ * see {@link ICallService#isCompatibleWith}. Upon failure, the specified error callback is
+ * invoked. Can be invoked even when the call service is unbound.
+ *
+ * @param callInfo The details of the call.
+ * @param errorCallback The callback to invoke upon failure.
+ */
+ void isCompatibleWith(final CallInfo callInfo, final Runnable errorCallback) {
+ BindCallback callback = new BindCallback() {
+ @Override public void onSuccess() {
+ if (isServiceValid("isCompatibleWith")) {
+ try {
+ mServiceInterface.isCompatibleWith(callInfo);
+ } catch (RemoteException e) {
+ Log.e(CallServiceWrapper.this, e, "Failed checking isCompatibleWith.");
+ }
+ }
}
- }
+ @Override public void onFailure() {
+ errorCallback.run();
+ }
+ };
+
+ mBinder.bind(callback);
}
- /** See {@link ICallService#call}. */
- public void call(CallInfo callInfo) {
- String callId = callInfo.getId();
- if (isServiceValid("call")) {
- try {
- mServiceInterface.call(callInfo);
- mAdapter.addPendingOutgoingCallId(callId);
- } catch (RemoteException e) {
- Log.e(this, e, "Failed to place call " + callId + ".");
+ /**
+ * Attempts to place the specified call, see {@link ICallService#call}. Upon failure, the
+ * specified error callback is invoked. Can be invoked even when the call service is unbound.
+ *
+ * @param callInfo The details of the call.
+ * @param errorCallback The callback to invoke upon failure.
+ */
+ void call(final CallInfo callInfo, final Runnable errorCallback) {
+ BindCallback callback = new BindCallback() {
+ @Override public void onSuccess() {
+ String callId = callInfo.getId();
+ if (isServiceValid("call")) {
+ try {
+ mServiceInterface.call(callInfo);
+ mAdapter.addPendingOutgoingCallId(callId);
+ } catch (RemoteException e) {
+ Log.e(CallServiceWrapper.this, e, "Failed to place call %s", callId);
+ }
+ }
}
- }
+ @Override public void onFailure() {
+ errorCallback.run();
+ }
+ };
+
+ mBinder.bind(callback);
}
/** See {@link ICallService#abort}. */
- public void abort(String callId) {
+ void abort(String callId) {
mAdapter.removePendingOutgoingCallId(callId);
if (isServiceValid("abort")) {
try {
@@ -112,21 +145,44 @@
}
}
- /** See {@link ICallService#setIncomingCallId}. */
- public void setIncomingCallId(String callId, Bundle extras) {
- if (isServiceValid("setIncomingCallId")) {
- mAdapter.addPendingIncomingCallId(callId);
- try {
- mServiceInterface.setIncomingCallId(callId, extras);
- } catch (RemoteException e) {
- Log.e(this, e, "Failed to setIncomingCallId for call %s", callId);
- mAdapter.removePendingIncomingCallId(callId);
+ /**
+ * Starts retrieval of details for an incoming call. Details are returned through the
+ * call-service adapter using the specified call ID. Upon failure, the specified error callback
+ * is invoked. Can be invoked even when the call service is unbound.
+ * See {@link ICallService#setIncomingCallId}.
+ *
+ * @param callId The call ID used for the incoming call.
+ * @param extras The {@link CallService}-provided extras which need to be sent back.
+ * @param errorCallback The callback to invoke upon failure.
+ */
+ void setIncomingCallId(
+ final String callId,
+ final Bundle extras,
+ final Runnable errorCallback) {
+
+ BindCallback callback = new BindCallback() {
+ @Override public void onSuccess() {
+ if (isServiceValid("setIncomingCallId")) {
+ mAdapter.addPendingIncomingCallId(callId);
+ try {
+ mServiceInterface.setIncomingCallId(callId, extras);
+ } catch (RemoteException e) {
+ Log.e(CallServiceWrapper.this, e,
+ "Failed to setIncomingCallId for call %s", callId);
+ mAdapter.removePendingIncomingCallId(callId);
+ }
+ }
}
- }
+ @Override public void onFailure() {
+ errorCallback.run();
+ }
+ };
+
+ mBinder.bind(callback);
}
/** See {@link ICallService#disconnect}. */
- public void disconnect(String callId) {
+ void disconnect(String callId) {
if (isServiceValid("disconnect")) {
try {
mServiceInterface.disconnect(callId);
@@ -137,7 +193,7 @@
}
/** See {@link ICallService#answer}. */
- public void answer(String callId) {
+ void answer(String callId) {
if (isServiceValid("answer")) {
try {
mServiceInterface.answer(callId);
@@ -148,7 +204,7 @@
}
/** See {@link ICallService#reject}. */
- public void reject(String callId) {
+ void reject(String callId) {
if (isServiceValid("reject")) {
try {
mServiceInterface.reject(callId);
@@ -159,30 +215,6 @@
}
/**
- * Starts retrieval of details for an incoming call. Details are returned through the
- * call-service adapter using the specified call ID. Upon failure, the specified error callback
- * is invoked. Can be invoked even when the call service is unbound.
- *
- * @param callId The call ID used for the incoming call.
- * @param extras The {@link CallService}-provided extras which need to be sent back.
- * @param errorCallback The callback invoked upon failure.
- */
- void retrieveIncomingCall(final String callId, final Bundle extras,
- final Runnable errorCallback) {
-
- BindCallback callback = new BindCallback() {
- @Override public void onSuccess() {
- setIncomingCallId(callId, extras);
- }
- @Override public void onFailure() {
- errorCallback.run();
- }
- };
-
- bind(callback);
- }
-
- /**
* Cancels the incoming call for the specified call ID.
* TODO(santoscordon): This method should be called by IncomingCallsManager when the incoming
* call has failed.
@@ -198,13 +230,4 @@
mServiceInterface = ICallService.Stub.asInterface(binder);
setCallServiceAdapter(mAdapter);
}
-
- private boolean isServiceValid(String actionName) {
- if (mServiceInterface != null) {
- return true;
- }
-
- Log.wtf(this, "%s invoked while service is unbound", actionName);
- return false;
- }
}
diff --git a/src/com/android/telecomm/CallsManager.java b/src/com/android/telecomm/CallsManager.java
index 871558f..b42e36c 100644
--- a/src/com/android/telecomm/CallsManager.java
+++ b/src/com/android/telecomm/CallsManager.java
@@ -94,6 +94,7 @@
mSwitchboard = new Switchboard(this);
mInCallController = new InCallController(this);
mRinger = new Ringer();
+ mCallLogManager = new CallLogManager(TelecommApp.getInstance());
}
static CallsManager getInstance() {
@@ -186,6 +187,15 @@
}
/**
+ * Informs mCallLogManager about the outgoing call that failed, so that it can be logged.
+ *
+ * @param call The failed outgoing call.
+ */
+ void handleFailedOutgoingCall(Call call) {
+ mCallLogManager.logFailedOutgoingCall(call);
+ }
+
+ /**
* Instructs Telecomm to answer the specified call. Intended to be invoked by the in-call
* app through {@link InCallAdapter} after Telecomm notifies it of an incoming call followed by
* the user opting to answer said call.
@@ -291,6 +301,9 @@
if (mCalls.isEmpty()) {
mInCallController.unbind();
}
+
+ // Log the call in the call log.
+ mCallLogManager.logDisconnectedCall(call);
}
/**
@@ -425,9 +438,9 @@
/**
* Stops playing the ringer if the specified call is the top-most incoming call. This exists
- * separately from {@link #removeIncomingCall} for cases where we would like to stop playing the
- * ringer for a call, but that call may still exist in {@link #mUnansweredIncomingCalls} - See
- * {@link #rejectCall}, {@link #answerCall}.
+ * 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.
*/
diff --git a/src/com/android/telecomm/IncomingCallsManager.java b/src/com/android/telecomm/IncomingCallsManager.java
index 7d33d6b..3b6365f 100644
--- a/src/com/android/telecomm/IncomingCallsManager.java
+++ b/src/com/android/telecomm/IncomingCallsManager.java
@@ -71,8 +71,7 @@
}
};
- // TODO(gilad): call.retrieve*Call() seems a bit unusual, consider revisiting.
- call.getCallService().retrieveIncomingCall(callId, extras, errorCallback);
+ call.getCallService().setIncomingCallId(callId, extras, errorCallback);
}
/**
diff --git a/src/com/android/telecomm/OutgoingCallProcessor.java b/src/com/android/telecomm/OutgoingCallProcessor.java
index bcfda61..a242866 100644
--- a/src/com/android/telecomm/OutgoingCallProcessor.java
+++ b/src/com/android/telecomm/OutgoingCallProcessor.java
@@ -16,10 +16,6 @@
package com.android.telecomm;
-import com.google.common.base.Preconditions;
-import com.google.common.collect.Maps;
-import com.google.common.collect.Lists;
-
import android.os.Handler;
import android.os.Looper;
import android.os.RemoteException;
@@ -28,7 +24,9 @@
import com.android.internal.telecomm.ICallServiceSelectionResponse;
import com.android.internal.telecomm.ICallServiceSelector;
-import com.android.telecomm.ServiceBinder.BindCallback;
+import com.google.android.collect.Sets;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Lists;
import java.util.Iterator;
import java.util.List;
@@ -68,6 +66,17 @@
private final Map<String, CallServiceWrapper> mCallServicesById = Maps.newHashMap();
/**
+ * The set of attempted call services, used to ensure services are attempted at most once per
+ * outgoing-call attempt.
+ */
+ private final Set<CallServiceWrapper> mAttemptedCallServices = Sets.newHashSet();
+
+ /**
+ * The set of incompatible call services, used to suppress unnecessary call switching attempts.
+ */
+ private final Set<CallServiceWrapper> mIncompatibleCallServices = Sets.newHashSet();
+
+ /**
* The list of currently-available call-service selector implementations.
*/
private final List<ICallServiceSelector> mSelectors;
@@ -80,6 +89,12 @@
private final Switchboard mSwitchboard;
+ private final Runnable mNextCallServiceCallback = new Runnable() {
+ @Override public void run() {
+ attemptNextCallService();
+ }
+ };
+
/**
* The iterator over the currently-selected ordered list of call-service descriptors.
*/
@@ -141,6 +156,34 @@
}
/**
+ * Handles the specified compatibility status from the call-service implementation.
+ * TODO(gilad): Consider making this class stateful, potentially rejecting out-of-order/
+ * unexpected invocations (i.e. beyond checking for unexpected call IDs).
+ *
+ * @param callId The ID of the call.
+ * @param isCompatible True if the call-service is compatible with the corresponding call and
+ * false otherwise.
+ */
+ void setIsCompatibleWith(String callId, boolean isCompatible) {
+ if (callId != mCall.getId()) {
+ Log.wtf(this, "setIsCompatibleWith invoked with unexpected call ID: %s", callId);
+ return;
+ }
+
+ if (!mIsAborted) {
+ CallServiceWrapper callService = mCall.getCallService();
+ if (callService != null) {
+ if (isCompatible) {
+ callService.call(mCall.toCallInfo(), mNextCallServiceCallback);
+ return;
+ }
+ mIncompatibleCallServices.add(callService);
+ }
+ attemptNextCallService();
+ }
+ }
+
+ /**
* Aborts the attempt to place the relevant call. Intended to be invoked by
* switchboard through the outgoing-calls manager.
*/
@@ -166,6 +209,9 @@
}
mCall.setState(CallState.DIALING);
+ for (CallServiceWrapper callService : mIncompatibleCallServices) {
+ mCall.addIncompatibleCallService(callService);
+ }
mSwitchboard.handleSuccessfulOutgoingCall(mCall);
}
@@ -263,23 +309,14 @@
final CallServiceWrapper callService =
mCallServicesById.get(descriptor.getCallServiceId());
- if (callService == null) {
+ if (callService == null || mAttemptedCallServices.contains(callService)) {
+ // The next call service is either null or has already been attempted, fast forward
+ // to the next.
attemptNextCallService();
} else {
- BindCallback callback = new BindCallback() {
- @Override public void onSuccess() {
- callService.call(mCall.toCallInfo());
- }
- @Override public void onFailure() {
- attemptNextCallService();
- }
- };
-
+ mAttemptedCallServices.add(callService);
mCall.setCallService(callService);
-
- // TODO(santoscordon): Consider making bind private to CallServiceWrapper and having
- // CSWrapper.call() do the bind automatically.
- callService.bind(callback);
+ callService.isCompatibleWith(mCall.toCallInfo(), mNextCallServiceCallback);
}
} else {
mCallServiceDescriptorIterator = null;
diff --git a/src/com/android/telecomm/OutgoingCallsManager.java b/src/com/android/telecomm/OutgoingCallsManager.java
index 0ed30e6..7c2a501 100644
--- a/src/com/android/telecomm/OutgoingCallsManager.java
+++ b/src/com/android/telecomm/OutgoingCallsManager.java
@@ -69,6 +69,24 @@
}
/**
+ * Forwards the compatibility status from the call-service implementation to the corresponding
+ * outgoing-call processor.
+ *
+ * @param callId The ID of the call.
+ * @param isCompatible True if the call-service is compatible with the corresponding call and
+ * false otherwise.
+ */
+ void setIsCompatibleWith(String callId, boolean isCompatible) {
+ OutgoingCallProcessor processor = mOutgoingCallProcessors.get(callId);
+ if (processor == null) {
+ // Shouldn't happen, so log a wtf if it does.
+ Log.wtf(this, "Received unexpected setCompatibleWith notification.");
+ } else {
+ processor.setIsCompatibleWith(callId, isCompatible);
+ }
+ }
+
+ /**
* Removes the outgoing call processor mapping for the successful call and returns execution to
* the switchboard. This method is invoked from {@link CallServiceAdapter} after a call service
* has notified Telecomm that it successfully placed the call.
diff --git a/src/com/android/telecomm/ServiceBinder.java b/src/com/android/telecomm/ServiceBinder.java
index 98ce4ca..b5c0050 100644
--- a/src/com/android/telecomm/ServiceBinder.java
+++ b/src/com/android/telecomm/ServiceBinder.java
@@ -44,6 +44,47 @@
public void onFailure();
}
+ /**
+ * Helper class to perform on-demand binding.
+ */
+ final class Binder {
+ /**
+ * Performs an asynchronous bind to the service (only if not already bound) and executes the
+ * specified callback.
+ *
+ * @param callback The callback to notify of the binding's success or failure.
+ */
+ void bind(BindCallback callback) {
+ ThreadUtil.checkOnMainThread();
+ Log.d(ServiceBinder.this, "bind()");
+
+ // Reset any abort request if we're asked to bind again.
+ clearAbort();
+
+ if (!mCallbacks.isEmpty()) {
+ // Binding already in progress, append to the list of callbacks and bail out.
+ mCallbacks.add(callback);
+ return;
+ }
+
+ mCallbacks.add(callback);
+ if (mServiceConnection == null) {
+ Intent serviceIntent = new Intent(mServiceAction).setComponent(mComponentName);
+ ServiceConnection connection = new ServiceBinderConnection();
+
+ Log.d(ServiceBinder.this, "Binding to call service with intent: %s", serviceIntent);
+ if (!mContext.bindService(serviceIntent, connection, Context.BIND_AUTO_CREATE)) {
+ handleFailedConnection();
+ return;
+ }
+ } else {
+ Log.d(ServiceBinder.this, "Service is already bound.");
+ Preconditions.checkNotNull(mBinder);
+ handleSuccessfulConnection();
+ }
+ }
+ }
+
private final class ServiceBinderConnection implements ServiceConnection {
@Override
public void onServiceConnected(ComponentName componentName, IBinder binder) {
@@ -58,8 +99,7 @@
}
mServiceConnection = this;
- mBinder = binder;
- setServiceInterface(binder);
+ setBinder(binder);
handleSuccessfulConnection();
}
@@ -114,45 +154,6 @@
mComponentName = componentName;
}
- /**
- * Performs an asynchronous bind to the service if not already bound.
- *
- * @param callback The callback to notify of the binding's success or failure.
- * @return The result of {#link Context#bindService} or true if already bound.
- */
- final boolean bind(BindCallback callback) {
- ThreadUtil.checkOnMainThread();
- Log.d(this, "bind()");
-
- // Reset any abort request if we're asked to bind again.
- clearAbort();
-
- // If we are already waiting on a binding request, simply append to the list of waiting
- // callbacks.
- if (!mCallbacks.isEmpty()) {
- mCallbacks.add(callback);
- return true;
- }
-
- mCallbacks.add(callback);
- if (mServiceConnection == null) {
- Intent serviceIntent = new Intent(mServiceAction).setComponent(mComponentName);
- ServiceConnection connection = new ServiceBinderConnection();
-
- Log.d(this, "Binding to call service with intent: %s", serviceIntent);
- if (!mContext.bindService(serviceIntent, connection, Context.BIND_AUTO_CREATE)) {
- handleFailedConnection();
- return false;
- }
- } else {
- Log.d(this, "Service is already bound.");
- Preconditions.checkNotNull(mBinder);
- handleSuccessfulConnection();
- }
-
- return true;
- }
-
final void incrementAssociatedCallCount() {
mAssociatedCallCount++;
}
@@ -182,7 +183,7 @@
} else {
mContext.unbindService(mServiceConnection);
mServiceConnection = null;
- mBinder = null;
+ setBinder(null);
}
}
@@ -190,6 +191,15 @@
return mComponentName;
}
+ final boolean isServiceValid(String actionName) {
+ if (mBinder == null) {
+ Log.wtf(this, "%s invoked while service is unbound", actionName);
+ return false;
+ }
+
+ return true;
+ }
+
/**
* Notifies all the outstanding callbacks that the service is successfully bound. The list of
* outstanding callbacks is cleared afterwards.
@@ -216,7 +226,7 @@
* Handles a service disconnection.
*/
private void handleServiceDisconnected() {
- setServiceInterface(null);
+ setBinder(null);
}
private void clearAbort() {
@@ -224,6 +234,16 @@
}
/**
+ * Sets the (private) binder and updates the child class.
+ *
+ * @param binder The new binder value.
+ */
+ private void setBinder(IBinder binder) {
+ mBinder = binder;
+ setServiceInterface(binder);
+ }
+
+ /**
* Sets the service interface after the service is bound or unbound.
*
* @param binder The actual bound service implementation.
diff --git a/src/com/android/telecomm/Switchboard.java b/src/com/android/telecomm/Switchboard.java
index 040eb5f..af8d380 100644
--- a/src/com/android/telecomm/Switchboard.java
+++ b/src/com/android/telecomm/Switchboard.java
@@ -211,8 +211,6 @@
void handleFailedOutgoingCall(Call call) {
Log.d(this, "handleFailedOutgoingCall");
- // TODO(gilad): Notify mCallsManager.
-
finalizeOutgoingCall(call);
}
diff --git a/tests/src/com/android/telecomm/testcallservice/TestCallService.java b/tests/src/com/android/telecomm/testcallservice/TestCallService.java
index db41055..4cbaf71 100644
--- a/tests/src/com/android/telecomm/testcallservice/TestCallService.java
+++ b/tests/src/com/android/telecomm/testcallservice/TestCallService.java
@@ -48,7 +48,7 @@
/**
* Adapter to call back into CallsManager.
*/
- private CallServiceAdapter mCallsManagerAdapter;
+ private CallServiceAdapter mTelecommAdapter;
/**
* Used to play an audio tone during a call.
@@ -60,7 +60,7 @@
public void setCallServiceAdapter(CallServiceAdapter callServiceAdapter) {
Log.i(TAG, "setCallServiceAdapter()");
- mCallsManagerAdapter = callServiceAdapter;
+ mTelecommAdapter = callServiceAdapter;
mLiveCallIds = Sets.newHashSet();
// Prepare the media player to play a tone when there is a call.
@@ -89,7 +89,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.
- mCallsManagerAdapter.setIsCompatibleWith(callInfo.getId(), isCompatible);
+ mTelecommAdapter.setIsCompatibleWith(callInfo.getId(), isCompatible);
}
/**
@@ -103,8 +103,7 @@
Log.i(TAG, "call(" + callInfo + ")");
createCall(callInfo.getId());
-
- mCallsManagerAdapter.handleSuccessfulOutgoingCall(callInfo.getId());
+ mTelecommAdapter.handleSuccessfulOutgoingCall(callInfo.getId());
}
/** {@inheritDoc} */
@@ -123,19 +122,19 @@
String handle = "5551234";
CallInfo callInfo = new CallInfo(callId, CallState.RINGING, handle);
- mCallsManagerAdapter.notifyIncomingCall(callInfo);
+ mTelecommAdapter.notifyIncomingCall(callInfo);
}
/** {@inheritDoc} */
@Override
public void answer(String callId) {
- mCallsManagerAdapter.setActive(callId);
+ mTelecommAdapter.setActive(callId);
}
/** {@inheritDoc} */
@Override
public void reject(String callId) {
- mCallsManagerAdapter.setDisconnected(callId);
+ mTelecommAdapter.setDisconnected(callId);
}
/** {@inheritDoc} */
@@ -144,7 +143,7 @@
Log.i(TAG, "disconnect(" + callId + ")");
destroyCall(callId);
- mCallsManagerAdapter.setDisconnected(callId);
+ mTelecommAdapter.setDisconnected(callId);
}
/** {@inheritDoc} */
diff --git a/tests/src/com/android/telecomm/testcallservice/TestCallServiceProvider.java b/tests/src/com/android/telecomm/testcallservice/TestCallServiceProvider.java
index 80c82db..781f629 100644
--- a/tests/src/com/android/telecomm/testcallservice/TestCallServiceProvider.java
+++ b/tests/src/com/android/telecomm/testcallservice/TestCallServiceProvider.java
@@ -16,8 +16,6 @@
package com.android.telecomm.testcallservice;
-import android.content.ComponentName;
-import android.os.IBinder;
import android.telecomm.CallServiceDescriptor;
import android.telecomm.CallServiceLookupResponse;
import android.telecomm.CallServiceProvider;
@@ -25,8 +23,6 @@
import com.google.common.collect.Lists;
-import java.util.List;
-
/**
* Service which provides fake calls to test the ICallService interface.
* TODO(santoscordon): Build more dummy providers for more CallServiceDescriptor.FLAG_* types.