Merge rvc-qpr-dev-plus-aosp-without-vendor@6881855

Bug: 172690556
Merged-In: Idd3ecd217229d37bfef5bed50c55cd1a837ce852
Change-Id: I9b4c72aa8dd0f16c7568e846374e3b837d580c8a
diff --git a/res/xml/layout_blocked_number.xml b/res/xml/layout_blocked_number.xml
index 3cdd771..9dbfc2f 100644
--- a/res/xml/layout_blocked_number.xml
+++ b/res/xml/layout_blocked_number.xml
@@ -28,9 +28,8 @@
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_alignParentStart="true"
+        android:layout_toStartOf="@+id/delete_blocked_number"
         android:paddingTop="@dimen/blocked_numbers_delete_icon_padding"
-        android:layout_alignParentLeft="true"
-        android:layout_toLeftOf="@+id/delete_blocked_number"
         android:textDirection="ltr" />
 
     <ImageView
diff --git a/src/com/android/server/telecom/Analytics.java b/src/com/android/server/telecom/Analytics.java
index 9c227a6..8b7c37d 100644
--- a/src/com/android/server/telecom/Analytics.java
+++ b/src/com/android/server/telecom/Analytics.java
@@ -208,6 +208,9 @@
 
         public void setCallSource(int callSource) {
         }
+
+        public void setMissedReason(long missedReason) {
+        }
     }
 
     /**
@@ -242,6 +245,7 @@
         public List<TelecomLogClass.InCallServiceInfo> inCallServiceInfos;
         public int callProperties = 0;
         public int callSource = CALL_SOURCE_UNSPECIFIED;
+        public long missedReason;
 
         private long mTimeOfLastVideoEvent = -1;
 
@@ -254,6 +258,7 @@
             connectionService = "";
             videoEvents = new LinkedList<>();
             inCallServiceInfos = new LinkedList<>();
+            missedReason = 0;
         }
 
         CallInfoImpl(CallInfoImpl other) {
@@ -272,6 +277,7 @@
             this.videoEvents = other.videoEvents;
             this.callProperties = other.callProperties;
             this.callSource = other.callSource;
+            this.missedReason = other.missedReason;
 
             if (other.callTerminationReason != null) {
                 this.callTerminationReason = new DisconnectCause(
@@ -342,6 +348,13 @@
         }
 
         @Override
+        public void setMissedReason(long missedReason) {
+            Log.d(TAG, "setting missedReason for call " + callId + ": "
+                    + missedReason);
+            this.missedReason = missedReason;
+        }
+
+        @Override
         public void setCallEvents(EventManager.EventRecord records) {
             this.callEvents = records;
         }
@@ -399,6 +412,7 @@
                     + "    isEmergency: " + isEmergency + '\n'
                     + "    callTechnologies: " + getCallTechnologiesAsString() + '\n'
                     + "    callTerminationReason: " + getCallDisconnectReasonString() + '\n'
+                    + "    missedReason: " + getMissedReasonString() + '\n'
                     + "    connectionService: " + connectionService + '\n'
                     + "    isVideoCall: " + isVideo + '\n'
                     + "    inCallServices: " + getInCallServicesString() + '\n'
@@ -526,6 +540,11 @@
             }
         }
 
+        private String getMissedReasonString() {
+            //TODO: Implement this
+            return null;
+        }
+
         private String getInCallServicesString() {
             StringBuilder s = new StringBuilder();
             s.append("[\n");
diff --git a/src/com/android/server/telecom/Call.java b/src/com/android/server/telecom/Call.java
index 6af999e..fb4f58d 100644
--- a/src/com/android/server/telecom/Call.java
+++ b/src/com/android/server/telecom/Call.java
@@ -16,6 +16,8 @@
 
 package com.android.server.telecom;
 
+import static android.provider.CallLog.Calls.MISSED_REASON_NOT_MISSED;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
@@ -33,6 +35,7 @@
 import android.os.SystemClock;
 import android.os.Trace;
 import android.os.UserHandle;
+import android.provider.CallLog;
 import android.provider.ContactsContract.Contacts;
 import android.telecom.CallAudioState;
 import android.telecom.CallerInfo;
@@ -588,7 +591,7 @@
 
     /**
      * Indicates whether this call is using one of the
-     * {@link com.android.server.telecom.callfiltering.IncomingCallFilter.CallFilter} modules.
+     * {@link com.android.server.telecom.callfiltering.CallFilter} modules.
      */
     private boolean mIsUsingCallFiltering = false;
 
@@ -611,6 +614,16 @@
     private String mPostCallPackageName;
 
     /**
+     * Call missed information code.
+     */
+    @CallLog.Calls.MissedReason private long mMissedReason;
+
+    /**
+     * Time that this call start ringing or simulated ringing.
+     */
+    private long mStartRingTime;
+
+    /**
      * Persists the specified parameters and initializes the new instance.
      * @param context The context.
      * @param repository The connection service repository.
@@ -692,6 +705,8 @@
         mClockProxy = clockProxy;
         mToastFactory = toastFactory;
         mCreationTimeMillis = mClockProxy.currentTimeMillis();
+        mMissedReason = MISSED_REASON_NOT_MISSED;
+        mStartRingTime = 0;
     }
 
     /**
@@ -3870,4 +3885,20 @@
     public String getPostCallPackageName() {
         return mPostCallPackageName;
     }
+
+    public long getMissedReason() {
+        return mMissedReason;
+    }
+
+    public void setMissedReason(long missedReason) {
+        mMissedReason = missedReason;
+    }
+
+    public long getStartRingTime() {
+        return mStartRingTime;
+    }
+
+    public void setStartRingTime(long startRingTime) {
+        mStartRingTime = startRingTime;
+    }
 }
diff --git a/src/com/android/server/telecom/CallLogManager.java b/src/com/android/server/telecom/CallLogManager.java
index b19e269..cedd41c 100755
--- a/src/com/android/server/telecom/CallLogManager.java
+++ b/src/com/android/server/telecom/CallLogManager.java
@@ -16,6 +16,7 @@
 
 package com.android.server.telecom;
 
+import static android.provider.CallLog.Calls.MISSED_REASON_NOT_MISSED;
 import static android.telephony.CarrierConfigManager.KEY_SUPPORT_IMS_CONFERENCE_EVENT_PACKAGE_BOOL;
 
 import android.annotation.Nullable;
@@ -84,7 +85,8 @@
                 int features, PhoneAccountHandle accountHandle, long creationDate,
                 long durationInMillis, Long dataUsage, UserHandle initiatingUser, boolean isRead,
                 @Nullable LogCallCompletedListener logCallCompletedListener, int callBlockReason,
-                CharSequence callScreeningAppName, String callScreeningComponentName) {
+                CharSequence callScreeningAppName, String callScreeningComponentName,
+                long missedReason) {
             this.context = context;
             this.callerInfo = callerInfo;
             this.number = number;
@@ -103,6 +105,7 @@
             this.callBockReason = callBlockReason;
             this.callScreeningAppName = callScreeningAppName;
             this.callScreeningComponentName = callScreeningComponentName;
+            this.missedReason = missedReason;
         }
         // Since the members are accessed directly, we don't use the
         // mXxxx notation.
@@ -127,6 +130,7 @@
         public final int callBockReason;
         public final CharSequence callScreeningAppName;
         public final String callScreeningComponentName;
+        public final long missedReason;
     }
 
     private static final String TAG = CallLogManager.class.getSimpleName();
@@ -353,20 +357,25 @@
                 call.wasEverRttCall(),
                 call.wasVolte());
 
-        if (callLogType == Calls.BLOCKED_TYPE) {
+        if (result == null) {
+            // Call auto missed before filtered
+            result = new CallFilteringResult.Builder().build();
+        }
+
+        if (callLogType == Calls.BLOCKED_TYPE || callLogType == Calls.MISSED_TYPE) {
             logCall(call.getCallerInfo(), logNumber, call.getPostDialDigits(), formattedViaNumber,
                     call.getHandlePresentation(), callLogType, callFeatures, accountHandle,
                     creationTime, age, callDataUsage, call.isEmergencyCall(),
                     call.getInitiatingUser(), call.isSelfManaged(), logCallCompletedListener,
                     result.mCallBlockReason, result.mCallScreeningAppName,
-                    result.mCallScreeningComponentName);
+                    result.mCallScreeningComponentName, call.getMissedReason());
         } else {
             logCall(call.getCallerInfo(), logNumber, call.getPostDialDigits(), formattedViaNumber,
                     call.getHandlePresentation(), callLogType, callFeatures, accountHandle,
                     creationTime, age, callDataUsage, call.isEmergencyCall(),
                     call.getInitiatingUser(), call.isSelfManaged(), logCallCompletedListener,
                     Calls.BLOCK_REASON_NOT_BLOCKED, null /*callScreeningAppName*/,
-                    null /*callScreeningComponentName*/);
+                    null /*callScreeningComponentName*/, call.getMissedReason());
         }
     }
 
@@ -390,6 +399,7 @@
      * @param callBlockReason The reason why the call is blocked.
      * @param callScreeningAppName The call screening application name which block the call.
      * @param callScreeningComponentName The call screening component name which block the call.
+     * @param missedReason The encoded information about reasons for missed call.
      */
     private void logCall(
             CallerInfo callerInfo,
@@ -409,7 +419,8 @@
             @Nullable LogCallCompletedListener logCallCompletedListener,
             int callBlockReason,
             CharSequence callScreeningAppName,
-            String callScreeningComponentName) {
+            String callScreeningComponentName,
+            long missedReason) {
 
         // On some devices, to avoid accidental redialing of emergency numbers, we *never* log
         // emergency calls to the Call Log.  (This behavior is set on a per-product basis, based
@@ -443,7 +454,7 @@
             AddCallArgs args = new AddCallArgs(mContext, callerInfo, number, postDialDigits,
                     viaNumber, presentation, callType, features, accountHandle, start, duration,
                     dataUsage, initiatingUser, isRead, logCallCompletedListener, callBlockReason,
-                    callScreeningAppName, callScreeningComponentName);
+                    callScreeningAppName, callScreeningComponentName, missedReason);
             logCallAsync(args);
         } else {
           Log.d(TAG, "Not adding emergency call to call log.");
@@ -596,7 +607,7 @@
                     c.presentation, c.callType, c.features, c.accountHandle, c.timestamp,
                     c.durationInSec, c.dataUsage, userToBeInserted == null,
                     userToBeInserted, c.isRead, c.callBockReason, c.callScreeningAppName,
-                    c.callScreeningComponentName);
+                    c.callScreeningComponentName, c.missedReason);
         }
 
 
diff --git a/src/com/android/server/telecom/CallsManager.java b/src/com/android/server/telecom/CallsManager.java
index c1fdadd..6b99633 100755
--- a/src/com/android/server/telecom/CallsManager.java
+++ b/src/com/android/server/telecom/CallsManager.java
@@ -16,6 +16,7 @@
 
 package com.android.server.telecom;
 
+import static android.provider.CallLog.Calls.MISSED_REASON_NOT_MISSED;
 import static android.telecom.TelecomManager.ACTION_POST_CALL;
 import static android.telecom.TelecomManager.DURATION_LONG;
 import static android.telecom.TelecomManager.DURATION_MEDIUM;
@@ -27,6 +28,9 @@
 import static android.telecom.TelecomManager.MEDIUM_CALL_TIME_MS;
 import static android.telecom.TelecomManager.SHORT_CALL_TIME_MS;
 import static android.telecom.TelecomManager.VERY_SHORT_CALL_TIME_MS;
+import static android.provider.CallLog.Calls.AUTO_MISSED_EMERGENCY_CALL;
+import static android.provider.CallLog.Calls.AUTO_MISSED_MAXIMUM_DIALING;
+import static android.provider.CallLog.Calls.AUTO_MISSED_MAXIMUM_RINGING;
 
 import android.Manifest;
 import android.annotation.NonNull;
@@ -739,6 +743,7 @@
         // Only set the incoming call as ringing if it isn't already disconnected. It is possible
         // that the connection service disconnected the call before it was even added to Telecom, in
         // which case it makes no sense to set it back to a ringing state.
+        Log.i(this, "onCallFilteringComplete");
         mGraphHandlerThreads.clear();
 
         if (incomingCall.getState() != CallState.DISCONNECTED &&
@@ -760,16 +765,17 @@
                 } else {
                     Log.i(this, "onCallFilteringCompleted: Call rejected! " +
                             "Exceeds maximum number of ringing calls.");
-                    rejectCallAndLog(incomingCall, result);
+                    incomingCall.setMissedReason(AUTO_MISSED_MAXIMUM_RINGING);
+                    autoMissCallAndLog(incomingCall, result);
                 }
             } else if (hasMaximumManagedDialingCalls(incomingCall)) {
                 if (shouldSilenceInsteadOfReject(incomingCall)) {
                     incomingCall.silence();
                 } else {
-
                     Log.i(this, "onCallFilteringCompleted: Call rejected! Exceeds maximum number of " +
                             "dialing calls.");
-                    rejectCallAndLog(incomingCall, result);
+                    incomingCall.setMissedReason(AUTO_MISSED_MAXIMUM_DIALING);
+                    autoMissCallAndLog(incomingCall, result);
                 }
             } else if (result.shouldScreenViaAudio) {
                 Log.i(this, "onCallFilteringCompleted: starting background audio processing");
@@ -1335,12 +1341,18 @@
             if (isConference) {
                 notifyCreateConferenceFailed(phoneAccountHandle, call);
             } else {
+                if (hasMaximumManagedRingingCalls(call)) {
+                    call.setMissedReason(AUTO_MISSED_MAXIMUM_RINGING);
+                    mCallLogManager.logCall(call, Calls.MISSED_TYPE,
+                            true /*showNotificationForMissedCall*/, null /*CallFilteringResult*/);
+                }
                 notifyCreateConnectionFailed(phoneAccountHandle, call);
             }
         } else if (isInEmergencyCall()) {
             // The incoming call is implicitly being rejected so the user does not get any incoming
             // call UI during an emergency call. In this case, log the call as missed instead of
             // rejected since the user did not explicitly reject.
+            call.setMissedReason(AUTO_MISSED_EMERGENCY_CALL);
             mCallLogManager.logCall(call, Calls.MISSED_TYPE,
                     true /*showNotificationForMissedCall*/, null /*CallFilteringResult*/);
             if (isConference) {
@@ -1586,6 +1598,17 @@
                     // call transitioning into the CONNECTING state.
                     if (isReusedCall) {
                         return CompletableFuture.completedFuture(finalCall);
+                    } else {
+                        Call reusableCall = reuseOutgoingCall(handle);
+                        if (reusableCall != null) {
+                            Log.i(CallsManager.this,
+                                    "reusable call %s came in later; disconnect it.",
+                                    reusableCall.getId());
+                            mPendingCallsToDisconnect.remove(reusableCall);
+                            reusableCall.disconnect();
+                            markCallAsDisconnected(reusableCall,
+                                    new DisconnectCause(DisconnectCause.CANCELED));
+                        }
                     }
 
                     // If we can not supportany more active calls, our options are to move a call
@@ -3437,7 +3460,8 @@
      * Reject an incoming call and manually add it to the Call Log.
      * @param incomingCall Incoming call that has been rejected
      */
-    private void rejectCallAndLog(Call incomingCall, CallFilteringResult result) {
+    private void autoMissCallAndLog(Call incomingCall, CallFilteringResult result) {
+        incomingCall.getAnalytics().setMissedReason(incomingCall.getMissedReason());
         if (incomingCall.getConnectionService() != null) {
             // Only reject the call if it has not already been destroyed.  If a call ends while
             // incoming call filtering is taking place, it is possible that the call has already
@@ -3575,6 +3599,10 @@
                         (newState == CallState.DISCONNECTED)) {
                     maybeSendPostCallScreenIntent(call);
                 }
+                if (((newState == CallState.ABORTED) || (newState == CallState.DISCONNECTED))
+                        && (call.getDisconnectCause().getCode() != DisconnectCause.MISSED)) {
+                    call.setMissedReason(MISSED_REASON_NOT_MISSED);
+                }
                 maybeShowErrorDialogOnDisconnect(call);
 
                 Trace.beginSection("onCallStateChanged");
@@ -4731,6 +4759,7 @@
      * @param call The {@link Call} which could not be added.
      */
     private void notifyCreateConnectionFailed(PhoneAccountHandle phoneAccountHandle, Call call) {
+        call.getAnalytics().setMissedReason(call.getMissedReason());
         if (phoneAccountHandle == null) {
             return;
         }
@@ -5340,4 +5369,9 @@
         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
         mContext.startActivityAsUser(intent, mCurrentUserHandle);
     }
+
+    @VisibleForTesting
+    public void addToPendingCallsToDisconnect(Call call) {
+        mPendingCallsToDisconnect.add(call);
+    }
 }
diff --git a/src/com/android/server/telecom/CreateConnectionProcessor.java b/src/com/android/server/telecom/CreateConnectionProcessor.java
index 2221cb9..2e67b08 100644
--- a/src/com/android/server/telecom/CreateConnectionProcessor.java
+++ b/src/com/android/server/telecom/CreateConnectionProcessor.java
@@ -403,6 +403,7 @@
             // When testing emergency calls, we want the calls to go through to the test connection
             // service, not the telephony ConnectionService.
             if (mCall.isTestEmergencyCall()) {
+                Log.i(this, "Processing test emergency call -- special rules");
                 allAccounts = mPhoneAccountRegistrar.filterRestrictedPhoneAccounts(allAccounts);
             }
 
@@ -411,7 +412,7 @@
                     preferredPAH);
             // Next, add all SIM phone accounts which can place emergency calls.
             sortSimPhoneAccountsForEmergency(allAccounts, preferredPA);
-            // and pick the fist one that can place emergency calls.
+            // and pick the first one that can place emergency calls.
             for (PhoneAccount phoneAccount : allAccounts) {
                 if (phoneAccount.hasCapabilities(PhoneAccount.CAPABILITY_PLACE_EMERGENCY_CALLS)
                         && phoneAccount.hasCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)) {
diff --git a/src/com/android/server/telecom/InCallController.java b/src/com/android/server/telecom/InCallController.java
index 9a1c876..954aa44 100644
--- a/src/com/android/server/telecom/InCallController.java
+++ b/src/com/android/server/telecom/InCallController.java
@@ -28,6 +28,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.PermissionChecker;
 import android.content.ServiceConnection;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
@@ -1576,9 +1577,17 @@
                 p -> packageManager.checkPermission(
                         Manifest.permission.CONTROL_INCALL_EXPERIENCE,
                         p) == PackageManager.PERMISSION_GRANTED);
+
+        boolean hasAppOpsPermittedManageOngoingCalls = false;
+        if (isAppOpsPermittedManageOngoingCalls(serviceInfo.applicationInfo.uid,
+                serviceInfo.packageName)) {
+            hasAppOpsPermittedManageOngoingCalls = true;
+        }
+
         boolean isCarModeUIService = serviceInfo.metaData != null &&
                 serviceInfo.metaData.getBoolean(
                         TelecomManager.METADATA_IN_CALL_SERVICE_CAR_MODE_UI, false);
+
         if (isCarModeUIService && hasControlInCallPermission) {
             return IN_CALL_SERVICE_TYPE_CAR_MODE_UI;
         }
@@ -1593,7 +1602,8 @@
 
         // Also allow any in-call service that has the control-experience permission (to ensure
         // that it is a system app) and doesn't claim to show any UI.
-        if (!isUIService && !isCarModeUIService && hasControlInCallPermission) {
+        if (!isUIService && !isCarModeUIService && (hasControlInCallPermission ||
+                hasAppOpsPermittedManageOngoingCalls)) {
             return IN_CALL_SERVICE_TYPE_NON_UI;
         }
 
@@ -2010,6 +2020,12 @@
         return mCallsManager.getAudioState().isMuted();
     }
 
+    private boolean isAppOpsPermittedManageOngoingCalls(int uid, String callingPackage) {
+        return PermissionChecker.checkPermissionForPreflight(mContext,
+                Manifest.permission.MANAGE_ONGOING_CALLS, PermissionChecker.PID_UNKNOWN, uid,
+                        callingPackage) == PermissionChecker.PERMISSION_GRANTED;
+    }
+
     private void sendCrashedInCallServiceNotification(String packageName) {
         PackageManager packageManager = mContext.getPackageManager();
         CharSequence appName;
diff --git a/src/com/android/server/telecom/PhoneAccountRegistrar.java b/src/com/android/server/telecom/PhoneAccountRegistrar.java
index e9ddfa0..9a4f7df 100644
--- a/src/com/android/server/telecom/PhoneAccountRegistrar.java
+++ b/src/com/android/server/telecom/PhoneAccountRegistrar.java
@@ -1266,11 +1266,11 @@
             // Comparator which places PhoneAccounts with a specified sort order first, followed by
             // those with no sort order.
             Comparator<PhoneAccount> bySortOrder = (p1, p2) -> {
-                String sort1 = p1.getExtras() == null ? null :
-                        p1.getExtras().getString(PhoneAccount.EXTRA_SORT_ORDER, null);
-                String sort2 = p2.getExtras() == null ? null :
-                        p2.getExtras().getString(PhoneAccount.EXTRA_SORT_ORDER, null);
-                return nullSafeStringComparator.compare(sort1, sort2);
+                int sort1 = p1.getExtras() == null ? Integer.MAX_VALUE:
+                        p1.getExtras().getInt(PhoneAccount.EXTRA_SORT_ORDER, Integer.MAX_VALUE);
+                int sort2 = p2.getExtras() == null ? Integer.MAX_VALUE:
+                        p2.getExtras().getInt(PhoneAccount.EXTRA_SORT_ORDER, Integer.MAX_VALUE);
+                return Integer.compare(sort1, sort2);
             };
 
             // Comparator which sorts PhoneAccounts by label.
diff --git a/src/com/android/server/telecom/PhoneStateBroadcaster.java b/src/com/android/server/telecom/PhoneStateBroadcaster.java
index f2531ed..f02f7e8 100644
--- a/src/com/android/server/telecom/PhoneStateBroadcaster.java
+++ b/src/com/android/server/telecom/PhoneStateBroadcaster.java
@@ -17,8 +17,16 @@
 package com.android.server.telecom;
 
 import android.telecom.Log;
+import android.telephony.PhoneNumberUtils;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.telephony.TelephonyRegistryManager;
+import android.telephony.emergency.EmergencyNumber;
+
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
 
 /**
  * Send a {@link TelephonyManager#ACTION_PHONE_STATE_CHANGED} broadcast when the call state
@@ -52,6 +60,10 @@
             return;
         }
         updateStates(call);
+
+        if (call.isEmergencyCall() && !call.isIncoming()) {
+            sendOutgoingEmergencyCallEvent(call);
+        }
     }
 
     @Override
@@ -113,4 +125,31 @@
             Log.i(this, "Broadcasted state change: %s", mCurrentState);
         }
     }
+
+    private void sendOutgoingEmergencyCallEvent(Call call) {
+        TelephonyManager tm = mCallsManager.getContext().getSystemService(TelephonyManager.class);
+        String strippedNumber =
+                PhoneNumberUtils.stripSeparators(call.getHandle().getSchemeSpecificPart());
+        Optional<EmergencyNumber> emergencyNumber = tm.getEmergencyNumberList().values().stream()
+                .flatMap(List::stream)
+                .filter(numberObj -> Objects.equals(numberObj.getNumber(), strippedNumber))
+                .findFirst();
+
+        int subscriptionId = tm.getSubscriptionId(call.getTargetPhoneAccount());
+        SubscriptionManager subscriptionManager =
+                mCallsManager.getContext().getSystemService(SubscriptionManager.class);
+        int simSlotIndex = SubscriptionManager.DEFAULT_PHONE_INDEX;
+        if (subscriptionManager != null) {
+            SubscriptionInfo subInfo =
+                    subscriptionManager.getActiveSubscriptionInfo(subscriptionId);
+            if (subInfo != null) {
+                simSlotIndex = subInfo.getSimSlotIndex();
+            }
+        }
+
+        if (emergencyNumber.isPresent()) {
+            mRegistry.notifyOutgoingEmergencyCall(
+                    simSlotIndex, subscriptionId, emergencyNumber.get());
+        }
+    }
 }
diff --git a/src/com/android/server/telecom/TelecomServiceImpl.java b/src/com/android/server/telecom/TelecomServiceImpl.java
index 1ee77a3..3481558 100644
--- a/src/com/android/server/telecom/TelecomServiceImpl.java
+++ b/src/com/android/server/telecom/TelecomServiceImpl.java
@@ -34,6 +34,7 @@
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
+import android.content.PermissionChecker;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
@@ -831,6 +832,22 @@
         }
 
         /**
+         * @see android.telecom.TelecomManager#hasCompanionInCallServiceAccess
+         */
+        @Override
+        public boolean hasCompanionInCallServiceAccess(String callingPackage) {
+            try {
+                Log.startSession("TSI.hCICSA");
+                return PermissionChecker.checkPermissionForPreflight(mContext,
+                        Manifest.permission.MANAGE_ONGOING_CALLS,
+                                PermissionChecker.PID_UNKNOWN, Binder.getCallingUid(),
+                                        callingPackage) == PermissionChecker.PERMISSION_GRANTED;
+            } finally {
+                Log.endSession();
+            }
+        }
+
+        /**
          * @see android.telecom.TelecomManager#isInManagedCall
          */
         @Override
diff --git a/src/com/android/server/telecom/ui/MissedCallNotifierImpl.java b/src/com/android/server/telecom/ui/MissedCallNotifierImpl.java
index e37fe6b..4e32c9f 100644
--- a/src/com/android/server/telecom/ui/MissedCallNotifierImpl.java
+++ b/src/com/android/server/telecom/ui/MissedCallNotifierImpl.java
@@ -559,7 +559,8 @@
             UserHandle userHandle) {
         Intent intent = new Intent(action, data, mContext, TelecomBroadcastReceiver.class);
         intent.putExtra(TelecomBroadcastIntentProcessor.EXTRA_USERHANDLE, userHandle);
-        return PendingIntent.getBroadcast(mContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
+        return PendingIntent.getBroadcast(mContext, 0, intent,
+                PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
     }
 
     /**
diff --git a/tests/src/com/android/server/telecom/tests/AnalyticsTests.java b/tests/src/com/android/server/telecom/tests/AnalyticsTests.java
index ec5f7ba..a64f39d 100644
--- a/tests/src/com/android/server/telecom/tests/AnalyticsTests.java
+++ b/tests/src/com/android/server/telecom/tests/AnalyticsTests.java
@@ -16,6 +16,8 @@
 
 package com.android.server.telecom.tests;
 
+import static android.provider.CallLog.Calls.MISSED_REASON_NOT_MISSED;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
@@ -127,7 +129,8 @@
         Analytics.dump(ip);
         String dumpResult = sr.toString();
         String[] expectedFields = {"startTime", "endTime", "direction", "isAdditionalCall",
-                "isInterrupted", "callTechnologies", "callTerminationReason", "connectionService"};
+                "isInterrupted", "callTechnologies", "callTerminationReason", "connectionService",
+                "missedReason"};
         for (String field : expectedFields) {
             assertTrue(dumpResult.contains(field));
         }
@@ -200,6 +203,8 @@
         assertTrue(callAnalytics2.startTime > 0);
         assertEquals(0, callAnalytics1.endTime);
         assertEquals(0, callAnalytics2.endTime);
+        assertEquals(MISSED_REASON_NOT_MISSED, callAnalytics1.missedReason);
+        assertEquals(MISSED_REASON_NOT_MISSED, callAnalytics2.missedReason);
 
         assertEquals(Analytics.INCOMING_DIRECTION, callAnalytics1.callDirection);
         assertEquals(Analytics.OUTGOING_DIRECTION, callAnalytics2.callDirection);
diff --git a/tests/src/com/android/server/telecom/tests/CallsManagerTest.java b/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
index 0cc04d6..3fd5e60 100644
--- a/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
@@ -42,8 +42,11 @@
 
 import android.content.ComponentName;
 import android.content.Context;
+import android.content.Intent;
 import android.net.Uri;
 import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
 import android.os.Process;
 import android.os.SystemClock;
 import android.os.UserHandle;
@@ -56,6 +59,7 @@
 import android.telephony.TelephonyManager;
 import android.test.suitebuilder.annotation.MediumTest;
 import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Pair;
 import android.widget.Toast;
 
 import com.android.server.telecom.AsyncRingtonePlayer;
@@ -116,6 +120,8 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.TimeUnit;
 
@@ -1381,6 +1387,80 @@
     }
 
     /**
+     * This test verifies a race condition seen with the new outgoing call broadcast.
+     * The scenario occurs when an incoming call is handled by an app which receives the
+     * NewOutgoingCallBroadcast.  That app cancels the call by modifying the new outgoing call
+     * broadcast.  Meanwhile, it places that same call again, expecting that Telecom will reuse the
+     * same same.  HOWEVER, if the system delays passing of the new outgoing call broadcast back to
+     * Telecom, the app will have placed a new outgoing call BEFORE telecom is aware that the call
+     * was cancelled.
+     * The consequence of this is that in CallsManager#startOutgoingCall, when we first get the
+     * call to reuse, it will come back empty.  Meanwhile, by the time we get into the various
+     * completable futures, the call WILL be in the list of calls which can be reused.  Since the
+     * reusable call was not found earlier on, we end up aborting the new outgoing call.
+     * @throws Exception
+     */
+    @SmallTest
+    @Test
+    public void testReuseCallConcurrency() throws Exception {
+        // Ensure contact info lookup succeeds
+        doAnswer(invocation -> {
+            Uri handle = invocation.getArgument(0);
+            CallerInfo info = new CallerInfo();
+            CompletableFuture<Pair<Uri, CallerInfo>> callerInfoFuture = new CompletableFuture<>();
+            callerInfoFuture.complete(new Pair<>(handle, info));
+            return callerInfoFuture;
+        }).when(mCallerInfoLookupHelper).startLookup(any(Uri.class));
+
+        // Ensure we have candidate phone account handle info.
+        when(mPhoneAccountRegistrar.getOutgoingPhoneAccountForScheme(any(), any())).thenReturn(
+                SIM_1_HANDLE);
+        when(mPhoneAccountRegistrar.getCallCapablePhoneAccounts(any(), anyBoolean(),
+                any(), anyInt(), anyInt())).thenReturn(
+                new ArrayList<>(Arrays.asList(SIM_1_HANDLE, SIM_2_HANDLE)));
+
+        // Let's add an existing call which is in connecting state; this emulates the case where
+        // we have an outgoing call which we have not yet disconnected as a result of the new
+        // outgoing call broadcast cancelling the call.
+        Call outgoingCall = addSpyCall(CallState.CONNECTING);
+
+        final CountDownLatch latch = new CountDownLatch(1);
+        // Get the handler for the main looper, which is the same one the CallsManager will use.
+        // We'll post a little something to block up the handler for now.  This prevents
+        // startOutgoingCall from process it's completablefutures.
+        Handler handler = new Handler(Looper.getMainLooper());
+        handler.post(() -> {
+            try {
+                latch.await();
+            } catch (InterruptedException e) {
+                e.printStackTrace();
+            }
+        });
+
+        // Now while the main handler is blocked up we'll start another outgoing call.
+        CompletableFuture<Call> callFuture = mCallsManager.startOutgoingCall(
+                outgoingCall.getHandle(), outgoingCall.getTargetPhoneAccount(), new Bundle(),
+                UserHandle.CURRENT, new Intent(), "com.test.stuff");
+
+        // And we'll add the initial outgoing call to the list of pending disconnects; this
+        // emulates a scenario where the pending disconnect call came in AFTER this call began.
+        mCallsManager.addToPendingCallsToDisconnect(outgoingCall);
+
+        // And we'll unblock the handler; this will let all the startOutgoingCall futures to happen.
+        latch.countDown();
+
+        // Wait for the future to become the present.
+        callFuture.join();
+
+        // We should have gotten a call out of this; if we did not then it means the call was
+        // aborted.
+        assertNotNull(callFuture.get());
+
+        // And the original call should be disconnected now.
+        assertEquals(CallState.DISCONNECTED, outgoingCall.getState());
+    }
+
+    /**
      * Ensures that if we have two calls hosted by the same connection manager, but with
      * different target phone accounts, we can swap between them.
      * @throws Exception
@@ -1460,7 +1540,6 @@
         // Mocks some methods to not call the real method.
         doNothing().when(callSpy).unhold();
         doNothing().when(callSpy).hold();
-        doNothing().when(callSpy).disconnect();
         doNothing().when(callSpy).answer(Matchers.anyInt());
         doNothing().when(callSpy).setStartWithSpeakerphoneOn(Matchers.anyBoolean());
 
diff --git a/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java b/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java
index af062d7..a4302b6 100644
--- a/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java
+++ b/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java
@@ -42,6 +42,7 @@
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
+import android.content.pm.PermissionInfo;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
 import android.content.res.Configuration;
@@ -485,6 +486,7 @@
     private final RoleManager mRoleManager = mock(RoleManager.class);
     private final TelephonyRegistryManager mTelephonyRegistryManager =
             mock(TelephonyRegistryManager.class);
+    private final PermissionInfo mPermissionInfo = mock(PermissionInfo.class);
 
     private TelecomManager mTelecomManager = mock(TelecomManager.class);
 
@@ -539,6 +541,14 @@
                 matches(Manifest.permission.CALL_COMPANION_APP), anyString()))
                 .thenReturn(PackageManager.PERMISSION_DENIED);
 
+        try {
+            when(mPackageManager.getPermissionInfo(anyString(), anyInt())).thenReturn(
+                    mPermissionInfo);
+        } catch (PackageManager.NameNotFoundException ex) {
+        }
+
+        when(mPermissionInfo.isAppOp()).thenReturn(true);
+
         // Used in CreateConnectionProcessor to rank emergency numbers by viability.
         // For the test, make them all equal to INVALID so that the preferred PhoneAccount will be
         // chosen.
diff --git a/tests/src/com/android/server/telecom/tests/InCallControllerTests.java b/tests/src/com/android/server/telecom/tests/InCallControllerTests.java
index 693859b..6a6b9f3 100644
--- a/tests/src/com/android/server/telecom/tests/InCallControllerTests.java
+++ b/tests/src/com/android/server/telecom/tests/InCallControllerTests.java
@@ -52,6 +52,7 @@
 import android.content.ServiceConnection;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
+import android.content.pm.PermissionInfo;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
 import android.content.res.Resources;
@@ -122,6 +123,7 @@
     @Mock ClockProxy mClockProxy;
     @Mock Analytics.CallInfoImpl mCallInfo;
     @Mock NotificationManager mNotificationManager;
+    @Mock PermissionInfo mMockPermissionInfo;
 
     private static final int CURRENT_USER_ID = 900973;
     private static final String DEF_PKG = "defpkg";
@@ -142,6 +144,9 @@
     private static final String NONUI_PKG = "nonui_pkg";
     private static final String NONUI_CLASS = "nonui_cls";
     private static final int NONUI_UID = 6;
+    private static final String APPOP_NONUI_PKG = "appop_nonui_pkg";
+    private static final String APPOP_NONUI_CLASS = "appop_nonui_cls";
+    private static final int APPOP_NONUI_UID = 7;
 
     private static final PhoneAccountHandle PA_HANDLE =
             new PhoneAccountHandle(new ComponentName("pa_pkg", "pa_cls"), "pa_id");
@@ -173,6 +178,8 @@
         when(mMockCallsManager.getRoleManagerAdapter()).thenReturn(mMockRoleManagerAdapter);
         when(mMockContext.getSystemService(eq(Context.NOTIFICATION_SERVICE)))
                 .thenReturn(mNotificationManager);
+        when(mMockPackageManager.getPermissionInfo(anyString(), anyInt())).thenReturn(
+                mMockPermissionInfo);
         mInCallController = new InCallController(mMockContext, mLock, mMockCallsManager,
                 mMockSystemStateHelper, mDefaultDialerCache, mTimeoutsAdapter,
                 mEmergencyCallHelper, mCarModeTracker, mClockProxy);
@@ -198,6 +205,8 @@
                     return new String[] { CAR2_PKG };
                 case NONUI_UID:
                     return new String[] { NONUI_PKG };
+                case APPOP_NONUI_UID:
+                    return new String[] { APPOP_NONUI_PKG };
             }
             return null;
         }).when(mMockPackageManager).getPackagesForUid(anyInt());
@@ -213,6 +222,9 @@
         when(mMockPackageManager.checkPermission(
                 matches(Manifest.permission.CONTROL_INCALL_EXPERIENCE),
                 matches(NONUI_PKG))).thenReturn(PackageManager.PERMISSION_GRANTED);
+        when(mMockPackageManager.checkPermission(
+                matches(Manifest.permission.CONTROL_INCALL_EXPERIENCE),
+                matches(APPOP_NONUI_PKG))).thenReturn(PackageManager.PERMISSION_DENIED);
         when(mMockCallsManager.getAudioState()).thenReturn(new CallAudioState(false, 0, 0));
     }
 
@@ -822,6 +834,49 @@
         verifyBinding(bindIntentCaptor, 0, DEF_PKG, DEF_CLASS);
     }
 
+   /**
+     * Ensures that the {@link InCallController} will bind to an {@link InCallService} which
+     * supports third party app
+     */
+    @MediumTest
+    @Test
+    public void testBindToService_ThirdPartyApp() throws Exception {
+        setupMocks(false /* isExternalCall */);
+        setupMockPackageManager(false /* default */, false /* nonui */, true /* appop_nonui */,
+                true /* system */, false /* external calls */, false /* self mgd in default */,
+                        false /* self mgd in car*/);
+
+        // Enable Third Party Companion App
+        when(mMockPackageManager.getPermissionInfo(anyString(), anyInt())).thenReturn(
+                mMockPermissionInfo);
+        when(mMockPermissionInfo.isAppOp()).thenReturn(true);
+        when(mMockAppOpsManager.unsafeCheckOpRawNoThrow(matches(
+                AppOpsManager.OPSTR_MANAGE_ONGOING_CALLS), eq(APPOP_NONUI_UID),
+                        matches(APPOP_NONUI_PKG))).thenReturn(AppOpsManager.MODE_ALLOWED);
+
+        // Now bind; we should bind to the system dialer and app op non ui app.
+        mInCallController.bindToServices(mMockCall);
+
+        // Bind InCallServices
+        ArgumentCaptor<Intent> bindIntentCaptor = ArgumentCaptor.forClass(Intent.class);
+        verify(mMockContext, times(2)).bindServiceAsUser(
+                bindIntentCaptor.capture(),
+                any(ServiceConnection.class),
+                eq(Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE
+                        | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS),
+                eq(UserHandle.CURRENT));
+
+        // Verify bind
+        assertEquals(2, bindIntentCaptor.getAllValues().size());
+
+        // Should have first bound to the system dialer.
+        verifyBinding(bindIntentCaptor, 0, SYS_PKG, SYS_CLASS);
+
+        // Should have next bound to the third party app op non ui app.
+        verifyBinding(bindIntentCaptor, 1, APPOP_NONUI_PKG, APPOP_NONUI_CLASS);
+    }
+
+
     @MediumTest
     @Test
     public void testSanitizeContactName() throws Exception {
@@ -934,8 +989,8 @@
                 nullable(ContentResolver.class))).thenReturn(500L);
 
         when(mMockCallsManager.getCalls()).thenReturn(Collections.singletonList(mMockCall));
-        setupMockPackageManager(true /* default */, true /* nonui */, true /* system */,
-                false /* external calls */,
+        setupMockPackageManager(true /* default */, true /* nonui */, false /* appop_nonui */ ,
+                true /* system */, false /* external calls */,
                 false /* self mgd in default*/, false /* self mgd in car*/);
         mInCallController.bindToServices(mMockCall);
 
@@ -1195,9 +1250,21 @@
         }};
     }
 
+    private ResolveInfo getAppOpNonUiResolveinfo() {
+        return new ResolveInfo() {{
+            serviceInfo = new ServiceInfo();
+            serviceInfo.packageName = APPOP_NONUI_PKG;
+            serviceInfo.name = APPOP_NONUI_CLASS;
+            serviceInfo.applicationInfo = new ApplicationInfo();
+            serviceInfo.applicationInfo.uid = APPOP_NONUI_UID;
+            serviceInfo.enabled = true;
+            serviceInfo.permission = Manifest.permission.BIND_INCALL_SERVICE;
+        }};
+    }
+
     private void setupMockPackageManager(final boolean useDefaultDialer,
             final boolean useSystemDialer, final boolean includeExternalCalls) {
-        setupMockPackageManager(useDefaultDialer, false, useSystemDialer, includeExternalCalls,
+        setupMockPackageManager(useDefaultDialer, false, false, useSystemDialer, includeExternalCalls,
                 false /* self mgd */, false /* self mgd */);
     }
 
@@ -1205,13 +1272,13 @@
             final boolean useSystemDialer, final boolean includeExternalCalls,
             final boolean includeSelfManagedCallsInDefaultDialer,
             final boolean includeSelfManagedCallsInCarModeDialer) {
-        setupMockPackageManager(useDefaultDialer, false /* nonui */, useSystemDialer,
-                includeExternalCalls, includeSelfManagedCallsInDefaultDialer,
+        setupMockPackageManager(useDefaultDialer, false /* nonui */, false /* appop_nonui */,
+                useSystemDialer, includeExternalCalls, includeSelfManagedCallsInDefaultDialer,
                 includeSelfManagedCallsInCarModeDialer);
     }
 
     private void setupMockPackageManager(final boolean useDefaultDialer,
-            final boolean useNonUiInCalls,
+            final boolean useNonUiInCalls, final boolean useAppOpNonUiInCalls,
             final boolean useSystemDialer, final boolean includeExternalCalls,
             final boolean includeSelfManagedCallsInDefaultDialer,
             final boolean includeSelfManagedCallsInCarModeDialer) {
@@ -1254,6 +1321,10 @@
                     if (useNonUiInCalls) {
                         resolveInfo.add(getNonUiResolveinfo());
                     }
+                    // InCallController uses a blank package name when querying for App Op non-ui incalls
+                    if (useAppOpNonUiInCalls) {
+                        resolveInfo.add(getAppOpNonUiResolveinfo());
+                    }
                 }
 
                 return resolveInfo;
diff --git a/tests/src/com/android/server/telecom/tests/MissedCallNotifierImplTest.java b/tests/src/com/android/server/telecom/tests/MissedCallNotifierImplTest.java
index 9dd90f5..db44dcd 100644
--- a/tests/src/com/android/server/telecom/tests/MissedCallNotifierImplTest.java
+++ b/tests/src/com/android/server/telecom/tests/MissedCallNotifierImplTest.java
@@ -400,9 +400,9 @@
                 TelecomBroadcastReceiver.class);
 
         assertNotNull(PendingIntent.getBroadcast(mContext, REQUEST_ID,
-                callBackIntent, PendingIntent.FLAG_NO_CREATE));
+                callBackIntent, PendingIntent.FLAG_NO_CREATE | PendingIntent.FLAG_IMMUTABLE));
         assertNotNull(PendingIntent.getBroadcast(mContext, REQUEST_ID,
-                smsIntent, PendingIntent.FLAG_NO_CREATE));
+                smsIntent, PendingIntent.FLAG_NO_CREATE | PendingIntent.FLAG_IMMUTABLE));
     }
 
     @SmallTest
@@ -437,9 +437,9 @@
                 TelecomBroadcastReceiver.class);
 
         assertNotNull(PendingIntent.getBroadcast(mContext, REQUEST_ID,
-                callBackIntent, PendingIntent.FLAG_NO_CREATE));
+                callBackIntent, PendingIntent.FLAG_NO_CREATE | PendingIntent.FLAG_IMMUTABLE));
         assertNull(PendingIntent.getBroadcast(mContext, REQUEST_ID,
-                smsIntent, PendingIntent.FLAG_NO_CREATE));
+                smsIntent, PendingIntent.FLAG_NO_CREATE | PendingIntent.FLAG_IMMUTABLE));
     }
 
     @SmallTest
diff --git a/tests/src/com/android/server/telecom/tests/MissedInformationTest.java b/tests/src/com/android/server/telecom/tests/MissedInformationTest.java
new file mode 100644
index 0000000..d2c832a
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/MissedInformationTest.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) 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.tests;
+
+import static android.provider.CallLog.Calls.AUTO_MISSED_EMERGENCY_CALL;
+import static android.provider.CallLog.Calls.AUTO_MISSED_MAXIMUM_DIALING;
+import static android.provider.CallLog.Calls.AUTO_MISSED_MAXIMUM_RINGING;
+import static android.provider.CallLog.Calls.MISSED_REASON_NOT_MISSED;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.IContentProvider;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.provider.CallLog;
+import android.telecom.DisconnectCause;
+import android.telecom.TelecomManager;
+
+import com.android.server.telecom.Analytics;
+import com.android.server.telecom.Call;
+import com.android.server.telecom.CallIntentProcessor;
+import com.android.server.telecom.CallState;
+import com.android.server.telecom.CallsManager;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+
+import java.util.Map;
+
+public class MissedInformationTest extends TelecomSystemTest {
+    private static final int TEST_TIMEOUT_MILLIS = 1000;
+    private static final String TEST_NUMBER = "650-555-1212";
+    private static final String TEST_NUMBER_1 = "7";
+    private static final String PACKAGE_NAME = "com.android.server.telecom.tests";
+    @Mock ContentResolver mContentResolver;
+    @Mock IContentProvider mContentProvider;
+    @Mock Call mEmergencyCall;
+    @Mock Analytics.CallInfo mCallInfo;
+    private CallsManager mCallsManager;
+    private CallIntentProcessor.AdapterImpl mAdapter;
+
+    @Override
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+        mCallsManager = mTelecomSystem.getCallsManager();
+        mAdapter = new CallIntentProcessor.AdapterImpl(mCallsManager.getDefaultDialerCache());
+        when(mContentResolver.getPackageName()).thenReturn(PACKAGE_NAME);
+        when(mContentResolver.acquireProvider(any(String.class))).thenReturn(mContentProvider);
+        when(mContentProvider.call(any(String.class), any(String.class),
+                any(String.class), any(Bundle.class))).thenReturn(new Bundle());
+        doReturn(mContentResolver).when(mSpyContext).getContentResolver();
+    }
+
+    @Override
+    @After
+    public void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    @Test
+    public void testNotMissedCall() throws Exception {
+        IdPair testCall = startAndMakeActiveIncomingCall(
+                TEST_NUMBER,
+                mPhoneAccountA0.getAccountHandle(),
+                mConnectionServiceFixtureA);
+
+        mConnectionServiceFixtureA.
+                sendSetDisconnected(testCall.mConnectionId, DisconnectCause.LOCAL);
+        ContentValues values = verifyInsertionWithCapture();
+
+        Map<String, Analytics.CallInfoImpl> analyticsMap = Analytics.cloneData();
+        Analytics.CallInfoImpl callAnalytics = analyticsMap.get(testCall.mCallId);
+        assertEquals(MISSED_REASON_NOT_MISSED, callAnalytics.missedReason);
+        assertEquals(MISSED_REASON_NOT_MISSED, (int) values.getAsInteger(CallLog.Calls.MISSED_REASON));
+    }
+
+    @Test
+    public void testEmergencyCallPlacing() throws Exception {
+        Analytics.dumpToParcelableAnalytics();
+        setUpEmergencyCall();
+        mCallsManager.addCall(mEmergencyCall);
+        assertTrue(mCallsManager.isInEmergencyCall());
+
+        Intent intent = new Intent();
+        intent.putExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE,
+               mPhoneAccountA0.getAccountHandle());
+        mAdapter.processIncomingCallIntent(mCallsManager, intent);
+
+        ContentValues values = verifyInsertionWithCapture();
+
+        Map<String, Analytics.CallInfoImpl> analyticsMap = Analytics.cloneData();
+        for (Analytics.CallInfoImpl ci : analyticsMap.values()) {
+            assertEquals(AUTO_MISSED_EMERGENCY_CALL, ci.missedReason);
+        }
+        assertEquals(AUTO_MISSED_EMERGENCY_CALL,
+                (int) values.getAsInteger(CallLog.Calls.MISSED_REASON));
+    }
+
+    @Test
+    public void testMaximumDialingCalls() throws Exception {
+        Analytics.dumpToParcelableAnalytics();
+        IdPair testDialingCall = startAndMakeDialingOutgoingCall(
+                TEST_NUMBER,
+                mPhoneAccountA0.getAccountHandle(),
+                mConnectionServiceFixtureA);
+
+        Intent intent = new Intent();
+        intent.putExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE,
+                mPhoneAccountA0.getAccountHandle());
+        mAdapter.processIncomingCallIntent(mCallsManager, intent);
+
+        ContentValues values = verifyInsertionWithCapture();
+
+        Map<String, Analytics.CallInfoImpl> analyticsMap = Analytics.cloneData();
+        for (String callId : analyticsMap.keySet()) {
+            if (callId.equals(testDialingCall.mCallId)) {
+                continue;
+            }
+            assertEquals(AUTO_MISSED_MAXIMUM_DIALING, analyticsMap.get(callId).missedReason);
+        }
+        assertEquals(AUTO_MISSED_MAXIMUM_DIALING,
+                (int) values.getAsInteger(CallLog.Calls.MISSED_REASON));
+    }
+
+    @Test
+    public void testMaximumRingingCalls() throws Exception {
+        Analytics.dumpToParcelableAnalytics();
+        IdPair testRingingCall = startAndMakeRingingIncomingCall(
+                TEST_NUMBER,
+                mPhoneAccountA0.getAccountHandle(),
+                mConnectionServiceFixtureA);
+
+        Intent intent = new Intent();
+        intent.putExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE,
+                mPhoneAccountA0.getAccountHandle());
+        mAdapter.processIncomingCallIntent(mCallsManager, intent);
+
+        ContentValues values = verifyInsertionWithCapture();
+
+        Map<String, Analytics.CallInfoImpl> analyticsMap = Analytics.cloneData();
+        for (String callId : analyticsMap.keySet()) {
+            if (callId.equals(testRingingCall.mCallId)) {
+                continue;
+            }
+            assertEquals(AUTO_MISSED_MAXIMUM_RINGING, analyticsMap.get(callId).missedReason);
+        }
+        assertEquals(AUTO_MISSED_MAXIMUM_RINGING,
+                (int) values.getAsInteger(CallLog.Calls.MISSED_REASON));
+    }
+
+    private ContentValues verifyInsertionWithCapture() {
+        ArgumentCaptor<ContentValues> captor = ArgumentCaptor.forClass(ContentValues.class);
+        verify(mContentResolver, timeout(TEST_TIMEOUT_MILLIS))
+                .insert(any(Uri.class), captor.capture());
+        return captor.getValue();
+    }
+
+    private void setUpEmergencyCall() {
+        when(mEmergencyCall.isEmergencyCall()).thenReturn(true);
+        when(mEmergencyCall.getIntentExtras()).thenReturn(new Bundle());
+        when(mEmergencyCall.getAnalytics()).thenReturn(mCallInfo);
+        when(mEmergencyCall.getState()).thenReturn(CallState.ACTIVE);
+        when(mEmergencyCall.getContext()).thenReturn(mSpyContext);
+        when(mEmergencyCall.getHandle()).thenReturn(Uri.parse("tel:" + TEST_NUMBER));
+    }
+}
diff --git a/tests/src/com/android/server/telecom/tests/PhoneAccountRegistrarTest.java b/tests/src/com/android/server/telecom/tests/PhoneAccountRegistrarTest.java
index 00ef87c..a56036a 100644
--- a/tests/src/com/android/server/telecom/tests/PhoneAccountRegistrarTest.java
+++ b/tests/src/com/android/server/telecom/tests/PhoneAccountRegistrarTest.java
@@ -725,16 +725,20 @@
         mComponentContextFixture.addConnectionService(componentC,
                 Mockito.mock(IConnectionService.class));
 
+        Bundle account1Extras = new Bundle();
+        account1Extras.putInt(PhoneAccount.EXTRA_SORT_ORDER, 1);
         PhoneAccount account1 = new PhoneAccount.Builder(
                 makeQuickAccountHandle(componentA, "c"), "c")
                 .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER)
-                .setExtras(Bundle.forPair(PhoneAccount.EXTRA_SORT_ORDER, "A"))
+                .setExtras(account1Extras)
                 .build();
 
+        Bundle account2Extras = new Bundle();
+        account2Extras.putInt(PhoneAccount.EXTRA_SORT_ORDER, 2);
         PhoneAccount account2 = new PhoneAccount.Builder(
                 makeQuickAccountHandle(componentB, "b"), "b")
                 .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER)
-                .setExtras(Bundle.forPair(PhoneAccount.EXTRA_SORT_ORDER, "B"))
+                .setExtras(account2Extras)
                 .build();
 
         PhoneAccount account3 = new PhoneAccount.Builder(
@@ -814,18 +818,23 @@
                 Mockito.mock(IConnectionService.class));
         mComponentContextFixture.addConnectionService(componentZ,
                 Mockito.mock(IConnectionService.class));
+
+        Bundle account1Extras = new Bundle();
+        account1Extras.putInt(PhoneAccount.EXTRA_SORT_ORDER, 2);
         PhoneAccount account1 = new PhoneAccount.Builder(makeQuickAccountHandle(
                 makeQuickConnectionServiceComponentName(), "y"), "y")
                 .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER |
                         PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)
-                .setExtras(Bundle.forPair(PhoneAccount.EXTRA_SORT_ORDER, "2"))
+                .setExtras(account1Extras)
                 .build();
 
+        Bundle account2Extras = new Bundle();
+        account2Extras.putInt(PhoneAccount.EXTRA_SORT_ORDER, 1);
         PhoneAccount account2 = new PhoneAccount.Builder(makeQuickAccountHandle(
                 makeQuickConnectionServiceComponentName(), "z"), "z")
                 .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER |
                         PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)
-                .setExtras(Bundle.forPair(PhoneAccount.EXTRA_SORT_ORDER, "1"))
+                .setExtras(account2Extras)
                 .build();
 
         PhoneAccount account3 = new PhoneAccount.Builder(makeQuickAccountHandle(
diff --git a/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java b/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
index 226e7ef..aeb68c5 100644
--- a/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
+++ b/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
@@ -1122,6 +1122,54 @@
         return ids;
     }
 
+    protected IdPair startAndMakeDialingOutgoingCall(
+            String number,
+            PhoneAccountHandle phoneAccountHandle,
+            ConnectionServiceFixture connectionServiceFixture) throws Exception {
+        IdPair ids = startOutgoingPhoneCall(number, phoneAccountHandle, connectionServiceFixture,
+                Process.myUserHandle(), VideoProfile.STATE_AUDIO_ONLY);
+
+        connectionServiceFixture.sendSetDialing(ids.mConnectionId);
+        if (phoneAccountHandle != mPhoneAccountSelfManaged.getAccountHandle()) {
+            assertEquals(Call.STATE_DIALING,
+                    mInCallServiceFixtureX.getCall(ids.mCallId).getState());
+            assertEquals(Call.STATE_DIALING,
+                    mInCallServiceFixtureY.getCall(ids.mCallId).getState());
+        }
+
+        return ids;
+    }
+
+    protected IdPair startAndMakeRingingIncomingCall(
+            String number,
+            PhoneAccountHandle phoneAccountHandle,
+            ConnectionServiceFixture connectionServiceFixture) throws Exception {
+        IdPair ids = startIncomingPhoneCall(number, phoneAccountHandle, connectionServiceFixture);
+
+        if (phoneAccountHandle != mPhoneAccountSelfManaged.getAccountHandle()) {
+            assertEquals(Call.STATE_RINGING,
+                    mInCallServiceFixtureX.getCall(ids.mCallId).getState());
+            assertEquals(Call.STATE_RINGING,
+                    mInCallServiceFixtureY.getCall(ids.mCallId).getState());
+
+            mInCallServiceFixtureX.mInCallAdapter
+                    .answerCall(ids.mCallId, VideoProfile.STATE_AUDIO_ONLY);
+
+            waitForHandlerAction(mTelecomSystem.getCallsManager()
+                    .getConnectionServiceFocusManager().getHandler(), TEST_TIMEOUT);
+
+            if (!VideoProfile.isVideo(VideoProfile.STATE_AUDIO_ONLY)) {
+                verify(connectionServiceFixture.getTestDouble(), timeout(TEST_TIMEOUT))
+                        .answer(eq(ids.mConnectionId), any());
+            } else {
+                verify(connectionServiceFixture.getTestDouble(), timeout(TEST_TIMEOUT))
+                        .answerVideo(eq(ids.mConnectionId), eq(VideoProfile.STATE_AUDIO_ONLY),
+                                any());
+            }
+        }
+        return ids;
+    }
+
     protected static void assertTrueWithTimeout(Predicate<Void> predicate) {
         int elapsed = 0;
         while (elapsed < TEST_TIMEOUT) {