Add audio processing notification and testapp code

Implement the persistent notification during audio processing and add
code to the testapp to allow manual testing of the background call
screening functionality.

Bug: 140317205
Test: manual
Change-Id: I15cb7fc064bbcccc5c08b76a704244a502d13268
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/Call.java b/src/com/android/server/telecom/Call.java
index 98eceb9..8d2c193 100644
--- a/src/com/android/server/telecom/Call.java
+++ b/src/com/android/server/telecom/Call.java
@@ -544,6 +544,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;
@@ -2041,6 +2047,14 @@
         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.
      *
diff --git a/src/com/android/server/telecom/CallsManager.java b/src/com/android/server/telecom/CallsManager.java
index 66132d9..5a82cfd 100644
--- a/src/com/android/server/telecom/CallsManager.java
+++ b/src/com/android/server/telecom/CallsManager.java
@@ -86,6 +86,7 @@
 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;
@@ -440,6 +441,7 @@
             EmergencyCallHelper emergencyCallHelper,
             InCallTonePlayer.ToneGeneratorFactory toneGeneratorFactory,
             ClockProxy clockProxy,
+            AudioProcessingNotification audioProcessingNotification,
             BluetoothStateReceiver bluetoothStateReceiver,
             CallAudioRouteStateMachine.Factory callAudioRouteStateMachineFactory,
             CallAudioModeStateMachine.Factory callAudioModeStateMachineFactory,
@@ -532,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);
@@ -692,6 +695,7 @@
             } else if (result.shouldScreenViaAudio) {
                 Log.i(this, "onCallFilteringCompleted: starting background audio processing");
                 answerCallForAudioProcessing(incomingCall);
+                incomingCall.setAudioProcessingRequestingApp(result.mCallScreeningAppName);
             } else {
                 addCall(incomingCall);
             }
@@ -2028,7 +2032,7 @@
      *
      * @param call The call to manipulate
      */
-    public void enterBackgroundAudioProcessing(Call call) {
+    public void enterBackgroundAudioProcessing(Call call, String requestingPackageName) {
         if (!mCalls.contains(call)) {
             Log.w(this, "Trying to exit audio processing on an untracked call");
             return;
@@ -2037,6 +2041,18 @@
         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
@@ -2044,9 +2060,11 @@
             // 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);
         }
     }
 
diff --git a/src/com/android/server/telecom/InCallAdapter.java b/src/com/android/server/telecom/InCallAdapter.java
index 039b446..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) {
@@ -322,13 +321,13 @@
     @Override
     public void enterBackgroundAudioProcessing(String callId) {
         try {
-            Log.startSession(LogUtils.Sessions.ICA_ENTER_AUDIO_PROCESSING, mOwnerComponentName);
+            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);
+                        mCallsManager.enterBackgroundAudioProcessing(call, mOwnerPackageName);
                     } else {
                         Log.w(this, "enterBackgroundAudioProcessing, unknown call id: %s", callId);
                     }
@@ -342,7 +341,7 @@
     @Override
     public void exitBackgroundAudioProcessing(String callId, boolean shouldRing) {
         try {
-            Log.startSession(LogUtils.Sessions.ICA_EXIT_AUDIO_PROCESSING, mOwnerComponentName);
+            Log.startSession(LogUtils.Sessions.ICA_EXIT_AUDIO_PROCESSING, mOwnerPackageName);
             Binder.withCleanCallingIdentity(() -> {
                 synchronized (mLock) {
                     Call call = mCallIdMapper.getCall(callId);
@@ -362,7 +361,7 @@
     @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) {
@@ -385,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) {
@@ -407,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) {
@@ -429,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) {
@@ -451,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) {
@@ -473,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) {
@@ -495,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) {
@@ -517,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) {
@@ -539,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) {
@@ -556,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) {
@@ -657,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/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/CallFilteringResult.java b/src/com/android/server/telecom/callfiltering/CallFilteringResult.java
index afd145e..c17a256 100644
--- a/src/com/android/server/telecom/callfiltering/CallFilteringResult.java
+++ b/src/com/android/server/telecom/callfiltering/CallFilteringResult.java
@@ -144,6 +144,14 @@
                 other.mCallScreeningAppName, other.mCallScreeningComponentName);
         }
 
+        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)
diff --git a/src/com/android/server/telecom/callfiltering/CallScreeningServiceFilter.java b/src/com/android/server/telecom/callfiltering/CallScreeningServiceFilter.java
index 7e0f6b0..a795f73 100644
--- a/src/com/android/server/telecom/callfiltering/CallScreeningServiceFilter.java
+++ b/src/com/android/server/telecom/callfiltering/CallScreeningServiceFilter.java
@@ -182,7 +182,8 @@
         @Override
         public void screenCallFurther(String callId) {
             Log.startSession("CSCR.sCF");
-            Binder.withCleanCallingIdentity(() -> {
+            long token = Binder.clearCallingIdentity();
+            try {
                 synchronized (mTelecomLock) {
                     // This is only allowed if the caller is also the default dialer.
                     if (!mCallsManager.getDefaultDialerCache().isDefaultOrSystemDialer(
@@ -199,13 +200,17 @@
                                 .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();
+            }
         }
     }
 
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..68129f2 100644
--- a/testapps/src/com/android/server/telecom/testapps/TestCallScreeningService.java
+++ b/testapps/src/com/android/server/telecom/testapps/TestCallScreeningService.java
@@ -16,8 +16,7 @@
 
 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;
@@ -30,6 +29,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 +46,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 +85,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..e31c6ba 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);
@@ -213,6 +217,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/CallsManagerTest.java b/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
index 6da4723..b25b4c8 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;
@@ -165,6 +166,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;
@@ -223,6 +225,7 @@
                 mEmergencyCallHelper,
                 mToneGeneratorFactory,
                 mClockProxy,
+                mAudioProcessingNotification,
                 mBluetoothStateReceiver,
                 mCallAudioRouteStateMachineFactory,
                 mCallAudioModeStateMachineFactory,