Merge "Ensure connection video capabilities can only be set if video supported." into nyc-dev
diff --git a/src/com/android/server/telecom/AsyncBlockCheckTask.java b/src/com/android/server/telecom/AsyncBlockCheckTask.java
deleted file mode 100644
index 67a797d..0000000
--- a/src/com/android/server/telecom/AsyncBlockCheckTask.java
+++ /dev/null
@@ -1,129 +0,0 @@
-/*
- * Copyright (C) 2016 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.server.telecom;
-
-import android.content.Context;
-import android.os.AsyncTask;
-import android.os.Handler;
-
-import com.android.internal.telephony.BlockChecker;
-
-/**
- * An {@link AsyncTask} that checks if a call needs to be blocked.
- * <p> An {@link AsyncTask} is used to perform the block check to avoid blocking the main thread.
- * The block check itself is performed in the {@link AsyncTask#doInBackground(Object[])}. However
- * a {@link Handler} passed by the caller is used to perform additional possibly intensive
- * operations such as call screening.
- */
-class AsyncBlockCheckTask extends AsyncTask<String, Void, Boolean> {
-    private final Context mContext;
-    private final Call mIncomingCall;
-    private final CallScreening mCallScreening;
-    private final boolean mShouldSendToVoicemail;
-    private final Handler mHandler = new Handler();
-    private final Object mCallScreeningListenerLock = new Object();
-
-    private Session mLogSubsession;
-    private Runnable mBlockCheckTimeoutRunnable = new Runnable("ABCT.bCTR") {
-        @Override
-        public void loggedRun() {
-            synchronized (mCallScreeningListenerLock) {
-                if (mCallScreeningListener != null) {
-                    timeoutBlockCheck();
-                    mCallScreeningListener = null;
-                }
-            }
-        }
-    };
-    private CallScreening.Listener mCallScreeningListener;
-
-    AsyncBlockCheckTask(Context context, Call incomingCall, CallScreening callScreening,
-                        CallScreening.Listener callScreeningListener,
-                        boolean shouldSendToVoicemail) {
-        mContext = context;
-        mIncomingCall = incomingCall;
-        mCallScreening = callScreening;
-        mCallScreeningListener = callScreeningListener;
-        mShouldSendToVoicemail = shouldSendToVoicemail;
-    }
-
-    @Override
-    protected void onPreExecute() {
-        // This Task will run onPostExecute after the containing session has ended. Add an invisible
-        // subsession to keep track of this.
-        Log.startSession("ABCT.oPE");
-        mLogSubsession = Log.createSubsession();
-        mHandler.postDelayed(mBlockCheckTimeoutRunnable.prepare(),
-                Timeouts.getBlockCheckTimeoutMillis(mContext.getContentResolver()));
-    }
-
-    private void timeoutBlockCheck() {
-        Log.event(mIncomingCall, Log.Events.BLOCK_CHECK_TIMED_OUT);
-        mCallScreeningListener.onCallScreeningCompleted(
-                mIncomingCall,
-                true /*shouldAllowCall*/,
-                false /*shouldReject*/,
-                false /*shouldAddToCallLog*/,
-                false /*shouldShowNotification*/);
-    }
-
-    @Override
-    protected Boolean doInBackground(String... params) {
-        try {
-            Log.continueSession(mLogSubsession, "ABCT.DIB");
-            Log.event(mIncomingCall, Log.Events.BLOCK_CHECK_INITIATED);
-            return BlockChecker.isBlocked(mContext, params[0]);
-        } finally {
-            Log.endSession();
-        }
-    }
-
-    @Override
-    protected void onPostExecute(Boolean isBlocked) {
-        synchronized (mCallScreeningListenerLock) {
-            mHandler.removeCallbacks(null);
-            mBlockCheckTimeoutRunnable.cancel();
-            if (mCallScreeningListener != null) {
-                processIsBlockedLocked(isBlocked);
-                mCallScreeningListener = null;
-            }
-        }
-        // End invisible subsession started in onPreExecute
-        Log.endSession();
-    }
-
-    private void processIsBlockedLocked(boolean isBlocked) {
-        Log.event(mIncomingCall, Log.Events.BLOCK_CHECK_FINISHED);
-        if (isBlocked) {
-            mCallScreeningListener.onCallScreeningCompleted(
-                    mIncomingCall,
-                    false /*shouldAllowCall*/,
-                    true /*shouldReject*/,
-                    false /*shouldAddToCallLog*/,
-                    false /*shouldShowNotification*/);
-        } else if (mShouldSendToVoicemail) {
-            mCallScreeningListener.onCallScreeningCompleted(
-                    mIncomingCall,
-                    false /*shouldAllowCall*/,
-                    true /*shouldReject*/,
-                    true /*shouldAddToCallLog*/,
-                    true /*shouldShowNotification*/);
-        } else {
-            mCallScreening.screenCall();
-        }
-    }
-}
diff --git a/src/com/android/server/telecom/Call.java b/src/com/android/server/telecom/Call.java
index fb61e15..4c70913 100644
--- a/src/com/android/server/telecom/Call.java
+++ b/src/com/android/server/telecom/Call.java
@@ -83,7 +83,7 @@
     public interface Listener {
         void onSuccessfulOutgoingCall(Call call, int callState);
         void onFailedOutgoingCall(Call call, DisconnectCause disconnectCause);
-        void onSuccessfulIncomingCall(Call call, boolean shouldSendToVoicemail);
+        void onSuccessfulIncomingCall(Call call);
         void onFailedIncomingCall(Call call);
         void onSuccessfulUnknownCall(Call call, int callState);
         void onFailedUnknownCall(Call call);
@@ -120,7 +120,7 @@
         @Override
         public void onFailedOutgoingCall(Call call, DisconnectCause disconnectCause) {}
         @Override
-        public void onSuccessfulIncomingCall(Call call, boolean shouldSendToVoicemail) {}
+        public void onSuccessfulIncomingCall(Call call) {}
         @Override
         public void onFailedIncomingCall(Call call) {}
         @Override
@@ -1080,28 +1080,6 @@
         }
     }
 
-    private void processDirectToVoicemail() {
-        if (mDirectToVoicemailQueryPending) {
-            boolean shouldSendToVoicemail;
-            if (mCallerInfo != null && mCallerInfo.shouldSendToVoicemail) {
-                Log.i(this, "Directing call to voicemail: %s.", this);
-                // TODO: Once we move State handling from CallsManager to Call, we
-                // will not need to set STATE_RINGING state prior to calling reject.
-                shouldSendToVoicemail = true;
-            } else {
-                shouldSendToVoicemail = false;
-            }
-            // TODO: Make this class (not CallsManager) responsible for changing
-            // the call state to STATE_RINGING.
-            // TODO: Replace this with state transition to STATE_RINGING.
-            for (Listener l : mListeners) {
-                l.onSuccessfulIncomingCall(this, shouldSendToVoicemail);
-            }
-
-            mDirectToVoicemailQueryPending = false;
-        }
-    }
-
     /**
      * Starts the create connection sequence. Upon completion, there should exist an active
      * connection through a connection service (or the call will have failed).
@@ -1147,22 +1125,11 @@
 
         switch (mCallDirection) {
             case CALL_DIRECTION_INCOMING:
-                // We do not handle incoming calls immediately when they are verified by the
-                // connection service. We allow the caller-info-query code to execute first so
-                // that we can read the direct-to-voicemail property before deciding if we want
-                // to show the incoming call to the user or if we want to reject the call.
-                mDirectToVoicemailQueryPending = true;
-
-                // Timeout the direct-to-voicemail lookup execution so that we dont wait too long
-                // before showing the user the incoming call screen.
-                mHandler.postDelayed(new Runnable("C.hCCS") {
-                    @Override
-                    public void loggedRun() {
-                         synchronized (mLock) {
-                             processDirectToVoicemail();
-                         }
-                    }
-                }.prepare(), Timeouts.getDirectToVoicemailMillis(mContext.getContentResolver()));
+                // Listeners (just CallsManager for now) will be responsible for checking whether
+                // the call should be blocked.
+                for (Listener l : mListeners) {
+                    l.onSuccessfulIncomingCall(this);
+                }
                 break;
             case CALL_DIRECTION_OUTGOING:
                 for (Listener l : mListeners) {
@@ -1795,7 +1762,6 @@
             }
         }
 
-        processDirectToVoicemail();
         Trace.endSection();
     }
 
diff --git a/src/com/android/server/telecom/CallAudioManager.java b/src/com/android/server/telecom/CallAudioManager.java
index 1280950..7bc727c 100644
--- a/src/com/android/server/telecom/CallAudioManager.java
+++ b/src/com/android/server/telecom/CallAudioManager.java
@@ -97,8 +97,8 @@
         Log.d(LOG_TAG, "Call state changed for TC@%s: %s -> %s", call.getId(),
                 CallState.toString(oldState), CallState.toString(newState));
 
-        if (mCallStateToCalls.get(oldState) != null) {
-            mCallStateToCalls.get(oldState).remove(call);
+        for (int i = 0; i < mCallStateToCalls.size(); i++) {
+            mCallStateToCalls.valueAt(i).remove(call);
         }
         if (mCallStateToCalls.get(newState) != null) {
             mCallStateToCalls.get(newState).add(call);
@@ -157,8 +157,8 @@
         Log.d(LOG_TAG, "Call removed with id TC@%s in state %s", call.getId(),
                 CallState.toString(call.getState()));
 
-        if (mCallStateToCalls.get(call.getState()) != null) {
-            mCallStateToCalls.get(call.getState()).remove(call);
+        for (int i = 0; i < mCallStateToCalls.size(); i++) {
+            mCallStateToCalls.valueAt(i).remove(call);
         }
 
         updateForegroundCall();
@@ -216,7 +216,7 @@
                     mCallStateToCalls.get(call.getState()).remove(call);
                 }
                 mActiveDialingOrConnectingCalls.add(call);
-                mCallAudioModeStateMachine.sendMessage(
+                mCallAudioModeStateMachine.sendMessageWithArgs(
                         CallAudioModeStateMachine.MT_AUDIO_SPEEDUP_FOR_RINGING_CALL,
                         makeArgsForModeStateMachine());
             }
@@ -267,7 +267,7 @@
         if (call != mForegroundCall) {
             return;
         }
-        mCallAudioModeStateMachine.sendMessage(
+        mCallAudioModeStateMachine.sendMessageWithArgs(
                 CallAudioModeStateMachine.FOREGROUND_VOIP_MODE_CHANGE,
                 makeArgsForModeStateMachine());
     }
@@ -388,7 +388,8 @@
         mRingingCalls.clear();
         mRinger.stopRinging();
         mRinger.stopCallWaiting();
-        mCallAudioModeStateMachine.sendMessage(CallAudioModeStateMachine.NO_MORE_RINGING_CALLS,
+        mCallAudioModeStateMachine.sendMessageWithArgs(
+                CallAudioModeStateMachine.NO_MORE_RINGING_CALLS,
                 makeArgsForModeStateMachine());
     }
 
@@ -451,7 +452,7 @@
     @VisibleForTesting
     public void setIsTonePlaying(boolean isTonePlaying) {
         mIsTonePlaying = isTonePlaying;
-        mCallAudioModeStateMachine.sendMessage(
+        mCallAudioModeStateMachine.sendMessageWithArgs(
                 isTonePlaying ? CallAudioModeStateMachine.TONE_STARTED_PLAYING
                         : CallAudioModeStateMachine.TONE_STOPPED_PLAYING,
                 makeArgsForModeStateMachine());
@@ -496,7 +497,7 @@
 
     private void onCallLeavingActiveDialingOrConnecting() {
         if (mActiveDialingOrConnectingCalls.size() == 0) {
-            mCallAudioModeStateMachine.sendMessage(
+            mCallAudioModeStateMachine.sendMessageWithArgs(
                     CallAudioModeStateMachine.NO_MORE_ACTIVE_OR_DIALING_CALLS,
                     makeArgsForModeStateMachine());
         }
@@ -504,21 +505,23 @@
 
     private void onCallLeavingRinging() {
         if (mRingingCalls.size() == 0) {
-            mCallAudioModeStateMachine.sendMessage(CallAudioModeStateMachine.NO_MORE_RINGING_CALLS,
+            mCallAudioModeStateMachine.sendMessageWithArgs(
+                    CallAudioModeStateMachine.NO_MORE_RINGING_CALLS,
                     makeArgsForModeStateMachine());
         }
     }
 
     private void onCallLeavingHold() {
         if (mHoldingCalls.size() == 0) {
-            mCallAudioModeStateMachine.sendMessage(CallAudioModeStateMachine.NO_MORE_HOLDING_CALLS,
+            mCallAudioModeStateMachine.sendMessageWithArgs(
+                    CallAudioModeStateMachine.NO_MORE_HOLDING_CALLS,
                     makeArgsForModeStateMachine());
         }
     }
 
     private void onCallEnteringActiveDialingOrConnecting() {
         if (mActiveDialingOrConnectingCalls.size() == 1) {
-            mCallAudioModeStateMachine.sendMessage(
+            mCallAudioModeStateMachine.sendMessageWithArgs(
                     CallAudioModeStateMachine.NEW_ACTIVE_OR_DIALING_CALL,
                     makeArgsForModeStateMachine());
         }
@@ -526,14 +529,16 @@
 
     private void onCallEnteringRinging() {
         if (mRingingCalls.size() == 1) {
-            mCallAudioModeStateMachine.sendMessage(CallAudioModeStateMachine.NEW_RINGING_CALL,
+            mCallAudioModeStateMachine.sendMessageWithArgs(
+                    CallAudioModeStateMachine.NEW_RINGING_CALL,
                     makeArgsForModeStateMachine());
         }
     }
 
     private void onCallEnteringHold() {
         if (mHoldingCalls.size() == 1) {
-            mCallAudioModeStateMachine.sendMessage(CallAudioModeStateMachine.NEW_HOLDING_CALL,
+            mCallAudioModeStateMachine.sendMessageWithArgs(
+                    CallAudioModeStateMachine.NEW_HOLDING_CALL,
                     makeArgsForModeStateMachine());
         }
     }
@@ -691,4 +696,14 @@
             mRinger.stopCallWaiting();
         }
     }
+
+    @VisibleForTesting
+    public Set<Call> getTrackedCalls() {
+        return mCalls;
+    }
+
+    @VisibleForTesting
+    public SparseArray<LinkedHashSet<Call>> getCallStateToCalls() {
+        return mCallStateToCalls;
+    }
 }
\ No newline at end of file
diff --git a/src/com/android/server/telecom/CallAudioModeStateMachine.java b/src/com/android/server/telecom/CallAudioModeStateMachine.java
index 76ddbea..92adca6 100644
--- a/src/com/android/server/telecom/CallAudioModeStateMachine.java
+++ b/src/com/android/server/telecom/CallAudioModeStateMachine.java
@@ -308,6 +308,13 @@
                     if (args.isTonePlaying) {
                         mCallAudioManager.stopCallWaiting();
                     }
+                    // If a MT-audio-speedup call gets disconnected by the connection service
+                    // concurrently with the user answering it, we may get this message
+                    // indicating that a ringing call has disconnected while this state machine
+                    // is in the SimCallFocusState.
+                    if (!args.hasActiveOrDialingCalls) {
+                        transitionTo(destinationStateAfterNoMoreActiveCalls(args));
+                    }
                     return HANDLED;
                 case NO_MORE_HOLDING_CALLS:
                     // Do nothing.
@@ -483,6 +490,10 @@
         return currentState == null ? "no state" : currentState.getName();
     }
 
+    public void sendMessageWithArgs(int messageCode, MessageArgs args) {
+        sendMessage(messageCode, args);
+    }
+
     @Override
     protected void onPreHandleMessage(Message msg) {
         if (msg.obj != null && msg.obj instanceof MessageArgs) {
diff --git a/src/com/android/server/telecom/CallScreening.java b/src/com/android/server/telecom/CallScreening.java
deleted file mode 100644
index 22201a3..0000000
--- a/src/com/android/server/telecom/CallScreening.java
+++ /dev/null
@@ -1,261 +0,0 @@
-/*
- * Copyright (C) 2016 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.server.telecom;
-
-import android.Manifest;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.os.Binder;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.os.UserHandle;
-import android.telecom.DefaultDialerManager;
-import android.telecom.CallScreeningService;
-import android.text.TextUtils;
-
-import com.android.internal.telecom.ICallScreeningAdapter;
-import com.android.internal.telecom.ICallScreeningService;
-
-import java.util.List;
-
-/**
- * Binds to {@link ICallScreeningService} to allow call blocking. A single instance of this class
- * handles a single call.
- */
-public class CallScreening {
-    public interface Listener {
-        void onCallScreeningCompleted(
-                Call call,
-                boolean shouldAllowCall,
-                boolean shouldReject,
-                boolean shouldAddToCallLog,
-                boolean shouldShowNotification);
-    }
-
-    private final Context mContext;
-    private final Listener mListener;
-    private final TelecomSystem.SyncRoot mLock;
-    private final PhoneAccountRegistrar mPhoneAccountRegistrar;
-    private final CallsManager mCallsManager;
-    private final Handler mHandler = new Handler();
-    private Call mCall;
-    private ICallScreeningService mService;
-    private ServiceConnection mConnection;
-
-    public CallScreening(
-            Context context,
-            CallsManager callsManager,
-            TelecomSystem.SyncRoot lock,
-            PhoneAccountRegistrar phoneAccountRegistrar,
-            Call call) {
-        mContext = context;
-        mCallsManager = callsManager;
-        mListener = callsManager;
-        mLock = lock;
-        mPhoneAccountRegistrar = phoneAccountRegistrar;
-        mCall = call;
-    }
-
-    public void screenCall() {
-        if (!bindService()) {
-            Log.d(this, "no service, giving up");
-            performCleanup();
-        } else {
-            mHandler.postDelayed(new Runnable("CS.sC") {
-                @Override
-                public void loggedRun() {
-                    synchronized (mLock) {
-                        Log.event(mCall, Log.Events.SCREENING_TIMED_OUT);
-                        performCleanup();
-                    }
-                }
-            }.prepare(), Timeouts.getCallScreeningTimeoutMillis(mContext.getContentResolver()));
-        }
-    }
-
-    private void performCleanup() {
-        if (mCall != null) {
-            mListener.onCallScreeningCompleted(mCall, true, false, false, false);
-            mCall = null;
-        }
-        if (mConnection != null) {
-            // We still need to call unbind even if the service disconnected.
-            mContext.unbindService(mConnection);
-            mConnection = null;
-        }
-        mHandler.removeCallbacksAndMessages(null);
-        mService = null;
-    }
-
-    private boolean bindService() {
-        String dialerPackage = DefaultDialerManager
-                .getDefaultDialerApplication(mContext, UserHandle.USER_CURRENT);
-        if (TextUtils.isEmpty(dialerPackage)) {
-            return false;
-        }
-
-        Intent intent = new Intent(CallScreeningService.SERVICE_INTERFACE)
-            .setPackage(dialerPackage);
-        List<ResolveInfo> entries = mContext.getPackageManager().queryIntentServicesAsUser(
-                intent, 0, mCallsManager.getCurrentUserHandle().getIdentifier());
-        if (entries.isEmpty()) {
-            return false;
-        }
-
-        ResolveInfo entry = entries.get(0);
-        if (entry.serviceInfo == null) {
-            return false;
-        }
-
-        if (entry.serviceInfo.permission == null || !entry.serviceInfo.permission.equals(
-                Manifest.permission.BIND_SCREENING_SERVICE)) {
-            Log.w(this, "CallScreeningService must require BIND_SCREENING_SERVICE permission: " +
-                    entry.serviceInfo.packageName);
-            return false;
-        }
-
-        ComponentName componentName =
-                new ComponentName(entry.serviceInfo.packageName, entry.serviceInfo.name);
-        Log.event(mCall, Log.Events.BIND_SCREENING, componentName);
-        intent.setComponent(componentName);
-        ServiceConnection connection = new CallScreeningServiceConnection();
-        if (mContext.bindServiceAsUser(
-                intent,
-                connection,
-                Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE,
-                UserHandle.CURRENT)) {
-            Log.d(this, "bindService, found service, waiting for it to connect");
-            mConnection = connection;
-            return true;
-        }
-
-        return false;
-    }
-
-    private void onServiceBound(ICallScreeningService service) {
-        mService = service;
-        try {
-            mService.screenCall(new CallScreeningAdapter(), ParcelableCallUtils.toParcelableCall(
-                    mCall, false /* includeVideoProvider */, mPhoneAccountRegistrar));
-        } catch (RemoteException e) {
-            Log.e(this, e, "Failed to set the call screening adapter.");
-            performCleanup();
-        }
-    }
-
-    private class CallScreeningServiceConnection implements ServiceConnection {
-        @Override
-        public void onServiceConnected(ComponentName componentName, IBinder service) {
-            Log.startSession("CSCR.oSC");
-            try {
-                synchronized (mLock) {
-                    Log.event(mCall, Log.Events.SCREENING_BOUND, componentName);
-                    if (mCall == null) {
-                        performCleanup();
-                    } else {
-                        onServiceBound(ICallScreeningService.Stub.asInterface(service));
-                    }
-                }
-            } finally {
-                Log.endSession();
-            }
-        }
-
-        @Override
-        public void onServiceDisconnected(ComponentName componentName) {
-            Log.startSession("CSCR.oSD");
-            try {
-                synchronized (mLock) {
-                    performCleanup();
-                }
-            } finally {
-                Log.endSession();
-            }
-        }
-    }
-
-    private class CallScreeningAdapter extends ICallScreeningAdapter.Stub {
-        @Override
-        public void allowCall(String callId) {
-            try {
-                Log.startSession("CSCR.aC");
-                long token = Binder.clearCallingIdentity();
-                try {
-                    synchronized (mLock) {
-                        Log.d(this, "allowCall(%s)", callId);
-                        if (mCall != null && mCall.getId().equals(callId)) {
-                            mListener.onCallScreeningCompleted(
-                                    mCall,
-                                    true /* shouldAllowCall */,
-                                    false /* shouldReject */,
-                                    false /* shouldAddToCallLog */,
-                                    false /* shouldShowNotification */);
-                        } else {
-                            Log.w(this, "allowCall, unknown call id: %s", callId);
-                        }
-                        mCall = null;
-                        performCleanup();
-                    }
-                } finally {
-                    Binder.restoreCallingIdentity(token);
-                }
-            } finally {
-                Log.endSession();
-            }
-        }
-
-        @Override
-        public void disallowCall(
-                String callId,
-                boolean shouldReject,
-                boolean shouldAddToCallLog,
-                boolean shouldShowNotification) {
-            try {
-                Log.startSession("CSCR.dC");
-                long token = Binder.clearCallingIdentity();
-                try {
-                    synchronized (mLock) {
-                        Log.i(this, "disallowCall(%s), shouldReject: %b, shouldAddToCallLog: %b, "
-                                + "shouldShowNotification: %b", callId, shouldReject,
-                                shouldAddToCallLog, shouldShowNotification);
-                        if (mCall != null && mCall.getId().equals(callId)) {
-                            mListener.onCallScreeningCompleted(
-                                    mCall,
-                                    false /* shouldAllowCall */,
-                                    shouldReject,
-                                    shouldAddToCallLog,
-                                    shouldShowNotification);
-                        } else {
-                            Log.w(this, "disallowCall, unknown call id: %s", callId);
-                        }
-                        mCall = null;
-                        performCleanup();
-                    }
-                } finally {
-                    Binder.restoreCallingIdentity(token);
-                }
-            } finally {
-                Log.endSession();
-            }
-        }
-    }
-}
diff --git a/src/com/android/server/telecom/CallsManager.java b/src/com/android/server/telecom/CallsManager.java
index ea3a342..913c1f6 100644
--- a/src/com/android/server/telecom/CallsManager.java
+++ b/src/com/android/server/telecom/CallsManager.java
@@ -16,7 +16,6 @@
 
 package com.android.server.telecom;
 
-import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.content.Context;
 import android.content.pm.UserInfo;
@@ -55,10 +54,17 @@
 import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.telephony.TelephonyProperties;
 import com.android.internal.util.IndentingPrintWriter;
-import com.android.server.telecom.CallLogManager.LogCallCompletedListener;
 import com.android.server.telecom.TelecomServiceImpl.DefaultDialerManagerAdapter;
+import com.android.server.telecom.callfiltering.AsyncBlockCheckFilter;
+import com.android.server.telecom.callfiltering.BlockCheckerAdapter;
+import com.android.server.telecom.callfiltering.CallFilterResultCallback;
+import com.android.server.telecom.callfiltering.CallFilteringResult;
+import com.android.server.telecom.callfiltering.CallScreeningServiceFilter;
+import com.android.server.telecom.callfiltering.DirectToVoicemailCallFilter;
+import com.android.server.telecom.callfiltering.IncomingCallFilter;
 import com.android.server.telecom.components.ErrorDialogActivity;
 
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
@@ -78,7 +84,7 @@
  */
 @VisibleForTesting
 public class CallsManager extends Call.ListenerBase
-        implements VideoProviderProxy.Listener, CallScreening.Listener {
+        implements VideoProviderProxy.Listener, CallFilterResultCallback {
 
     // TODO: Consider renaming this CallsManagerPlugin.
     @VisibleForTesting
@@ -181,6 +187,8 @@
     private final PhoneAccountRegistrar mPhoneAccountRegistrar;
     private final MissedCallNotifier mMissedCallNotifier;
     private final CallerInfoLookupHelper mCallerInfoLookupHelper;
+    private final DefaultDialerManagerAdapter mDefaultDialerManagerAdapter;
+    private final Timeouts.Adapter mTimeoutsAdapter;
     private final Set<Call> mLocallyDisconnectingCalls = new HashSet<>();
     private final Set<Call> mPendingCallsToDisconnect = new HashSet<>();
     /* Handler tied to thread in which CallManager was initialized. */
@@ -210,6 +218,7 @@
             WiredHeadsetManager wiredHeadsetManager,
             SystemStateProvider systemStateProvider,
             DefaultDialerManagerAdapter defaultDialerAdapter,
+            Timeouts.Adapter timeoutsAdapter,
             AsyncRingtonePlayer asyncRingtonePlayer) {
         mContext = context;
         mLock = lock;
@@ -220,7 +229,9 @@
         StatusBarNotifier statusBarNotifier = new StatusBarNotifier(context, this);
         mWiredHeadsetManager = wiredHeadsetManager;
         mBluetoothManager = bluetoothManager;
+        mDefaultDialerManagerAdapter = defaultDialerAdapter;
         mDockManager = new DockManager(context);
+        mTimeoutsAdapter = timeoutsAdapter;
         mCallerInfoLookupHelper = new CallerInfoLookupHelper(context, mCallerInfoAsyncQueryFactory,
                 mContactsAsyncHelper, mLock);
 
@@ -329,62 +340,56 @@
     }
 
     @Override
-    public void onSuccessfulIncomingCall(Call incomingCall, boolean shouldSendToVoicemail) {
+    public void onSuccessfulIncomingCall(Call incomingCall) {
         Log.d(this, "onSuccessfulIncomingCall");
-
-        // TODO: Parallelize Call screening, block check, and send to voicemail.
-        final String number = incomingCall.getHandle() == null ? null : incomingCall.getHandle()
-                .getSchemeSpecificPart();
-        Log.v(this, "Looking up information for: %s.", Log.piiHandle(number));
-
-        new AsyncBlockCheckTask(mContext, incomingCall,
-                new CallScreening(mContext, CallsManager.this, mLock,
-                        mPhoneAccountRegistrar, incomingCall), this, shouldSendToVoicemail)
-                .execute(number);
+        List<IncomingCallFilter.CallFilter> filters = new ArrayList<>();
+        filters.add(new DirectToVoicemailCallFilter(mCallerInfoLookupHelper));
+        filters.add(new AsyncBlockCheckFilter(mContext, new BlockCheckerAdapter()));
+        filters.add(new CallScreeningServiceFilter(mContext, this, mPhoneAccountRegistrar,
+                mDefaultDialerManagerAdapter,
+                new ParcelableCallUtils.Converter(), mLock));
+        new IncomingCallFilter(mContext, this, incomingCall, mLock,
+                mTimeoutsAdapter, filters).performFiltering();
     }
 
     @Override
-    public void onCallScreeningCompleted(
-                Call incomingCall,
-                boolean shouldAllowCall,
-                boolean shouldReject,
-                boolean shouldAddToCallLog,
-                boolean shouldShowNotification) {
+    public void onCallFilteringComplete(Call incomingCall, CallFilteringResult result) {
         // Only set the incoming call as ringing if it isn't already disconnected. It is possible
         // that the connection service disconnected the call before it was even added to Telecom, in
         // which case it makes no sense to set it back to a ringing state.
         if (incomingCall.getState() != CallState.DISCONNECTED &&
                 incomingCall.getState() != CallState.DISCONNECTING) {
             setCallState(incomingCall, CallState.RINGING,
-                    shouldAllowCall ? "successful incoming call" : "blocking call");
+                    result.shouldAllowCall ? "successful incoming call" : "blocking call");
         } else {
-            Log.i(this, "onCallScreeningCompleted: call already disconnected.");
+            Log.i(this, "onCallFilteringCompleted: call already disconnected.");
         }
 
-        if (shouldAllowCall) {
+        if (result.shouldAllowCall) {
             if (hasMaximumRingingCalls()) {
-                Log.i(this, "onCallScreeningCompleted: Call rejected! Exceeds maximum number of " +
+                Log.i(this, "onCallFilteringCompleted: Call rejected! Exceeds maximum number of " +
                         "ringing calls.");
                 rejectCallAndLog(incomingCall);
             } else if (hasMaximumDialingCalls()) {
-                Log.i(this, "onCallScreeningCompleted: Call rejected! Exceeds maximum number of " +
+                Log.i(this, "onCallFilteringCompleted: Call rejected! Exceeds maximum number of " +
                         "dialing calls.");
                 rejectCallAndLog(incomingCall);
             } else {
                 addCall(incomingCall);
             }
         } else {
-            if (shouldReject) {
-                Log.i(this, "onCallScreeningCompleted: blocked call, rejecting.");
+            if (result.shouldReject) {
+                Log.i(this, "onCallFilteringCompleted: blocked call, rejecting.");
                 incomingCall.reject(false, null);
             }
-            if (shouldAddToCallLog) {
+            if (result.shouldAddToCallLog) {
                 Log.i(this, "onCallScreeningCompleted: blocked call, adding to call log.");
-                if (shouldShowNotification) {
+                if (result.shouldShowNotification) {
                     Log.w(this, "onCallScreeningCompleted: blocked call, showing notification.");
                 }
-                mCallLogManager.logCall(incomingCall, Calls.MISSED_TYPE, shouldShowNotification);
-            } else if (shouldShowNotification) {
+                mCallLogManager.logCall(incomingCall, Calls.MISSED_TYPE,
+                        result.shouldShowNotification);
+            } else if (result.shouldShowNotification) {
                 Log.i(this, "onCallScreeningCompleted: blocked call, showing notification.");
                 mMissedCallNotifier.showMissedCallNotification(incomingCall);
             }
diff --git a/src/com/android/server/telecom/DtmfLocalTonePlayer.java b/src/com/android/server/telecom/DtmfLocalTonePlayer.java
index 20c9dd8..64c0c55 100644
--- a/src/com/android/server/telecom/DtmfLocalTonePlayer.java
+++ b/src/com/android/server/telecom/DtmfLocalTonePlayer.java
@@ -34,7 +34,7 @@
  * class employs a concept of a call "session" that starts and stops when the foreground call
  * changes.
  */
-class DtmfLocalTonePlayer {
+public class DtmfLocalTonePlayer {
     /** Generator used to actually play the tone. */
     private ToneGenerator mToneGenerator;
 
diff --git a/src/com/android/server/telecom/Log.java b/src/com/android/server/telecom/Log.java
index d4cb77a..fda21fd 100644
--- a/src/com/android/server/telecom/Log.java
+++ b/src/com/android/server/telecom/Log.java
@@ -97,10 +97,14 @@
         public static final String BIND_SCREENING = "BIND_SCREENING";
         public static final String SCREENING_BOUND = "SCREENING_BOUND";
         public static final String SCREENING_SENT = "SCREENING_SENT";
-        public static final String SCREENING_TIMED_OUT = "SCREENING_TIMED_OUT";
+        public static final String SCREENING_COMPLETED = "SCREENING_COMPLETED";
         public static final String BLOCK_CHECK_INITIATED = "BLOCK_CHECK_INITIATED";
-        public static final String BLOCK_CHECK_TIMED_OUT = "BLOCK_CHECK_TIMED_OUT";
         public static final String BLOCK_CHECK_FINISHED = "BLOCK_CHECK_FINISHED";
+        public static final String DIRECT_TO_VM_INITIATED = "DIRECT_TO_VM_INITIATED";
+        public static final String DIRECT_TO_VM_FINISHED = "DIRECT_TO_VM_FINISHED";
+        public static final String FILTERING_INITIATED = "FILTERING_INITIATED";
+        public static final String FILTERING_COMPLETED = "FILTERING_COMPLETED";
+        public static final String FILTERING_TIMED_OUT = "FILTERING_TIMED_OUT";
         public static final String REMOTELY_HELD = "REMOTELY_HELD";
         public static final String REMOTELY_UNHELD = "REMOTELY_UNHELD";
         public static final String PULL = "PULL";
@@ -121,8 +125,10 @@
                     put(REQUEST_UNHOLD, SET_ACTIVE);
                     put(START_CONNECTION, SET_DIALING);
                     put(BIND_CS, CS_BOUND);
-                    put(SCREENING_SENT, SCREENING_TIMED_OUT);
-                    put(BLOCK_CHECK_INITIATED, BLOCK_CHECK_TIMED_OUT);
+                    put(SCREENING_SENT, SCREENING_COMPLETED);
+                    put(BLOCK_CHECK_INITIATED, BLOCK_CHECK_FINISHED);
+                    put(DIRECT_TO_VM_INITIATED, DIRECT_TO_VM_FINISHED);
+                    put(FILTERING_INITIATED, FILTERING_COMPLETED);
                 }};
     }
 
diff --git a/src/com/android/server/telecom/ParcelableCallUtils.java b/src/com/android/server/telecom/ParcelableCallUtils.java
index 28b74e2..c1c1c57 100644
--- a/src/com/android/server/telecom/ParcelableCallUtils.java
+++ b/src/com/android/server/telecom/ParcelableCallUtils.java
@@ -28,6 +28,14 @@
  * Utilities dealing with {@link ParcelableCall}.
  */
 public class ParcelableCallUtils {
+    public static class Converter {
+        public ParcelableCall toParcelableCall(Call call, boolean includeVideoProvider,
+                PhoneAccountRegistrar phoneAccountRegistrar) {
+            return ParcelableCallUtils.toParcelableCall(
+                    call, includeVideoProvider, phoneAccountRegistrar);
+        }
+    }
+
     /**
      * Parcels all information for a {@link Call} into a new {@link ParcelableCall} instance.
      *
diff --git a/src/com/android/server/telecom/RingbackPlayer.java b/src/com/android/server/telecom/RingbackPlayer.java
index af60b68..bfaf8a2 100644
--- a/src/com/android/server/telecom/RingbackPlayer.java
+++ b/src/com/android/server/telecom/RingbackPlayer.java
@@ -24,7 +24,7 @@
  * able to turn off and on as the user switches between calls. This is why it is implemented as its
  * own class.
  */
-class RingbackPlayer {
+public class RingbackPlayer {
 
     private final InCallTonePlayer.Factory mPlayerFactory;
 
diff --git a/src/com/android/server/telecom/Ringer.java b/src/com/android/server/telecom/Ringer.java
index 8a2b41c..77b1590 100644
--- a/src/com/android/server/telecom/Ringer.java
+++ b/src/com/android/server/telecom/Ringer.java
@@ -31,7 +31,7 @@
  * Controls the ringtone player.
  */
 @VisibleForTesting
-public final class Ringer {
+public class Ringer {
     private static final long[] VIBRATION_PATTERN = new long[] {
         0, // No delay before starting
         1000, // How long to vibrate
diff --git a/src/com/android/server/telecom/TelecomSystem.java b/src/com/android/server/telecom/TelecomSystem.java
index 9da8a24..304f13b 100644
--- a/src/com/android/server/telecom/TelecomSystem.java
+++ b/src/com/android/server/telecom/TelecomSystem.java
@@ -153,6 +153,7 @@
             AudioServiceFactory audioServiceFactory,
             BluetoothPhoneServiceImplFactory
                     bluetoothPhoneServiceImplFactory,
+            Timeouts.Adapter timeoutsAdapter,
             AsyncRingtonePlayer asyncRingtonePlayer) {
         mContext = context.getApplicationContext();
         Log.setContext(mContext);
@@ -194,6 +195,7 @@
                 wiredHeadsetManager,
                 systemStateProvider,
                 defaultDialerAdapter,
+                timeoutsAdapter,
                 asyncRingtonePlayer);
 
         mRespondViaSmsManager = new RespondViaSmsManager(mCallsManager, mLock);
diff --git a/src/com/android/server/telecom/Timeouts.java b/src/com/android/server/telecom/Timeouts.java
index 88a3a06..08d7410 100644
--- a/src/com/android/server/telecom/Timeouts.java
+++ b/src/com/android/server/telecom/Timeouts.java
@@ -26,7 +26,7 @@
  * These methods are safe to call from any thread, including the UI thread.
  */
 public final class Timeouts {
-    public class Adapter {
+    public static class Adapter {
         public Adapter() { }
 
         public long getCallScreeningTimeoutMillis(ContentResolver cr) {
diff --git a/src/com/android/server/telecom/callfiltering/AsyncBlockCheckFilter.java b/src/com/android/server/telecom/callfiltering/AsyncBlockCheckFilter.java
new file mode 100644
index 0000000..8872297
--- /dev/null
+++ b/src/com/android/server/telecom/callfiltering/AsyncBlockCheckFilter.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2016 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.server.telecom.callfiltering;
+
+import android.content.Context;
+import android.os.AsyncTask;
+
+import com.android.server.telecom.Call;
+import com.android.server.telecom.Log;
+import com.android.server.telecom.Session;
+
+/**
+ * An {@link AsyncTask} that checks if a call needs to be blocked.
+ * <p> An {@link AsyncTask} is used to perform the block check to avoid blocking the main thread.
+ * The block check itself is performed in the {@link AsyncTask#doInBackground(Object[])}.
+ */
+public class AsyncBlockCheckFilter extends AsyncTask<String, Void, Boolean>
+        implements IncomingCallFilter.CallFilter {
+    private final Context mContext;
+    private final BlockCheckerAdapter mBlockCheckerAdapter;
+    private Call mIncomingCall;
+    private Session mLogSubsession;
+    private CallFilterResultCallback mCallback;
+
+    public AsyncBlockCheckFilter(Context context, BlockCheckerAdapter blockCheckerAdapter) {
+        mContext = context;
+        mBlockCheckerAdapter = blockCheckerAdapter;
+    }
+
+    @Override
+    public void startFilterLookup(Call call, CallFilterResultCallback callback) {
+        mCallback = callback;
+        mIncomingCall = call;
+        String number = call.getHandle() == null ?
+                null : call.getHandle().getSchemeSpecificPart();
+        this.execute(number);
+    }
+
+    @Override
+    protected void onPreExecute() {
+        mLogSubsession = Log.createSubsession();
+    }
+
+    @Override
+    protected Boolean doInBackground(String... params) {
+        try {
+            Log.continueSession(mLogSubsession, "ABCF.dIB");
+            Log.event(mIncomingCall, Log.Events.BLOCK_CHECK_INITIATED);
+            return mBlockCheckerAdapter.isBlocked(mContext, params[0]);
+        } finally {
+            Log.endSession();
+        }
+    }
+
+    @Override
+    protected void onPostExecute(Boolean isBlocked) {
+        CallFilteringResult result;
+        if (isBlocked) {
+            result = new CallFilteringResult(
+                    false, // shouldAllowCall
+                    true, //shouldReject
+                    false, //shouldAddToCallLog
+                    false // shouldShowNotification
+            );
+        } else {
+            result = new CallFilteringResult(
+                    true, // shouldAllowCall
+                    false, // shouldReject
+                    true, // shouldAddToCallLog
+                    true // shouldShowNotification
+            );
+        }
+        Log.event(mIncomingCall, Log.Events.BLOCK_CHECK_FINISHED, result);
+        mCallback.onCallFilteringComplete(mIncomingCall, result);
+    }
+}
diff --git a/src/com/android/server/telecom/callfiltering/BlockCheckerAdapter.java b/src/com/android/server/telecom/callfiltering/BlockCheckerAdapter.java
new file mode 100644
index 0000000..f15a507
--- /dev/null
+++ b/src/com/android/server/telecom/callfiltering/BlockCheckerAdapter.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2016 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.server.telecom.callfiltering;
+
+import android.content.Context;
+
+import com.android.internal.telephony.BlockChecker;
+
+public class BlockCheckerAdapter {
+    public BlockCheckerAdapter() { }
+
+    public boolean isBlocked(Context context, String number) {
+        return BlockChecker.isBlocked(context, number);
+    }
+}
diff --git a/src/com/android/server/telecom/callfiltering/CallFilteringResult.java b/src/com/android/server/telecom/callfiltering/CallFilteringResult.java
index 88d707c..9e35d86 100644
--- a/src/com/android/server/telecom/callfiltering/CallFilteringResult.java
+++ b/src/com/android/server/telecom/callfiltering/CallFilteringResult.java
@@ -67,4 +67,28 @@
         result = 31 * result + (shouldShowNotification ? 1 : 0);
         return result;
     }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append("[");
+        if (shouldAllowCall) {
+            sb.append("Allow");
+        } else if (shouldReject) {
+            sb.append("Reject");
+        } else {
+            sb.append("Ignore");
+        }
+
+        if (shouldAddToCallLog) {
+            sb.append(", logged");
+        }
+
+        if (shouldShowNotification) {
+            sb.append(", notified");
+        }
+        sb.append("]");
+
+        return sb.toString();
+    }
 }
diff --git a/src/com/android/server/telecom/callfiltering/CallScreeningServiceFilter.java b/src/com/android/server/telecom/callfiltering/CallScreeningServiceFilter.java
new file mode 100644
index 0000000..db99e2f
--- /dev/null
+++ b/src/com/android/server/telecom/callfiltering/CallScreeningServiceFilter.java
@@ -0,0 +1,262 @@
+/*
+ * Copyright (C) 2016 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.server.telecom.callfiltering;
+
+import android.Manifest;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.pm.ResolveInfo;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.telecom.CallScreeningService;
+import android.text.TextUtils;
+
+import com.android.internal.telecom.ICallScreeningAdapter;
+import com.android.internal.telecom.ICallScreeningService;
+import com.android.server.telecom.Call;
+import com.android.server.telecom.CallsManager;
+import com.android.server.telecom.Log;
+import com.android.server.telecom.ParcelableCallUtils;
+import com.android.server.telecom.PhoneAccountRegistrar;
+import com.android.server.telecom.TelecomServiceImpl;
+import com.android.server.telecom.TelecomSystem;
+
+import java.util.List;
+
+/**
+ * Binds to {@link ICallScreeningService} to allow call blocking. A single instance of this class
+ * handles a single call.
+ */
+public class CallScreeningServiceFilter implements IncomingCallFilter.CallFilter {
+    private class CallScreeningServiceConnection implements ServiceConnection {
+        @Override
+        public void onServiceConnected(ComponentName componentName, IBinder service) {
+            Log.startSession("CSCR.oSC");
+            try {
+                synchronized (mTelecomLock) {
+                    Log.event(mCall, Log.Events.SCREENING_BOUND, componentName);
+                    if (!mHasFinished) {
+                        onServiceBound(ICallScreeningService.Stub.asInterface(service));
+                    }
+                }
+            } finally {
+                Log.endSession();
+            }
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName componentName) {
+            Log.startSession("CSCR.oSD");
+            try {
+                synchronized (mTelecomLock) {
+                    finishCallScreening();
+                }
+            } finally {
+                Log.endSession();
+            }
+        }
+    }
+
+    private class CallScreeningAdapter extends ICallScreeningAdapter.Stub {
+        @Override
+        public void allowCall(String callId) {
+            Log.startSession("CSCR.aC");
+            long token = Binder.clearCallingIdentity();
+            try {
+                synchronized (mTelecomLock) {
+                    Log.d(this, "allowCall(%s)", callId);
+                    if (mCall != null && mCall.getId().equals(callId)) {
+                        mResult = new CallFilteringResult(
+                                true, // shouldAllowCall
+                                false, //shouldReject
+                                true, //shouldAddToCallLog
+                                true // shouldShowNotification
+                        );
+                    } else {
+                        Log.w(this, "allowCall, unknown call id: %s", callId);
+                    }
+                    finishCallScreening();
+                }
+            } finally {
+                Binder.restoreCallingIdentity(token);
+                Log.endSession();
+            }
+        }
+
+        @Override
+        public void disallowCall(
+                String callId,
+                boolean shouldReject,
+                boolean shouldAddToCallLog,
+                boolean shouldShowNotification) {
+            Log.startSession("CSCR.dC");
+            long token = Binder.clearCallingIdentity();
+            try {
+                synchronized (mTelecomLock) {
+                    Log.i(this, "disallowCall(%s), shouldReject: %b, shouldAddToCallLog: %b, "
+                                    + "shouldShowNotification: %b", callId, shouldReject,
+                            shouldAddToCallLog, shouldShowNotification);
+                    if (mCall != null && mCall.getId().equals(callId)) {
+                        mResult = new CallFilteringResult(
+                                false, // shouldAllowCall
+                                shouldReject, //shouldReject
+                                shouldAddToCallLog, //shouldAddToCallLog
+                                shouldShowNotification // shouldShowNotification
+                        );
+                    } else {
+                        Log.w(this, "disallowCall, unknown call id: %s", callId);
+                    }
+                    finishCallScreening();
+                }
+            } finally {
+                Binder.restoreCallingIdentity(token);
+                Log.endSession();
+            }
+        }
+    }
+
+    private final Context mContext;
+    private final PhoneAccountRegistrar mPhoneAccountRegistrar;
+    private final CallsManager mCallsManager;
+    private final TelecomServiceImpl.DefaultDialerManagerAdapter mDefaultDialerManagerAdapter;
+    private final ParcelableCallUtils.Converter mParcelableCallUtilsConverter;
+    private final TelecomSystem.SyncRoot mTelecomLock;
+
+    private Call mCall;
+    private CallFilterResultCallback mCallback;
+    private ICallScreeningService mService;
+    private ServiceConnection mConnection;
+
+    private boolean mHasFinished = false;
+    private CallFilteringResult mResult = new CallFilteringResult(
+            true, // shouldAllowCall
+            false, //shouldReject
+            true, //shouldAddToCallLog
+            true // shouldShowNotification
+    );
+
+    public CallScreeningServiceFilter(
+            Context context,
+            CallsManager callsManager,
+            PhoneAccountRegistrar phoneAccountRegistrar,
+            TelecomServiceImpl.DefaultDialerManagerAdapter defaultDialerManagerAdapter,
+            ParcelableCallUtils.Converter parcelableCallUtilsConverter,
+            TelecomSystem.SyncRoot lock) {
+        mContext = context;
+        mPhoneAccountRegistrar = phoneAccountRegistrar;
+        mCallsManager = callsManager;
+        mDefaultDialerManagerAdapter = defaultDialerManagerAdapter;
+        mParcelableCallUtilsConverter = parcelableCallUtilsConverter;
+        mTelecomLock = lock;
+    }
+
+    @Override
+    public void startFilterLookup(Call call, CallFilterResultCallback callback) {
+        if (mHasFinished) {
+            Log.w(this, "Attempting to reuse CallScreeningServiceFilter. Ignoring.");
+            return;
+        }
+        Log.event(call, Log.Events.SCREENING_SENT);
+        mCall = call;
+        mCallback = callback;
+        if (!bindService()) {
+            Log.i(this, "Could not bind to call screening service");
+            finishCallScreening();
+        }
+    }
+
+    private void finishCallScreening() {
+        if (!mHasFinished) {
+            Log.event(mCall, Log.Events.SCREENING_COMPLETED, mResult);
+            mCallback.onCallFilteringComplete(mCall, mResult);
+
+            if (mConnection != null) {
+                // We still need to call unbind even if the service disconnected.
+                mContext.unbindService(mConnection);
+                mConnection = null;
+            }
+            mService = null;
+            mHasFinished = true;
+        }
+    }
+
+    private boolean bindService() {
+        String dialerPackage = mDefaultDialerManagerAdapter
+                .getDefaultDialerApplication(mContext, UserHandle.USER_CURRENT);
+        if (TextUtils.isEmpty(dialerPackage)) {
+            Log.i(this, "Default dialer is empty. Not performing call screening.");
+            return false;
+        }
+
+        Intent intent = new Intent(CallScreeningService.SERVICE_INTERFACE)
+            .setPackage(dialerPackage);
+        List<ResolveInfo> entries = mContext.getPackageManager().queryIntentServicesAsUser(
+                intent, 0, mCallsManager.getCurrentUserHandle().getIdentifier());
+        if (entries.isEmpty()) {
+            Log.i(this, "There are no call screening services installed on this device.");
+            return false;
+        }
+
+        ResolveInfo entry = entries.get(0);
+        if (entry.serviceInfo == null) {
+            Log.w(this, "The call screening service has invalid service info");
+            return false;
+        }
+
+        if (entry.serviceInfo.permission == null || !entry.serviceInfo.permission.equals(
+                Manifest.permission.BIND_SCREENING_SERVICE)) {
+            Log.w(this, "CallScreeningService must require BIND_SCREENING_SERVICE permission: " +
+                    entry.serviceInfo.packageName);
+            return false;
+        }
+
+        ComponentName componentName =
+                new ComponentName(entry.serviceInfo.packageName, entry.serviceInfo.name);
+        Log.event(mCall, Log.Events.BIND_SCREENING, componentName);
+        intent.setComponent(componentName);
+        ServiceConnection connection = new CallScreeningServiceConnection();
+        if (mContext.bindServiceAsUser(
+                intent,
+                connection,
+                Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE,
+                UserHandle.CURRENT)) {
+            Log.d(this, "bindService, found service, waiting for it to connect");
+            mConnection = connection;
+            return true;
+        }
+
+        return false;
+    }
+
+    private void onServiceBound(ICallScreeningService service) {
+        mService = service;
+        try {
+            mService.screenCall(new CallScreeningAdapter(),
+                    mParcelableCallUtilsConverter.toParcelableCall(
+                            mCall,
+                            false, /* includeVideoProvider */
+                            mPhoneAccountRegistrar));
+        } catch (RemoteException e) {
+            Log.e(this, e, "Failed to set the call screening adapter.");
+            finishCallScreening();
+        }
+    }
+}
diff --git a/src/com/android/server/telecom/callfiltering/DirectToVoicemailCallFilter.java b/src/com/android/server/telecom/callfiltering/DirectToVoicemailCallFilter.java
index 8163cfd..1363d62 100644
--- a/src/com/android/server/telecom/callfiltering/DirectToVoicemailCallFilter.java
+++ b/src/com/android/server/telecom/callfiltering/DirectToVoicemailCallFilter.java
@@ -32,29 +32,31 @@
 
     @Override
     public void startFilterLookup(final Call call, CallFilterResultCallback callback) {
+        Log.event(call, Log.Events.DIRECT_TO_VM_INITIATED);
         final Uri callHandle = call.getHandle();
         mCallerInfoLookupHelper.startLookup(callHandle,
                 new CallerInfoLookupHelper.OnQueryCompleteListener() {
                     @Override
                     public void onCallerInfoQueryComplete(Uri handle, CallerInfo info) {
+                        CallFilteringResult result;
                         if (callHandle.equals(handle)) {
                             if (info.shouldSendToVoicemail) {
-                                callback.onCallFilteringComplete(call,
-                                        new CallFilteringResult(
-                                                false, // shouldAllowCall
-                                                true, // shouldReject
-                                                true, // shouldAddToCallLog
-                                                true // shouldShowNotification
-                                        ));
+                                result = new CallFilteringResult(
+                                        false, // shouldAllowCall
+                                        true, // shouldReject
+                                        true, // shouldAddToCallLog
+                                        true // shouldShowNotification
+                                );
                             } else {
-                                callback.onCallFilteringComplete(call,
-                                        new CallFilteringResult(
-                                                true, // shouldAllowCall
-                                                false, // shouldReject
-                                                true, // shouldAddToCallLog
-                                                true // shouldShowNotification
-                                        ));
+                                result = new CallFilteringResult(
+                                        true, // shouldAllowCall
+                                        false, // shouldReject
+                                        true, // shouldAddToCallLog
+                                        true // shouldShowNotification
+                                );
                             }
+                            Log.event(call, Log.Events.DIRECT_TO_VM_FINISHED, result);
+                            callback.onCallFilteringComplete(call, result);
                         } else {
                             Log.w(this, "CallerInfo lookup returned with a different handle than " +
                                     "what was passed in. Was %s, should be %s", handle, callHandle);
diff --git a/src/com/android/server/telecom/callfiltering/IncomingCallFilter.java b/src/com/android/server/telecom/callfiltering/IncomingCallFilter.java
index 0a046b6..4685ec0 100644
--- a/src/com/android/server/telecom/callfiltering/IncomingCallFilter.java
+++ b/src/com/android/server/telecom/callfiltering/IncomingCallFilter.java
@@ -35,7 +35,7 @@
         void startFilterLookup(Call call, CallFilterResultCallback listener);
     }
 
-    private final TelecomSystem.SyncRoot mLock;
+    private final TelecomSystem.SyncRoot mTelecomLock;
     private final Context mContext;
     private final Handler mHandler = new Handler(Looper.getMainLooper());
     private final List<CallFilter> mFilters;
@@ -59,22 +59,25 @@
         mContext = context;
         mListener = listener;
         mCall = call;
-        mLock = lock;
+        mTelecomLock = lock;
         mFilters = filters;
         mNumPendingFilters = filters.size();
         mTimeoutsAdapter = timeoutsAdapter;
     }
 
     public void performFiltering() {
+        Log.event(mCall, Log.Events.FILTERING_INITIATED);
         for (CallFilter filter : mFilters) {
             filter.startFilterLookup(mCall, this);
         }
         mHandler.postDelayed(new Runnable("ICF.pFTO") { // performFiltering time-out
             @Override
             public void loggedRun() {
-                synchronized (mLock) { // synchronized to prevent a race on mResult
+                // synchronized to prevent a race on mResult and to enter into Telecom.
+                synchronized (mTelecomLock) {
                     if (mIsPending) {
                         Log.i(IncomingCallFilter.this, "Call filtering has timed out.");
+                        Log.event(mCall, Log.Events.FILTERING_TIMED_OUT);
                         mListener.onCallFilteringComplete(mCall, mResult);
                         mIsPending = false;
                     }
@@ -84,16 +87,20 @@
     }
 
     public void onCallFilteringComplete(Call call, CallFilteringResult result) {
-        synchronized (mLock) {
+        synchronized (mTelecomLock) { // synchronizing to prevent race on mResult
             mNumPendingFilters--;
             mResult = result.combine(mResult);
             if (mNumPendingFilters == 0) {
                 mHandler.post(new Runnable("ICF.oCFC") {
                     @Override
                     public void loggedRun() {
-                        if (mIsPending) {
-                            mListener.onCallFilteringComplete(mCall, mResult);
-                            mIsPending = false;
+                        // synchronized to enter into Telecom.
+                        synchronized (mTelecomLock) {
+                            if (mIsPending) {
+                                Log.event(mCall, Log.Events.FILTERING_COMPLETED, mResult);
+                                mListener.onCallFilteringComplete(mCall, mResult);
+                                mIsPending = false;
+                            }
                         }
                     }
                 }.prepare());
diff --git a/src/com/android/server/telecom/components/TelecomService.java b/src/com/android/server/telecom/components/TelecomService.java
index 38b0b81..bef8453 100644
--- a/src/com/android/server/telecom/components/TelecomService.java
+++ b/src/com/android/server/telecom/components/TelecomService.java
@@ -42,6 +42,7 @@
 import com.android.server.telecom.ProximitySensorManager;
 import com.android.server.telecom.TelecomSystem;
 import com.android.server.telecom.TelecomWakeLock;
+import com.android.server.telecom.Timeouts;
 import com.android.server.telecom.ui.MissedCallNotifierImpl;
 
 /**
@@ -146,6 +147,7 @@
                                             phoneAccountRegistrar);
                                 }
                             },
+                            new Timeouts.Adapter(),
                             new AsyncRingtonePlayer()
                     ));
         }
diff --git a/tests/src/com/android/server/telecom/tests/AsyncBlockCheckFilterTest.java b/tests/src/com/android/server/telecom/tests/AsyncBlockCheckFilterTest.java
new file mode 100644
index 0000000..79d711b
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/AsyncBlockCheckFilterTest.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2016 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.server.telecom.tests;
+
+import android.content.Context;
+import android.net.Uri;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.server.telecom.Call;
+import com.android.server.telecom.callfiltering.AsyncBlockCheckFilter;
+import com.android.server.telecom.callfiltering.BlockCheckerAdapter;
+import com.android.server.telecom.callfiltering.CallFilterResultCallback;
+import com.android.server.telecom.callfiltering.CallFilteringResult;
+
+import org.mockito.Mock;
+
+import java.util.concurrent.CountDownLatch;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class AsyncBlockCheckFilterTest extends TelecomTestCase {
+    @Mock private Context mContext;
+    @Mock private BlockCheckerAdapter mBlockCheckerAdapter;
+    @Mock private Call mCall;
+    @Mock private CallFilterResultCallback mCallback;
+
+    private AsyncBlockCheckFilter mFilter;
+    private static final CallFilteringResult BLOCK_RESULT = new CallFilteringResult(
+            false, // shouldAllowCall
+            true, //shouldReject
+            false, //shouldAddToCallLog
+            false // shouldShowNotification
+    );
+
+    private static final CallFilteringResult PASS_RESULT = new CallFilteringResult(
+            true, // shouldAllowCall
+            false, // shouldReject
+            true, // shouldAddToCallLog
+            true // shouldShowNotification
+    );
+
+    private static final Uri TEST_HANDLE = Uri.parse("tel:1235551234");
+    private static final int TEST_TIMEOUT = 100;
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        when(mCall.getHandle()).thenReturn(TEST_HANDLE);
+        mFilter = new AsyncBlockCheckFilter(mContext, mBlockCheckerAdapter);
+    }
+
+    @SmallTest
+    public void testBlockNumber() {
+        final CountDownLatch latch = new CountDownLatch(1);
+        doAnswer(invocation -> {
+            latch.countDown();
+            return true;
+        }).when(mBlockCheckerAdapter)
+                .isBlocked(any(Context.class), eq(TEST_HANDLE.getSchemeSpecificPart()));
+        mFilter.startFilterLookup(mCall, mCallback);
+        waitOnLatch(latch);
+        verify(mCallback, timeout(TEST_TIMEOUT))
+                .onCallFilteringComplete(eq(mCall), eq(BLOCK_RESULT));
+    }
+
+    @SmallTest
+    public void testDontBlockNumber() {
+        final CountDownLatch latch = new CountDownLatch(1);
+        doAnswer(invocation -> {
+            latch.countDown();
+            return false;
+        }).when(mBlockCheckerAdapter)
+                .isBlocked(any(Context.class), eq(TEST_HANDLE.getSchemeSpecificPart()));
+        mFilter.startFilterLookup(mCall, mCallback);
+        waitOnLatch(latch);
+        verify(mCallback, timeout(TEST_TIMEOUT))
+                .onCallFilteringComplete(eq(mCall), eq(PASS_RESULT));
+    }
+
+    private void waitOnLatch(CountDownLatch latch) {
+        while (latch.getCount() > 0) {
+            try {
+                latch.await();
+            } catch (InterruptedException e) {
+                // do nothing
+            }
+        }
+    }
+}
diff --git a/tests/src/com/android/server/telecom/tests/BasicCallTests.java b/tests/src/com/android/server/telecom/tests/BasicCallTests.java
index 8bee217..11a5624 100644
--- a/tests/src/com/android/server/telecom/tests/BasicCallTests.java
+++ b/tests/src/com/android/server/telecom/tests/BasicCallTests.java
@@ -27,11 +27,14 @@
 import static org.mockito.Mockito.verifyZeroInteractions;
 import static org.mockito.Mockito.when;
 
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.IContentProvider;
 import android.media.AudioManager;
 import android.net.Uri;
 import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
 import android.os.Process;
 import android.provider.BlockedNumberContract;
 import android.telecom.Call;
@@ -285,6 +288,7 @@
         mTelecomSystem.getTelecomServiceImpl().getBinder()
                 .addNewIncomingCall(mPhoneAccountA0.getAccountHandle(), extras);
 
+        waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
         verify(mConnectionServiceFixtureA.getTestDouble())
                 .createConnection(any(PhoneAccountHandle.class), anyString(),
                         any(ConnectionRequest.class), eq(true), eq(false));
diff --git a/tests/src/com/android/server/telecom/tests/CallAudioManagerTest.java b/tests/src/com/android/server/telecom/tests/CallAudioManagerTest.java
new file mode 100644
index 0000000..34325f0
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/CallAudioManagerTest.java
@@ -0,0 +1,282 @@
+/*
+ * Copyright (C) 2016 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.server.telecom.tests;
+
+import android.media.ToneGenerator;
+import android.telecom.DisconnectCause;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.util.SparseArray;
+
+import com.android.server.telecom.Call;
+import com.android.server.telecom.CallAudioModeStateMachine;
+import com.android.server.telecom.CallAudioRouteStateMachine;
+import com.android.server.telecom.CallState;
+import com.android.server.telecom.CallsManager;
+import com.android.server.telecom.CallAudioManager;
+import com.android.server.telecom.DtmfLocalTonePlayer;
+import com.android.server.telecom.InCallTonePlayer;
+import com.android.server.telecom.RingbackPlayer;
+import com.android.server.telecom.Ringer;
+
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+
+import java.util.LinkedHashSet;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class CallAudioManagerTest extends TelecomTestCase {
+    @Mock private CallAudioRouteStateMachine mCallAudioRouteStateMachine;
+    @Mock private CallsManager mCallsManager;
+    @Mock private CallAudioModeStateMachine mCallAudioModeStateMachine;
+    @Mock private InCallTonePlayer.Factory mPlayerFactory;
+    @Mock private Ringer mRinger;
+    @Mock private RingbackPlayer mRingbackPlayer;
+    @Mock private DtmfLocalTonePlayer mDtmfLocalTonePlayer;
+
+    private CallAudioManager mCallAudioManager;
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        doAnswer((invocation) -> {
+            InCallTonePlayer mockInCallTonePlayer = mock(InCallTonePlayer.class);
+            doAnswer((invocation2) -> {
+                mCallAudioManager.setIsTonePlaying(true);
+                return null;
+            }).when(mockInCallTonePlayer).startTone();
+            return mockInCallTonePlayer;
+        }).when(mPlayerFactory).createPlayer(anyInt());
+        mCallAudioManager = new CallAudioManager(
+                mCallAudioRouteStateMachine,
+                mCallsManager,
+                mCallAudioModeStateMachine,
+                mPlayerFactory,
+                mRinger,
+                mRingbackPlayer,
+                mDtmfLocalTonePlayer);
+    }
+
+    @MediumTest
+    public void testSingleIncomingCallFlowWithoutMTSpeedUp() {
+        Call call = createIncomingCall();
+        when(call.can(android.telecom.Call.Details.CAPABILITY_SPEED_UP_MT_AUDIO))
+                .thenReturn(false);
+
+        ArgumentCaptor<CallAudioModeStateMachine.MessageArgs> captor =
+                ArgumentCaptor.forClass(CallAudioModeStateMachine.MessageArgs.class);
+        // Answer the incoming call
+        mCallAudioManager.onIncomingCallAnswered(call);
+        when(call.getState()).thenReturn(CallState.ACTIVE);
+        mCallAudioManager.onCallStateChanged(call, CallState.RINGING, CallState.ACTIVE);
+        verify(mCallAudioModeStateMachine).sendMessageWithArgs(
+                eq(CallAudioModeStateMachine.NO_MORE_RINGING_CALLS), captor.capture());
+        CallAudioModeStateMachine.MessageArgs correctArgs =
+                new CallAudioModeStateMachine.MessageArgs(
+                        true, // hasActiveOrDialingCalls
+                        false, // hasRingingCalls
+                        false, // hasHoldingCalls
+                        false, // isTonePlaying
+                        false, // foregroundCallIsVoip
+                        null // session
+                );
+        assertMessageArgEquality(correctArgs, captor.getValue());
+        verify(mCallAudioModeStateMachine).sendMessageWithArgs(
+                eq(CallAudioModeStateMachine.NEW_ACTIVE_OR_DIALING_CALL), captor.capture());
+        assertMessageArgEquality(correctArgs, captor.getValue());
+
+        disconnectCall(call);
+        stopTone();
+
+        mCallAudioManager.onCallRemoved(call);
+        verifyProperCleanup();
+    }
+
+    @MediumTest
+    public void testSingleIncomingCallFlowWithMTSpeedUp() {
+        Call call = createIncomingCall();
+        when(call.can(android.telecom.Call.Details.CAPABILITY_SPEED_UP_MT_AUDIO))
+                .thenReturn(true);
+
+        ArgumentCaptor<CallAudioModeStateMachine.MessageArgs> captor =
+                ArgumentCaptor.forClass(CallAudioModeStateMachine.MessageArgs.class);
+        // Answer the incoming call
+        mCallAudioManager.onIncomingCallAnswered(call);
+        verify(mCallAudioModeStateMachine).sendMessageWithArgs(
+                eq(CallAudioModeStateMachine.MT_AUDIO_SPEEDUP_FOR_RINGING_CALL), captor.capture());
+        CallAudioModeStateMachine.MessageArgs correctArgs =
+                new CallAudioModeStateMachine.MessageArgs(
+                        true, // hasActiveOrDialingCalls
+                        false, // hasRingingCalls
+                        false, // hasHoldingCalls
+                        false, // isTonePlaying
+                        false, // foregroundCallIsVoip
+                        null // session
+                );
+        assertMessageArgEquality(correctArgs, captor.getValue());
+        assertMessageArgEquality(correctArgs, captor.getValue());
+        when(call.getState()).thenReturn(CallState.ACTIVE);
+        mCallAudioManager.onCallStateChanged(call, CallState.RINGING, CallState.ACTIVE);
+
+        disconnectCall(call);
+        stopTone();
+
+        mCallAudioManager.onCallRemoved(call);
+        verifyProperCleanup();
+    }
+
+    @MediumTest
+    public void testSingleOutgoingCall() {
+        Call call = mock(Call.class);
+        when(call.getState()).thenReturn(CallState.CONNECTING);
+
+        mCallAudioManager.onCallAdded(call);
+        assertEquals(call, mCallAudioManager.getForegroundCall());
+        ArgumentCaptor<CallAudioModeStateMachine.MessageArgs> captor =
+                ArgumentCaptor.forClass(CallAudioModeStateMachine.MessageArgs.class);
+        verify(mCallAudioRouteStateMachine).sendMessageWithSessionInfo(
+                CallAudioRouteStateMachine.UPDATE_SYSTEM_AUDIO_ROUTE);
+        verify(mCallAudioModeStateMachine).sendMessageWithArgs(
+                eq(CallAudioModeStateMachine.NEW_ACTIVE_OR_DIALING_CALL), captor.capture());
+        CallAudioModeStateMachine.MessageArgs expectedArgs =
+                new CallAudioModeStateMachine.MessageArgs(
+                        true, // hasActiveOrDialingCalls
+                        false, // hasRingingCalls
+                        false, // hasHoldingCalls
+                        false, // isTonePlaying
+                        false, // foregroundCallIsVoip
+                        null // session
+                );
+        assertMessageArgEquality(expectedArgs, captor.getValue());
+
+        when(call.getState()).thenReturn(CallState.DIALING);
+        mCallAudioManager.onCallStateChanged(call, CallState.CONNECTING, CallState.DIALING);
+        verify(mCallAudioModeStateMachine, times(2)).sendMessageWithArgs(
+                eq(CallAudioModeStateMachine.NEW_ACTIVE_OR_DIALING_CALL), captor.capture());
+        assertMessageArgEquality(expectedArgs, captor.getValue());
+        verify(mCallAudioModeStateMachine, times(2)).sendMessageWithArgs(
+                anyInt(), any(CallAudioModeStateMachine.MessageArgs.class));
+
+
+        when(call.getState()).thenReturn(CallState.ACTIVE);
+        mCallAudioManager.onCallStateChanged(call, CallState.DIALING, CallState.ACTIVE);
+        verify(mCallAudioModeStateMachine, times(3)).sendMessageWithArgs(
+                eq(CallAudioModeStateMachine.NEW_ACTIVE_OR_DIALING_CALL), captor.capture());
+        assertMessageArgEquality(expectedArgs, captor.getValue());
+        verify(mCallAudioModeStateMachine, times(3)).sendMessageWithArgs(
+                anyInt(), any(CallAudioModeStateMachine.MessageArgs.class));
+
+        disconnectCall(call);
+        stopTone();
+
+        mCallAudioManager.onCallRemoved(call);
+        verifyProperCleanup();
+    }
+
+    private Call createIncomingCall() {
+        Call call = mock(Call.class);
+        when(call.getState()).thenReturn(CallState.RINGING);
+
+        mCallAudioManager.onCallAdded(call);
+        assertEquals(call, mCallAudioManager.getForegroundCall());
+        ArgumentCaptor<CallAudioModeStateMachine.MessageArgs> captor =
+                ArgumentCaptor.forClass(CallAudioModeStateMachine.MessageArgs.class);
+        verify(mCallAudioRouteStateMachine).sendMessageWithSessionInfo(
+                CallAudioRouteStateMachine.UPDATE_SYSTEM_AUDIO_ROUTE);
+        verify(mCallAudioModeStateMachine).sendMessageWithArgs(
+                eq(CallAudioModeStateMachine.NEW_RINGING_CALL), captor.capture());
+        assertMessageArgEquality(new CallAudioModeStateMachine.MessageArgs(
+                false, // hasActiveOrDialingCalls
+                true, // hasRingingCalls
+                false, // hasHoldingCalls
+                false, // isTonePlaying
+                false, // foregroundCallIsVoip
+                null // session
+        ), captor.getValue());
+
+        return call;
+    }
+
+    private void disconnectCall(Call call) {
+        ArgumentCaptor<CallAudioModeStateMachine.MessageArgs> captor =
+                ArgumentCaptor.forClass(CallAudioModeStateMachine.MessageArgs.class);
+        CallAudioModeStateMachine.MessageArgs correctArgs;
+
+        when(call.getState()).thenReturn(CallState.DISCONNECTED);
+        when(call.getDisconnectCause()).thenReturn(new DisconnectCause(DisconnectCause.LOCAL,
+                "", "", "", ToneGenerator.TONE_PROP_PROMPT));
+
+        mCallAudioManager.onCallStateChanged(call, CallState.ACTIVE, CallState.DISCONNECTED);
+        verify(mPlayerFactory).createPlayer(InCallTonePlayer.TONE_CALL_ENDED);
+        correctArgs = new CallAudioModeStateMachine.MessageArgs(
+                false, // hasActiveOrDialingCalls
+                false, // hasRingingCalls
+                false, // hasHoldingCalls
+                true, // isTonePlaying
+                false, // foregroundCallIsVoip
+                null // session
+        );
+        verify(mCallAudioModeStateMachine).sendMessageWithArgs(
+                eq(CallAudioModeStateMachine.NO_MORE_ACTIVE_OR_DIALING_CALLS), captor.capture());
+        assertMessageArgEquality(correctArgs, captor.getValue());
+        verify(mCallAudioModeStateMachine).sendMessageWithArgs(
+                eq(CallAudioModeStateMachine.TONE_STARTED_PLAYING), captor.capture());
+        assertMessageArgEquality(correctArgs, captor.getValue());
+    }
+
+    private void stopTone() {
+        ArgumentCaptor<CallAudioModeStateMachine.MessageArgs> captor =
+                ArgumentCaptor.forClass(CallAudioModeStateMachine.MessageArgs.class);
+        mCallAudioManager.setIsTonePlaying(false);
+        CallAudioModeStateMachine.MessageArgs correctArgs =
+                new CallAudioModeStateMachine.MessageArgs(
+                        false, // hasActiveOrDialingCalls
+                        false, // hasRingingCalls
+                        false, // hasHoldingCalls
+                        false, // isTonePlaying
+                        false, // foregroundCallIsVoip
+                        null // session
+                );
+        verify(mCallAudioModeStateMachine).sendMessageWithArgs(
+                eq(CallAudioModeStateMachine.TONE_STOPPED_PLAYING), captor.capture());
+        assertMessageArgEquality(correctArgs, captor.getValue());
+    }
+
+    private void verifyProperCleanup() {
+        assertEquals(0, mCallAudioManager.getTrackedCalls().size());
+        SparseArray<LinkedHashSet<Call>> callStateToCalls = mCallAudioManager.getCallStateToCalls();
+        for (int i = 0; i < callStateToCalls.size(); i++) {
+            assertEquals(0, callStateToCalls.valueAt(i).size());
+        }
+    }
+
+    private void assertMessageArgEquality(CallAudioModeStateMachine.MessageArgs expected,
+            CallAudioModeStateMachine.MessageArgs actual) {
+        assertEquals(expected.hasActiveOrDialingCalls, actual.hasActiveOrDialingCalls);
+        assertEquals(expected.hasHoldingCalls, actual.hasHoldingCalls);
+        assertEquals(expected.hasRingingCalls, actual.hasRingingCalls);
+        assertEquals(expected.isTonePlaying, actual.isTonePlaying);
+        assertEquals(expected.foregroundCallIsVoip, actual.foregroundCallIsVoip);
+    }
+}
diff --git a/tests/src/com/android/server/telecom/tests/CallScreeningServiceFilterTest.java b/tests/src/com/android/server/telecom/tests/CallScreeningServiceFilterTest.java
new file mode 100644
index 0000000..3d3306d
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/CallScreeningServiceFilterTest.java
@@ -0,0 +1,220 @@
+/*
+ * Copyright (C) 2016 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.server.telecom.tests;
+import android.Manifest;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.telecom.CallScreeningService;
+import android.telecom.ParcelableCall;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.internal.telecom.ICallScreeningAdapter;
+import com.android.internal.telecom.ICallScreeningService;
+import com.android.server.telecom.Call;
+import com.android.server.telecom.CallsManager;
+import com.android.server.telecom.ParcelableCallUtils;
+import com.android.server.telecom.PhoneAccountRegistrar;
+import com.android.server.telecom.TelecomServiceImpl;
+import com.android.server.telecom.callfiltering.CallFilterResultCallback;
+import com.android.server.telecom.callfiltering.CallFilteringResult;
+import com.android.server.telecom.callfiltering.CallScreeningServiceFilter;
+import com.android.server.telecom.TelecomSystem;
+
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+
+import java.util.Collections;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyBoolean;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class CallScreeningServiceFilterTest extends TelecomTestCase {
+    @Mock Context mContext;
+    @Mock CallsManager mCallsManager;
+    @Mock PhoneAccountRegistrar mPhoneAccountRegistrar;
+    @Mock TelecomServiceImpl.DefaultDialerManagerAdapter mDefaultDialerManagerAdapter;
+    @Mock
+    ParcelableCallUtils.Converter mParcelableCallUtilsConverter;
+    private TelecomSystem.SyncRoot mLock = new TelecomSystem.SyncRoot() { };
+
+    @Mock Call mCall;
+    @Mock CallFilterResultCallback mCallback;
+
+    @Mock PackageManager mPackageManager;
+    @Mock IBinder mBinder;
+    @Mock ICallScreeningService mCallScreeningService;
+
+    private static final String PKG_NAME = "com.android.services.telecom.tests";
+    private static final String CLS_NAME = "CallScreeningService";
+    private static final ComponentName COMPONENT_NAME = new ComponentName(PKG_NAME, CLS_NAME);
+    private static final String CALL_ID = "u89prgt9ps78y5";
+
+    private ResolveInfo mResolveInfo;
+
+    private static final CallFilteringResult PASS_RESULT = new CallFilteringResult(
+            true, // shouldAllowCall
+            false, // shouldReject
+            true, // shouldAddToCallLog
+            true // shouldShowNotification
+    );
+
+    private CallScreeningServiceFilter mFilter;
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        when(mCallsManager.getCurrentUserHandle()).thenReturn(UserHandle.CURRENT);
+        when(mContext.getPackageManager()).thenReturn(mPackageManager);
+        when(mCall.getId()).thenReturn(CALL_ID);
+//        when(mBinder.queryLocalInterface(anyString())).thenReturn(mCallScreeningService);
+        doReturn(mCallScreeningService).when(mBinder).queryLocalInterface(anyString());
+
+        mResolveInfo =  new ResolveInfo() {{
+            serviceInfo = new ServiceInfo();
+            serviceInfo.packageName = PKG_NAME;
+            serviceInfo.name = CLS_NAME;
+            serviceInfo.permission = Manifest.permission.BIND_SCREENING_SERVICE;
+        }};
+
+        mFilter = new CallScreeningServiceFilter(mContext, mCallsManager, mPhoneAccountRegistrar,
+                mDefaultDialerManagerAdapter, mParcelableCallUtilsConverter, mLock);
+
+        when(mDefaultDialerManagerAdapter.getDefaultDialerApplication(
+                eq(mContext), eq(UserHandle.USER_CURRENT))).thenReturn(PKG_NAME);
+        when(mPackageManager.queryIntentServicesAsUser(any(Intent.class), anyInt(), anyInt()))
+                .thenReturn(Collections.singletonList(mResolveInfo));
+        when(mParcelableCallUtilsConverter.toParcelableCall(
+                eq(mCall), anyBoolean(), eq(mPhoneAccountRegistrar))).thenReturn(null);
+        when(mContext.bindServiceAsUser(any(Intent.class), any(ServiceConnection.class),
+                anyInt(), eq(UserHandle.CURRENT))).thenReturn(true);
+    }
+
+    @SmallTest
+    public void testNoDefaultDialer() {
+        when(mDefaultDialerManagerAdapter.getDefaultDialerApplication(
+                eq(mContext), eq(UserHandle.USER_CURRENT))).thenReturn(null);
+        mFilter.startFilterLookup(mCall, mCallback);
+        verify(mCallback).onCallFilteringComplete(eq(mCall), eq(PASS_RESULT));
+    }
+
+    @SmallTest
+    public void testNoResolveEntries() {
+        when(mPackageManager.queryIntentServicesAsUser(any(Intent.class), anyInt(), anyInt()))
+                .thenReturn(Collections.emptyList());
+        mFilter.startFilterLookup(mCall, mCallback);
+        verify(mCallback).onCallFilteringComplete(eq(mCall), eq(PASS_RESULT));
+    }
+
+    @SmallTest
+    public void testBadResolveEntry() {
+        mResolveInfo.serviceInfo = null;
+        mFilter.startFilterLookup(mCall, mCallback);
+        verify(mCallback).onCallFilteringComplete(eq(mCall), eq(PASS_RESULT));
+    }
+
+    @SmallTest
+    public void testPermissionlessFilterService() {
+        mResolveInfo.serviceInfo.permission = null;
+        mFilter.startFilterLookup(mCall, mCallback);
+        verify(mCallback).onCallFilteringComplete(eq(mCall), eq(PASS_RESULT));
+    }
+
+    @SmallTest
+    public void testContextFailToBind() {
+        when(mContext.bindServiceAsUser(any(Intent.class), any(ServiceConnection.class),
+                anyInt(), eq(UserHandle.CURRENT))).thenReturn(false);
+        mFilter.startFilterLookup(mCall, mCallback);
+        verify(mCallback).onCallFilteringComplete(eq(mCall), eq(PASS_RESULT));
+    }
+
+    @SmallTest
+    public void testExceptionInScreeningService() throws Exception {
+        doThrow(new RemoteException()).when(mCallScreeningService).screenCall(
+                any(ICallScreeningAdapter.class), any(ParcelableCall.class));
+        mFilter.startFilterLookup(mCall, mCallback);
+        ServiceConnection serviceConnection = verifyBindingIntent();
+        serviceConnection.onServiceConnected(COMPONENT_NAME, mBinder);
+        verify(mCallback).onCallFilteringComplete(eq(mCall), eq(PASS_RESULT));
+    }
+
+    @SmallTest
+    public void testAllowCall() throws Exception {
+        mFilter.startFilterLookup(mCall, mCallback);
+        ServiceConnection serviceConnection = verifyBindingIntent();
+        serviceConnection.onServiceConnected(COMPONENT_NAME, mBinder);
+        ICallScreeningAdapter csAdapter = getCallScreeningAdapter();
+        csAdapter.allowCall(CALL_ID);
+        verify(mCallback).onCallFilteringComplete(eq(mCall), eq(PASS_RESULT));
+    }
+
+    @SmallTest
+    public void testDisallowCall() throws Exception {
+        mFilter.startFilterLookup(mCall, mCallback);
+        ServiceConnection serviceConnection = verifyBindingIntent();
+        serviceConnection.onServiceConnected(COMPONENT_NAME, mBinder);
+        ICallScreeningAdapter csAdapter = getCallScreeningAdapter();
+        csAdapter.disallowCall(CALL_ID,
+                true, // shouldReject
+                false, // shouldAddToCallLog
+                true // shouldShowNotification
+        );
+        verify(mCallback).onCallFilteringComplete(eq(mCall), eq(new CallFilteringResult(
+                false, // shouldAllowCall
+                true, // shouldReject
+                false, // shouldAddToCallLog
+                true // shouldShowNotification
+        )));
+    }
+
+    private ServiceConnection verifyBindingIntent() {
+        ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+        ArgumentCaptor<ServiceConnection> serviceCaptor =
+                ArgumentCaptor.forClass(ServiceConnection.class);
+        verify(mContext).bindServiceAsUser(intentCaptor.capture(), serviceCaptor.capture(),
+                eq(Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE),
+                eq(UserHandle.CURRENT));
+
+        Intent capturedIntent = intentCaptor.getValue();
+        assertEquals(CallScreeningService.SERVICE_INTERFACE, capturedIntent.getAction());
+        assertEquals(PKG_NAME, capturedIntent.getPackage());
+        assertEquals(COMPONENT_NAME, capturedIntent.getComponent());
+
+        return serviceCaptor.getValue();
+    }
+
+    private ICallScreeningAdapter getCallScreeningAdapter() throws Exception {
+        ArgumentCaptor<ICallScreeningAdapter> captor =
+                ArgumentCaptor.forClass(ICallScreeningAdapter.class);
+        verify(mCallScreeningService).screenCall(captor.capture(), any(ParcelableCall.class));
+        return captor.getValue();
+    }
+}
diff --git a/tests/src/com/android/server/telecom/tests/IncomingCallFilterTest.java b/tests/src/com/android/server/telecom/tests/IncomingCallFilterTest.java
index e766786..6861ad5 100644
--- a/tests/src/com/android/server/telecom/tests/IncomingCallFilterTest.java
+++ b/tests/src/com/android/server/telecom/tests/IncomingCallFilterTest.java
@@ -157,6 +157,13 @@
                 any(CallFilteringResult.class));
     }
 
+    @SmallTest
+    public void testToString() {
+        assertEquals("[Allow, logged, notified]", RESULT1.toString());
+        assertEquals("[Reject, notified]", RESULT2.toString());
+        assertEquals("[Reject, logged]", RESULT3.toString());
+    }
+
     private void setTimeoutLength(long length) throws Exception {
         when(mTimeoutsAdapter.getCallScreeningTimeoutMillis(any(ContentResolver.class)))
                 .thenReturn(length);
diff --git a/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java b/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
index 655b68f..cee5882 100644
--- a/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
+++ b/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
@@ -31,9 +31,11 @@
 import static org.mockito.Mockito.timeout;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.IContentProvider;
 import android.content.Intent;
@@ -70,6 +72,7 @@
 import com.android.server.telecom.ProximitySensorManager;
 import com.android.server.telecom.ProximitySensorManagerFactory;
 import com.android.server.telecom.TelecomSystem;
+import com.android.server.telecom.Timeouts;
 import com.android.server.telecom.components.UserCallIntentProcessor;
 import com.android.server.telecom.ui.MissedCallNotifierImpl.MissedCallNotifierImplFactory;
 
@@ -204,6 +207,7 @@
 
     ConnectionServiceFixture mConnectionServiceFixtureA;
     ConnectionServiceFixture mConnectionServiceFixtureB;
+    Timeouts.Adapter mTimeoutsAdapter;
 
     CallerInfoAsyncQueryFactoryFixture mCallerInfoAsyncQueryFactoryFixture;
 
@@ -290,6 +294,10 @@
 
         mCallerInfoAsyncQueryFactoryFixture = new CallerInfoAsyncQueryFactoryFixture();
 
+        mTimeoutsAdapter = mock(Timeouts.Adapter.class);
+        when(mTimeoutsAdapter.getCallScreeningTimeoutMillis(any(ContentResolver.class)))
+                .thenReturn(TEST_TIMEOUT / 10L);
+
         mTelecomSystem = new TelecomSystem(
                 mComponentContextFixture.getTestDouble(),
                 new MissedCallNotifierImplFactory() {
@@ -317,6 +325,7 @@
                         return mBluetoothPhoneServiceImpl;
                     }
                 },
+                mTimeoutsAdapter,
                 mAsyncRingtonePlayer);
 
         mComponentContextFixture.setTelecomManager(new TelecomManager(