Merge "Remove @hide method usage in Telecom."
diff --git a/res/values/strings.xml b/res/values/strings.xml
index e4e588c..92a7808 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -48,6 +48,18 @@
          [CHAR LIMIT=18] -->
     <string name="notification_missedCall_message">Message</string>
 
+    <!-- Title for the persistent notification presented when an app has requested that a call
+         be put into the background so that the app can access the audio from the call
+         [CHAR LIMIT=20] -->
+    <string name="notification_audioProcessing_title">Background call</string>
+    <!-- Body of the persistent notification presented when an app requests
+         that a call be put into the background so that the app can access the audio from the call.
+         [CHAR LIMIT=NONE] -->
+    <string name="notification_audioProcessing_body">
+        <xliff:g id="audio_processing_app_name">%s</xliff:g> has placed a call into the
+        background. This app may be accessing and playing audio over the call.
+    </string>
+
     <!-- Content description of the call muted notification icon for
          accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
     <string name="accessibility_call_muted">Call muted.</string>
@@ -276,6 +288,8 @@
     <string name="notification_channel_missed_call">Missed calls</string>
     <!-- Notification channel name for a channel containing call blocking notifications. -->
     <string name="notification_channel_call_blocking">Call Blocking</string>
+    <!-- Notification channel name for a channel containing background call notifications. -->
+    <string name="notification_channel_background_calls">Background calls</string>
 
     <!-- Alert dialog content used to inform the user that placing a new outgoing call will end the
          ongoing call in the app "other_app". -->
diff --git a/src/com/android/server/telecom/BluetoothPhoneServiceImpl.java b/src/com/android/server/telecom/BluetoothPhoneServiceImpl.java
index 33dddbd..e80eeba 100644
--- a/src/com/android/server/telecom/BluetoothPhoneServiceImpl.java
+++ b/src/com/android/server/telecom/BluetoothPhoneServiceImpl.java
@@ -78,12 +78,15 @@
     // Add all held calls to a conference
     private static final int CHLD_TYPE_ADDHELDTOCONF = 3;
 
+    // Indicates that no call is ringing
+    private static final int DEFAULT_RINGING_ADDRESS_TYPE = 128;
+
     private int mNumActiveCalls = 0;
     private int mNumHeldCalls = 0;
     private int mNumChildrenOfActiveCall = 0;
     private int mBluetoothCallState = CALL_STATE_IDLE;
-    private String mRingingAddress = null;
-    private int mRingingAddressType = 0;
+    private String mRingingAddress = "";
+    private int mRingingAddressType = DEFAULT_RINGING_ADDRESS_TYPE;
     private Call mOldHeldCall = null;
     private boolean mIsDisconnectedTonePlaying = false;
 
@@ -101,7 +104,7 @@
                 long token = Binder.clearCallingIdentity();
                 try {
                     Log.i(TAG, "BT - answering call");
-                    Call call = mCallsManager.getRingingCall();
+                    Call call = mCallsManager.getRingingOrSimulatedRingingCall();
                     if (call != null) {
                         mCallsManager.answerCall(call, VideoProfile.STATE_AUDIO_ONLY);
                         return true;
@@ -493,7 +496,7 @@
 
     private boolean processChld(int chld) {
         Call activeCall = mCallsManager.getActiveCall();
-        Call ringingCall = mCallsManager.getRingingCall();
+        Call ringingCall = mCallsManager.getRingingOrSimulatedRingingCall();
         Call heldCall = mCallsManager.getHeldCall();
 
         // TODO: Keeping as Log.i for now.  Move to Log.d after L release if BT proves stable.
@@ -699,13 +702,13 @@
      */
     private void updateHeadsetWithCallState(boolean force) {
         Call activeCall = mCallsManager.getActiveCall();
-        Call ringingCall = mCallsManager.getRingingCall();
+        Call ringingCall = mCallsManager.getRingingOrSimulatedRingingCall();
         Call heldCall = mCallsManager.getHeldCall();
 
         int bluetoothCallState = getBluetoothCallStateForUpdate();
 
         String ringingAddress = null;
-        int ringingAddressType = 128;
+        int ringingAddressType = DEFAULT_RINGING_ADDRESS_TYPE;
         String ringingName = null;
         if (ringingCall != null && ringingCall.getHandle() != null
             && !ringingCall.isSilentRingingRequested()) {
@@ -832,7 +835,7 @@
     }
 
     private int getBluetoothCallStateForUpdate() {
-        Call ringingCall = mCallsManager.getRingingCall();
+        Call ringingCall = mCallsManager.getRingingOrSimulatedRingingCall();
         Call dialingCall = mCallsManager.getOutgoingCall();
         boolean hasOnlyDisconnectedCalls = mCallsManager.hasOnlyDisconnectedCalls();
 
@@ -862,6 +865,7 @@
             case CallState.NEW:
             case CallState.ABORTED:
             case CallState.DISCONNECTED:
+            case CallState.AUDIO_PROCESSING:
                 return CALL_STATE_IDLE;
 
             case CallState.ACTIVE:
@@ -885,6 +889,7 @@
 
             case CallState.RINGING:
             case CallState.ANSWERED:
+            case CallState.SIMULATED_RINGING:
                 if (call.isSilentRingingRequested()) {
                     return CALL_STATE_IDLE;
                 } else if (isForeground) {
diff --git a/src/com/android/server/telecom/Call.java b/src/com/android/server/telecom/Call.java
index e5b9de9..68e71fb 100644
--- a/src/com/android/server/telecom/Call.java
+++ b/src/com/android/server/telecom/Call.java
@@ -365,6 +365,12 @@
      */
     private DisconnectCause mDisconnectCause = new DisconnectCause(DisconnectCause.UNKNOWN);
 
+    /**
+     * Override the disconnect cause set by the connection service. Used for audio processing and
+     * simulated ringing calls.
+     */
+    private int mOverrideDisconnectCauseCode = DisconnectCause.UNKNOWN;
+
     private Bundle mIntentExtras = new Bundle();
 
     /**
@@ -439,6 +445,7 @@
 
     private boolean mWasConferencePreviouslyMerged = false;
     private boolean mWasHighDefAudio = false;
+    private boolean mWasWifi = false;
 
     // For conferences which support merge/swap at their level, we retain a notion of an active
     // call. This is used for BluetoothPhoneService.  In order to support hold/merge, it must have
@@ -538,6 +545,12 @@
     private Call mHandoverSourceCall = null;
 
     /**
+     * The user-visible app name of the app that requested for this call to be put into the
+     * AUDIO_PROCESSING state. Used to display a notification to the user.
+     */
+    private CharSequence mAudioProcessingRequestingApp = null;
+
+    /**
      * Indicates the current state of this call if it is in the process of a handover.
      */
     private int mHandoverState = HandoverState.HANDOVER_NONE;
@@ -1144,6 +1157,11 @@
     public void setDisconnectCause(DisconnectCause disconnectCause) {
         // TODO: Consider combining this method with a setDisconnected() method that is totally
         // separate from setState.
+        if (mOverrideDisconnectCauseCode != DisconnectCause.UNKNOWN) {
+            disconnectCause = new DisconnectCause(mOverrideDisconnectCauseCode,
+                    disconnectCause.getLabel(), disconnectCause.getDescription(),
+                    disconnectCause.getReason(), disconnectCause.getTone());
+        }
         mAnalytics.setCallDisconnectCause(disconnectCause);
         mDisconnectCause = disconnectCause;
     }
@@ -1598,6 +1616,7 @@
             }
             mWasHighDefAudio = (connectionProperties & Connection.PROPERTY_HIGH_DEF_AUDIO) ==
                     Connection.PROPERTY_HIGH_DEF_AUDIO;
+            mWasWifi = (connectionProperties & Connection.PROPERTY_WIFI) > 0;
             for (Listener l : mListeners) {
                 l.onConnectionPropertiesChanged(this, didRttChange);
             }
@@ -1922,6 +1941,13 @@
             Log.v(this, "Aborting call %s", this);
             abort(disconnectionTimeout);
         } else if (mState != CallState.ABORTED && mState != CallState.DISCONNECTED) {
+            if (mState == CallState.AUDIO_PROCESSING) {
+                mOverrideDisconnectCauseCode = DisconnectCause.REJECTED;
+            } else if (mState == CallState.SIMULATED_RINGING) {
+                // This is the case where the dialer calls disconnect() because the call timed out
+                // Override the disconnect cause to MISSED
+                mOverrideDisconnectCauseCode = DisconnectCause.MISSED;
+            }
             if (mConnectionService == null) {
                 Log.e(this, new Exception(), "disconnect() request on a call without a"
                         + " connection service.");
@@ -2000,6 +2026,38 @@
     }
 
     /**
+     * Answers the call on the connectionservice side in order to start audio processing.
+     *
+     * This pathway keeps the call in the ANSWERED state until the connection service confirms the
+     * answer, at which point we'll set it to AUDIO_PROCESSING. However, to prevent any other
+     * components from seeing the churn between RINGING -> ANSWERED -> AUDIO_PROCESSING, we'll
+     * refrain from tracking this call in CallsManager until we've stabilized in AUDIO_PROCESSING
+     */
+    public void answerForAudioProcessing() {
+        if (mState != CallState.RINGING) {
+            Log.w(this, "Trying to audio-process a non-ringing call: id=%s", mId);
+            return;
+        }
+
+        if (mConnectionService != null) {
+            mConnectionService.answer(this, VideoProfile.STATE_AUDIO_ONLY);
+        } else {
+            Log.e(this, new NullPointerException(),
+                    "answer call (audio processing) failed due to null CS callId=%s", getId());
+        }
+
+        Log.addEvent(this, LogUtils.Events.REQUEST_PICKUP_FOR_AUDIO_PROCESSING);
+    }
+
+    public void setAudioProcessingRequestingApp(CharSequence appName) {
+        mAudioProcessingRequestingApp = appName;
+    }
+
+    public CharSequence getAudioProcessingRequestingApp() {
+        return mAudioProcessingRequestingApp;
+    }
+
+    /**
      * Deflects the call if it is ringing.
      *
      * @param address address to be deflected to.
@@ -2045,9 +2103,19 @@
      */
     @VisibleForTesting
     public void reject(boolean rejectWithMessage, String textMessage, String reason) {
-        // Check to verify that the call is still in the ringing state. A call can change states
-        // between the time the user hits 'reject' and Telecomm receives the command.
-        if (isRinging("reject")) {
+        if (mState == CallState.SIMULATED_RINGING) {
+            // This handles the case where the user manually rejects a call that's in simulated
+            // ringing. Since the call is already active on the connectionservice side, we want to
+            // hangup, not reject.
+            mOverrideDisconnectCauseCode = DisconnectCause.REJECTED;
+            if (mConnectionService != null) {
+                mConnectionService.disconnect(this);
+            } else {
+                Log.e(this, new NullPointerException(),
+                        "reject call failed due to null CS callId=%s", getId());
+            }
+            Log.addEvent(this, LogUtils.Events.REQUEST_REJECT, reason);
+        } else if (isRinging("reject")) {
             // Ensure video state history tracks video state at time of rejection.
             mVideoStateHistory |= mVideoState;
 
@@ -3232,6 +3300,15 @@
         return mWasHighDefAudio;
     }
 
+    /**
+     * Returns wether or not Wifi call was used.
+     *
+     * @return true if wifi call was used during this call.
+     */
+    boolean wasWifi() {
+        return mWasWifi;
+    }
+
     public void setIsUsingCallFiltering(boolean isUsingCallFiltering) {
         mIsUsingCallFiltering = isUsingCallFiltering;
     }
diff --git a/src/com/android/server/telecom/CallAudioManager.java b/src/com/android/server/telecom/CallAudioManager.java
index c01c9aa..1e0af5d 100644
--- a/src/com/android/server/telecom/CallAudioManager.java
+++ b/src/com/android/server/telecom/CallAudioManager.java
@@ -115,6 +115,7 @@
         if (newBinForCall != null) {
             newBinForCall.add(call);
         }
+        sendCallStatusToBluetoothStateReceiver();
 
         updateForegroundCall();
         if (shouldPlayDisconnectTone(oldState, newState)) {
@@ -158,9 +159,7 @@
         }
         updateForegroundCall();
         mCalls.add(call);
-        if (mCalls.size() == 1) {
-            mBluetoothStateReceiver.setIsInCall(true);
-        }
+        sendCallStatusToBluetoothStateReceiver();
 
         onCallEnteringState(call, call.getState());
     }
@@ -177,13 +176,17 @@
 
         updateForegroundCall();
         mCalls.remove(call);
-        if (mCalls.size() == 0) {
-            mBluetoothStateReceiver.setIsInCall(false);
-        }
+        sendCallStatusToBluetoothStateReceiver();
 
         onCallLeavingState(call, call.getState());
     }
 
+    private void sendCallStatusToBluetoothStateReceiver() {
+        // We're in a call if there are calls in mCalls that are not in mAudioProcessingCalls.
+        boolean isInCall = !mAudioProcessingCalls.containsAll(mCalls);
+        mBluetoothStateReceiver.setIsInCall(isInCall);
+    }
+
     /**
      * Handles changes to the external state of a call.  External calls which become regular calls
      * should be tracked, and regular calls which become external should no longer be tracked.
diff --git a/src/com/android/server/telecom/CallAudioModeStateMachine.java b/src/com/android/server/telecom/CallAudioModeStateMachine.java
index 06be216..1d3752b 100644
--- a/src/com/android/server/telecom/CallAudioModeStateMachine.java
+++ b/src/com/android/server/telecom/CallAudioModeStateMachine.java
@@ -329,8 +329,7 @@
                     transitionTo(mOtherFocusState);
                     return HANDLED;
                 case NEW_AUDIO_PROCESSING_CALL:
-                    Log.w(LOG_TAG, "Unexpected behavior! New audio processing call appeared while"
-                            + " in audio processing state.");
+                    // Can happen as a duplicate message
                     return HANDLED;
                 case TONE_STARTED_PLAYING:
                     // This shouldn't happen either, but perform the action anyway.
@@ -404,8 +403,7 @@
                                 + "ringing");
                     }
                 case NEW_RINGING_CALL:
-                    Log.w(LOG_TAG, "Unexpected behavior! New ringing call appeared while in " +
-                            "ringing state.");
+                    // Can happen as a duplicate message
                     return HANDLED;
                 case NEW_HOLDING_CALL:
                     // This really shouldn't happen, but transition to the focused state anyway.
diff --git a/src/com/android/server/telecom/CallLogManager.java b/src/com/android/server/telecom/CallLogManager.java
index 838d1ab..719277e 100755
--- a/src/com/android/server/telecom/CallLogManager.java
+++ b/src/com/android/server/telecom/CallLogManager.java
@@ -214,6 +214,14 @@
             return false;
         }
 
+        // A conference call which had no children should not be logged; this case will occur on IMS
+        // when no conference event package data is received.  We will have logged the participants
+        // as they merge into the conference, so we should not log the conference itself.
+        if (call.isConference() && !call.hadChildren() &&
+                !call.hasProperty(Connection.PROPERTY_REMOTELY_HOSTED)) {
+            return false;
+        }
+
         // A child call of a conference which was remotely hosted; these didn't originate on this
         // device and should not be logged.
         if (call.getParentCall() != null && call.hasProperty(Connection.PROPERTY_REMOTELY_HOSTED)) {
@@ -325,7 +333,7 @@
 
         int callFeatures = getCallFeatures(call.getVideoStateHistory(),
                 call.getDisconnectCause().getCode() == DisconnectCause.CALL_PULLED,
-                shouldSaveHdInfo(call, accountHandle),
+                call.wasHighDefAudio(), call.wasWifi(),
                 (call.getConnectionProperties() & Connection.PROPERTY_ASSISTED_DIALING_USED) ==
                         Connection.PROPERTY_ASSISTED_DIALING_USED,
                 call.wasEverRttCall());
@@ -448,11 +456,12 @@
      * @param videoState The video state.
      * @param isPulledCall {@code true} if this call was pulled to another device.
      * @param isStoreHd {@code true} if this call was used HD.
+     * @param isWifi {@code true} if this call was used wifi.
      * @param isUsingAssistedDialing {@code true} if this call used assisted dialing.
      * @return The call features.
      */
     private static int getCallFeatures(int videoState, boolean isPulledCall, boolean isStoreHd,
-            boolean isUsingAssistedDialing, boolean isRtt) {
+            boolean isWifi, boolean isUsingAssistedDialing, boolean isRtt) {
         int features = 0;
         if (VideoProfile.isVideo(videoState)) {
             features |= Calls.FEATURES_VIDEO;
@@ -463,6 +472,9 @@
         if (isStoreHd) {
             features |= Calls.FEATURES_HD_CALL;
         }
+        if (isWifi) {
+            features |= Calls.FEATURES_WIFI;
+        }
         if (isUsingAssistedDialing) {
             features |= Calls.FEATURES_ASSISTED_DIALING_USED;
         }
@@ -472,22 +484,6 @@
         return features;
     }
 
-    private boolean shouldSaveHdInfo(Call call, PhoneAccountHandle accountHandle) {
-        CarrierConfigManager configManager = (CarrierConfigManager) mContext.getSystemService(
-                Context.CARRIER_CONFIG_SERVICE);
-        PersistableBundle configBundle = null;
-        if (configManager != null) {
-            configBundle = configManager.getConfigForSubId(
-                    mPhoneAccountRegistrar.getSubscriptionIdForPhoneAccount(accountHandle));
-        }
-        if (configBundle != null && configBundle.getBoolean(
-                CarrierConfigManager.KEY_IDENTIFY_HIGH_DEFINITION_CALLS_IN_CALL_LOG_BOOL)
-                && call.wasHighDefAudio()) {
-            return true;
-        }
-        return false;
-    }
-
     /**
      * Retrieve the phone number from the call, and then process it before returning the
      * actual number that is to be logged.
diff --git a/src/com/android/server/telecom/CallsManager.java b/src/com/android/server/telecom/CallsManager.java
index bc09c3a..824ae9f 100644
--- a/src/com/android/server/telecom/CallsManager.java
+++ b/src/com/android/server/telecom/CallsManager.java
@@ -79,12 +79,14 @@
 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.CallFilteringResult.Builder;
 import com.android.server.telecom.callfiltering.CallScreeningServiceController;
 import com.android.server.telecom.callfiltering.DirectToVoicemailCallFilter;
 import com.android.server.telecom.callfiltering.IncomingCallFilter;
 import com.android.server.telecom.callredirection.CallRedirectionProcessor;
 import com.android.server.telecom.components.ErrorDialogActivity;
 import com.android.server.telecom.settings.BlockedNumbersUtil;
+import com.android.server.telecom.ui.AudioProcessingNotification;
 import com.android.server.telecom.ui.CallRedirectionConfirmDialogActivity;
 import com.android.server.telecom.ui.CallRedirectionTimeoutDialogActivity;
 import com.android.server.telecom.ui.ConfirmCallDialogActivity;
@@ -251,6 +253,13 @@
      * Used by {@link #onCallRedirectionComplete}.
      */
     private Call mPendingRedirectedOutgoingCall;
+
+    /**
+     * Cached call that's been answered but will be added to mCalls pending confirmation of active
+     * status from the connection service.
+     */
+    private Call mPendingAudioProcessingCall;
+
     /**
      * Cached latest pending redirected call information which require user-intervention in order
      * to be placed. Used by {@link #onCallRedirectionComplete}.
@@ -432,6 +441,7 @@
             EmergencyCallHelper emergencyCallHelper,
             InCallTonePlayer.ToneGeneratorFactory toneGeneratorFactory,
             ClockProxy clockProxy,
+            AudioProcessingNotification audioProcessingNotification,
             BluetoothStateReceiver bluetoothStateReceiver,
             CallAudioRouteStateMachine.Factory callAudioRouteStateMachineFactory,
             CallAudioModeStateMachine.Factory callAudioModeStateMachineFactory,
@@ -524,6 +534,7 @@
         mListeners.add(missedCallNotifier);
         mListeners.add(mHeadsetMediaButton);
         mListeners.add(mProximitySensorManager);
+        mListeners.add(audioProcessingNotification);
 
         // There is no USER_SWITCHED broadcast for user 0, handle it here explicitly.
         final UserManager userManager = UserManager.get(mContext);
@@ -609,7 +620,12 @@
                     incomingCall.hasProperty(Connection.PROPERTY_EMERGENCY_CALLBACK_MODE),
                     incomingCall.isSelfManaged(),
                     extras.getBoolean(PhoneAccount.EXTRA_SKIP_CALL_FILTERING));
-            onCallFilteringComplete(incomingCall, new CallFilteringResult(true, false, true, true));
+            onCallFilteringComplete(incomingCall, new Builder()
+                    .setShouldAllowCall(true)
+                    .setShouldReject(false)
+                    .setShouldAddToCallLog(true)
+                    .setShouldShowNotification(true)
+                    .build());
             incomingCall.setIsUsingCallFiltering(false);
             return;
         }
@@ -676,6 +692,10 @@
                 Log.i(this, "onCallFilteringCompleted: setting the call to silent ringing state");
                 incomingCall.setSilentRingingRequested(true);
                 addCall(incomingCall);
+            } else if (result.shouldScreenViaAudio) {
+                Log.i(this, "onCallFilteringCompleted: starting background audio processing");
+                answerCallForAudioProcessing(incomingCall);
+                incomingCall.setAudioProcessingRequestingApp(result.mCallScreeningAppName);
             } else {
                 addCall(incomingCall);
             }
@@ -969,6 +989,10 @@
         return mEmergencyCallHelper;
     }
 
+    public DefaultDialerCache getDefaultDialerCache() {
+        return mDefaultDialerCache;
+    }
+
     @VisibleForTesting
     public PhoneAccountRegistrar.Listener getPhoneAccountListener() {
         return mPhoneAccountListener;
@@ -1971,6 +1995,107 @@
         }
     }
 
+    private void answerCallForAudioProcessing(Call call) {
+        // We don't check whether the call has been added to the internal lists yet -- it's optional
+        // until the call is actually in the AUDIO_PROCESSING state.
+        Call activeCall = (Call) mConnectionSvrFocusMgr.getCurrentFocusCall();
+        if (activeCall != null && activeCall != call) {
+            Log.w(this, "answerCallForAudioProcessing: another active call already exists. "
+                    + "Ignoring request for audio processing and letting the incoming call "
+                    + "through.");
+            // The call should already be in the RINGING state, so all we have to do is add the
+            // call to the internal tracker.
+            addCall(call);
+            return;
+        }
+        Log.d(this, "answerCallForAudioProcessing: Incoming call = %s", call);
+        mConnectionSvrFocusMgr.requestFocus(
+                call,
+                new RequestCallback(() -> {
+                    synchronized (mLock) {
+                        Log.d(this, "answering call %s for audio processing with cs focus", call);
+                        call.answerForAudioProcessing();
+                        // Skip setting the call state to ANSWERED -- that's only for calls that
+                        // were answered by user intervention.
+                        mPendingAudioProcessingCall = call;
+                    }
+                }));
+
+    }
+
+    /**
+     * Instructs Telecom to bring a call into the AUDIO_PROCESSING state.
+     *
+     * Used by the background audio call screener (also the default dialer) to signal that
+     * they want to manually enter the AUDIO_PROCESSING state. The user will be aware that there is
+     * an ongoing call at this time.
+     *
+     * @param call The call to manipulate
+     */
+    public void enterBackgroundAudioProcessing(Call call, String requestingPackageName) {
+        if (!mCalls.contains(call)) {
+            Log.w(this, "Trying to exit audio processing on an untracked call");
+            return;
+        }
+
+        Call activeCall = getActiveCall();
+        if (activeCall != call) {
+            Log.w(this, "Ignoring enter audio processing because there's already a call active");
+            return;
+        }
+
+        CharSequence requestingAppName;
+
+        PackageManager pm = mContext.getPackageManager();
+        try {
+            ApplicationInfo info = pm.getApplicationInfo( requestingPackageName, 0);
+            requestingAppName = pm.getApplicationLabel(info);
+        } catch (PackageManager.NameNotFoundException nnfe) {
+            Log.w(this, "Could not determine package name.");
+            requestingAppName = requestingPackageName;
+        }
+
+        // We only want this to work on active or ringing calls
+        if (call.getState() == CallState.RINGING) {
+            // After the connection service sets up the call with the other end, it'll set the call
+            // state to AUDIO_PROCESSING
+            answerCallForAudioProcessing(call);
+            call.setAudioProcessingRequestingApp(requestingAppName);
+        } else if (call.getState() == CallState.ACTIVE) {
+            setCallState(call, CallState.AUDIO_PROCESSING,
+                    "audio processing set by dialer request");
+            call.setAudioProcessingRequestingApp(requestingAppName);
+        }
+    }
+
+    /**
+     * Instructs Telecom to bring a call out of the AUDIO_PROCESSING state.
+     *
+     * Used by the background audio call screener (also the default dialer) to signal that it's
+     * finished doing its thing and the user should be made aware of the call.
+     *
+     * @param call The call to manipulate
+     * @param shouldRing if true, puts the call into SIMULATED_RINGING. Otherwise, makes the call
+     *                   active.
+     */
+    public void exitBackgroundAudioProcessing(Call call, boolean shouldRing) {
+        if (!mCalls.contains(call)) {
+            Log.w(this, "Trying to exit audio processing on an untracked call");
+            return;
+        }
+
+        Call activeCall = getActiveCall();
+        if (activeCall != null) {
+            Log.w(this, "Ignoring exit audio processing because there's already a call active");
+        }
+
+        if (shouldRing) {
+            setCallState(call, CallState.SIMULATED_RINGING, "exitBackgroundAudioProcessing");
+        } else {
+            setCallState(call, CallState.ACTIVE, "exitBackgroundAudioProcessing");
+        }
+    }
+
     /**
      * Instructs Telecom to deflect the specified call. Intended to be invoked by the in-call
      * app through {@link InCallAdapter} after Telecom notifies it of an incoming call followed by
@@ -2476,6 +2601,15 @@
                             CallState.ACTIVE,
                             "active set explicitly for self-managed")));
         } else {
+            if (mPendingAudioProcessingCall == call) {
+                if (mCalls.contains(call)) {
+                    setCallState(call, CallState.AUDIO_PROCESSING, "active set explicitly");
+                } else {
+                    call.setState(CallState.AUDIO_PROCESSING, "active set explicitly and adding");
+                    addCall(call);
+                }
+                return;
+            }
             setCallState(call, CallState.ACTIVE, "active set explicitly");
             maybeMoveToSpeakerPhone(call);
             ensureCallAudible();
@@ -2606,6 +2740,11 @@
         return getFirstCallWithState(CallState.RINGING, CallState.ANSWERED) != null;
     }
 
+    boolean hasRingingOrSimulatedRingingCall() {
+        return getFirstCallWithState(
+                CallState.SIMULATED_RINGING, CallState.RINGING, CallState.ANSWERED) != null;
+    }
+
     @VisibleForTesting
     public boolean onMediaButton(int type) {
         if (hasAnyCalls()) {
@@ -2708,8 +2847,9 @@
     }
 
     @VisibleForTesting
-    public Call getRingingCall() {
-        return getFirstCallWithState(CallState.RINGING, CallState.ANSWERED);
+    public Call getRingingOrSimulatedRingingCall() {
+        return getFirstCallWithState(CallState.RINGING,
+                CallState.ANSWERED, CallState.SIMULATED_RINGING);
     }
 
     public Call getActiveCall() {
@@ -4547,9 +4687,14 @@
 
                 // We do not update the UI until we get confirmation of the answer() through
                 // {@link #markCallAsActive}.
-                mCall.answer(mVideoState);
                 if (mCall.getState() == CallState.RINGING) {
+                    mCall.answer(mVideoState);
                     setCallState(mCall, CallState.ANSWERED, "answered");
+                } else if (mCall.getState() == CallState.SIMULATED_RINGING) {
+                    // If the call's in simulated ringing, we don't have to wait for the CS --
+                    // we can just declare it active.
+                    setCallState(mCall, CallState.ACTIVE, "answering simulated ringing");
+                    Log.addEvent(mCall, LogUtils.Events.REQUEST_SIMULATED_ACCEPT);
                 }
                 if (isSpeakerphoneAutoEnabledForVideoCalls(mVideoState)) {
                     mCall.setStartWithSpeakerphoneOn(true);
diff --git a/src/com/android/server/telecom/ConnectionServiceFocusManager.java b/src/com/android/server/telecom/ConnectionServiceFocusManager.java
index 9c0bfa2..fbb23f4 100644
--- a/src/com/android/server/telecom/ConnectionServiceFocusManager.java
+++ b/src/com/android/server/telecom/ConnectionServiceFocusManager.java
@@ -154,7 +154,7 @@
     }
 
     private static final int[] PRIORITY_FOCUS_CALL_STATE = new int[] {
-            CallState.ACTIVE, CallState.CONNECTING, CallState.DIALING
+            CallState.ACTIVE, CallState.CONNECTING, CallState.DIALING, CallState.AUDIO_PROCESSING
     };
 
     private static final int MSG_REQUEST_FOCUS = 1;
diff --git a/src/com/android/server/telecom/InCallAdapter.java b/src/com/android/server/telecom/InCallAdapter.java
index 888010d..0c4c1db 100644
--- a/src/com/android/server/telecom/InCallAdapter.java
+++ b/src/com/android/server/telecom/InCallAdapter.java
@@ -19,7 +19,6 @@
 import android.net.Uri;
 import android.os.Binder;
 import android.os.Bundle;
-import android.os.RemoteException;
 import android.telecom.Log;
 import android.telecom.PhoneAccountHandle;
 
@@ -36,21 +35,21 @@
     private final CallsManager mCallsManager;
     private final CallIdMapper mCallIdMapper;
     private final TelecomSystem.SyncRoot mLock;
-    private final String mOwnerComponentName;
+    private final String mOwnerPackageName;
 
     /** Persists the specified parameters. */
     public InCallAdapter(CallsManager callsManager, CallIdMapper callIdMapper,
-            TelecomSystem.SyncRoot lock, String ownerComponentName) {
+            TelecomSystem.SyncRoot lock, String ownerPackageName) {
         mCallsManager = callsManager;
         mCallIdMapper = callIdMapper;
         mLock = lock;
-        mOwnerComponentName = ownerComponentName;
+        mOwnerPackageName = ownerPackageName;
     }
 
     @Override
     public void answerCall(String callId, int videoState) {
         try {
-            Log.startSession(LogUtils.Sessions.ICA_ANSWER_CALL, mOwnerComponentName);
+            Log.startSession(LogUtils.Sessions.ICA_ANSWER_CALL, mOwnerPackageName);
             long token = Binder.clearCallingIdentity();
             try {
                 synchronized (mLock) {
@@ -73,7 +72,7 @@
     @Override
     public void deflectCall(String callId, Uri address) {
         try {
-            Log.startSession(LogUtils.Sessions.ICA_DEFLECT_CALL, mOwnerComponentName);
+            Log.startSession(LogUtils.Sessions.ICA_DEFLECT_CALL, mOwnerPackageName);
             long token = Binder.clearCallingIdentity();
             try {
                 synchronized (mLock) {
@@ -96,7 +95,7 @@
     @Override
     public void rejectCall(String callId, boolean rejectWithMessage, String textMessage) {
         try {
-            Log.startSession(LogUtils.Sessions.ICA_REJECT_CALL, mOwnerComponentName);
+            Log.startSession(LogUtils.Sessions.ICA_REJECT_CALL, mOwnerPackageName);
 
             int callingUid = Binder.getCallingUid();
             long token = Binder.clearCallingIdentity();
@@ -129,7 +128,7 @@
     @Override
     public void playDtmfTone(String callId, char digit) {
         try {
-            Log.startSession("ICA.pDT", mOwnerComponentName);
+            Log.startSession("ICA.pDT", mOwnerPackageName);
             long token = Binder.clearCallingIdentity();
             try {
                 synchronized (mLock) {
@@ -152,7 +151,7 @@
     @Override
     public void stopDtmfTone(String callId) {
         try {
-            Log.startSession("ICA.sDT", mOwnerComponentName);
+            Log.startSession("ICA.sDT", mOwnerPackageName);
             long token = Binder.clearCallingIdentity();
             try {
                 synchronized (mLock) {
@@ -175,7 +174,7 @@
     @Override
     public void postDialContinue(String callId, boolean proceed) {
         try {
-            Log.startSession("ICA.pDC", mOwnerComponentName);
+            Log.startSession("ICA.pDC", mOwnerPackageName);
             long token = Binder.clearCallingIdentity();
             try {
                 synchronized (mLock) {
@@ -198,7 +197,7 @@
     @Override
     public void disconnectCall(String callId) {
         try {
-            Log.startSession(LogUtils.Sessions.ICA_DISCONNECT_CALL, mOwnerComponentName);
+            Log.startSession(LogUtils.Sessions.ICA_DISCONNECT_CALL, mOwnerPackageName);
             long token = Binder.clearCallingIdentity();
             try {
                 synchronized (mLock) {
@@ -221,7 +220,7 @@
     @Override
     public void holdCall(String callId) {
         try {
-            Log.startSession(LogUtils.Sessions.ICA_HOLD_CALL, mOwnerComponentName);
+            Log.startSession(LogUtils.Sessions.ICA_HOLD_CALL, mOwnerPackageName);
             long token = Binder.clearCallingIdentity();
             try {
                 synchronized (mLock) {
@@ -243,7 +242,7 @@
     @Override
     public void unholdCall(String callId) {
         try {
-            Log.startSession(LogUtils.Sessions.ICA_UNHOLD_CALL, mOwnerComponentName);
+            Log.startSession(LogUtils.Sessions.ICA_UNHOLD_CALL, mOwnerPackageName);
             long token = Binder.clearCallingIdentity();
             try {
                 synchronized (mLock) {
@@ -266,7 +265,7 @@
     public void phoneAccountSelected(String callId, PhoneAccountHandle accountHandle,
             boolean setDefault) {
         try {
-            Log.startSession("ICA.pAS", mOwnerComponentName);
+            Log.startSession("ICA.pAS", mOwnerPackageName);
             long token = Binder.clearCallingIdentity();
             try {
                 synchronized (mLock) {
@@ -288,7 +287,7 @@
     @Override
     public void mute(boolean shouldMute) {
         try {
-            Log.startSession(LogUtils.Sessions.ICA_MUTE, mOwnerComponentName);
+            Log.startSession(LogUtils.Sessions.ICA_MUTE, mOwnerPackageName);
             long token = Binder.clearCallingIdentity();
             try {
                 synchronized (mLock) {
@@ -305,7 +304,7 @@
     @Override
     public void setAudioRoute(int route, String bluetoothAddress) {
         try {
-            Log.startSession(LogUtils.Sessions.ICA_SET_AUDIO_ROUTE, mOwnerComponentName);
+            Log.startSession(LogUtils.Sessions.ICA_SET_AUDIO_ROUTE, mOwnerPackageName);
             long token = Binder.clearCallingIdentity();
             try {
                 synchronized (mLock) {
@@ -321,18 +320,48 @@
 
     @Override
     public void enterBackgroundAudioProcessing(String callId) {
-        // TODO: implement this
+        try {
+            Log.startSession(LogUtils.Sessions.ICA_ENTER_AUDIO_PROCESSING, mOwnerPackageName);
+            // TODO: enforce the extra permission.
+            Binder.withCleanCallingIdentity(() -> {
+                synchronized (mLock) {
+                    Call call = mCallIdMapper.getCall(callId);
+                    if (call != null) {
+                        mCallsManager.enterBackgroundAudioProcessing(call, mOwnerPackageName);
+                    } else {
+                        Log.w(this, "enterBackgroundAudioProcessing, unknown call id: %s", callId);
+                    }
+                }
+            });
+        } finally {
+            Log.endSession();
+        }
     }
 
     @Override
     public void exitBackgroundAudioProcessing(String callId, boolean shouldRing) {
-        // TODO: implement this
+        try {
+            Log.startSession(LogUtils.Sessions.ICA_EXIT_AUDIO_PROCESSING, mOwnerPackageName);
+            Binder.withCleanCallingIdentity(() -> {
+                synchronized (mLock) {
+                    Call call = mCallIdMapper.getCall(callId);
+                    if (call != null) {
+                        mCallsManager.exitBackgroundAudioProcessing(call, shouldRing);
+                    } else {
+                        Log.w(InCallAdapter.this,
+                                "exitBackgroundAudioProcessing, unknown call id: %s", callId);
+                    }
+                }
+            });
+        } finally {
+            Log.endSession();
+        }
     }
 
     @Override
     public void conference(String callId, String otherCallId) {
         try {
-            Log.startSession(LogUtils.Sessions.ICA_CONFERENCE, mOwnerComponentName);
+            Log.startSession(LogUtils.Sessions.ICA_CONFERENCE, mOwnerPackageName);
             long token = Binder.clearCallingIdentity();
             try {
                 synchronized (mLock) {
@@ -355,7 +384,7 @@
     @Override
     public void splitFromConference(String callId) {
         try {
-            Log.startSession("ICA.sFC", mOwnerComponentName);
+            Log.startSession("ICA.sFC", mOwnerPackageName);
             long token = Binder.clearCallingIdentity();
             try {
                 synchronized (mLock) {
@@ -377,7 +406,7 @@
     @Override
     public void mergeConference(String callId) {
         try {
-            Log.startSession("ICA.mC", mOwnerComponentName);
+            Log.startSession("ICA.mC", mOwnerPackageName);
             long token = Binder.clearCallingIdentity();
             try {
                 synchronized (mLock) {
@@ -399,7 +428,7 @@
     @Override
     public void swapConference(String callId) {
         try {
-            Log.startSession("ICA.sC", mOwnerComponentName);
+            Log.startSession("ICA.sC", mOwnerPackageName);
             long token = Binder.clearCallingIdentity();
             try {
                 synchronized (mLock) {
@@ -421,7 +450,7 @@
     @Override
     public void pullExternalCall(String callId) {
         try {
-            Log.startSession("ICA.pEC", mOwnerComponentName);
+            Log.startSession("ICA.pEC", mOwnerPackageName);
             long token = Binder.clearCallingIdentity();
             try {
                 synchronized (mLock) {
@@ -443,7 +472,7 @@
     @Override
     public void sendCallEvent(String callId, String event, int targetSdkVer, Bundle extras) {
         try {
-            Log.startSession("ICA.sCE", mOwnerComponentName);
+            Log.startSession("ICA.sCE", mOwnerPackageName);
             long token = Binder.clearCallingIdentity();
             try {
                 synchronized (mLock) {
@@ -465,7 +494,7 @@
     @Override
     public void putExtras(String callId, Bundle extras) {
         try {
-            Log.startSession("ICA.pE", mOwnerComponentName);
+            Log.startSession("ICA.pE", mOwnerPackageName);
             long token = Binder.clearCallingIdentity();
             try {
                 synchronized (mLock) {
@@ -487,7 +516,7 @@
     @Override
     public void removeExtras(String callId, List<String> keys) {
         try {
-            Log.startSession("ICA.rE", mOwnerComponentName);
+            Log.startSession("ICA.rE", mOwnerPackageName);
             long token = Binder.clearCallingIdentity();
             try {
                 synchronized (mLock) {
@@ -509,7 +538,7 @@
     @Override
     public void turnOnProximitySensor() {
         try {
-            Log.startSession("ICA.tOnPS", mOwnerComponentName);
+            Log.startSession("ICA.tOnPS", mOwnerPackageName);
             long token = Binder.clearCallingIdentity();
             try {
                 synchronized (mLock) {
@@ -526,7 +555,7 @@
     @Override
     public void turnOffProximitySensor(boolean screenOnImmediately) {
         try {
-            Log.startSession("ICA.tOffPS", mOwnerComponentName);
+            Log.startSession("ICA.tOffPS", mOwnerPackageName);
             long token = Binder.clearCallingIdentity();
             try {
                 synchronized (mLock) {
@@ -627,7 +656,7 @@
     public void handoverTo(String callId, PhoneAccountHandle destAcct, int videoState,
                            Bundle extras) {
         try {
-            Log.startSession("ICA.hT", mOwnerComponentName);
+            Log.startSession("ICA.hT", mOwnerPackageName);
             long token = Binder.clearCallingIdentity();
             try {
                 synchronized (mLock) {
diff --git a/src/com/android/server/telecom/InCallWakeLockController.java b/src/com/android/server/telecom/InCallWakeLockController.java
index ac93bb5..b37883d 100644
--- a/src/com/android/server/telecom/InCallWakeLockController.java
+++ b/src/com/android/server/telecom/InCallWakeLockController.java
@@ -62,7 +62,7 @@
 
     private void handleWakeLock() {
         // We grab a full lock as long as there exists a ringing call.
-        Call ringingCall = mCallsManager.getRingingCall();
+        Call ringingCall = mCallsManager.getRingingOrSimulatedRingingCall();
         if (ringingCall != null) {
             mTelecomWakeLock.acquire();
             Log.i(this, "Acquiring full wake lock");
diff --git a/src/com/android/server/telecom/LogUtils.java b/src/com/android/server/telecom/LogUtils.java
index 1e82fc0..9b089a2 100644
--- a/src/com/android/server/telecom/LogUtils.java
+++ b/src/com/android/server/telecom/LogUtils.java
@@ -68,6 +68,8 @@
         public static final String ICA_UNHOLD_CALL = "ICA.uC";
         public static final String ICA_MUTE = "ICA.m";
         public static final String ICA_SET_AUDIO_ROUTE = "ICA.sAR";
+        public static final String ICA_ENTER_AUDIO_PROCESSING = "ICA.enBAP";
+        public static final String ICA_EXIT_AUDIO_PROCESSING = "ICA.exBAP";
         public static final String ICA_CONFERENCE = "ICA.c";
         public static final String CSW_HANDLE_CREATE_CONNECTION_COMPLETE = "CSW.hCCC";
         public static final String CSW_SET_ACTIVE = "CSW.sA";
@@ -101,6 +103,9 @@
         public static final String REQUEST_UNHOLD = "REQUEST_UNHOLD";
         public static final String REQUEST_DISCONNECT = "REQUEST_DISCONNECT";
         public static final String REQUEST_ACCEPT = "REQUEST_ACCEPT";
+        public static final String REQUEST_SIMULATED_ACCEPT = "REQUEST_SIMULATED_ACCEPT";
+        public static final String REQUEST_PICKUP_FOR_AUDIO_PROCESSING =
+                "REQUEST_PICKUP_FOR_AUDIO_PROCESSING";
         public static final String REQUEST_DEFLECT = "REQUEST_DEFLECT";
         public static final String REQUEST_REJECT = "REQUEST_REJECT";
         public static final String START_DTMF = "START_DTMF";
@@ -137,6 +142,7 @@
         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_SKIPPED = "SCREENING_SKIPPED";
         public static final String CONTROLLER_SCREENING_COMPLETED =
                 "CONTROLLER_SCREENING_COMPLETED";
         public static final String SCREENING_COMPLETED = "SCREENING_COMPLETED";
diff --git a/src/com/android/server/telecom/ParcelableCallUtils.java b/src/com/android/server/telecom/ParcelableCallUtils.java
index 6a66ccf..be44131 100644
--- a/src/com/android/server/telecom/ParcelableCallUtils.java
+++ b/src/com/android/server/telecom/ParcelableCallUtils.java
@@ -421,6 +421,12 @@
             case CallState.SELECT_PHONE_ACCOUNT:
                 state = android.telecom.Call.STATE_SELECT_PHONE_ACCOUNT;
                 break;
+            case CallState.AUDIO_PROCESSING:
+                state = android.telecom.Call.STATE_AUDIO_PROCESSING;
+                break;
+            case CallState.SIMULATED_RINGING:
+                state = android.telecom.Call.STATE_SIMULATED_RINGING;
+                break;
         }
 
         // If we are marked as 'locally disconnecting' then mark ourselves as disconnecting instead.
diff --git a/src/com/android/server/telecom/PhoneStateBroadcaster.java b/src/com/android/server/telecom/PhoneStateBroadcaster.java
index d574589..a5e3848 100644
--- a/src/com/android/server/telecom/PhoneStateBroadcaster.java
+++ b/src/com/android/server/telecom/PhoneStateBroadcaster.java
@@ -85,7 +85,7 @@
         // Note: CallsManager#hasRingingCall() and CallsManager#getFirstCallWithState(..) do not
         // consider external calls, so an external call is going to cause the state to be idle.
         int callState = TelephonyManager.CALL_STATE_IDLE;
-        if (mCallsManager.hasRingingCall()) {
+        if (mCallsManager.hasRingingOrSimulatedRingingCall()) {
             callState = TelephonyManager.CALL_STATE_RINGING;
         } else if (mCallsManager.getFirstCallWithState(CallState.DIALING, CallState.PULLING,
                 CallState.ACTIVE, CallState.ON_HOLD) != null) {
diff --git a/src/com/android/server/telecom/Ringer.java b/src/com/android/server/telecom/Ringer.java
index 2469c1e..11271b8 100644
--- a/src/com/android/server/telecom/Ringer.java
+++ b/src/com/android/server/telecom/Ringer.java
@@ -211,7 +211,8 @@
             return false;
         }
 
-        if (foregroundCall.getState() != CallState.RINGING) {
+        if (foregroundCall.getState() != CallState.RINGING
+                && foregroundCall.getState() != CallState.SIMULATED_RINGING) {
             // Its possible for bluetooth to connect JUST as a call goes active, which would mean
             // the call would start ringing again.
             Log.i(this, "startRinging called for non-ringing foreground callid=%s",
diff --git a/src/com/android/server/telecom/TelecomServiceImpl.java b/src/com/android/server/telecom/TelecomServiceImpl.java
index f82215f..f04c825 100644
--- a/src/com/android/server/telecom/TelecomServiceImpl.java
+++ b/src/com/android/server/telecom/TelecomServiceImpl.java
@@ -1616,9 +1616,7 @@
             try {
                 Log.startSession("TSI.aORTCCA");
                 enforceModifyPermission();
-                if (!Build.IS_USERDEBUG) {
-                    throw new SecurityException("Test-only API.");
-                }
+                enforceShellOnly(Binder.getCallingUid(), "addOrRemoveTestCallCompanionApp");
                 synchronized (mLock) {
                     long token = Binder.clearCallingIdentity();
                     try {
diff --git a/src/com/android/server/telecom/TelecomSystem.java b/src/com/android/server/telecom/TelecomSystem.java
index f354b94..f3b7b25 100644
--- a/src/com/android/server/telecom/TelecomSystem.java
+++ b/src/com/android/server/telecom/TelecomSystem.java
@@ -23,6 +23,7 @@
 import com.android.server.telecom.callfiltering.IncomingCallFilter;
 import com.android.server.telecom.components.UserCallIntentProcessor;
 import com.android.server.telecom.components.UserCallIntentProcessorFactory;
+import com.android.server.telecom.ui.AudioProcessingNotification;
 import com.android.server.telecom.ui.IncomingCallNotifier;
 import com.android.server.telecom.ui.MissedCallNotifierImpl.MissedCallNotifierImplFactory;
 import com.android.server.telecom.BluetoothPhoneServiceImpl.BluetoothPhoneServiceImplFactory;
@@ -267,6 +268,9 @@
             }
         };
 
+        AudioProcessingNotification audioProcessingNotification =
+                new AudioProcessingNotification(mContext);
+
         mCallsManager = new CallsManager(
                 mContext,
                 mLock,
@@ -288,6 +292,7 @@
                 emergencyCallHelper,
                 toneGeneratorFactory,
                 clockProxy,
+                audioProcessingNotification,
                 bluetoothStateReceiver,
                 callAudioRouteStateMachineFactory,
                 callAudioModeStateMachineFactory,
diff --git a/src/com/android/server/telecom/callfiltering/AsyncBlockCheckFilter.java b/src/com/android/server/telecom/callfiltering/AsyncBlockCheckFilter.java
index 0abd15d..edc68dd 100644
--- a/src/com/android/server/telecom/callfiltering/AsyncBlockCheckFilter.java
+++ b/src/com/android/server/telecom/callfiltering/AsyncBlockCheckFilter.java
@@ -30,6 +30,7 @@
 import com.android.server.telecom.Call;
 import com.android.server.telecom.CallerInfoLookupHelper;
 import com.android.server.telecom.LogUtils;
+import com.android.server.telecom.callfiltering.CallFilteringResult.Builder;
 import com.android.server.telecom.settings.BlockedNumbersUtil;
 
 /**
@@ -121,15 +122,15 @@
         try {
             CallFilteringResult result;
             if (isBlocked) {
-                result = new CallFilteringResult(
-                        false, // shouldAllowCall
-                        true, //shouldReject
-                        true, //shouldAddToCallLog
-                        false, // shouldShowNotification
-                        convertBlockStatusToReason(), //callBlockReason
-                        null, //callScreeningAppName
-                        null //callScreeningComponentName
-                );
+                result = new Builder()
+                        .setShouldAllowCall(false)
+                        .setShouldReject(true)
+                        .setShouldAddToCallLog(true)
+                        .setShouldShowNotification(false)
+                        .setCallBlockReason(convertBlockStatusToReason())
+                        .setCallScreeningAppName(null)
+                        .setCallScreeningComponentName(null)
+                        .build();
                 if (mCallBlockListener != null) {
                     String number = mIncomingCall.getHandle() == null ? null
                             : mIncomingCall.getHandle().getSchemeSpecificPart();
@@ -137,12 +138,12 @@
                             mIncomingCall.getInitiatingUser());
                 }
             } else {
-                result = new CallFilteringResult(
-                        true, // shouldAllowCall
-                        false, // shouldReject
-                        true, // shouldAddToCallLog
-                        true // shouldShowNotification
-                );
+                result = new Builder()
+                        .setShouldAllowCall(true)
+                        .setShouldReject(false)
+                        .setShouldAddToCallLog(true)
+                        .setShouldShowNotification(true)
+                        .build();
             }
             Log.addEvent(mIncomingCall, LogUtils.Events.BLOCK_CHECK_FINISHED,
                     BlockedNumberContract.SystemContract.blockStatusToString(mBlockStatus) + " "
diff --git a/src/com/android/server/telecom/callfiltering/CallFilteringResult.java b/src/com/android/server/telecom/callfiltering/CallFilteringResult.java
index af3ee1e..c17a256 100644
--- a/src/com/android/server/telecom/callfiltering/CallFilteringResult.java
+++ b/src/com/android/server/telecom/callfiltering/CallFilteringResult.java
@@ -21,54 +21,89 @@
 import android.text.TextUtils;
 
 public class CallFilteringResult {
+    public static class Builder {
+        private boolean mShouldAllowCall;
+        private boolean mShouldReject;
+        private boolean mShouldAddToCallLog;
+        private boolean mShouldShowNotification;
+        private boolean mShouldSilence = false;
+        private boolean mShouldScreenViaAudio = false;
+        private int mCallBlockReason = Calls.BLOCK_REASON_NOT_BLOCKED;
+        private CharSequence mCallScreeningAppName = null;
+        private String mCallScreeningComponentName = null;
+
+        public Builder setShouldAllowCall(boolean shouldAllowCall) {
+            mShouldAllowCall = shouldAllowCall;
+            return this;
+        }
+
+        public Builder setShouldReject(boolean shouldReject) {
+            mShouldReject = shouldReject;
+            return this;
+        }
+
+        public Builder setShouldAddToCallLog(boolean shouldAddToCallLog) {
+            mShouldAddToCallLog = shouldAddToCallLog;
+            return this;
+        }
+
+        public Builder setShouldShowNotification(boolean shouldShowNotification) {
+            mShouldShowNotification = shouldShowNotification;
+            return this;
+        }
+
+        public Builder setShouldSilence(boolean shouldSilence) {
+            mShouldSilence = shouldSilence;
+            return this;
+        }
+
+        public Builder setCallBlockReason(int callBlockReason) {
+            mCallBlockReason = callBlockReason;
+            return this;
+        }
+
+        public Builder setShouldScreenViaAudio(boolean shouldScreenViaAudio) {
+            mShouldScreenViaAudio = shouldScreenViaAudio;
+            return this;
+        }
+
+        public Builder setCallScreeningAppName(CharSequence callScreeningAppName) {
+            mCallScreeningAppName = callScreeningAppName;
+            return this;
+        }
+
+        public Builder setCallScreeningComponentName(String callScreeningComponentName) {
+            mCallScreeningComponentName = callScreeningComponentName;
+            return this;
+        }
+
+        public CallFilteringResult build() {
+            return new CallFilteringResult(mShouldAllowCall, mShouldReject, mShouldSilence,
+                    mShouldAddToCallLog, mShouldShowNotification, mShouldScreenViaAudio,
+                    mCallBlockReason, mCallScreeningAppName, mCallScreeningComponentName);
+        }
+    }
+
     public boolean shouldAllowCall;
     public boolean shouldReject;
     public boolean shouldSilence;
     public boolean shouldAddToCallLog;
+    public boolean shouldScreenViaAudio = false;
     public boolean shouldShowNotification;
-    public int mCallBlockReason = CallLog.Calls.BLOCK_REASON_NOT_BLOCKED;
-    public CharSequence mCallScreeningAppName = null;
-    public String mCallScreeningComponentName = null;
+    public int mCallBlockReason;
+    public CharSequence mCallScreeningAppName;
+    public String mCallScreeningComponentName;
 
-    public CallFilteringResult(boolean shouldAllowCall, boolean shouldReject, boolean
-            shouldAddToCallLog, boolean shouldShowNotification) {
-        this.shouldAllowCall = shouldAllowCall;
-        this.shouldReject = shouldReject;
-        this.shouldSilence = false;
-        this.shouldAddToCallLog = shouldAddToCallLog;
-        this.shouldShowNotification = shouldShowNotification;
-    }
-
-    public CallFilteringResult(boolean shouldAllowCall, boolean shouldReject, boolean
-            shouldAddToCallLog, boolean shouldShowNotification, int callBlockReason,
-            CharSequence callScreeningAppName, String callScreeningComponentName) {
-        this.shouldAllowCall = shouldAllowCall;
-        this.shouldReject = shouldReject;
-        this.shouldSilence = false;
-        this.shouldAddToCallLog = shouldAddToCallLog;
-        this.shouldShowNotification = shouldShowNotification;
-        this.mCallBlockReason = callBlockReason;
-        this.mCallScreeningAppName = callScreeningAppName;
-        this.mCallScreeningComponentName = callScreeningComponentName;
-    }
-
-    public CallFilteringResult(boolean shouldAllowCall, boolean shouldReject, boolean
-            shouldSilence, boolean shouldAddToCallLog, boolean shouldShowNotification) {
+    private CallFilteringResult(boolean shouldAllowCall, boolean shouldReject, boolean
+            shouldSilence, boolean shouldAddToCallLog, boolean shouldShowNotification,
+            boolean shouldScreenViaAudio, int callBlockReason, CharSequence callScreeningAppName,
+            String callScreeningComponentName) {
         this.shouldAllowCall = shouldAllowCall;
         this.shouldReject = shouldReject;
         this.shouldSilence = shouldSilence;
         this.shouldAddToCallLog = shouldAddToCallLog;
         this.shouldShowNotification = shouldShowNotification;
-    }
-
-    public CallFilteringResult(boolean shouldAllowCall, boolean shouldReject, boolean
-            shouldSilence, boolean shouldAddToCallLog, boolean shouldShowNotification, int
-            callBlockReason, CharSequence callScreeningAppName, String callScreeningComponentName) {
-        this.shouldAllowCall = shouldAllowCall;
-        this.shouldReject = shouldReject;
-        this.shouldSilence = shouldSilence;
-        this.shouldAddToCallLog = shouldAddToCallLog;
-        this.shouldShowNotification = shouldShowNotification;
+        this.shouldScreenViaAudio = shouldScreenViaAudio;
         this.mCallBlockReason = callBlockReason;
         this.mCallScreeningAppName = callScreeningAppName;
         this.mCallScreeningComponentName = callScreeningComponentName;
@@ -109,12 +144,22 @@
                 other.mCallScreeningAppName, other.mCallScreeningComponentName);
         }
 
-        return new CallFilteringResult(
-            shouldAllowCall && other.shouldAllowCall,
-            shouldReject || other.shouldReject,
-            shouldSilence || other.shouldSilence,
-            shouldAddToCallLog && other.shouldAddToCallLog,
-            shouldShowNotification && other.shouldShowNotification);
+        if (shouldScreenViaAudio) {
+            return getCombinedCallFilteringResult(other, Calls.BLOCK_REASON_NOT_BLOCKED,
+                    mCallScreeningAppName, mCallScreeningComponentName);
+        } else if (other.shouldScreenViaAudio) {
+            return getCombinedCallFilteringResult(other, Calls.BLOCK_REASON_NOT_BLOCKED,
+                    other.mCallScreeningAppName, other.mCallScreeningComponentName);
+        }
+
+        return new Builder()
+                .setShouldAllowCall(shouldAllowCall && other.shouldAllowCall)
+                .setShouldReject(shouldReject || other.shouldReject)
+                .setShouldSilence(shouldSilence || other.shouldSilence)
+                .setShouldAddToCallLog(shouldAddToCallLog && other.shouldAddToCallLog)
+                .setShouldShowNotification(shouldShowNotification && other.shouldShowNotification)
+                .setShouldScreenViaAudio(shouldScreenViaAudio || other.shouldScreenViaAudio)
+                .build();
     }
 
     private boolean isBlockedByProvider(int blockReason) {
@@ -131,15 +176,17 @@
 
     private CallFilteringResult getCombinedCallFilteringResult(CallFilteringResult other,
         int callBlockReason, CharSequence callScreeningAppName, String callScreeningComponentName) {
-        return new CallFilteringResult(
-            shouldAllowCall && other.shouldAllowCall,
-            shouldReject || other.shouldReject,
-            shouldSilence|| other.shouldSilence,
-            shouldAddToCallLog && other.shouldAddToCallLog,
-            shouldShowNotification && other.shouldShowNotification,
-            callBlockReason,
-            callScreeningAppName,
-            callScreeningComponentName);
+        return new Builder()
+                .setShouldAllowCall(shouldAllowCall && other.shouldAllowCall)
+                .setShouldReject(shouldReject || other.shouldReject)
+                .setShouldSilence(shouldSilence || other.shouldSilence)
+                .setShouldAddToCallLog(shouldAddToCallLog && other.shouldAddToCallLog)
+                .setShouldShowNotification(shouldShowNotification && other.shouldShowNotification)
+                .setShouldScreenViaAudio(shouldScreenViaAudio || other.shouldScreenViaAudio)
+                .setCallBlockReason(callBlockReason)
+                .setCallScreeningAppName(callScreeningAppName)
+                .setCallScreeningComponentName(callScreeningComponentName)
+                .build();
     }
 
 
@@ -198,6 +245,10 @@
             sb.append("Ignore");
         }
 
+        if (shouldScreenViaAudio) {
+            sb.append(", audio processing");
+        }
+
         if (shouldAddToCallLog) {
             sb.append(", logged");
         }
diff --git a/src/com/android/server/telecom/callfiltering/CallScreeningServiceController.java b/src/com/android/server/telecom/callfiltering/CallScreeningServiceController.java
index 291fef8..42d9a59 100644
--- a/src/com/android/server/telecom/callfiltering/CallScreeningServiceController.java
+++ b/src/com/android/server/telecom/callfiltering/CallScreeningServiceController.java
@@ -39,6 +39,7 @@
 import com.android.server.telecom.PhoneAccountRegistrar;
 import com.android.server.telecom.TelecomServiceImpl;
 import com.android.server.telecom.TelecomSystem;
+import com.android.server.telecom.callfiltering.CallFilteringResult.Builder;
 
 /**
  * This class supports binding to the various {@link android.telecom.CallScreeningService}:
@@ -66,12 +67,12 @@
     private Call mCall;
     private CallFilterResultCallback mCallback;
 
-    private CallFilteringResult mResult = new CallFilteringResult(
-            true, // shouldAllowCall
-            false, // shouldReject
-            true, // shouldAddToCallLog
-            true // shouldShowNotification
-    );
+    private CallFilteringResult mResult = new Builder()
+            .setShouldAllowCall(true)
+            .setShouldReject(false)
+            .setShouldAddToCallLog(true)
+            .setShouldShowNotification(true)
+            .build();
 
     private boolean mIsFinished;
     private boolean mIsCarrierFinished;
@@ -195,10 +196,18 @@
             if (TextUtils.isEmpty(userChosenPackageName)) {
                 mIsUserChosenFinished = true;
             } else {
-                createCallScreeningServiceFilter().startCallScreeningFilter(mCall,
-                        CallScreeningServiceController.this, userChosenPackageName,
-                        mAppLabelProxy.getAppLabel(userChosenPackageName),
-                        CallScreeningServiceFilter.CALL_SCREENING_FILTER_TYPE_USER_SELECTED);
+                // If the user chosen call screening service is the same as the default dialer, then
+                // we have already bound to it above and don't need to do so again here.
+                if (userChosenPackageName.equals(dialerPackageName)) {
+                    Log.addEvent(mCall, LogUtils.Events.SCREENING_SKIPPED,
+                            "user pkg same as dialer: " + userChosenPackageName);
+                    mIsUserChosenFinished = true;
+                } else {
+                    createCallScreeningServiceFilter().startCallScreeningFilter(mCall,
+                            CallScreeningServiceController.this, userChosenPackageName,
+                            mAppLabelProxy.getAppLabel(userChosenPackageName),
+                            CallScreeningServiceFilter.CALL_SCREENING_FILTER_TYPE_USER_SELECTED);
+                }
             }
 
             if (mIsDefaultDialerFinished && mIsUserChosenFinished) {
diff --git a/src/com/android/server/telecom/callfiltering/CallScreeningServiceFilter.java b/src/com/android/server/telecom/callfiltering/CallScreeningServiceFilter.java
index 2a0e338..a795f73 100644
--- a/src/com/android/server/telecom/callfiltering/CallScreeningServiceFilter.java
+++ b/src/com/android/server/telecom/callfiltering/CallScreeningServiceFilter.java
@@ -24,7 +24,7 @@
 import android.os.PersistableBundle;
 import android.os.RemoteException;
 import android.os.UserHandle;
-import android.provider.CallLog;
+import android.provider.CallLog.Calls;
 import android.provider.Settings;
 import android.telecom.Log;
 import android.telecom.TelecomManager;
@@ -41,6 +41,7 @@
 import com.android.server.telecom.PhoneAccountRegistrar;
 import com.android.server.telecom.TelecomServiceImpl.SettingsSecureAdapter;
 import com.android.server.telecom.TelecomSystem;
+import com.android.server.telecom.callfiltering.CallFilteringResult.Builder;
 
 /**
  * Binds to {@link ICallScreeningService} to allow call blocking. A single instance of this class
@@ -96,13 +97,13 @@
                 synchronized (mTelecomLock) {
                     Log.d(this, "allowCall(%s)", callId);
                     if (mCall != null && mCall.getId().equals(callId)) {
-                        mResult = new CallFilteringResult(
-                                true, // shouldAllowCall
-                                false, //shouldReject
-                                false, //shouldSilence
-                                true, //shouldAddToCallLog
-                                true // shouldShowNotification
-                        );
+                        mResult = new Builder()
+                                .setShouldAllowCall(true)
+                                .setShouldReject(false)
+                                .setShouldSilence(false)
+                                .setShouldAddToCallLog(true)
+                                .setShouldShowNotification(true)
+                                .build();
                     } else {
                         Log.w(this, "allowCall, unknown call id: %s", callId);
                     }
@@ -131,16 +132,16 @@
                                     + "shouldShowNotification: %b", callId, shouldReject,
                             isServiceRequestingLogging, shouldShowNotification);
                     if (mCall != null && mCall.getId().equals(callId)) {
-                        mResult = new CallFilteringResult(
-                                false, // shouldAllowCall
-                                shouldReject, //shouldReject
-                                false, // shouldSilenceCall
-                                isServiceRequestingLogging, //shouldAddToCallLog
-                                shouldShowNotification, // shouldShowNotification
-                                CallLog.Calls.BLOCK_REASON_CALL_SCREENING_SERVICE, //callBlockReason
-                                mAppName, //callScreeningAppName
-                                componentName.flattenToString() //callScreeningComponentName
-                        );
+                        mResult = new Builder()
+                                .setShouldAllowCall(false)
+                                .setShouldReject(shouldReject)
+                                .setShouldSilence(false)
+                                .setShouldAddToCallLog(isServiceRequestingLogging)
+                                .setShouldShowNotification(shouldShowNotification)
+                                .setCallBlockReason(Calls.BLOCK_REASON_CALL_SCREENING_SERVICE)
+                                .setCallScreeningAppName(mAppName)
+                                .setCallScreeningComponentName(componentName.flattenToString())
+                                .build();
                     } else {
                         Log.w(this, "disallowCall, unknown call id: %s", callId);
                     }
@@ -160,13 +161,13 @@
                 synchronized (mTelecomLock) {
                     Log.d(this, "silenceCall(%s)", callId);
                     if (mCall != null && mCall.getId().equals(callId)) {
-                        mResult = new CallFilteringResult(
-                                true, // shouldAllowCall
-                                false, //shouldReject
-                                true, //shouldSilence
-                                true, //shouldAddToCallLog
-                                true // shouldShowNotification
-                        );
+                        mResult = new Builder()
+                                .setShouldAllowCall(true)
+                                .setShouldReject(false)
+                                .setShouldSilence(true)
+                                .setShouldAddToCallLog(true)
+                                .setShouldShowNotification(true)
+                                .build();
                     } else {
                         Log.w(this, "silenceCall, unknown call id: %s", callId);
                     }
@@ -180,7 +181,36 @@
 
         @Override
         public void screenCallFurther(String callId) {
-            // TODO: implement this
+            Log.startSession("CSCR.sCF");
+            long token = Binder.clearCallingIdentity();
+            try {
+                synchronized (mTelecomLock) {
+                    // This is only allowed if the caller is also the default dialer.
+                    if (!mCallsManager.getDefaultDialerCache().isDefaultOrSystemDialer(
+                            mPackageName, UserHandle.USER_CURRENT)) {
+                        throw new SecurityException("Only the default/system dialer may request"
+                                + " screening via background call audio");
+                    }
+                    // TODO: add permissions check for the additional role-based permission
+
+                    Log.d(this, "screenCallFurther: %s", callId);
+                    if (mCall != null && mCall.getId().equals(callId)) {
+                        mResult = new CallFilteringResult.Builder()
+                                .setShouldAllowCall(true)
+                                .setShouldReject(false)
+                                .setShouldSilence(false)
+                                .setShouldScreenViaAudio(true)
+                                .setCallScreeningAppName(mAppName)
+                                .build();
+                    } else {
+                        Log.w(this, "screenCallFurther, unknown call id: %s", callId);
+                    }
+                    finishCallScreening();
+                }
+            } finally {
+                Binder.restoreCallingIdentity(token);
+                Log.endSession();
+            }
         }
     }
 
@@ -199,12 +229,12 @@
     private boolean mHasFinished = false;
     private int mCallScreeningServiceType;
 
-    private CallFilteringResult mResult = new CallFilteringResult(
-            true, // shouldAllowCall
-            false, //shouldReject
-            true, //shouldAddToCallLog
-            true // shouldShowNotification
-    );
+    private CallFilteringResult mResult = new Builder()
+            .setShouldAllowCall(true)
+            .setShouldReject(false)
+            .setShouldAddToCallLog(true)
+            .setShouldShowNotification(true)
+            .build();
 
     public CallScreeningServiceFilter(
             Context context,
diff --git a/src/com/android/server/telecom/callfiltering/DirectToVoicemailCallFilter.java b/src/com/android/server/telecom/callfiltering/DirectToVoicemailCallFilter.java
index 3a8ff7d..16d731b 100644
--- a/src/com/android/server/telecom/callfiltering/DirectToVoicemailCallFilter.java
+++ b/src/com/android/server/telecom/callfiltering/DirectToVoicemailCallFilter.java
@@ -17,13 +17,14 @@
 package com.android.server.telecom.callfiltering;
 
 import android.net.Uri;
-import android.provider.CallLog;
+import android.provider.CallLog.Calls;
 import android.telecom.Log;
 
 import com.android.internal.telephony.CallerInfo;
 import com.android.server.telecom.Call;
 import com.android.server.telecom.CallerInfoLookupHelper;
 import com.android.server.telecom.LogUtils;
+import com.android.server.telecom.callfiltering.CallFilteringResult.Builder;
 
 import java.util.Objects;
 
@@ -46,33 +47,32 @@
                         CallFilteringResult result;
                         if ((handle != null) && Objects.equals(callHandle, handle)) {
                             if (info != null && info.shouldSendToVoicemail) {
-                                result = new CallFilteringResult(
-                                        false, // shouldAllowCall
-                                        true, // shouldReject
-                                        true, // shouldAddToCallLog
-                                        true, // shouldShowNotification
-                                        CallLog.Calls.BLOCK_REASON_DIRECT_TO_VOICEMAIL,
-                                        //callBlockReason
-                                        null, //callScreeningAppName
-                                        null // callScreeningComponentName
-                                );
+                                result = new Builder()
+                                        .setShouldAllowCall(false)
+                                        .setShouldReject(true)
+                                        .setShouldAddToCallLog(true)
+                                        .setShouldShowNotification(true)
+                                        .setCallBlockReason(Calls.BLOCK_REASON_DIRECT_TO_VOICEMAIL)
+                                        .setCallScreeningAppName(null)
+                                        .setCallScreeningComponentName(null)
+                                        .build();
                             } else {
-                                result = new CallFilteringResult(
-                                        true, // shouldAllowCall
-                                        false, // shouldReject
-                                        true, // shouldAddToCallLog
-                                        true // shouldShowNotification
-                                );
+                                result = new Builder()
+                                        .setShouldAllowCall(true)
+                                        .setShouldReject(false)
+                                        .setShouldAddToCallLog(true)
+                                        .setShouldShowNotification(true)
+                                        .build();
                             }
                             Log.addEvent(call, LogUtils.Events.DIRECT_TO_VM_FINISHED, result);
                             callback.onCallFilteringComplete(call, result);
                         } else {
-                            result = new CallFilteringResult(
-                                true, // shouldAllowCall
-                                false, // shouldReject
-                                true, // shouldAddToCallLog
-                                true // shouldShowNotification
-                            );
+                            result = new Builder()
+                                    .setShouldAllowCall(true)
+                                    .setShouldReject(false)
+                                    .setShouldAddToCallLog(true)
+                                    .setShouldShowNotification(true)
+                                    .build();
                             Log.addEvent(call, LogUtils.Events.DIRECT_TO_VM_FINISHED, result);
                             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 cc686ab..860de1f 100644
--- a/src/com/android/server/telecom/callfiltering/IncomingCallFilter.java
+++ b/src/com/android/server/telecom/callfiltering/IncomingCallFilter.java
@@ -27,6 +27,7 @@
 import com.android.server.telecom.LogUtils;
 import com.android.server.telecom.TelecomSystem;
 import com.android.server.telecom.Timeouts;
+import com.android.server.telecom.callfiltering.CallFilteringResult.Builder;
 
 import java.util.List;
 
@@ -53,12 +54,12 @@
     private final CallFilterResultCallback mListener;
     private final Timeouts.Adapter mTimeoutsAdapter;
 
-    private CallFilteringResult mResult = new CallFilteringResult(
-            true, // shouldAllowCall
-            false, // shouldReject
-            true, // shouldAddToCallLog
-            true // shouldShowNotification
-    );
+    private CallFilteringResult mResult = new Builder()
+            .setShouldAllowCall(true)
+            .setShouldReject(false)
+            .setShouldAddToCallLog(true)
+            .setShouldShowNotification(true)
+            .build();
 
     private boolean mIsPending = true;
     private int mNumPendingFilters;
diff --git a/src/com/android/server/telecom/ui/AudioProcessingNotification.java b/src/com/android/server/telecom/ui/AudioProcessingNotification.java
new file mode 100644
index 0000000..7a61460
--- /dev/null
+++ b/src/com/android/server/telecom/ui/AudioProcessingNotification.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2019, 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.ui;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.content.Context;
+import android.telecom.Log;
+
+import com.android.server.telecom.Call;
+import com.android.server.telecom.CallState;
+import com.android.server.telecom.CallsManagerListenerBase;
+import com.android.server.telecom.R;
+
+/**
+ * Displays a persistent notification whenever there's a call in the AUDIO_PROCESSING state so that
+ * the user is aware that there's some app
+ */
+public class AudioProcessingNotification extends CallsManagerListenerBase {
+
+    private static final int AUDIO_PROCESSING_NOTIFICATION_ID = 2;
+    private static final String NOTIFICATION_TAG =
+            AudioProcessingNotification.class.getSimpleName();
+
+    private final Context mContext;
+    private final NotificationManager mNotificationManager;
+    private Call mCallInAudioProcessing;
+
+    public AudioProcessingNotification(Context context) {
+        mContext = context;
+        mNotificationManager =
+                (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+    }
+
+    @Override
+    public void onCallStateChanged(Call call, int oldState, int newState) {
+        if (newState == CallState.AUDIO_PROCESSING && oldState != CallState.AUDIO_PROCESSING) {
+            showAudioProcessingNotification(call);
+        } else if (oldState == CallState.AUDIO_PROCESSING
+                && newState != CallState.AUDIO_PROCESSING) {
+            cancelAudioProcessingNotification();
+        }
+    }
+
+    @Override
+    public void onCallAdded(Call call) {
+        if (call.getState() == CallState.AUDIO_PROCESSING) {
+            showAudioProcessingNotification(call);
+        }
+    }
+
+    @Override
+    public void onCallRemoved(Call call) {
+        if (call == mCallInAudioProcessing) {
+            cancelAudioProcessingNotification();
+        }
+    }
+
+    /**
+     * Create a system notification for the audio processing call.
+     *
+     * @param call The missed call.
+     */
+    private void showAudioProcessingNotification(Call call) {
+        Log.i(this, "showAudioProcessingNotification");
+        mCallInAudioProcessing = call;
+
+        Notification.Builder builder = new Notification.Builder(mContext,
+                NotificationChannelManager.CHANNEL_ID_AUDIO_PROCESSING);
+        builder.setSmallIcon(R.drawable.ic_phone)
+                .setColor(mContext.getResources().getColor(R.color.theme_color))
+                .setContentTitle(mContext.getText(R.string.notification_audioProcessing_title))
+                .setStyle(new Notification.BigTextStyle()
+                        .bigText(mContext.getString(
+                                R.string.notification_audioProcessing_body,
+                                call.getAudioProcessingRequestingApp())))
+                .setOngoing(true);
+
+        Notification notification = builder.build();
+
+        mNotificationManager.notify(
+                NOTIFICATION_TAG, AUDIO_PROCESSING_NOTIFICATION_ID, notification);
+    }
+
+    /** Cancels the audio processing notification. */
+    private void cancelAudioProcessingNotification() {
+        mNotificationManager.cancel(NOTIFICATION_TAG, AUDIO_PROCESSING_NOTIFICATION_ID);
+    }
+}
diff --git a/src/com/android/server/telecom/ui/NotificationChannelManager.java b/src/com/android/server/telecom/ui/NotificationChannelManager.java
index 70d4d0d..d812ad8 100644
--- a/src/com/android/server/telecom/ui/NotificationChannelManager.java
+++ b/src/com/android/server/telecom/ui/NotificationChannelManager.java
@@ -37,6 +37,7 @@
     public static final String CHANNEL_ID_MISSED_CALLS = "TelecomMissedCalls";
     public static final String CHANNEL_ID_INCOMING_CALLS = "TelecomIncomingCalls";
     public static final String CHANNEL_ID_CALL_BLOCKING = "TelecomCallBlocking";
+    public static final String CHANNEL_ID_AUDIO_PROCESSING = "TelecomBackgroundAudioProcessing";
 
     private BroadcastReceiver mLocaleChangeReceiver = new BroadcastReceiver() {
         @Override
@@ -57,6 +58,7 @@
         createOrUpdateChannel(context, CHANNEL_ID_MISSED_CALLS);
         createOrUpdateChannel(context, CHANNEL_ID_INCOMING_CALLS);
         createOrUpdateChannel(context, CHANNEL_ID_CALL_BLOCKING);
+        createOrUpdateChannel(context, CHANNEL_ID_AUDIO_PROCESSING);
     }
 
     private void createOrUpdateChannel(Context context, String channelId) {
@@ -98,6 +100,14 @@
                 vibration = false;
                 sound = null;
                 break;
+            case CHANNEL_ID_AUDIO_PROCESSING:
+                name = context.getText(R.string.notification_channel_background_calls);
+                importance = NotificationManager.IMPORTANCE_LOW;
+                canShowBadge = false;
+                lights = false;
+                vibration = false;
+                sound = null;
+                break;
         }
 
         NotificationChannel channel = new NotificationChannel(channelId, name, importance);
diff --git a/testapps/AndroidManifest.xml b/testapps/AndroidManifest.xml
index e339356..f19c13e 100644
--- a/testapps/AndroidManifest.xml
+++ b/testapps/AndroidManifest.xml
@@ -20,7 +20,7 @@
 
     <uses-sdk
         android:minSdkVersion="28"
-        android:targetSdkVersion="28" />
+        android:targetSdkVersion="30" />
 
     <uses-permission android:name="android.permission.ACCEPT_HANDOVER" />
     <uses-permission android:name="android.permission.BLUETOOTH" />
diff --git a/testapps/res/layout/incall_screen.xml b/testapps/res/layout/incall_screen.xml
index 452cb2b..414bdb8 100644
--- a/testapps/res/layout/incall_screen.xml
+++ b/testapps/res/layout/incall_screen.xml
@@ -71,6 +71,21 @@
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:text="@string/handoverButton"/>
+        <Button
+            android:id="@+id/exit_audio_processing_ring_button"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/exit_audio_processing_ring_button"/>
+        <Button
+            android:id="@+id/exit_audio_processing_noring_button"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/exit_audio_processing_noring_button"/>
+        <Button
+            android:id="@+id/reject_button"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/reject_button"/>
     </GridLayout>
     <LinearLayout
         android:layout_width="wrap_content"
diff --git a/testapps/res/values/donottranslate_strings.xml b/testapps/res/values/donottranslate_strings.xml
index abd2949..6a085ee 100644
--- a/testapps/res/values/donottranslate_strings.xml
+++ b/testapps/res/values/donottranslate_strings.xml
@@ -56,6 +56,12 @@
 
     <string name="holdButton">Hold</string>
 
+    <string name="exit_audio_processing_noring_button">Exit AP (no ring)</string>
+
+    <string name="exit_audio_processing_ring_button">Exit AP (ring)</string>
+
+    <string name="reject_button">Reject</string>
+
     <string name="handoverButton">Handover</string>
 
     <string name="inCallUiAppLabel">Test InCall UI</string>
diff --git a/testapps/src/com/android/server/telecom/testapps/CallListAdapter.java b/testapps/src/com/android/server/telecom/testapps/CallListAdapter.java
index 4de6eed..5fa13e3 100644
--- a/testapps/src/com/android/server/telecom/testapps/CallListAdapter.java
+++ b/testapps/src/com/android/server/telecom/testapps/CallListAdapter.java
@@ -150,6 +150,10 @@
                 return "ringing";
             case Call.STATE_SELECT_PHONE_ACCOUNT:
                 return "select phone account";
+            case Call.STATE_AUDIO_PROCESSING:
+                return "audio processing";
+            case Call.STATE_SIMULATED_RINGING:
+                return "simulated ringing";
             default:
                 return "unknown";
         }
diff --git a/testapps/src/com/android/server/telecom/testapps/CallScreeningActivity.java b/testapps/src/com/android/server/telecom/testapps/CallScreeningActivity.java
deleted file mode 100644
index 05ba500..0000000
--- a/testapps/src/com/android/server/telecom/testapps/CallScreeningActivity.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright (C) 2018 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.testapps;
-
-import android.app.Activity;
-import android.app.AlertDialog;
-import android.content.DialogInterface;
-import android.os.Bundle;
-import android.telecom.CallScreeningService;
-import android.view.WindowManager;
-
-public class CallScreeningActivity extends Activity {
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        AlertDialog alertDialog = new AlertDialog.Builder(this)
-                .setTitle("Test Call Screening")
-                .setMessage("Allow the call?")
-                .setNegativeButton("Block", new DialogInterface.OnClickListener() {
-                    @Override
-                    public void onClick(DialogInterface dialog, int which) {
-                        if (TestCallScreeningService.getInstance() != null) {
-                            TestCallScreeningService.getInstance().blockCall();
-                        }
-                        finish();
-                    }
-                })
-                .setPositiveButton("Allow", new DialogInterface.OnClickListener() {
-                    @Override
-                    public void onClick(DialogInterface dialog, int which) {
-                        if (TestCallScreeningService.getInstance() != null) {
-                            TestCallScreeningService.getInstance().allowCall();
-                        }
-                        finish();
-                    }
-                }).create();
-        alertDialog.show();
-    }
-}
diff --git a/testapps/src/com/android/server/telecom/testapps/TestCallScreeningService.java b/testapps/src/com/android/server/telecom/testapps/TestCallScreeningService.java
index 5f7f4d5..544eb28 100644
--- a/testapps/src/com/android/server/telecom/testapps/TestCallScreeningService.java
+++ b/testapps/src/com/android/server/telecom/testapps/TestCallScreeningService.java
@@ -16,12 +16,16 @@
 
 package com.android.server.telecom.testapps;
 
-import android.content.Intent;
-import android.graphics.drawable.Icon;
+import android.os.SystemProperties;
 import android.telecom.Call;
 import android.telecom.CallScreeningService;
 import android.telecom.Log;
 
+/**
+ * To use this while testing, use:
+ * adb shell setprop com.android.server.telecom.testapps.callscreeningresult n,
+ * where n is one of the codes defined below.
+ */
 public class TestCallScreeningService extends CallScreeningService {
     private Call.Details mDetails;
     private static TestCallScreeningService sTestCallScreeningService;
@@ -30,6 +34,13 @@
         return sTestCallScreeningService;
     }
 
+    private static final int ALLOW_CALL = 0;
+    private static final int BLOCK_CALL = 1;
+    private static final int SCREEN_CALL_FURTHER = 2;
+
+    private static final String SCREENING_RESULT_KEY =
+            TestCallScreeningService.class.getPackage().getName() + ".callscreeningresult";
+
     /**
      * Handles request from the system to screen an incoming call.
      * @param callDetails Information about a new incoming call, see {@link Call.Details}.
@@ -40,10 +51,21 @@
         sTestCallScreeningService = this;
 
         mDetails = callDetails;
+
         if (callDetails.getCallDirection() == Call.Details.DIRECTION_INCOMING) {
-            Intent errorIntent = new Intent(this, CallScreeningActivity.class);
-            errorIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-            startActivity(errorIntent);
+            Log.i(this, "%s = %d", SCREENING_RESULT_KEY,
+                    SystemProperties.getInt(SCREENING_RESULT_KEY, 0));
+            switch (SystemProperties.getInt(SCREENING_RESULT_KEY, 0)) {
+                case ALLOW_CALL:
+                    allowCall();
+                    break;
+                case BLOCK_CALL:
+                    blockCall();
+                    break;
+                case SCREEN_CALL_FURTHER:
+                    screenCallFurther();
+                    break;
+            }
         }
     }
 
@@ -68,4 +90,16 @@
                 .build();
         respondToCall(mDetails, response);
     }
+
+    void screenCallFurther() {
+        CallScreeningService.CallResponse
+                response = new CallScreeningService.CallResponse.Builder()
+                .setDisallowCall(false)
+                .setRejectCall(false)
+                .setSkipCallLog(false)
+                .setSkipNotification(false)
+                .setShouldScreenCallFurther(true)
+                .build();
+        respondToCall(mDetails, response);
+    }
 }
diff --git a/testapps/src/com/android/server/telecom/testapps/TestInCallUI.java b/testapps/src/com/android/server/telecom/testapps/TestInCallUI.java
index 2a5b33a..edcbba1 100644
--- a/testapps/src/com/android/server/telecom/testapps/TestInCallUI.java
+++ b/testapps/src/com/android/server/telecom/testapps/TestInCallUI.java
@@ -125,6 +125,10 @@
         View setBtDeviceButton = findViewById(R.id.set_bt_device_button);
         View earpieceButton = findViewById(R.id.earpiece_button);
         View speakerButton = findViewById(R.id.speaker_button);
+        View exitAudioProcessingRingButton = findViewById(R.id.exit_audio_processing_ring_button);
+        View exitAudioProcessingNoRingButton =
+                findViewById(R.id.exit_audio_processing_noring_button);
+        View rejectButton = findViewById(R.id.reject_button);
         mBtDeviceList = findViewById(R.id.available_bt_devices);
         mBluetoothDeviceAdapter = new BluetoothDeviceAdapter();
         mBtDeviceList.setAdapter(mBluetoothDeviceAdapter);
@@ -173,7 +177,8 @@
 
         answerButton.setOnClickListener(view -> {
             Call call = mCallList.getCall(0);
-            if (call.getState() == Call.STATE_RINGING) {
+            if (call.getState() == Call.STATE_RINGING
+                    || call.getState() == Call.STATE_SIMULATED_RINGING) {
                 call.answer(VideoProfile.STATE_AUDIO_ONLY);
             }
         });
@@ -213,6 +218,21 @@
             call.handoverTo(getHandoverToPhoneAccountHandle(), VideoProfile.STATE_BIDIRECTIONAL,
                     null);
         });
+
+        exitAudioProcessingRingButton.setOnClickListener((v) -> {
+            Call call = mCallList.getCall(0);
+            call.exitBackgroundAudioProcessing(true);
+        });
+
+        exitAudioProcessingNoRingButton.setOnClickListener((v) -> {
+            Call call = mCallList.getCall(0);
+            call.exitBackgroundAudioProcessing(false);
+        });
+
+        rejectButton.setOnClickListener((v) -> {
+            Call call = mCallList.getCall(0);
+            call.reject(false, null);
+        });
     }
 
     public void updateCallAudioState(CallAudioState cas) {
diff --git a/tests/src/com/android/server/telecom/tests/AsyncBlockCheckFilterTest.java b/tests/src/com/android/server/telecom/tests/AsyncBlockCheckFilterTest.java
index ebc2826..ee34b0c 100644
--- a/tests/src/com/android/server/telecom/tests/AsyncBlockCheckFilterTest.java
+++ b/tests/src/com/android/server/telecom/tests/AsyncBlockCheckFilterTest.java
@@ -23,7 +23,7 @@
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.PersistableBundle;
-import android.provider.CallLog;
+import android.provider.CallLog.Calls;
 import android.telecom.TelecomManager;
 import android.telephony.CarrierConfigManager;
 import android.test.suitebuilder.annotation.SmallTest;
@@ -34,6 +34,7 @@
 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.CallFilteringResult.Builder;
 
 import org.junit.After;
 import org.junit.Before;
@@ -61,23 +62,22 @@
     @Mock private CarrierConfigManager mCarrierConfigManager;
 
     private AsyncBlockCheckFilter mFilter;
-    private static final CallFilteringResult BLOCK_RESULT = new CallFilteringResult(
-            false, // shouldAllowCall
-            true, //shouldReject
-            true, //shouldAddToCallLog
-            false, // shouldShowNotification
-            CallLog.Calls.BLOCK_REASON_BLOCKED_NUMBER, //blockReason
-            null, // callScreeningAppName
-            null //callScreeningComponentName
+    private static final CallFilteringResult BLOCK_RESULT = new Builder()
+            .setShouldAllowCall(false)
+            .setShouldReject(true)
+            .setShouldAddToCallLog(true)
+            .setShouldShowNotification(false)
+            .setCallBlockReason(Calls.BLOCK_REASON_BLOCKED_NUMBER)
+            .setCallScreeningAppName(null)
+            .setCallScreeningComponentName(null)
+            .build();
 
-    );
-
-    private static final CallFilteringResult PASS_RESULT = new CallFilteringResult(
-            true, // shouldAllowCall
-            false, // shouldReject
-            true, // shouldAddToCallLog
-            true // shouldShowNotification
-    );
+    private static final CallFilteringResult PASS_RESULT = new Builder()
+            .setShouldAllowCall(true)
+            .setShouldReject(false)
+            .setShouldAddToCallLog(true)
+            .setShouldShowNotification(true)
+            .build();
 
     private static final Uri TEST_HANDLE = Uri.parse("tel:1235551234");
     private static final int TEST_TIMEOUT = 1000;
diff --git a/tests/src/com/android/server/telecom/tests/BluetoothPhoneServiceTest.java b/tests/src/com/android/server/telecom/tests/BluetoothPhoneServiceTest.java
index 882f437..16e6355 100644
--- a/tests/src/com/android/server/telecom/tests/BluetoothPhoneServiceTest.java
+++ b/tests/src/com/android/server/telecom/tests/BluetoothPhoneServiceTest.java
@@ -113,7 +113,7 @@
         doNothing().when(mMockCallsManager).addListener(any(
                 CallsManager.CallsManagerListener.class));
         doReturn(null).when(mMockCallsManager).getActiveCall();
-        doReturn(null).when(mMockCallsManager).getRingingCall();
+        doReturn(null).when(mMockCallsManager).getRingingOrSimulatedRingingCall();
         doReturn(null).when(mMockCallsManager).getHeldCall();
         doReturn(null).when(mMockCallsManager).getOutgoingCall();
         doReturn(0).when(mMockCallsManager).getNumHeldCalls();
@@ -147,7 +147,7 @@
     @SmallTest
     @Test
     public void testHeadsetAnswerCallNull() throws Exception {
-        when(mMockCallsManager.getRingingCall()).thenReturn(null);
+        when(mMockCallsManager.getRingingOrSimulatedRingingCall()).thenReturn(null);
 
         boolean callAnswered = mBluetoothPhoneService.mBinder.answerCall();
 
@@ -282,7 +282,7 @@
         when(silentRingingCall.isConference()).thenReturn(false);
         when(silentRingingCall.getHandle()).thenReturn(Uri.parse("tel:555-000"));
         when(mMockCallsManager.getCalls()).thenReturn(calls);
-        when(mMockCallsManager.getRingingCall()).thenReturn(silentRingingCall);
+        when(mMockCallsManager.getRingingOrSimulatedRingingCall()).thenReturn(silentRingingCall);
 
         mBluetoothPhoneService.mBinder.listCurrentCalls();
 
@@ -819,8 +819,8 @@
 
         mBluetoothPhoneService.mCallsManagerListener.onCallAdded(ringingCall);
 
-        verify(mMockBluetoothHeadset).phoneStateChanged(eq(0), eq(0), eq(CALL_STATE_IDLE),
-            eq(""), eq(128), nullable(String.class));
+        verify(mMockBluetoothHeadset, never()).phoneStateChanged(anyInt(), anyInt(), anyInt(),
+                anyString(), anyInt(), nullable(String.class));
     }
 
     @MediumTest
@@ -876,6 +876,64 @@
 
     @MediumTest
     @Test
+    public void testOnCallAddedAudioProcessing() throws Exception {
+        Call call = mock(Call.class);
+        when(call.getState()).thenReturn(CallState.AUDIO_PROCESSING);
+        mBluetoothPhoneService.mCallsManagerListener.onCallAdded(call);
+
+        verify(mMockBluetoothHeadset, never()).phoneStateChanged(anyInt(), anyInt(), anyInt(),
+                anyString(), anyInt(), nullable(String.class));
+    }
+
+    @MediumTest
+    @Test
+    public void testOnCallStateChangedRingingToAudioProcessing() throws Exception {
+        Call ringingCall = createRingingCall();
+        when(ringingCall.getHandle()).thenReturn(Uri.parse("tel:555000"));
+
+        mBluetoothPhoneService.mCallsManagerListener.onCallAdded(ringingCall);
+
+        verify(mMockBluetoothHeadset).phoneStateChanged(eq(0), eq(0), eq(CALL_STATE_INCOMING),
+                eq("555000"), eq(PhoneNumberUtils.TOA_Unknown), nullable(String.class));
+
+        when(ringingCall.getState()).thenReturn(CallState.AUDIO_PROCESSING);
+        when(mMockCallsManager.getRingingOrSimulatedRingingCall()).thenReturn(null);
+
+        mBluetoothPhoneService.mCallsManagerListener.onCallStateChanged(ringingCall,
+                CallState.RINGING, CallState.AUDIO_PROCESSING);
+
+        verify(mMockBluetoothHeadset).phoneStateChanged(eq(0), eq(0), eq(CALL_STATE_IDLE),
+                eq(""), eq(128), nullable(String.class));
+    }
+
+    @MediumTest
+    @Test
+    public void testOnCallStateChangedAudioProcessingToSimulatedRinging() throws Exception {
+        Call ringingCall = createRingingCall();
+        when(ringingCall.getHandle()).thenReturn(Uri.parse("tel:555-0000"));
+
+        mBluetoothPhoneService.mCallsManagerListener.onCallStateChanged(ringingCall,
+                CallState.AUDIO_PROCESSING, CallState.SIMULATED_RINGING);
+
+        verify(mMockBluetoothHeadset).phoneStateChanged(eq(0), eq(0), eq(CALL_STATE_INCOMING),
+                eq("555-0000"), eq(PhoneNumberUtils.TOA_Unknown), nullable(String.class));
+    }
+
+    @MediumTest
+    @Test
+    public void testOnCallStateChangedAudioProcessingToActive() throws Exception {
+        Call activeCall = createActiveCall();
+        when(activeCall.getState()).thenReturn(CallState.ACTIVE);
+
+        mBluetoothPhoneService.mCallsManagerListener.onCallStateChanged(activeCall,
+                CallState.AUDIO_PROCESSING, CallState.ACTIVE);
+
+        verify(mMockBluetoothHeadset).phoneStateChanged(eq(1), eq(0), eq(CALL_STATE_IDLE),
+                eq(""), eq(128), nullable(String.class));
+    }
+
+    @MediumTest
+    @Test
     public void testOnCallStateChangedDialing() throws Exception {
         Call activeCall = createActiveCall();
 
@@ -931,7 +989,7 @@
                 eq("555-0000"), eq(PhoneNumberUtils.TOA_Unknown), nullable(String.class));
 
         //Switch to active
-        doReturn(null).when(mMockCallsManager).getRingingCall();
+        doReturn(null).when(mMockCallsManager).getRingingOrSimulatedRingingCall();
         when(mMockCallsManager.getActiveCall()).thenReturn(ringingCall);
 
         mBluetoothPhoneService.mCallsManagerListener.onCallStateChanged(ringingCall,
@@ -1021,7 +1079,7 @@
 
     private Call createRingingCall() {
         Call call = mock(Call.class);
-        when(mMockCallsManager.getRingingCall()).thenReturn(call);
+        when(mMockCallsManager.getRingingOrSimulatedRingingCall()).thenReturn(call);
         return call;
     }
 
diff --git a/tests/src/com/android/server/telecom/tests/CallLogManagerTest.java b/tests/src/com/android/server/telecom/tests/CallLogManagerTest.java
index becac11..d53c73c 100644
--- a/tests/src/com/android/server/telecom/tests/CallLogManagerTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallLogManagerTest.java
@@ -792,7 +792,7 @@
 
     @SmallTest
     @Test
-    public void testLogConferenceWithNoChildren() {
+    public void testDoNotLogConferenceWithNoChildren() {
         Call fakeCall = makeFakeCall(
                 DisconnectCause.LOCAL, // disconnectCauseCode
                 true, // isConference
@@ -808,7 +808,7 @@
         );
         when(fakeCall.hadChildren()).thenReturn(false);
 
-        assertTrue(mCallLogManager.shouldLogDisconnectedCall(fakeCall, CallState.DISCONNECTED,
+        assertFalse(mCallLogManager.shouldLogDisconnectedCall(fakeCall, CallState.DISCONNECTED,
                 false /* isCanceled */));
     }
 
diff --git a/tests/src/com/android/server/telecom/tests/CallScreeningServiceControllerTest.java b/tests/src/com/android/server/telecom/tests/CallScreeningServiceControllerTest.java
index c4acfe0..24cc8d2 100644
--- a/tests/src/com/android/server/telecom/tests/CallScreeningServiceControllerTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallScreeningServiceControllerTest.java
@@ -29,6 +29,7 @@
 import android.os.PersistableBundle;
 import android.os.UserHandle;
 import android.provider.CallLog;
+import android.provider.CallLog.Calls;
 import android.telecom.TelecomManager;
 import android.telephony.CarrierConfigManager;
 import android.test.suitebuilder.annotation.SmallTest;
@@ -45,6 +46,7 @@
 import com.android.server.telecom.TelecomSystem;
 import com.android.server.telecom.callfiltering.CallFilterResultCallback;
 import com.android.server.telecom.callfiltering.CallFilteringResult;
+import com.android.server.telecom.callfiltering.CallFilteringResult.Builder;
 import com.android.server.telecom.callfiltering.CallScreeningServiceController;
 
 import org.junit.After;
@@ -70,31 +72,6 @@
 @RunWith(JUnit4.class)
 public class CallScreeningServiceControllerTest extends TelecomTestCase {
 
-    @Mock Context mContext;
-    @Mock Call mCall;
-    @Mock private CallFilterResultCallback mCallback;
-    @Mock CallsManager mCallsManager;
-    @Mock RoleManagerAdapter mRoleManagerAdapter;
-    @Mock CarrierConfigManager mCarrierConfigManager;
-    @Mock private TelecomManager mTelecomManager;
-    @Mock PackageManager mPackageManager;
-    @Mock ParcelableCallUtils.Converter mParcelableCallUtilsConverter;
-    @Mock PhoneAccountRegistrar mPhoneAccountRegistrar;
-    @Mock private CallerInfoLookupHelper mCallerInfoLookupHelper;
-
-    CallScreeningServiceHelper.AppLabelProxy mAppLabelProxy =
-            new CallScreeningServiceHelper.AppLabelProxy() {
-        @Override
-        public CharSequence getAppLabel(String packageName) {
-            return APP_NAME;
-        }
-    };
-
-    private ResolveInfo mResolveInfo;
-    private TelecomServiceImpl.SettingsSecureAdapter mSettingsSecureAdapter =
-            spy(new CallScreeningServiceFilterTest.SettingsSecureAdapterFake());
-    private TelecomSystem.SyncRoot mLock = new TelecomSystem.SyncRoot() { };
-
     private static final String CALL_ID = "u89prgt9ps78y5";
     private static final Uri TEST_HANDLE = Uri.parse("tel:1235551234");
     private static final String DEFAULT_DIALER_PACKAGE = "com.android.dialer";
@@ -107,27 +84,46 @@
             "com.android.dialer", "com.android.dialer.callscreeningserviceimpl");
     private static final ComponentName USER_CHOSEN_CALL_SCREENING = new ComponentName(
             "com.android.userchosen", "com.android.userchosen.callscreeningserviceimpl");
-
-    private static final CallFilteringResult PASS_RESULT = new CallFilteringResult(
-            true, // shouldAllowCall
-            false, // shouldReject
-            true, // shouldAddToCallLog
-            true // shouldShowNotification
-    );
-
-    public static class SettingsSecureAdapterFake implements
-            TelecomServiceImpl.SettingsSecureAdapter {
-        @Override
-        public void putStringForUser(ContentResolver resolver, String name, String value,
-                                     int userHandle) {
-
-        }
-
-        @Override
-        public String getStringForUser(ContentResolver resolver, String name, int userHandle) {
-            return USER_CHOSEN_CALL_SCREENING.flattenToString();
-        }
-    }
+    private static final CallFilteringResult PASS_RESULT = new Builder()
+            .setShouldAllowCall(true)
+            .setShouldReject(false)
+            .setShouldAddToCallLog(true)
+            .setShouldShowNotification(true)
+            .build();
+    @Mock
+    Context mContext;
+    @Mock
+    Call mCall;
+    @Mock
+    CallsManager mCallsManager;
+    @Mock
+    RoleManagerAdapter mRoleManagerAdapter;
+    @Mock
+    CarrierConfigManager mCarrierConfigManager;
+    @Mock
+    PackageManager mPackageManager;
+    @Mock
+    ParcelableCallUtils.Converter mParcelableCallUtilsConverter;
+    @Mock
+    PhoneAccountRegistrar mPhoneAccountRegistrar;
+    CallScreeningServiceHelper.AppLabelProxy mAppLabelProxy =
+            new CallScreeningServiceHelper.AppLabelProxy() {
+                @Override
+                public CharSequence getAppLabel(String packageName) {
+                    return APP_NAME;
+                }
+            };
+    @Mock
+    private CallFilterResultCallback mCallback;
+    @Mock
+    private TelecomManager mTelecomManager;
+    @Mock
+    private CallerInfoLookupHelper mCallerInfoLookupHelper;
+    private ResolveInfo mResolveInfo;
+    private TelecomServiceImpl.SettingsSecureAdapter mSettingsSecureAdapter =
+            spy(new CallScreeningServiceFilterTest.SettingsSecureAdapterFake());
+    private TelecomSystem.SyncRoot mLock = new TelecomSystem.SyncRoot() {
+    };
 
     @Override
     @Before
@@ -145,7 +141,7 @@
         when(TelecomManager.from(mContext)).thenReturn(mTelecomManager);
         when(mTelecomManager.getDefaultDialerPackage()).thenReturn(DEFAULT_DIALER_PACKAGE);
 
-        mResolveInfo =  new ResolveInfo() {{
+        mResolveInfo = new ResolveInfo() {{
             serviceInfo = new ServiceInfo();
             serviceInfo.packageName = PKG_NAME;
             serviceInfo.name = CLS_NAME;
@@ -236,30 +232,27 @@
 
         controller.startFilterLookup(mCall, mCallback);
 
-        controller.onCallScreeningFilterComplete(mCall, new CallFilteringResult(
-                false, // shouldAllowCall
-                true, // shouldReject
-                false, // shouldAddToCallLog
-                true, // shouldShowNotification
-                CallLog.Calls.BLOCK_REASON_CALL_SCREENING_SERVICE,
-                APP_NAME,
-                CARRIER_DEFINED_CALL_SCREENING.flattenToString()
-        ), CARRIER_DEFINED_CALL_SCREENING.getPackageName());
+        CallFilteringResult expectedResult = new Builder()
+                .setShouldAllowCall(false)
+                .setShouldReject(true)
+                .setShouldAddToCallLog(false)
+                .setShouldShowNotification(true)
+                .setCallBlockReason(Calls.BLOCK_REASON_CALL_SCREENING_SERVICE)
+                .setCallScreeningAppName(APP_NAME)
+                .setCallScreeningComponentName(
+                        CARRIER_DEFINED_CALL_SCREENING.flattenToString())
+                .build();
+
+        controller.onCallScreeningFilterComplete(mCall, expectedResult,
+                CARRIER_DEFINED_CALL_SCREENING.getPackageName());
 
         verify(mContext, times(1)).bindServiceAsUser(any(Intent.class),
                 any(ServiceConnection.class),
                 eq(Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE),
                 eq(UserHandle.CURRENT));
 
-        verify(mCallback).onCallFilteringComplete(eq(mCall), eq(new CallFilteringResult(
-                false, // shouldAllowCall
-                true, // shouldReject
-                false, // shouldAddToCallLog
-                true, // shouldShowNotification
-                CallLog.Calls.BLOCK_REASON_CALL_SCREENING_SERVICE, //callBlockReason
-                APP_NAME, //callScreeningAppName
-                CARRIER_DEFINED_CALL_SCREENING.flattenToString() //callScreeningComponentName
-        )));
+        verify(mCallback)
+                .onCallFilteringComplete(eq(mCall), eq(expectedResult));
     }
 
     @SmallTest
@@ -282,30 +275,31 @@
         callerInfo.contactExists = false;
         queryListener.onCallerInfoQueryComplete(TEST_HANDLE, callerInfo);
 
-        controller.onCallScreeningFilterComplete(mCall, new CallFilteringResult(
-                false, // shouldAllowCall
-                true, // shouldReject
-                false, // shouldAddToCallLog
-                true, // shouldShowNotification
-                CallLog.Calls.BLOCK_REASON_CALL_SCREENING_SERVICE,
-                APP_NAME,
-                DEFAULT_DIALER_CALL_SCREENING.flattenToString()
-        ), DEFAULT_DIALER_CALL_SCREENING.getPackageName());
+        CallFilteringResult.Builder resultBuilder = new Builder()
+                .setShouldAllowCall(false)
+                .setShouldReject(true)
+                .setShouldShowNotification(true)
+                .setCallBlockReason(Calls.BLOCK_REASON_CALL_SCREENING_SERVICE)
+                .setCallScreeningAppName(APP_NAME)
+                .setCallScreeningComponentName(DEFAULT_DIALER_CALL_SCREENING.flattenToString());
+
+        CallFilteringResult providedResult = resultBuilder
+                .setShouldAddToCallLog(false)
+                .build();
+
+        controller.onCallScreeningFilterComplete(mCall, providedResult,
+                DEFAULT_DIALER_CALL_SCREENING.getPackageName());
 
         verify(mContext, times(3)).bindServiceAsUser(any(Intent.class),
                 any(ServiceConnection.class),
                 eq(Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE),
                 eq(UserHandle.CURRENT));
 
-        verify(mCallback).onCallFilteringComplete(eq(mCall), eq(new CallFilteringResult(
-                false, // shouldAllowCall
-                true, // shouldReject
-                true, // shouldAddToCallLog (we don't allow services to skip call log)
-                true, // shouldShowNotification
-                CallLog.Calls.BLOCK_REASON_CALL_SCREENING_SERVICE, //callBlockReason
-                APP_NAME, //callScreeningAppName
-                DEFAULT_DIALER_CALL_SCREENING.flattenToString() //callScreeningComponentName
-        )));
+        CallFilteringResult expectedResult = resultBuilder
+                .setShouldAddToCallLog(true)
+                .build();
+
+        verify(mCallback).onCallFilteringComplete(eq(mCall), eq(expectedResult));
     }
 
     @SmallTest
@@ -330,30 +324,99 @@
 
         controller.onCallScreeningFilterComplete(mCall, PASS_RESULT,
                 DEFAULT_DIALER_CALL_SCREENING.getPackageName());
-        controller.onCallScreeningFilterComplete(mCall, new CallFilteringResult(
-                false, // shouldAllowCall
-                true, // shouldReject
-                false, // shouldAddToCallLog
-                true, // shouldShowNotification
-                CallLog.Calls.BLOCK_REASON_CALL_SCREENING_SERVICE,
-                APP_NAME,
-                USER_CHOSEN_CALL_SCREENING.flattenToString()
-        ), USER_CHOSEN_CALL_SCREENING.getPackageName());
+
+        CallFilteringResult.Builder resultBuilder = new Builder()
+                .setShouldAllowCall(false)
+                .setShouldReject(true)
+                .setShouldShowNotification(true)
+                .setCallBlockReason(Calls.BLOCK_REASON_CALL_SCREENING_SERVICE)
+                .setCallScreeningAppName(APP_NAME)
+                .setCallScreeningComponentName(DEFAULT_DIALER_CALL_SCREENING.flattenToString());
+        CallFilteringResult providedResult = resultBuilder
+                .setShouldAddToCallLog(false)
+                .build();
+
+        controller.onCallScreeningFilterComplete(mCall, providedResult,
+                USER_CHOSEN_CALL_SCREENING.getPackageName());
 
         verify(mContext, times(3)).bindServiceAsUser(any(Intent.class),
                 any(ServiceConnection.class),
                 eq(Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE),
                 eq(UserHandle.CURRENT));
 
-        verify(mCallback).onCallFilteringComplete(eq(mCall), eq(new CallFilteringResult(
-                false, // shouldAllowCall
-                true, // shouldReject
-                true, // shouldAddToCallLog (we don't allow services to skip call log)
-                true, // shouldShowNotification
-                CallLog.Calls.BLOCK_REASON_CALL_SCREENING_SERVICE, //callBlockReason
-                APP_NAME, //callScreeningAppName
-                USER_CHOSEN_CALL_SCREENING.flattenToString() //callScreeningComponentName
-        )));
+        CallFilteringResult expectedResult = resultBuilder
+                .setShouldAddToCallLog(true)
+                .build();
+
+        verify(mCallback).onCallFilteringComplete(eq(mCall), eq(expectedResult));
+    }
+
+    /**
+     * This test verifies that where the default dialer role is filled by the same app as the caller
+     * id and spam role, we will only bind to that call screening service once.
+     */
+    @SmallTest
+    @Test
+    public void testOnlyBindOnce() {
+        // Assume the user chose the default dialer to also fill the caller id and spam role.
+        when(mRoleManagerAdapter.getDefaultCallScreeningApp()).thenReturn(
+                DEFAULT_DIALER_CALL_SCREENING.getPackageName());
+        CallScreeningServiceController controller = new CallScreeningServiceController(mContext,
+                mCallsManager,
+                mPhoneAccountRegistrar, mParcelableCallUtilsConverter, mLock,
+                mSettingsSecureAdapter, mCallerInfoLookupHelper, mAppLabelProxy);
+
+        controller.startFilterLookup(mCall, mCallback);
+
+        controller.onCallScreeningFilterComplete(mCall, PASS_RESULT,
+                CARRIER_DEFINED_CALL_SCREENING.getPackageName());
+
+        CallerInfoLookupHelper.OnQueryCompleteListener queryListener = verifyLookupStart();
+        CallerInfo callerInfo = new CallerInfo();
+        callerInfo.contactExists = false;
+        queryListener.onCallerInfoQueryComplete(TEST_HANDLE, callerInfo);
+
+        controller.onCallScreeningFilterComplete(mCall, new CallFilteringResult.Builder()
+                        .setShouldAllowCall(false)
+                        .setShouldReject(true)
+                        .setShouldAddToCallLog(false)
+                        .setShouldShowNotification(true)
+                        .setCallBlockReason(CallLog.Calls.BLOCK_REASON_CALL_SCREENING_SERVICE)
+                        .setCallScreeningAppName(APP_NAME)
+                        .setCallScreeningComponentName(
+                                DEFAULT_DIALER_CALL_SCREENING.flattenToString())
+                        .build(),
+                DEFAULT_DIALER_CALL_SCREENING.getPackageName());
+
+        controller.onCallScreeningFilterComplete(mCall, new CallFilteringResult.Builder()
+                .setShouldAllowCall(false)
+                .setShouldReject(true)
+                .setShouldAddToCallLog(false)
+                .setShouldShowNotification(true)
+                .setCallBlockReason(CallLog.Calls.BLOCK_REASON_CALL_SCREENING_SERVICE)
+                .setCallScreeningAppName(APP_NAME)
+                .setCallScreeningComponentName(DEFAULT_DIALER_CALL_SCREENING.flattenToString())
+                .build(), USER_CHOSEN_CALL_SCREENING.getPackageName());
+
+        // Expect to bind twice; once to the carrier defined service, and then again to the default
+        // dialer.
+        verify(mContext, times(2)).bindServiceAsUser(any(Intent.class),
+                any(ServiceConnection.class),
+                eq(Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE),
+                eq(UserHandle.CURRENT));
+
+        // Expect filtering to complete only a single time from the default dialer service.
+        verify(mCallback, times(1)).onCallFilteringComplete(eq(mCall),
+                eq(new CallFilteringResult.Builder()
+                        .setShouldAllowCall(false)
+                        .setShouldReject(true)
+                        .setShouldAddToCallLog(true)
+                        .setShouldShowNotification(true)
+                        .setCallBlockReason(CallLog.Calls.BLOCK_REASON_CALL_SCREENING_SERVICE)
+                        .setCallScreeningAppName(APP_NAME)
+                        .setCallScreeningComponentName(
+                                DEFAULT_DIALER_CALL_SCREENING.flattenToString())
+                        .build()));
     }
 
     private CallerInfoLookupHelper.OnQueryCompleteListener verifyLookupStart() {
@@ -377,4 +440,18 @@
                 .thenReturn(mCarrierConfigManager);
         when(mCarrierConfigManager.getConfig()).thenReturn(bundle);
     }
+
+    public static class SettingsSecureAdapterFake implements
+            TelecomServiceImpl.SettingsSecureAdapter {
+        @Override
+        public void putStringForUser(ContentResolver resolver, String name, String value,
+                int userHandle) {
+
+        }
+
+        @Override
+        public String getStringForUser(ContentResolver resolver, String name, int userHandle) {
+            return USER_CHOSEN_CALL_SCREENING.flattenToString();
+        }
+    }
 }
diff --git a/tests/src/com/android/server/telecom/tests/CallScreeningServiceFilterTest.java b/tests/src/com/android/server/telecom/tests/CallScreeningServiceFilterTest.java
index b11b397..7b3a499 100644
--- a/tests/src/com/android/server/telecom/tests/CallScreeningServiceFilterTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallScreeningServiceFilterTest.java
@@ -29,7 +29,7 @@
 import android.os.PersistableBundle;
 import android.os.RemoteException;
 import android.os.UserHandle;
-import android.provider.CallLog;
+import android.provider.CallLog.Calls;
 import android.telecom.CallScreeningService;
 import android.telecom.ParcelableCall;
 import android.telecom.TelecomManager;
@@ -44,6 +44,7 @@
 import com.android.server.telecom.PhoneAccountRegistrar;
 import com.android.server.telecom.TelecomServiceImpl;
 import com.android.server.telecom.callfiltering.CallFilteringResult;
+import com.android.server.telecom.callfiltering.CallFilteringResult.Builder;
 import com.android.server.telecom.callfiltering.CallScreeningServiceFilter;
 import com.android.server.telecom.TelecomSystem;
 
@@ -105,20 +106,20 @@
     private static final String USER_CHOSEN_CALL_SCREENING_APP_NAME = "UserChosen";
     private ResolveInfo mResolveInfo;
 
-    private static final CallFilteringResult PASS_RESULT = new CallFilteringResult(
-            true, // shouldAllowCall
-            false, // shouldReject
-            true, // shouldAddToCallLog
-            true // shouldShowNotification
-    );
+    private static final CallFilteringResult PASS_RESULT = new Builder()
+            .setShouldAllowCall(true)
+            .setShouldReject(false)
+            .setShouldAddToCallLog(true)
+            .setShouldShowNotification(true)
+            .build();
 
-    private static final CallFilteringResult PASS_RESULT_WITH_SILENCE = new CallFilteringResult(
-            true, // shouldAllowCall
-            false, // shouldReject
-            true, // shouldSilence
-            true, // shouldAddToCallLog
-            true // shouldShowNotification
-    );
+    private static final CallFilteringResult PASS_RESULT_WITH_SILENCE = new Builder()
+            .setShouldAllowCall(true)
+            .setShouldReject(false)
+            .setShouldSilence(true)
+            .setShouldAddToCallLog(true)
+            .setShouldShowNotification(true)
+            .build();
 
     private CallScreeningServiceFilter mFilter;
 
@@ -274,15 +275,17 @@
                 true, // shouldShowNotification
                 CARRIER_DEFINED_CALL_SCREENING
         );
-        verify(mCallback).onCallScreeningFilterComplete(eq(mCall), eq(new CallFilteringResult(
-                false, // shouldAllowCall
-                true, // shouldReject
-                false, // shouldAddToCallLog
-                true, // shouldShowNotification
-                CallLog.Calls.BLOCK_REASON_CALL_SCREENING_SERVICE, //callBlockReason
-                CARRIER_DEFINED_CALL_SCREENING_APP_NAME, //callScreeningAppName
-                CARRIER_DEFINED_CALL_SCREENING.flattenToString() //callScreeningComponentName
-        )), eq(CARRIER_DEFINED_CALL_SCREENING.getPackageName()));
+        verify(mCallback).onCallScreeningFilterComplete(eq(mCall), eq(new Builder()
+                        .setShouldAllowCall(false)
+                        .setShouldReject(true)
+                        .setShouldAddToCallLog(false)
+                        .setShouldShowNotification(true)
+                        .setCallBlockReason(Calls.BLOCK_REASON_CALL_SCREENING_SERVICE)
+                        .setCallScreeningAppName(CARRIER_DEFINED_CALL_SCREENING_APP_NAME)
+                        .setCallScreeningComponentName(
+                                CARRIER_DEFINED_CALL_SCREENING.flattenToString())
+                        .build()),
+                eq(CARRIER_DEFINED_CALL_SCREENING.getPackageName()));
     }
 
     @SmallTest
@@ -307,15 +310,16 @@
             true, // shouldShowNotification
             DEFAULT_DIALER_CALL_SCREENING
         );
-        verify(mCallback).onCallScreeningFilterComplete(eq(mCall), eq(new CallFilteringResult(
-            false, // shouldAllowCall
-            true, // shouldReject
-            true, // shouldAddToCallLog
-            true, // shouldShowNotification
-            CallLog.Calls.BLOCK_REASON_CALL_SCREENING_SERVICE, //callBlockReason
-            DEFAULT_DIALER_APP_NAME, //callScreeningAppName
-            DEFAULT_DIALER_CALL_SCREENING.flattenToString() //callScreeningComponentName
-        )), eq(DEFAULT_DIALER_CALL_SCREENING.getPackageName()));
+        verify(mCallback).onCallScreeningFilterComplete(eq(mCall), eq(new Builder()
+                .setShouldAllowCall(false)
+                .setShouldReject(true)
+                .setShouldAddToCallLog(true)
+                .setShouldShowNotification(true)
+                .setCallBlockReason(Calls.BLOCK_REASON_CALL_SCREENING_SERVICE)
+                .setCallScreeningAppName(DEFAULT_DIALER_APP_NAME)
+                .setCallScreeningComponentName(DEFAULT_DIALER_CALL_SCREENING.flattenToString())
+                .build()),
+                eq(DEFAULT_DIALER_CALL_SCREENING.getPackageName()));
     }
 
     @SmallTest
@@ -340,15 +344,16 @@
             true, // shouldShowNotification
             USER_CHOSEN_CALL_SCREENING
         );
-        verify(mCallback).onCallScreeningFilterComplete(eq(mCall), eq(new CallFilteringResult(
-            false, // shouldAllowCall
-            true, // shouldReject
-            true, // shouldAddToCallLog
-            true, // shouldShowNotification
-            CallLog.Calls.BLOCK_REASON_CALL_SCREENING_SERVICE, //callBlockReason
-            USER_CHOSEN_CALL_SCREENING_APP_NAME, //callScreeningAppName
-            USER_CHOSEN_CALL_SCREENING.flattenToString() //callScreeningComponentName
-        )), eq(USER_CHOSEN_CALL_SCREENING.getPackageName()));
+        verify(mCallback).onCallScreeningFilterComplete(eq(mCall), eq(new Builder()
+                        .setShouldAllowCall(false)
+                        .setShouldReject(true)
+                        .setShouldAddToCallLog(true)
+                        .setShouldShowNotification(true)
+                        .setCallBlockReason(Calls.BLOCK_REASON_CALL_SCREENING_SERVICE)
+                        .setCallScreeningAppName(USER_CHOSEN_CALL_SCREENING_APP_NAME)
+                        .setCallScreeningComponentName(USER_CHOSEN_CALL_SCREENING.flattenToString())
+                        .build()),
+                eq(USER_CHOSEN_CALL_SCREENING.getPackageName()));
     }
 
     private ServiceConnection verifyBindingIntent() {
diff --git a/tests/src/com/android/server/telecom/tests/CallsManagerTest.java b/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
index 783ad8c..065c9b8 100644
--- a/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
@@ -83,6 +83,7 @@
 import com.android.server.telecom.bluetooth.BluetoothRouteManager;
 import com.android.server.telecom.bluetooth.BluetoothStateReceiver;
 import com.android.server.telecom.callfiltering.IncomingCallFilter;
+import com.android.server.telecom.ui.AudioProcessingNotification;
 
 import org.junit.After;
 import org.junit.Before;
@@ -163,6 +164,7 @@
     @Mock private EmergencyCallHelper mEmergencyCallHelper;
     @Mock private InCallTonePlayer.ToneGeneratorFactory mToneGeneratorFactory;
     @Mock private ClockProxy mClockProxy;
+    @Mock private AudioProcessingNotification mAudioProcessingNotification;
     @Mock private InCallControllerFactory mInCallControllerFactory;
     @Mock private InCallController mInCallController;
     @Mock private ConnectionServiceFocusManager mConnectionSvrFocusMgr;
@@ -221,6 +223,7 @@
                 mEmergencyCallHelper,
                 mToneGeneratorFactory,
                 mClockProxy,
+                mAudioProcessingNotification,
                 mBluetoothStateReceiver,
                 mCallAudioRouteStateMachineFactory,
                 mCallAudioModeStateMachineFactory,
@@ -535,13 +538,13 @@
     @Test
     public void testUnholdCallWhenOngoingCallCanNotBeHeldAndFromDifferentConnectionService() {
         // GIVEN a CallsManager with ongoing call, and this call can not be held
-        Call ongoingCall = addSpyCall(SIM_1_HANDLE);
+        Call ongoingCall = addSpyCall(SIM_1_HANDLE, CallState.ACTIVE);
         doReturn(false).when(ongoingCall).can(Connection.CAPABILITY_HOLD);
         doReturn(false).when(ongoingCall).can(Connection.CAPABILITY_SUPPORT_HOLD);
         when(mConnectionSvrFocusMgr.getCurrentFocusCall()).thenReturn(ongoingCall);
 
         // and a held call which has different ConnectionService
-        Call heldCall = addSpyCall(VOIP_1_HANDLE);
+        Call heldCall = addSpyCall(VOIP_1_HANDLE, CallState.ON_HOLD);
 
         // WHEN unhold the held call
         mCallsManager.unholdCall(heldCall);
@@ -559,14 +562,14 @@
     public void testUnholdCallWhenOngoingEmergCallCanNotBeHeldAndFromDifferentConnectionService() {
         // GIVEN a CallsManager with ongoing call, and this call can not be held, but it also an
         // emergency call.
-        Call ongoingCall = addSpyCall(SIM_1_HANDLE);
+        Call ongoingCall = addSpyCall(SIM_1_HANDLE, CallState.ACTIVE);
         doReturn(false).when(ongoingCall).can(Connection.CAPABILITY_HOLD);
         doReturn(false).when(ongoingCall).can(Connection.CAPABILITY_SUPPORT_HOLD);
         doReturn(true).when(ongoingCall).isEmergencyCall();
         when(mConnectionSvrFocusMgr.getCurrentFocusCall()).thenReturn(ongoingCall);
 
         // and a held call which has different ConnectionService
-        Call heldCall = addSpyCall(VOIP_1_HANDLE);
+        Call heldCall = addSpyCall(VOIP_1_HANDLE, CallState.ON_HOLD);
 
         // WHEN unhold the held call
         mCallsManager.unholdCall(heldCall);
@@ -582,13 +585,13 @@
     @Test
     public void testUnholdCallWhenOngoingCallCanNotBeHeldAndHasSameConnectionService() {
         // GIVEN a CallsManager with ongoing call, and this call can not be held
-        Call ongoingCall = addSpyCall(SIM_1_HANDLE);
+        Call ongoingCall = addSpyCall(SIM_1_HANDLE, CallState.ACTIVE);
         doReturn(false).when(ongoingCall).can(Connection.CAPABILITY_HOLD);
         doReturn(true).when(ongoingCall).can(Connection.CAPABILITY_SUPPORT_HOLD);
         when(mConnectionSvrFocusMgr.getCurrentFocusCall()).thenReturn(ongoingCall);
 
         // and a held call which has the same ConnectionService
-        Call heldCall = addSpyCall(SIM_2_HANDLE);
+        Call heldCall = addSpyCall(SIM_2_HANDLE, CallState.ON_HOLD);
 
         // WHEN unhold the held call
         mCallsManager.unholdCall(heldCall);
@@ -611,7 +614,7 @@
         when(mConnectionSvrFocusMgr.getCurrentFocusCall()).thenReturn(ongoingCall);
 
         // WHEN answer an incoming call
-        Call incomingCall = addSpyCall();
+        Call incomingCall = addSpyCall(CallState.RINGING);
         mCallsManager.answerCall(incomingCall, VideoProfile.STATE_AUDIO_ONLY);
 
         // THEN the ongoing call is held and the focus request for incoming call is sent
@@ -626,12 +629,12 @@
     @Test
     public void testAnswerCallWhenOngoingHasSameConnectionService() {
         // GIVEN a CallsManager with ongoing call, and this call can not be held
-        Call ongoingCall = addSpyCall(SIM_1_HANDLE);
+        Call ongoingCall = addSpyCall(SIM_1_HANDLE, CallState.ACTIVE);
         doReturn(false).when(ongoingCall).can(Connection.CAPABILITY_HOLD);
         when(mConnectionSvrFocusMgr.getCurrentFocusCall()).thenReturn(ongoingCall);
 
         // WHEN answer an incoming call
-        Call incomingCall = addSpyCall(VOIP_1_HANDLE);
+        Call incomingCall = addSpyCall(VOIP_1_HANDLE, CallState.RINGING);
         mCallsManager.answerCall(incomingCall, VideoProfile.STATE_AUDIO_ONLY);
 
         // THEN nothing happened on the ongoing call and the focus request for incoming call is sent
@@ -645,13 +648,13 @@
     @Test
     public void testAnswerCallWhenOngoingHasDifferentConnectionService() {
         // GIVEN a CallsManager with ongoing call, and this call can not be held
-        Call ongoingCall = addSpyCall(SIM_1_HANDLE);
+        Call ongoingCall = addSpyCall(SIM_1_HANDLE, CallState.ACTIVE);
         doReturn(false).when(ongoingCall).can(Connection.CAPABILITY_HOLD);
         doReturn(true).when(ongoingCall).can(Connection.CAPABILITY_SUPPORT_HOLD);
         when(mConnectionSvrFocusMgr.getCurrentFocusCall()).thenReturn(ongoingCall);
 
         // WHEN answer an incoming call
-        Call incomingCall = addSpyCall(VOIP_1_HANDLE);
+        Call incomingCall = addSpyCall(VOIP_1_HANDLE, CallState.RINGING);
         mCallsManager.answerCall(incomingCall, VideoProfile.STATE_AUDIO_ONLY);
 
         // THEN the ongoing call is disconnected and the focus request for incoming call is sent
@@ -666,14 +669,14 @@
     @Test
     public void testAnswerCallWhenOngoingHasDifferentConnectionServiceButIsEmerg() {
         // GIVEN a CallsManager with ongoing call, and this call can not be held
-        Call ongoingCall = addSpyCall(SIM_1_HANDLE);
+        Call ongoingCall = addSpyCall(SIM_1_HANDLE, CallState.ACTIVE);
         doReturn(false).when(ongoingCall).can(Connection.CAPABILITY_HOLD);
         doReturn(true).when(ongoingCall).can(Connection.CAPABILITY_SUPPORT_HOLD);
         doReturn(true).when(ongoingCall).isEmergencyCall();
         when(mConnectionSvrFocusMgr.getCurrentFocusCall()).thenReturn(ongoingCall);
 
         // WHEN answer an incoming call
-        Call incomingCall = addSpyCall(VOIP_1_HANDLE);
+        Call incomingCall = addSpyCall(VOIP_1_HANDLE, CallState.RINGING);
         mCallsManager.answerCall(incomingCall, VideoProfile.STATE_AUDIO_ONLY);
 
         // THEN the ongoing call is not disconnected
@@ -689,21 +692,21 @@
     public void testAnswerCallWhenMultipleHeldCallsExisted() {
         // Given an ongoing call and held call with the ConnectionService connSvr1. The
         // ConnectionService connSvr1 can handle one held call
-        Call ongoingCall = addSpyCall(SIM_1_HANDLE);
+        Call ongoingCall = addSpyCall(SIM_1_HANDLE, CallState.ACTIVE);
         doReturn(false).when(ongoingCall).can(Connection.CAPABILITY_HOLD);
         doReturn(true).when(ongoingCall).can(Connection.CAPABILITY_SUPPORT_HOLD);
         doReturn(CallState.ACTIVE).when(ongoingCall).getState();
         when(mConnectionSvrFocusMgr.getCurrentFocusCall()).thenReturn(ongoingCall);
 
-        Call heldCall = addSpyCall(SIM_1_HANDLE);
+        Call heldCall = addSpyCall(SIM_1_HANDLE, CallState.ON_HOLD);
         doReturn(CallState.ON_HOLD).when(heldCall).getState();
 
         // and other held call has difference ConnectionService
-        Call heldCall2 = addSpyCall(VOIP_1_HANDLE);
+        Call heldCall2 = addSpyCall(VOIP_1_HANDLE, CallState.ON_HOLD);
         doReturn(CallState.ON_HOLD).when(heldCall2).getState();
 
         // WHEN answer an incoming call which ConnectionService is connSvr1
-        Call incomingCall = addSpyCall(SIM_1_HANDLE);
+        Call incomingCall = addSpyCall(SIM_1_HANDLE, CallState.RINGING);
         doReturn(true).when(incomingCall).can(Connection.CAPABILITY_SUPPORT_HOLD);
         mCallsManager.answerCall(incomingCall, VideoProfile.STATE_AUDIO_ONLY);
 
@@ -729,7 +732,7 @@
         // GIVEN a CallsManager with no ongoing call.
 
         // WHEN answer an incoming call
-        Call incomingCall = addSpyCall();
+        Call incomingCall = addSpyCall(CallState.RINGING);
         mCallsManager.answerCall(incomingCall, VideoProfile.STATE_AUDIO_ONLY);
 
         // THEN the focus request for incoming call is sent
@@ -745,7 +748,7 @@
         // GIVEN a CallsManager with no ongoing call.
 
         // WHEN answer an already active call
-        Call incomingCall = addSpyCall();
+        Call incomingCall = addSpyCall(CallState.RINGING);
         mCallsManager.answerCall(incomingCall, VideoProfile.STATE_AUDIO_ONLY);
 
         // THEN the focus request for incoming call is sent
@@ -754,21 +757,21 @@
         // and the incoming call is answered.
         verify(incomingCall).answer(VideoProfile.STATE_AUDIO_ONLY);
 
-        // and the incoming call's state is still ACTIVE
-        assertEquals(CallState.ACTIVE, incomingCall.getState());
+        // and the incoming call's state is now ANSWERED
+        assertEquals(CallState.ANSWERED, incomingCall.getState());
     }
 
     @SmallTest
     @Test
     public void testSetActiveCallWhenOngoingCallCanNotBeHeldAndFromDifferentConnectionService() {
         // GIVEN a CallsManager with ongoing call, and this call can not be held
-        Call ongoingCall = addSpyCall(SIM_1_HANDLE);
+        Call ongoingCall = addSpyCall(SIM_1_HANDLE, CallState.ACTIVE);
         doReturn(false).when(ongoingCall).can(Connection.CAPABILITY_HOLD);
         doReturn(false).when(ongoingCall).can(Connection.CAPABILITY_SUPPORT_HOLD);
         doReturn(ongoingCall).when(mConnectionSvrFocusMgr).getCurrentFocusCall();
 
         // and a new self-managed call which has different ConnectionService
-        Call newCall = addSpyCall(VOIP_1_HANDLE);
+        Call newCall = addSpyCall(VOIP_1_HANDLE, CallState.ACTIVE);
         doReturn(true).when(newCall).isSelfManaged();
 
         // WHEN active the new call
@@ -786,13 +789,13 @@
     @Test
     public void testSetActiveCallWhenOngoingCallCanNotBeHeldAndHasSameConnectionService() {
         // GIVEN a CallsManager with ongoing call, and this call can not be held
-        Call ongoingCall = addSpyCall(SIM_1_HANDLE);
+        Call ongoingCall = addSpyCall(SIM_1_HANDLE, CallState.ACTIVE);
         doReturn(false).when(ongoingCall).can(Connection.CAPABILITY_HOLD);
         doReturn(false).when(ongoingCall).can(Connection.CAPABILITY_SUPPORT_HOLD);
         when(mConnectionSvrFocusMgr.getCurrentFocusCall()).thenReturn(ongoingCall);
 
         // and a new self-managed call which has the same ConnectionService
-        Call newCall = addSpyCall(SIM_1_HANDLE);
+        Call newCall = addSpyCall(SIM_1_HANDLE, CallState.ACTIVE);
         doReturn(true).when(newCall).isSelfManaged();
 
         // WHEN active the new call
@@ -834,7 +837,7 @@
     @Test
     public void testDisconnectDialingCallOnIncoming() {
         // GIVEN a CallsManager with a self-managed call which is dialing, and this call can be held
-        Call ongoingCall = addSpyCall(SELF_MANAGED_HANDLE);
+        Call ongoingCall = addSpyCall(SELF_MANAGED_HANDLE, CallState.DIALING);
         ongoingCall.setState(CallState.DIALING, "test");
         doReturn(true).when(ongoingCall).can(Connection.CAPABILITY_HOLD);
         doReturn(true).when(ongoingCall).can(Connection.CAPABILITY_SUPPORT_HOLD);
@@ -867,7 +870,7 @@
     @Test
     public void testNoFilteringOfSelfManagedCalls() {
         // GIVEN an incoming call which is self managed.
-        Call incomingCall = addSpyCall(SELF_MANAGED_HANDLE);
+        Call incomingCall = addSpyCall(SELF_MANAGED_HANDLE, CallState.NEW);
         doReturn(false).when(incomingCall).can(Connection.CAPABILITY_HOLD);
         doReturn(false).when(incomingCall).can(Connection.CAPABILITY_SUPPORT_HOLD);
         doReturn(true).when(incomingCall).isSelfManaged();
@@ -984,7 +987,7 @@
     @Test
     public void testNoFilteringOfCallsWhenPhoneAccountRequestsSkipped() {
         // GIVEN an incoming call which is from a PhoneAccount that requested to skip filtering.
-        Call incomingCall = addSpyCall(SIM_1_HANDLE);
+        Call incomingCall = addSpyCall(SIM_1_HANDLE, CallState.NEW);
         Bundle extras = new Bundle();
         extras.putBoolean(PhoneAccount.EXTRA_SKIP_CALL_FILTERING, true);
         PhoneAccount skipRequestedAccount = new PhoneAccount.Builder(SIM_2_HANDLE, "Skipper")
@@ -1097,10 +1100,14 @@
     }
 
     private Call addSpyCall() {
-        return addSpyCall(SIM_2_HANDLE);
+        return addSpyCall(SIM_2_HANDLE, CallState.ACTIVE);
     }
 
-    private Call addSpyCall(PhoneAccountHandle targetPhoneAccount) {
+    private Call addSpyCall(int initialState) {
+        return addSpyCall(SIM_2_HANDLE, initialState);
+    }
+
+    private Call addSpyCall(PhoneAccountHandle targetPhoneAccount, int initialState) {
         Call ongoingCall = new Call(String.format("TC@%d", sCallId++), /* callId */
                 mContext,
                 mCallsManager,
@@ -1115,7 +1122,7 @@
                 false /* shouldAttachToExistingConnection*/,
                 false /* isConference */,
                 mClockProxy);
-        ongoingCall.setState(CallState.ACTIVE, "just cuz");
+        ongoingCall.setState(initialState, "just cuz");
         Call callSpy = Mockito.spy(ongoingCall);
 
         // Mocks some methods to not call the real method.
diff --git a/tests/src/com/android/server/telecom/tests/DirectToVoicemailCallFilterTest.java b/tests/src/com/android/server/telecom/tests/DirectToVoicemailCallFilterTest.java
index 551165d..e62a9fc 100644
--- a/tests/src/com/android/server/telecom/tests/DirectToVoicemailCallFilterTest.java
+++ b/tests/src/com/android/server/telecom/tests/DirectToVoicemailCallFilterTest.java
@@ -17,14 +17,14 @@
 package com.android.server.telecom.tests;
 
 import android.net.Uri;
-import android.provider.CallLog;
+import android.provider.CallLog.Calls;
 import android.test.suitebuilder.annotation.SmallTest;
 
 import com.android.internal.telephony.CallerInfo;
 import com.android.server.telecom.Call;
 import com.android.server.telecom.callfiltering.CallFilterResultCallback;
 import com.android.server.telecom.CallerInfoLookupHelper;
-import com.android.server.telecom.callfiltering.CallFilteringResult;
+import com.android.server.telecom.callfiltering.CallFilteringResult.Builder;
 import com.android.server.telecom.callfiltering.DirectToVoicemailCallFilter;
 
 import org.junit.After;
@@ -68,16 +68,15 @@
         callerInfo.shouldSendToVoicemail = true;
 
         queryListener.onCallerInfoQueryComplete(TEST_HANDLE, callerInfo);
-        verify(mCallback).onCallFilteringComplete(mCall,
-                new CallFilteringResult(
-                        false, // shouldAllowCall
-                        true, // shouldReject
-                        true, // shouldAddToCallLog
-                        true, // shouldShowNotification
-                        CallLog.Calls.BLOCK_REASON_DIRECT_TO_VOICEMAIL, //callBlockReason
-                        null, //callScreeningAppName
-                        null // callScreeningComponentName
-                ));
+        verify(mCallback).onCallFilteringComplete(mCall, new Builder()
+                .setShouldAllowCall(false)
+                .setShouldReject(true)
+                .setShouldAddToCallLog(true)
+                .setShouldShowNotification(true)
+                .setCallBlockReason(Calls.BLOCK_REASON_DIRECT_TO_VOICEMAIL)
+                .setCallScreeningAppName(null)
+                .setCallScreeningComponentName(null)
+                .build());
     }
 
     @SmallTest
@@ -89,13 +88,12 @@
         callerInfo.shouldSendToVoicemail = false;
 
         queryListener.onCallerInfoQueryComplete(TEST_HANDLE, callerInfo);
-        verify(mCallback).onCallFilteringComplete(mCall,
-                new CallFilteringResult(
-                        true, // shouldAllowCall
-                        false, // shouldReject
-                        true, // shouldAddToCallLog
-                        true // shouldShowNotification
-                ));
+        verify(mCallback).onCallFilteringComplete(mCall, new Builder()
+                .setShouldAllowCall(true)
+                .setShouldReject(false)
+                .setShouldAddToCallLog(true)
+                .setShouldShowNotification(true)
+                .build());
     }
 
     @SmallTest
@@ -104,13 +102,12 @@
         CallerInfoLookupHelper.OnQueryCompleteListener queryListener = verifyLookupStart(null);
 
         queryListener.onCallerInfoQueryComplete(null, null);
-        verify(mCallback).onCallFilteringComplete(mCall,
-                new CallFilteringResult(
-                        true, // shouldAllowCall
-                        false, // shouldReject
-                        true, // shouldAddToCallLog
-                        true // shouldShowNotification
-                ));
+        verify(mCallback).onCallFilteringComplete(mCall, new Builder()
+                .setShouldAllowCall(true)
+                .setShouldReject(false)
+                .setShouldAddToCallLog(true)
+                .setShouldShowNotification(true)
+                .build());
     }
 
     private CallerInfoLookupHelper.OnQueryCompleteListener verifyLookupStart() {
diff --git a/tests/src/com/android/server/telecom/tests/InCallWakeLockControllerTest.java b/tests/src/com/android/server/telecom/tests/InCallWakeLockControllerTest.java
index fd581f3..fd7505b 100644
--- a/tests/src/com/android/server/telecom/tests/InCallWakeLockControllerTest.java
+++ b/tests/src/com/android/server/telecom/tests/InCallWakeLockControllerTest.java
@@ -66,7 +66,7 @@
     @SmallTest
     @Test
     public void testRingingCallAdded() throws Exception {
-        when(mCallsManager.getRingingCall()).thenReturn(mCall);
+        when(mCallsManager.getRingingOrSimulatedRingingCall()).thenReturn(mCall);
         when(mWakeLockAdapter.isHeld()).thenReturn(false);
 
         mInCallWakeLockController.onCallAdded(mCall);
@@ -77,7 +77,7 @@
     @SmallTest
     @Test
     public void testNonRingingCallAdded() throws Exception {
-        when(mCallsManager.getRingingCall()).thenReturn(null);
+        when(mCallsManager.getRingingOrSimulatedRingingCall()).thenReturn(null);
         when(mWakeLockAdapter.isHeld()).thenReturn(false);
 
         mInCallWakeLockController.onCallAdded(mCall);
@@ -88,7 +88,7 @@
     @SmallTest
     @Test
     public void testRingingCallTransition() throws Exception {
-        when(mCallsManager.getRingingCall()).thenReturn(mCall);
+        when(mCallsManager.getRingingOrSimulatedRingingCall()).thenReturn(mCall);
         when(mWakeLockAdapter.isHeld()).thenReturn(false);
 
         mInCallWakeLockController.onCallStateChanged(mCall, CallState.NEW, CallState.RINGING);
@@ -99,7 +99,7 @@
     @SmallTest
     @Test
     public void testRingingCallRemoved() throws Exception {
-        when(mCallsManager.getRingingCall()).thenReturn(null);
+        when(mCallsManager.getRingingOrSimulatedRingingCall()).thenReturn(null);
         when(mWakeLockAdapter.isHeld()).thenReturn(false);
 
         mInCallWakeLockController.onCallRemoved(mCall);
@@ -110,7 +110,7 @@
     @SmallTest
     @Test
     public void testWakeLockReleased() throws Exception {
-        when(mCallsManager.getRingingCall()).thenReturn(null);
+        when(mCallsManager.getRingingOrSimulatedRingingCall()).thenReturn(null);
         when(mWakeLockAdapter.isHeld()).thenReturn(true);
 
         mInCallWakeLockController.onCallRemoved(mCall);
@@ -121,7 +121,7 @@
     @SmallTest
     @Test
     public void testAcquireWakeLockWhenHeld() throws Exception {
-        when(mCallsManager.getRingingCall()).thenReturn(mCall);
+        when(mCallsManager.getRingingOrSimulatedRingingCall()).thenReturn(mCall);
         when(mWakeLockAdapter.isHeld()).thenReturn(true);
 
         mInCallWakeLockController.onCallAdded(mock(Call.class));
diff --git a/tests/src/com/android/server/telecom/tests/IncomingCallFilterTest.java b/tests/src/com/android/server/telecom/tests/IncomingCallFilterTest.java
index 76341b2..8e2d11e 100644
--- a/tests/src/com/android/server/telecom/tests/IncomingCallFilterTest.java
+++ b/tests/src/com/android/server/telecom/tests/IncomingCallFilterTest.java
@@ -17,17 +17,17 @@
 package com.android.server.telecom.tests;
 
 import android.content.ContentResolver;
-import android.content.IContentProvider;
 import android.net.Uri;
 import android.os.Handler;
 import android.os.Looper;
-import android.provider.CallLog;
+import android.provider.CallLog.Calls;
 import android.test.suitebuilder.annotation.SmallTest;
 
 import com.android.server.telecom.Call;
 import com.android.server.telecom.Timeouts;
 import com.android.server.telecom.callfiltering.CallFilterResultCallback;
 import com.android.server.telecom.callfiltering.CallFilteringResult;
+import com.android.server.telecom.callfiltering.CallFilteringResult.Builder;
 import com.android.server.telecom.callfiltering.IncomingCallFilter;
 import com.android.server.telecom.TelecomSystem;
 
@@ -68,46 +68,47 @@
     private static final long SHORT_TIMEOUT = 100;
 
     private static final CallFilteringResult PASS_CALL_RESULT =
-            new CallFilteringResult(
-                    true, // shouldAllowCall
-                    false, // shouldReject
-                    true, // shouldAddToCallLog
-                    true // shouldShowNotification
-            );
+            new Builder()
+                    .setShouldAllowCall(true)
+                    .setShouldReject(false)
+                    .setShouldAddToCallLog(true)
+                    .setShouldShowNotification(true)
+                    .build();
 
     private static final CallFilteringResult ASYNC_BLOCK_CHECK_BLOCK_RESULT =
-            new CallFilteringResult(
-                    false, // shouldAllowCall
-                    true, // shouldReject
-                    true, // shouldAddToCallLog
-                    false, // shouldShowNotification
-                    CallLog.Calls.BLOCK_REASON_BLOCKED_NUMBER, //callBlockReason
-                    null, //callScreeningAppName
-                    null //callScreeningComponentName
-            );
+            new Builder()
+                    .setShouldAllowCall(false)
+                    .setShouldReject(true)
+                    .setShouldAddToCallLog(true)
+                    .setShouldShowNotification(false)
+                    .setCallBlockReason(Calls.BLOCK_REASON_BLOCKED_NUMBER)
+                    .setCallScreeningAppName(null)
+                    .setCallScreeningComponentName(null)
+                    .build();
 
     private static final CallFilteringResult DIRECT_TO_VOICEMAIL_CALL_BLOCK_RESULT =
-            new CallFilteringResult(
-                    false, // shouldAllowCall
-                    true, // shouldReject
-                    true, // shouldAddToCallLog
-                    true, // shouldShowNotification
-                    CallLog.Calls.BLOCK_REASON_DIRECT_TO_VOICEMAIL, //callBlockReason
-                    null, //callScreeningAppName
-                    null //callScreeningComponentName
-            );
+            new Builder()
+                    .setShouldAllowCall(false)
+                    .setShouldReject(true)
+                    .setShouldAddToCallLog(true)
+                    .setShouldShowNotification(true)
+                    .setCallBlockReason(Calls.BLOCK_REASON_DIRECT_TO_VOICEMAIL)
+                    .setCallScreeningAppName(null)
+                    .setCallScreeningComponentName(null)
+                    .build();
 
     private static final CallFilteringResult CALL_SCREENING_SERVICE_BLOCK_RESULT =
-            new CallFilteringResult(
-                    false, // shouldAllowCall
-                    true, // shouldReject
-                    false, // shouldAddToCallLog
-                    true, // shouldShowNotification
-                    CallLog.Calls.BLOCK_REASON_CALL_SCREENING_SERVICE, //callBlockReason
-                    "com.android.thirdparty", //callScreeningAppName
-                    "com.android.thirdparty/com.android.thirdparty.callscreeningserviceimpl"
-                    //callScreeningComponentName
-            );
+            new Builder()
+                    .setShouldAllowCall(false)
+                    .setShouldReject(true)
+                    .setShouldAddToCallLog(false)
+                    .setShouldShowNotification(true)
+                    .setCallBlockReason(Calls.BLOCK_REASON_CALL_SCREENING_SERVICE)
+                    .setCallScreeningAppName("com.android.thirdparty")
+                    .setCallScreeningComponentName(
+                            "com.android.thirdparty/"
+                                    + "com.android.thirdparty.callscreeningserviceimpl")
+                    .build();
 
     private static final CallFilteringResult DEFAULT_RESULT = PASS_CALL_RESULT;
     private Handler mHandler = new Handler(Looper.getMainLooper());
@@ -207,16 +208,15 @@
         testFilter.onCallFilteringComplete(mCall, DIRECT_TO_VOICEMAIL_CALL_BLOCK_RESULT);
         testFilter.onCallFilteringComplete(mCall, CALL_SCREENING_SERVICE_BLOCK_RESULT);
         waitForHandlerAction(testFilter.getHandler(), SHORT_TIMEOUT * 2);
-        verify(mResultCallback).onCallFilteringComplete(eq(mCall), eq(
-                new CallFilteringResult(
-                        false, // shouldAllowCall
-                        true, // shouldReject
-                        false, // shouldAddToCallLog
-                        false, // shouldShowNotification
-                        CallLog.Calls.BLOCK_REASON_BLOCKED_NUMBER, //callBlockReason
-                        null, //callScreeningAppName
-                        null //callScreeningComponentName
-                )));
+        verify(mResultCallback).onCallFilteringComplete(eq(mCall), eq(new Builder()
+                .setShouldAllowCall(false)
+                .setShouldReject(true)
+                .setShouldAddToCallLog(false)
+                .setShouldShowNotification(false)
+                .setCallBlockReason(Calls.BLOCK_REASON_BLOCKED_NUMBER)
+                .setCallScreeningAppName(null)
+                .setCallScreeningComponentName(null)
+                .build()));
     }
 
     @SmallTest
@@ -239,16 +239,15 @@
         testFilter.onCallFilteringComplete(mCall, DIRECT_TO_VOICEMAIL_CALL_BLOCK_RESULT);
         testFilter.onCallFilteringComplete(mCall, CALL_SCREENING_SERVICE_BLOCK_RESULT);
         waitForHandlerAction(testFilter.getHandler(), SHORT_TIMEOUT * 2);
-        verify(mResultCallback).onCallFilteringComplete(eq(mCall), eq(
-                new CallFilteringResult(
-                        false, // shouldAllowCall
-                        true, // shouldReject
-                        false, // shouldAddToCallLog
-                        true, // shouldShowNotification
-                        CallLog.Calls.BLOCK_REASON_DIRECT_TO_VOICEMAIL, //callBlockReason
-                        null, ////callScreeningAppName
-                        null ////callScreeningComponentName
-                )));
+        verify(mResultCallback).onCallFilteringComplete(eq(mCall), eq(new Builder()
+                .setShouldAllowCall(false)
+                .setShouldReject(true)
+                .setShouldAddToCallLog(false)
+                .setShouldShowNotification(true)
+                .setCallBlockReason(Calls.BLOCK_REASON_DIRECT_TO_VOICEMAIL)
+                .setCallScreeningAppName(null)
+                .setCallScreeningComponentName(null)
+                .build()));
     }
 
     @SmallTest
@@ -268,17 +267,16 @@
         testFilter.onCallFilteringComplete(mCall, PASS_CALL_RESULT);
         testFilter.onCallFilteringComplete(mCall, CALL_SCREENING_SERVICE_BLOCK_RESULT);
         waitForHandlerAction(testFilter.getHandler(), SHORT_TIMEOUT * 2);
-        verify(mResultCallback).onCallFilteringComplete(eq(mCall), eq(
-                new CallFilteringResult(
-                        false, // shouldAllowCall
-                        true, // shouldReject
-                        false, // shouldAddToCallLog
-                        true, // shouldShowNotification
-                        CallLog.Calls.BLOCK_REASON_CALL_SCREENING_SERVICE, //callBlockReason
-                        "com.android.thirdparty", //callScreeningAppName
-                        "com.android.thirdparty/com.android.thirdparty.callscreeningserviceimpl"
-                        //callScreeningComponentName
-                )));
+        verify(mResultCallback).onCallFilteringComplete(eq(mCall), eq(new Builder()
+                .setShouldAllowCall(false)
+                .setShouldReject(true)
+                .setShouldAddToCallLog(false)
+                .setShouldShowNotification(true)
+                .setCallBlockReason(Calls.BLOCK_REASON_CALL_SCREENING_SERVICE)
+                .setCallScreeningAppName("com.android.thirdparty")
+                .setCallScreeningComponentName(
+                        "com.android.thirdparty/com.android.thirdparty.callscreeningserviceimpl")
+                .build()));
     }
 
     @SmallTest