Merge "Check caller perm before clearing binder identity" into udc-dev
diff --git a/Android.bp b/Android.bp
index 1b422aa..c5141ca 100644
--- a/Android.bp
+++ b/Android.bp
@@ -28,6 +28,9 @@
     static_libs: [
         "androidx.annotation_annotation",
     ],
+    libs: [
+        "services",
+    ],
     resource_dirs: ["res"],
     proto: {
         type: "nano",
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 648b4a9..d42dcff 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -64,6 +64,7 @@
     <uses-permission android:name="android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME"/>
     <uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
     <uses-permission android:name="com.android.phone.permission.ACCESS_LAST_KNOWN_CELL_ID"/>
+    <uses-permission android:name="android.permission.STATUS_BAR_SERVICE" />
 
     <permission android:name="android.permission.BROADCAST_CALLLOG_INFO"
          android:label="Broadcast the call type/duration information"
diff --git a/res/values-te/strings.xml b/res/values-te/strings.xml
index 98115db..5ed2ebe 100644
--- a/res/values-te/strings.xml
+++ b/res/values-te/strings.xml
@@ -51,7 +51,7 @@
     <string name="outgoing_call_error_no_phone_number_supplied" msgid="7665135102566099778">"కాల్ చేయడానికి, చెల్లుబాటు అయ్యే నంబర్‌ను నమోదు చేయండి."</string>
     <string name="duplicate_video_call_not_allowed" msgid="5754746140185781159">"ఈ సమయంలో కాల్‌ను జోడించడం సాధ్యపడదు."</string>
     <string name="no_vm_number" msgid="2179959110602180844">"వాయిస్ మెయిల్ నంబర్ లేదు"</string>
-    <string name="no_vm_number_msg" msgid="1339245731058529388">"సిమ్ కార్డులో వాయిస్ మెయిల్ నంబర్ ఏదీ నిల్వ చేయబడలేదు."</string>
+    <string name="no_vm_number_msg" msgid="1339245731058529388">"సిమ్ కార్డులో వాయిస్ మెయిల్ నంబర్ ఏదీ స్టోరేజ్‌ చేయబడలేదు."</string>
     <string name="add_vm_number_str" msgid="5179510133063168998">"నంబర్‌ను జోడించండి"</string>
     <string name="change_default_dialer_dialog_title" msgid="5861469279421508060">"<xliff:g id="NEW_APP">%s</xliff:g>ను మీ ఆటోమేటిక్ ఫోన్ యాప్‌గా చేయాలా?"</string>
     <string name="change_default_dialer_dialog_affirmative" msgid="8604665314757739550">"ఆటోమేటిక్‌గా సెట్ చేయండి"</string>
diff --git a/res/values-ur/strings.xml b/res/values-ur/strings.xml
index 7033f36..afd8d0a 100644
--- a/res/values-ur/strings.xml
+++ b/res/values-ur/strings.xml
@@ -120,7 +120,7 @@
     <string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="1713632946174016619">"کال مسدود کرنا غیر فعال ہو گیا ہے"</string>
     <string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="6629412508584507377">"ہنگامی کال کی گئی"</string>
     <string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="3140411733995271126">"ہنگامی حالت میں جواب دہندگان کو آپ سے رابطہ کرنے کی اجازت دینے کیلئے کال مسدود کرنا غیر فعال ہو گیا ہے۔"</string>
-    <string name="developer_title" msgid="9146088855661672353">"ٹیلی کام ڈویلپر مینو"</string>
+    <string name="developer_title" msgid="9146088855661672353">"ٹیلی کام ڈویلپر مینیو"</string>
     <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"ہنگامی کال کے دوران کالز نہیں لی جائیں گی۔"</string>
     <string name="cancel" msgid="6733466216239934756">"منسوخ کریں"</string>
     <string name="back" msgid="6915955601805550206">"پیچھے"</string>
diff --git a/src/com/android/server/telecom/Call.java b/src/com/android/server/telecom/Call.java
index e4dc6af..6e5826b 100644
--- a/src/com/android/server/telecom/Call.java
+++ b/src/com/android/server/telecom/Call.java
@@ -39,6 +39,7 @@
 import android.provider.CallLog;
 import android.provider.ContactsContract.Contacts;
 import android.telecom.BluetoothCallQualityReport;
+import android.telecom.CallAttributes;
 import android.telecom.CallAudioState;
 import android.telecom.CallDiagnosticService;
 import android.telecom.CallDiagnostics;
@@ -556,6 +557,25 @@
     private boolean mIsSelfManaged = false;
 
     private boolean mIsTransactionalCall = false;
+    private CallingPackageIdentity mCallingPackageIdentity = new CallingPackageIdentity();
+
+    /**
+     * CallingPackageIdentity is responsible for storing properties about the calling package that
+     * initiated the call. For example, if MyVoipApp requests to add a call with Telecom, we can
+     * store their UID and PID when we are still bound to that package.
+     */
+    public static class CallingPackageIdentity {
+        public int mCallingPackageUid = -1;
+        public int mCallingPackagePid = -1;
+
+        public CallingPackageIdentity() {
+        }
+
+        CallingPackageIdentity(Bundle extras) {
+            mCallingPackageUid = extras.getInt(CallAttributes.CALLER_UID_KEY, -1);
+            mCallingPackagePid = extras.getInt(CallAttributes.CALLER_PID_KEY, -1);
+        }
+    }
 
     /**
      * Indicates whether this call is streaming.
@@ -1831,6 +1851,17 @@
         setConnectionProperties(getConnectionProperties());
     }
 
+    public void setCallingPackageIdentity(Bundle extras) {
+        mCallingPackageIdentity = new CallingPackageIdentity(extras);
+        // These extras should NOT be propagated to Dialer and should be removed.
+        extras.remove(CallAttributes.CALLER_PID_KEY);
+        extras.remove(CallAttributes.CALLER_UID_KEY);
+    }
+
+    public CallingPackageIdentity getCallingPackageIdentity() {
+        return mCallingPackageIdentity;
+    }
+
     public void setTransactionServiceWrapper(TransactionalServiceWrapper service) {
         mTransactionalService = service;
     }
diff --git a/src/com/android/server/telecom/CallsManager.java b/src/com/android/server/telecom/CallsManager.java
index b9a83e5..9503c10 100644
--- a/src/com/android/server/telecom/CallsManager.java
+++ b/src/com/android/server/telecom/CallsManager.java
@@ -136,6 +136,7 @@
 import com.android.server.telecom.ui.IncomingCallNotifier;
 import com.android.server.telecom.ui.ToastFactory;
 import com.android.server.telecom.voip.TransactionManager;
+import com.android.server.telecom.voip.VoipCallMonitor;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -437,6 +438,7 @@
     private final Handler mHandler = new Handler(Looper.getMainLooper());
     private final EmergencyCallHelper mEmergencyCallHelper;
     private final RoleManagerAdapter mRoleManagerAdapter;
+    private final VoipCallMonitor mVoipCallMonitor;
     private final CallEndpointController mCallEndpointController;
     private final CallAnomalyWatchdog mCallAnomalyWatchdog;
 
@@ -642,6 +644,7 @@
         mTransactionManager = transactionManager;
         mBlockedNumbersAdapter = blockedNumbersAdapter;
         mCallStreamingController = new CallStreamingController(mContext, mLock);
+        mVoipCallMonitor = new VoipCallMonitor(mContext, mLock);
 
         mListeners.add(mInCallWakeLockController);
         mListeners.add(statusBarNotifier);
@@ -662,6 +665,9 @@
 
         // this needs to be after the mCallAudioManager
         mListeners.add(mPhoneStateBroadcaster);
+        mListeners.add(mVoipCallMonitor);
+
+        mVoipCallMonitor.startMonitor();
 
         // There is no USER_SWITCHED broadcast for user 0, handle it here explicitly.
         final UserManager userManager = UserManager.get(mContext);
@@ -1399,6 +1405,7 @@
         // set properties for transactional call
         if (extras.containsKey(TelecomManager.TRANSACTION_CALL_ID_KEY)) {
             call.setIsTransactionalCall(true);
+            call.setCallingPackageIdentity(extras);
             call.setConnectionCapabilities(
                     extras.getInt(CallAttributes.CALL_CAPABILITIES_KEY,
                             CallAttributes.SUPPORTS_SET_INACTIVE), true);
@@ -1709,6 +1716,7 @@
 
             if (extras.containsKey(TelecomManager.TRANSACTION_CALL_ID_KEY)) {
                 call.setIsTransactionalCall(true);
+                call.setCallingPackageIdentity(extras);
                 call.setConnectionCapabilities(
                         extras.getInt(CallAttributes.CALL_CAPABILITIES_KEY,
                                 CallAttributes.SUPPORTS_SET_INACTIVE), true);
@@ -6278,6 +6286,10 @@
         return mRinger;
     }
 
+    @VisibleForTesting
+    public VoipCallMonitor getVoipCallMonitor() {
+        return mVoipCallMonitor;
+    }
 
     /**
      * This method should only be used for testing.
diff --git a/src/com/android/server/telecom/ConnectionServiceWrapper.java b/src/com/android/server/telecom/ConnectionServiceWrapper.java
index 59a84f9..cc5932c 100644
--- a/src/com/android/server/telecom/ConnectionServiceWrapper.java
+++ b/src/com/android/server/telecom/ConnectionServiceWrapper.java
@@ -89,7 +89,6 @@
         ConnectionServiceFocusManager.ConnectionServiceFocus {
 
     private static final String TELECOM_ABBREVIATION = "cast";
-
     private CompletableFuture<Pair<Integer, Location>> mQueryLocationFuture = null;
     private @Nullable CancellationSignal mOngoingQueryLocationRequest = null;
     private final ExecutorService mQueryLocationExecutor = Executors.newSingleThreadExecutor();
@@ -1432,7 +1431,11 @@
                 callback.send(0,
                         getQueryLocationErrorResult(QueryLocationException.ERROR_UNSPECIFIED));
             }
+            //make sure we don't pass mock locations diretly, always reset() mock locations
             if (result.second != null) {
+                if(result.second.isMock()) {
+                    result.second.reset();
+                }
                 callback.send(1, getQueryLocationResult(result.second));
             } else {
                 callback.send(0, getQueryLocationErrorResult(result.first));
diff --git a/src/com/android/server/telecom/DefaultDialerCache.java b/src/com/android/server/telecom/DefaultDialerCache.java
index a4a0242..3ce394e 100644
--- a/src/com/android/server/telecom/DefaultDialerCache.java
+++ b/src/com/android/server/telecom/DefaultDialerCache.java
@@ -265,7 +265,7 @@
             if (packageName == null ||
                     Objects.equals(packageName, mCurrentDefaultDialerPerUser.get(userId))) {
                 String newDefaultDialer = refreshCacheForUser(userId);
-                Log.i(LOG_TAG, "Refreshing default dialer for user %d: now %s",
+                Log.v(LOG_TAG, "Refreshing default dialer for user %d: now %s",
                         userId, newDefaultDialer);
             }
         }
diff --git a/src/com/android/server/telecom/PhoneAccountRegistrar.java b/src/com/android/server/telecom/PhoneAccountRegistrar.java
index ca82c49..f5a3450 100644
--- a/src/com/android/server/telecom/PhoneAccountRegistrar.java
+++ b/src/com/android/server/telecom/PhoneAccountRegistrar.java
@@ -1371,10 +1371,6 @@
      * @return {@code True} if the phone account has permission.
      */
     public boolean phoneAccountRequiresBindPermission(PhoneAccountHandle phoneAccountHandle) {
-        if (hasTransactionalCallCapabilities(getPhoneAccountUnchecked(phoneAccountHandle))) {
-            return false;
-        }
-
         List<ResolveInfo> resolveInfos = resolveComponent(phoneAccountHandle);
         if (resolveInfos.isEmpty()) {
             Log.w(this, "phoneAccount %s not found", phoneAccountHandle.getComponentName());
diff --git a/src/com/android/server/telecom/TelecomServiceImpl.java b/src/com/android/server/telecom/TelecomServiceImpl.java
index 5ae37eb..99a8d3d 100644
--- a/src/com/android/server/telecom/TelecomServiceImpl.java
+++ b/src/com/android/server/telecom/TelecomServiceImpl.java
@@ -47,6 +47,7 @@
 import android.content.pm.PackageManager;
 import android.content.pm.ParceledListSlice;
 import android.content.pm.ResolveInfo;
+import android.graphics.drawable.Icon;
 import android.net.Uri;
 import android.os.Binder;
 import android.os.Build;
@@ -193,16 +194,21 @@
                 enforcePhoneAccountIsRegisteredEnabled(handle, handle.getUserHandle());
                 enforceCallingPackage(callingPackage, "addCall");
 
+                // add extras about info used for FGS delegation
+                Bundle extras = new Bundle();
+                extras.putInt(CallAttributes.CALLER_UID_KEY, Binder.getCallingUid());
+                extras.putInt(CallAttributes.CALLER_PID_KEY, Binder.getCallingPid());
+
                 VoipCallTransaction transaction = null;
                 // create transaction based on the call direction
                 switch (callAttributes.getDirection()) {
                     case DIRECTION_OUTGOING:
                         transaction = new OutgoingCallTransaction(callId, mContext, callAttributes,
-                                mCallsManager);
+                                mCallsManager, extras);
                         break;
                     case DIRECTION_INCOMING:
                         transaction = new IncomingCallTransaction(callId, callAttributes,
-                                mCallsManager);
+                                mCallsManager, extras);
                         break;
                     default:
                         throw new IllegalArgumentException(String.format("Invalid Call Direction. "
@@ -772,6 +778,9 @@
                                     .build();
                         }
 
+                        // Validate the profile boundary of the given image URI.
+                        validateAccountIconUserBoundary(account.getIcon());
+
                         final long token = Binder.clearCallingIdentity();
                         try {
                             Log.i(this, "registerPhoneAccount: account=%s",
@@ -3129,4 +3138,22 @@
             mContext.sendBroadcast(intent);
         }
     }
+
+    private void validateAccountIconUserBoundary(Icon icon) {
+        // Refer to Icon#getUriString for context. The URI string is invalid for icons of
+        // incompatible types.
+        if (icon != null && (icon.getType() == Icon.TYPE_URI
+                || icon.getType() == Icon.TYPE_URI_ADAPTIVE_BITMAP)) {
+            String encodedUser = icon.getUri().getEncodedUserInfo();
+            // If there is no encoded user, the URI is calling into the calling user space
+            if (encodedUser != null) {
+                int userId = Integer.parseInt(encodedUser);
+                if (userId != UserHandle.getUserId(Binder.getCallingUid())) {
+                    // If we are transcending the profile boundary, throw an error.
+                    throw new IllegalArgumentException("Attempting to register a phone account with"
+                            + " an image icon belonging to another user.");
+                }
+            }
+        }
+    }
 }
diff --git a/src/com/android/server/telecom/Timeouts.java b/src/com/android/server/telecom/Timeouts.java
index 1ba06ec..c5fdd4c 100644
--- a/src/com/android/server/telecom/Timeouts.java
+++ b/src/com/android/server/telecom/Timeouts.java
@@ -416,7 +416,7 @@
      */
     public static long getNonVoipCallTransitoryStateTimeoutMillis() {
         return DeviceConfig.getLong(DeviceConfig.NAMESPACE_TELEPHONY,
-                TRANSITORY_STATE_NON_VOIP_NORMAL_TIMEOUT_MILLIS, 5000L);
+                TRANSITORY_STATE_NON_VOIP_NORMAL_TIMEOUT_MILLIS, 10000L);
     }
 
     /**
@@ -426,7 +426,7 @@
      */
     public static long getNonVoipEmergencyCallTransitoryStateTimeoutMillis() {
         return DeviceConfig.getLong(DeviceConfig.NAMESPACE_TELEPHONY,
-                TRANSITORY_STATE_NON_VOIP_EMERGENCY_TIMEOUT_MILLIS, 5000L);
+                TRANSITORY_STATE_NON_VOIP_EMERGENCY_TIMEOUT_MILLIS, 10000L);
     }
 
     /**
diff --git a/src/com/android/server/telecom/components/TelecomService.java b/src/com/android/server/telecom/components/TelecomService.java
index efa9417..ef85fc7 100644
--- a/src/com/android/server/telecom/components/TelecomService.java
+++ b/src/com/android/server/telecom/components/TelecomService.java
@@ -214,7 +214,7 @@
                                             .stopFlashNotificationSequence(context);
                                 }
                             },
-                            Executors.newSingleThreadExecutor(),
+                            Executors.newCachedThreadPool(),
                             new BlockedNumbersAdapter() {
                                 @Override
                                 public boolean shouldShowEmergencyCallNotification(Context
diff --git a/src/com/android/server/telecom/voip/IncomingCallTransaction.java b/src/com/android/server/telecom/voip/IncomingCallTransaction.java
index 7413014..c0bb93d 100644
--- a/src/com/android/server/telecom/voip/IncomingCallTransaction.java
+++ b/src/com/android/server/telecom/voip/IncomingCallTransaction.java
@@ -20,7 +20,6 @@
 
 import android.os.Bundle;
 import android.telecom.CallAttributes;
-import android.telecom.CallControl;
 import android.telecom.CallException;
 import android.telecom.TelecomManager;
 import android.util.Log;
@@ -37,15 +36,22 @@
     private final String mCallId;
     private final CallAttributes mCallAttributes;
     private final CallsManager mCallsManager;
+    private final Bundle mExtras;
 
     public IncomingCallTransaction(String callId, CallAttributes callAttributes,
-            CallsManager callsManager) {
+            CallsManager callsManager, Bundle extras) {
         super(callsManager.getLock());
+        mExtras = extras;
         mCallId = callId;
         mCallAttributes = callAttributes;
         mCallsManager = callsManager;
     }
 
+    public IncomingCallTransaction(String callId, CallAttributes callAttributes,
+            CallsManager callsManager) {
+        this(callId, callAttributes, callsManager, new Bundle());
+    }
+
     @Override
     public CompletionStage<VoipCallTransactionResult> processTransaction(Void v) {
         Log.d(TAG, "processTransaction");
@@ -70,11 +76,10 @@
         }
     }
 
-    private Bundle generateExtras(CallAttributes callAttributes){
-        Bundle extras = new Bundle();
-        extras.putString(TelecomManager.TRANSACTION_CALL_ID_KEY, mCallId);
-        extras.putInt(CALL_CAPABILITIES_KEY, callAttributes.getCallCapabilities());
-        extras.putInt(TelecomManager.EXTRA_INCOMING_VIDEO_STATE, callAttributes.getCallType());
-        return extras;
+    private Bundle generateExtras(CallAttributes callAttributes) {
+        mExtras.putString(TelecomManager.TRANSACTION_CALL_ID_KEY, mCallId);
+        mExtras.putInt(CALL_CAPABILITIES_KEY, callAttributes.getCallCapabilities());
+        mExtras.putInt(TelecomManager.EXTRA_INCOMING_VIDEO_STATE, callAttributes.getCallType());
+        return mExtras;
     }
 }
diff --git a/src/com/android/server/telecom/voip/OutgoingCallTransaction.java b/src/com/android/server/telecom/voip/OutgoingCallTransaction.java
index 124f5f3..0b17da2 100644
--- a/src/com/android/server/telecom/voip/OutgoingCallTransaction.java
+++ b/src/com/android/server/telecom/voip/OutgoingCallTransaction.java
@@ -43,15 +43,22 @@
     private final String mCallingPackage;
     private final CallAttributes mCallAttributes;
     private final CallsManager mCallsManager;
+    private final Bundle mExtras;
 
     public OutgoingCallTransaction(String callId, Context context, CallAttributes callAttributes,
-            CallsManager callsManager) {
+            CallsManager callsManager, Bundle extras) {
         super(callsManager.getLock());
         mCallId = callId;
         mContext = context;
-        mCallingPackage = mContext.getOpPackageName();
         mCallAttributes = callAttributes;
         mCallsManager = callsManager;
+        mExtras = extras;
+        mCallingPackage = mContext.getOpPackageName();
+    }
+
+    public OutgoingCallTransaction(String callId, Context context, CallAttributes callAttributes,
+            CallsManager callsManager) {
+        this(callId, context, callAttributes, callsManager, new Bundle());
     }
 
     @Override
@@ -113,13 +120,12 @@
         }
     }
 
-    private Bundle generateExtras(CallAttributes callAttributes){
-        Bundle extras = new Bundle();
-        extras.setDefusable(true);
-        extras.putString(TelecomManager.TRANSACTION_CALL_ID_KEY, mCallId);
-        extras.putInt(CALL_CAPABILITIES_KEY, callAttributes.getCallCapabilities());
-        extras.putInt(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE,
+    private Bundle generateExtras(CallAttributes callAttributes) {
+        mExtras.setDefusable(true);
+        mExtras.putString(TelecomManager.TRANSACTION_CALL_ID_KEY, mCallId);
+        mExtras.putInt(CALL_CAPABILITIES_KEY, callAttributes.getCallCapabilities());
+        mExtras.putInt(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE,
                 callAttributes.getCallType());
-        return extras;
+        return mExtras;
     }
 }
diff --git a/src/com/android/server/telecom/voip/VoipCallMonitor.java b/src/com/android/server/telecom/voip/VoipCallMonitor.java
new file mode 100644
index 0000000..67af11d
--- /dev/null
+++ b/src/com/android/server/telecom/voip/VoipCallMonitor.java
@@ -0,0 +1,252 @@
+/*
+ * Copyright (C) 2022 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.voip;
+
+import android.app.ActivityManager;
+import android.app.ActivityManagerInternal;
+import android.app.ForegroundServiceDelegationOptions;
+import android.app.Notification;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.ServiceConnection;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.service.notification.NotificationListenerService;
+import android.service.notification.StatusBarNotification;
+import android.telecom.Log;
+import android.telecom.PhoneAccountHandle;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.LocalServices;
+import com.android.server.telecom.Call;
+import com.android.server.telecom.CallsManagerListenerBase;
+import com.android.server.telecom.TelecomSystem;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+public class VoipCallMonitor extends CallsManagerListenerBase {
+
+    private final List<Call> mPendingCalls;
+    private final Map<StatusBarNotification, Call> mNotifications;
+    private final Map<PhoneAccountHandle, Set<Call>> mPhoneAccountHandleListMap;
+    private ActivityManagerInternal mActivityManagerInternal;
+    private final Map<PhoneAccountHandle, ServiceConnection> mServices;
+    private NotificationListenerService mNotificationListener;
+    private final Object mLock = new Object();
+    private final HandlerThread mHandlerThread;
+    private final Handler mHandler;
+    private final Context mContext;
+
+    public VoipCallMonitor(Context context, TelecomSystem.SyncRoot lock) {
+        mContext = context;
+        mHandlerThread = new HandlerThread(this.getClass().getSimpleName());
+        mHandlerThread.start();
+        mHandler = new Handler(mHandlerThread.getLooper());
+        mPendingCalls = new ArrayList<>();
+        mNotifications = new HashMap<>();
+        mServices = new HashMap<>();
+        mPhoneAccountHandleListMap = new HashMap<>();
+        mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
+
+        mNotificationListener = new NotificationListenerService() {
+            @Override
+            public void onNotificationPosted(StatusBarNotification sbn) {
+                synchronized (mLock) {
+                    if (mPendingCalls.isEmpty()) {
+                        return;
+                    }
+                    if (sbn.getNotification().isStyle(Notification.CallStyle.class)) {
+                        String packageName = sbn.getPackageName();
+                        UserHandle userHandle = sbn.getUser();
+
+                        for (Call call : mPendingCalls) {
+                            if (packageName != null &&
+                                    packageName.equals(call.getTargetPhoneAccount()
+                                            .getComponentName().getPackageName())
+                                    && userHandle != null
+                                    && userHandle.equals(call.getInitiatingUser())) {
+                                mPendingCalls.remove(call);
+                                mNotifications.put(sbn, call);
+                                break;
+                            }
+                        }
+                    }
+                }
+            }
+
+            @Override
+            public void onNotificationRemoved(StatusBarNotification sbn) {
+                synchronized (mLock) {
+                    if (mNotifications.isEmpty()) {
+                        return;
+                    }
+                    Call call = mNotifications.getOrDefault(sbn, null);
+                    if (call != null) {
+                        mNotifications.remove(sbn, call);
+                        stopFGSDelegation(call.getTargetPhoneAccount());
+                    }
+                }
+            }
+        };
+
+    }
+
+    public void startMonitor() {
+        try {
+            mNotificationListener.registerAsSystemService(mContext,
+                    new ComponentName(this.getClass().getPackageName(),
+                            this.getClass().getCanonicalName()), ActivityManager.getCurrentUser());
+        } catch (RemoteException e) {
+            Log.e(this, e, "Cannot register notification listener");
+        }
+    }
+
+    public void stopMonitor() {
+        try {
+            mNotificationListener.unregisterAsSystemService();
+        } catch (RemoteException e) {
+            Log.e(this, e, "Cannot unregister notification listener");
+        }
+    }
+
+    @Override
+    public void onCallAdded(Call call) {
+        if (!call.isTransactionalCall()) {
+            return;
+        }
+
+        synchronized (mLock) {
+            PhoneAccountHandle phoneAccountHandle = call.getTargetPhoneAccount();
+            Set<Call> callList = mPhoneAccountHandleListMap.computeIfAbsent(phoneAccountHandle,
+                    k -> new HashSet<>());
+            callList.add(call);
+
+            mHandler.post(
+                    () -> startFGSDelegation(call.getCallingPackageIdentity().mCallingPackagePid,
+                            call.getCallingPackageIdentity().mCallingPackageUid, call));
+        }
+    }
+
+    @Override
+    public void onCallRemoved(Call call) {
+        if (!call.isTransactionalCall()) {
+            return;
+        }
+
+        synchronized (mLock) {
+            stopMonitorWorks(call);
+            PhoneAccountHandle phoneAccountHandle = call.getTargetPhoneAccount();
+            Set<Call> callList = mPhoneAccountHandleListMap.computeIfAbsent(phoneAccountHandle,
+                    k -> new HashSet<>());
+            callList.remove(call);
+
+            if (callList.isEmpty()) {
+                stopFGSDelegation(phoneAccountHandle);
+            }
+        }
+    }
+
+    private void startFGSDelegation(int pid, int uid, Call call) {
+        Log.i(this, "startFGSDelegation for call %s", call.getId());
+        if (mActivityManagerInternal != null) {
+            PhoneAccountHandle handle = call.getTargetPhoneAccount();
+            ForegroundServiceDelegationOptions options = new ForegroundServiceDelegationOptions(pid,
+                    uid, handle.getComponentName().getPackageName(), null /* clientAppThread */,
+                    false /* isSticky */, String.valueOf(handle.hashCode()),
+                    0 /* foregroundServiceType */,
+                    ForegroundServiceDelegationOptions.DELEGATION_SERVICE_PHONE_CALL);
+            ServiceConnection fgsConnection = new ServiceConnection() {
+                @Override
+                public void onServiceConnected(ComponentName name, IBinder service) {
+                    mServices.put(handle, this);
+                    startMonitorWorks(call);
+                }
+
+                @Override
+                public void onServiceDisconnected(ComponentName name) {
+                    mServices.remove(handle);
+                }
+            };
+            try {
+                mActivityManagerInternal.startForegroundServiceDelegate(options, fgsConnection);
+            } catch (Exception e) {
+                Log.i(this, "startForegroundServiceDelegate failed due to: " + e);
+            }
+        }
+    }
+
+    @VisibleForTesting
+    public void stopFGSDelegation(PhoneAccountHandle handle) {
+        synchronized (mLock) {
+            Log.i(this, "stopFGSDelegation of handle %s", handle);
+            Set<Call> calls = mPhoneAccountHandleListMap.get(handle);
+            for (Call call : calls) {
+                stopMonitorWorks(call);
+            }
+            mPhoneAccountHandleListMap.remove(handle);
+
+            if (mActivityManagerInternal != null) {
+                ServiceConnection fgsConnection = mServices.get(handle);
+                if (fgsConnection != null) {
+                    mActivityManagerInternal.stopForegroundServiceDelegate(fgsConnection);
+                }
+            }
+        }
+    }
+
+    private void startMonitorWorks(Call call) {
+        startMonitorNotification(call);
+    }
+
+    private void stopMonitorWorks(Call call) {
+        stopMonitorNotification(call);
+    }
+
+    private void startMonitorNotification(Call call) {
+        mPendingCalls.add(call);
+        mHandler.postDelayed(() -> {
+            synchronized (mLock) {
+                if (mPendingCalls.contains(call)) {
+                    stopFGSDelegation(call.getTargetPhoneAccount());
+                    mPendingCalls.remove(call);
+                }
+            }
+        }, 5000L);
+    }
+
+    private void stopMonitorNotification(Call call) {
+        mPendingCalls.remove(call);
+    }
+
+    @VisibleForTesting
+    public void setActivityManagerInternal(ActivityManagerInternal ami) {
+        mActivityManagerInternal = ami;
+    }
+
+    @VisibleForTesting
+    public void setNotificationListenerService(NotificationListenerService listener) {
+        mNotificationListener = listener;
+    }
+}
diff --git a/tests/AndroidManifest.xml b/tests/AndroidManifest.xml
index c84db3b..4ca6030 100644
--- a/tests/AndroidManifest.xml
+++ b/tests/AndroidManifest.xml
@@ -44,6 +44,9 @@
     <!-- Used to access PlatformCompat APIs -->
     <uses-permission android:name="android.permission.READ_COMPAT_CHANGE_CONFIG" />
     <uses-permission android:name="android.permission.LOG_COMPAT_CHANGE" />
+    
+    <!-- Used to register NotificationListenerService -->
+    <uses-permission android:name="android.permission.STATUS_BAR_SERVICE" />
 
     <application android:label="@string/app_name"
                  android:debuggable="true">
diff --git a/tests/src/com/android/server/telecom/tests/CallTest.java b/tests/src/com/android/server/telecom/tests/CallTest.java
index 6b817d8..997e7dd 100644
--- a/tests/src/com/android/server/telecom/tests/CallTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallTest.java
@@ -26,16 +26,11 @@
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.argThat;
 import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.ArgumentMatchers.isA;
-import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.timeout;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.verifyZeroInteractions;
 
 import android.content.ComponentName;
 import android.content.Intent;
@@ -43,7 +38,7 @@
 import android.graphics.drawable.ColorDrawable;
 import android.net.Uri;
 import android.os.Bundle;
-import android.os.Parcel;
+import android.telecom.CallAttributes;
 import android.telecom.CallerInfo;
 import android.telecom.Connection;
 import android.telecom.DisconnectCause;
@@ -56,12 +51,10 @@
 import android.telecom.VideoProfile;
 import android.telephony.CallQuality;
 import android.test.suitebuilder.annotation.SmallTest;
-import android.util.Log;
 import android.widget.Toast;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 
-import com.android.internal.telecom.IVideoProvider;
 import com.android.server.telecom.Call;
 import com.android.server.telecom.CallIdMapper;
 import com.android.server.telecom.CallState;
@@ -69,8 +62,6 @@
 import com.android.server.telecom.CallsManager;
 import com.android.server.telecom.ClockProxy;
 import com.android.server.telecom.ConnectionServiceWrapper;
-import com.android.server.telecom.InCallController;
-import com.android.server.telecom.InCallController.InCallServiceInfo;
 import com.android.server.telecom.PhoneAccountRegistrar;
 import com.android.server.telecom.PhoneNumberUtilsAdapter;
 import com.android.server.telecom.TelecomSystem;
@@ -640,8 +631,40 @@
         verify(listener).onFailedUnknownCall(unknownCall);
     }
 
+    /**
+     * ensure a Call object does not throw an NPE when the CallingPackageIdentity is not set and
+     * the correct values are returned when set
+     */
     @Test
     @SmallTest
+    public void testCallingPackageIdentity() {
+        final int packageUid = 123;
+        final int packagePid = 1;
+
+        Call call = createCall("1");
+
+        // assert default values for a Calls CallingPackageIdentity are -1 unless set via the setter
+        assertEquals(-1, call.getCallingPackageIdentity().mCallingPackageUid);
+        assertEquals(-1, call.getCallingPackageIdentity().mCallingPackagePid);
+
+        // set the Call objects CallingPackageIdentity via the setter and a bundle
+        Bundle extras = new Bundle();
+        extras.putInt(CallAttributes.CALLER_UID_KEY, packageUid);
+        extras.putInt(CallAttributes.CALLER_PID_KEY, packagePid);
+        // assert that the setter removed the extras
+        assertEquals(packageUid, extras.getInt(CallAttributes.CALLER_UID_KEY));
+        assertEquals(packagePid, extras.getInt(CallAttributes.CALLER_PID_KEY));
+        call.setCallingPackageIdentity(extras);
+        // assert that the setter removed the extras
+        assertEquals(0, extras.getInt(CallAttributes.CALLER_UID_KEY));
+        assertEquals(0, extras.getInt(CallAttributes.CALLER_PID_KEY));
+        // assert the properties are fetched correctly
+        assertEquals(packageUid, call.getCallingPackageIdentity().mCallingPackageUid);
+        assertEquals(packagePid, call.getCallingPackageIdentity().mCallingPackagePid);
+    }
+
+        @Test
+    @SmallTest
     public void testOnConnectionEventNotifiesListener() {
         Call.Listener listener = mock(Call.Listener.class);
         Call call = createCall("1");
diff --git a/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java b/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java
index 49ad6bb..cc22de2 100644
--- a/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java
+++ b/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java
@@ -59,7 +59,9 @@
 import android.os.Bundle;
 import android.os.DropBoxManager;
 import android.os.Handler;
+import android.os.HandlerThread;
 import android.os.IInterface;
+import android.os.Looper;
 import android.os.PersistableBundle;
 import android.os.Process;
 import android.os.UserHandle;
@@ -112,6 +114,7 @@
  * property points to an application context implementing all the nontrivial functionality.
  */
 public class ComponentContextFixture implements TestFixture<Context> {
+    private HandlerThread mHandlerThread;
 
     public class FakeApplicationContext extends MockContext {
         @Override
@@ -311,6 +314,15 @@
         }
 
         @Override
+        public Looper getMainLooper() {
+            if (mHandlerThread == null) {
+                mHandlerThread = new HandlerThread(this.getClass().getSimpleName());
+                mHandlerThread.start();
+            }
+            return mHandlerThread.getLooper();
+        }
+
+        @Override
         public ContentResolver getContentResolver() {
             return new ContentResolver(mApplicationContextSpy) {
                 @Override
diff --git a/tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java b/tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java
index a98da83..7b5afe6 100644
--- a/tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java
+++ b/tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java
@@ -33,6 +33,7 @@
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
+import android.graphics.drawable.Icon;
 import android.net.Uri;
 import android.os.Binder;
 import android.os.Build;
@@ -950,6 +951,26 @@
 
     @SmallTest
     @Test
+    public void testRegisterPhoneAccountImageIconCrossUser() throws RemoteException {
+        String packageNameToUse = "com.android.officialpackage";
+        PhoneAccountHandle phHandle = new PhoneAccountHandle(new ComponentName(
+                packageNameToUse, "cs"), "test", Binder.getCallingUserHandle());
+        Icon icon = Icon.createWithContentUri("content://10@media/external/images/media/");
+        PhoneAccount phoneAccount = makePhoneAccount(phHandle).setIcon(icon).build();
+        doReturn(PackageManager.PERMISSION_GRANTED)
+                .when(mContext).checkCallingOrSelfPermission(MODIFY_PHONE_STATE);
+
+        // This should fail; security exception will be thrown.
+        registerPhoneAccountTestHelper(phoneAccount, false);
+
+        icon = Icon.createWithContentUri("content://0@media/external/images/media/");
+        phoneAccount = makePhoneAccount(phHandle).setIcon(icon).build();
+        // This should succeed.
+        registerPhoneAccountTestHelper(phoneAccount, true);
+    }
+
+    @SmallTest
+    @Test
     public void testUnregisterPhoneAccount() throws RemoteException {
         String packageNameToUse = "com.android.officialpackage";
         PhoneAccountHandle phHandle = new PhoneAccountHandle(new ComponentName(
diff --git a/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java b/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
index dd5856d..d013fae 100644
--- a/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
+++ b/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
@@ -394,6 +394,7 @@
                 handlerThread.quitSafely();
             }
             handlerThreads.clear();
+            mTelecomSystem.getCallsManager().getVoipCallMonitor().stopMonitor();
         }
         waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
         waitForHandlerAction(mHandlerThread.getThreadHandler(), TEST_TIMEOUT);
diff --git a/tests/src/com/android/server/telecom/tests/VoipCallMonitorTest.java b/tests/src/com/android/server/telecom/tests/VoipCallMonitorTest.java
new file mode 100644
index 0000000..346b3d8
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/VoipCallMonitorTest.java
@@ -0,0 +1,218 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.telecom.tests;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityManagerInternal;
+import android.app.ForegroundServiceDelegationOptions;
+import android.content.ComponentName;
+import android.content.ServiceConnection;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.UserHandle;
+import android.service.notification.NotificationListenerService;
+import android.telecom.PhoneAccountHandle;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.server.telecom.Call;
+import com.android.server.telecom.CallsManager;
+import com.android.server.telecom.TelecomSystem;
+import com.android.server.telecom.voip.VoipCallMonitor;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+
+@RunWith(JUnit4.class)
+public class VoipCallMonitorTest extends TelecomTestCase {
+    private VoipCallMonitor mMonitor;
+    private static final String PKG_NAME_1 = "telecom.voip.test1";
+    private static final String PKG_NAME_2 = "telecom.voip.test2";
+    private static final String CLS_NAME = "VoipActivity";
+    private static final String ID_1 = "id1";
+    private static final UserHandle USER_HANDLE_1 = new UserHandle(1);
+    private static final long TIMEOUT = 5000L;
+
+    @Mock private TelecomSystem.SyncRoot mLock;
+    @Mock private ActivityManagerInternal mActivityManagerInternal;
+    @Mock private NotificationListenerService mListenerService;
+
+    private final PhoneAccountHandle mHandle1User1 = new PhoneAccountHandle(
+            new ComponentName(PKG_NAME_1, CLS_NAME), ID_1, USER_HANDLE_1);
+    private final PhoneAccountHandle mHandle2User1 = new PhoneAccountHandle(
+            new ComponentName(PKG_NAME_2, CLS_NAME), ID_1, USER_HANDLE_1);
+
+    @Override
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+        mMonitor = new VoipCallMonitor(mContext, mLock);
+        mActivityManagerInternal = mock(ActivityManagerInternal.class);
+        mListenerService = mock(NotificationListenerService.class);
+        mMonitor.setActivityManagerInternal(mActivityManagerInternal);
+        mMonitor.setNotificationListenerService(mListenerService);
+        doNothing().when(mListenerService).registerAsSystemService(eq(mContext),
+                any(ComponentName.class), anyInt());
+        mMonitor.startMonitor();
+    }
+
+    @SmallTest
+    @Test
+    public void testStartMonitorForOneCall() {
+        Call call = createTestCall("testCall", mHandle1User1);
+        IBinder service = mock(IBinder.class);
+
+        ArgumentCaptor<ServiceConnection> captor = ArgumentCaptor.forClass(ServiceConnection.class);
+        mMonitor.onCallAdded(call);
+        verify(mActivityManagerInternal, timeout(TIMEOUT)).startForegroundServiceDelegate(any(
+                ForegroundServiceDelegationOptions.class), captor.capture());
+        ServiceConnection conn = captor.getValue();
+        conn.onServiceConnected(mHandle1User1.getComponentName(), service);
+
+        mMonitor.onCallRemoved(call);
+        verify(mActivityManagerInternal, timeout(TIMEOUT)).stopForegroundServiceDelegate(eq(conn));
+    }
+
+    @SmallTest
+    @Test
+    public void testMonitorForTwoCallsOnSameHandle() {
+        Call call1 = createTestCall("testCall1", mHandle1User1);
+        Call call2 = createTestCall("testCall2", mHandle1User1);
+        IBinder service = mock(IBinder.class);
+
+        ArgumentCaptor<ServiceConnection> captor1 =
+                ArgumentCaptor.forClass(ServiceConnection.class);
+        mMonitor.onCallAdded(call1);
+        verify(mActivityManagerInternal, timeout(TIMEOUT).times(1))
+                .startForegroundServiceDelegate(any(ForegroundServiceDelegationOptions.class),
+                        captor1.capture());
+        ServiceConnection conn1 = captor1.getValue();
+        conn1.onServiceConnected(mHandle1User1.getComponentName(), service);
+
+        ArgumentCaptor<ServiceConnection> captor2 =
+                ArgumentCaptor.forClass(ServiceConnection.class);
+        mMonitor.onCallAdded(call2);
+        verify(mActivityManagerInternal, timeout(TIMEOUT).times(2))
+                .startForegroundServiceDelegate(any(ForegroundServiceDelegationOptions.class),
+                        captor2.capture());
+        ServiceConnection conn2 = captor2.getValue();
+        conn2.onServiceConnected(mHandle1User1.getComponentName(), service);
+
+        mMonitor.onCallRemoved(call1);
+        verify(mActivityManagerInternal, never()).stopForegroundServiceDelegate(
+                any(ServiceConnection.class));
+        mMonitor.onCallRemoved(call2);
+        verify(mActivityManagerInternal, timeout(TIMEOUT).times(1))
+                .stopForegroundServiceDelegate(eq(conn2));
+    }
+
+    @SmallTest
+    @Test
+    public void testMonitorForTwoCallsOnDifferentHandle() {
+        Call call1 = createTestCall("testCall1", mHandle1User1);
+        Call call2 = createTestCall("testCall2", mHandle2User1);
+        IBinder service = mock(IBinder.class);
+
+        ArgumentCaptor<ServiceConnection> connCaptor1 = ArgumentCaptor.forClass(
+                ServiceConnection.class);
+        ArgumentCaptor<ForegroundServiceDelegationOptions> optionsCaptor1 =
+                ArgumentCaptor.forClass(ForegroundServiceDelegationOptions.class);
+        mMonitor.onCallAdded(call1);
+        verify(mActivityManagerInternal, timeout(TIMEOUT).times(1))
+                .startForegroundServiceDelegate(optionsCaptor1.capture(), connCaptor1.capture());
+        ForegroundServiceDelegationOptions options1 = optionsCaptor1.getValue();
+        ServiceConnection conn1 = connCaptor1.getValue();
+        conn1.onServiceConnected(mHandle1User1.getComponentName(), service);
+        assertEquals(PKG_NAME_1, options1.getComponentName().getPackageName());
+
+        ArgumentCaptor<ServiceConnection> connCaptor2 = ArgumentCaptor.forClass(
+                ServiceConnection.class);
+        ArgumentCaptor<ForegroundServiceDelegationOptions> optionsCaptor2 =
+                ArgumentCaptor.forClass(ForegroundServiceDelegationOptions.class);
+        mMonitor.onCallAdded(call2);
+        verify(mActivityManagerInternal, timeout(TIMEOUT).times(2))
+                .startForegroundServiceDelegate(optionsCaptor2.capture(), connCaptor2.capture());
+        ForegroundServiceDelegationOptions options2 = optionsCaptor2.getValue();
+        ServiceConnection conn2 = connCaptor2.getValue();
+        conn2.onServiceConnected(mHandle2User1.getComponentName(), service);
+        assertEquals(PKG_NAME_2, options2.getComponentName().getPackageName());
+
+        mMonitor.onCallRemoved(call2);
+        verify(mActivityManagerInternal).stopForegroundServiceDelegate(eq(conn2));
+        mMonitor.onCallRemoved(call1);
+        verify(mActivityManagerInternal).stopForegroundServiceDelegate(eq(conn1));
+    }
+
+    @SmallTest
+    @Test
+    public void testStopDelegation() {
+        Call call1 = createTestCall("testCall1", mHandle1User1);
+        Call call2 = createTestCall("testCall2", mHandle1User1);
+        IBinder service = mock(IBinder.class);
+
+        ArgumentCaptor<ServiceConnection> captor1 =
+                ArgumentCaptor.forClass(ServiceConnection.class);
+        mMonitor.onCallAdded(call1);
+        verify(mActivityManagerInternal, timeout(TIMEOUT).times(1))
+                .startForegroundServiceDelegate(any(ForegroundServiceDelegationOptions.class),
+                        captor1.capture());
+        ServiceConnection conn1 = captor1.getValue();
+        conn1.onServiceConnected(mHandle1User1.getComponentName(), service);
+
+        ArgumentCaptor<ServiceConnection> captor2 =
+                ArgumentCaptor.forClass(ServiceConnection.class);
+        mMonitor.onCallAdded(call2);
+        verify(mActivityManagerInternal, timeout(TIMEOUT).times(2))
+                .startForegroundServiceDelegate(any(ForegroundServiceDelegationOptions.class),
+                        captor2.capture());
+        ServiceConnection conn2 = captor2.getValue();
+        conn2.onServiceConnected(mHandle1User1.getComponentName(), service);
+
+        mMonitor.stopFGSDelegation(mHandle1User1);
+        verify(mActivityManagerInternal, timeout(TIMEOUT).times(1))
+                .stopForegroundServiceDelegate(eq(conn2));
+        conn2.onServiceDisconnected(mHandle1User1.getComponentName());
+        mMonitor.onCallRemoved(call1);
+        verify(mActivityManagerInternal, timeout(TIMEOUT).times(1))
+                .stopForegroundServiceDelegate(any(ServiceConnection.class));
+    }
+
+    private Call createTestCall(String id, PhoneAccountHandle handle) {
+        Call call = mock(Call.class);
+        when(call.getTargetPhoneAccount()).thenReturn(handle);
+        when(call.isTransactionalCall()).thenReturn(true);
+        when(call.getExtras()).thenReturn(new Bundle());
+        when(call.getId()).thenReturn(id);
+        when(call.getCallingPackageIdentity()).thenReturn( new Call.CallingPackageIdentity() );
+        return call;
+    }
+}