Add audio processing notification and testapp code am: ae3af1a30b am: 974406e652 am: 187be5dce9
am: 98d0d8eeac

Change-Id: I4700fec213636c44c64ff83b95b5553ac1fff22b
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 8a6207f..86e058e 100644
--- a/src/com/android/server/telecom/Call.java
+++ b/src/com/android/server/telecom/Call.java
@@ -545,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;
@@ -2043,6 +2049,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 33c7db9..e96a61a 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;
@@ -441,6 +442,7 @@
             EmergencyCallHelper emergencyCallHelper,
             InCallTonePlayer.ToneGeneratorFactory toneGeneratorFactory,
             ClockProxy clockProxy,
+            AudioProcessingNotification audioProcessingNotification,
             BluetoothStateReceiver bluetoothStateReceiver,
             CallAudioRouteStateMachine.Factory callAudioRouteStateMachineFactory,
             CallAudioModeStateMachine.Factory callAudioModeStateMachineFactory,
@@ -533,6 +535,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);
@@ -693,6 +696,7 @@
             } else if (result.shouldScreenViaAudio) {
                 Log.i(this, "onCallFilteringCompleted: starting background audio processing");
                 answerCallForAudioProcessing(incomingCall);
+                incomingCall.setAudioProcessingRequestingApp(result.mCallScreeningAppName);
             } else {
                 addCall(incomingCall);
             }
@@ -2021,7 +2025,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;
@@ -2030,6 +2034,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
@@ -2037,9 +2053,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 bf209d5..774d0b1 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;
@@ -268,6 +269,9 @@
             }
         };
 
+        AudioProcessingNotification audioProcessingNotification =
+                new AudioProcessingNotification(mContext);
+
         mCallsManager = new CallsManager(
                 mContext,
                 mLock,
@@ -289,6 +293,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 ddb87e9..151bf37 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,